work on new project file backend
[jSite2.git] / src / net / pterodactylus / jsite / gui / FileManager.java
1 /*
2  * jSite2 - FileManager.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.jsite.gui;
21
22 import java.awt.BorderLayout;
23 import java.awt.Color;
24 import java.awt.Component;
25 import java.awt.Dimension;
26 import java.awt.FlowLayout;
27 import java.awt.Font;
28 import java.awt.GridBagConstraints;
29 import java.awt.GridBagLayout;
30 import java.awt.Insets;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.io.File;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38
39 import javax.swing.BorderFactory;
40 import javax.swing.JButton;
41 import javax.swing.JDialog;
42 import javax.swing.JLabel;
43 import javax.swing.JOptionPane;
44 import javax.swing.JPanel;
45 import javax.swing.JScrollPane;
46 import javax.swing.JTextField;
47 import javax.swing.JTree;
48 import javax.swing.event.TreeSelectionEvent;
49 import javax.swing.event.TreeSelectionListener;
50 import javax.swing.tree.DefaultTreeCellRenderer;
51 import javax.swing.tree.DefaultTreeModel;
52 import javax.swing.tree.TreeNode;
53 import javax.swing.tree.TreePath;
54
55 import net.pterodactylus.jsite.i18n.I18n;
56 import net.pterodactylus.jsite.i18n.I18nable;
57 import net.pterodactylus.jsite.i18n.gui.I18nAction;
58 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
59 import net.pterodactylus.jsite.project.Project;
60 import net.pterodactylus.util.logging.Logging;
61 import net.pterodactylus.util.swing.SortableTreeNode;
62 import net.pterodactylus.util.swing.SwingUtils;
63
64 /**
65  * Manages physical and virtual files in a project.
66  *
67  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
68  */
69 public class FileManager extends JDialog implements I18nable, ActionListener, TreeSelectionListener {
70
71         /** Logger. */
72         private static final Logger logger = Logging.getLogger(FileManager.class.getName());
73
74         /** The Swing interface. */
75         private final SwingInterface swingInterface;
76
77         /** The project whose files to manage. */
78         private final Project project;
79
80         /** The root of the file tree. */
81         private final SortableTreeNode fileTreeRoot;
82
83         /** The tree model for the project files. */
84         private final DefaultTreeModel fileTreeModel;
85
86         /** Files that are hidden as per {@link File#isHidden()}. */
87         private final List<String> hiddenFiles = new ArrayList<String>();
88
89         /** The tree cell renderer. */
90         private final FileCellRenderer fileCellRenderer;
91
92         /** The “rescan” action. */
93         private I18nAction rescanAction;
94
95         /** The “close” action. */
96         private I18nAction closeAction;
97
98         /** The “project files” label. */
99         private I18nLabel projectFilesLabel;
100
101         /** The tree that shows the files. */
102         private JTree fileTree;
103
104         /** The scroll pane that holds the file tree. */
105         private JScrollPane fileScrollPane;
106
107         /** The “file properties” label. */
108         private I18nLabel filePropertiesLabel;
109
110         /** The “file path” label. */
111         private I18nLabel filePathLabel;
112
113         /** The “file path” textfield. */
114         private JTextField filePathTextField;
115
116         /** The “file name” label. */
117         private I18nLabel fileNameLabel;
118
119         /** The “file name” textfield. */
120         private JTextField fileNameTextField;
121
122         /** The “file size” label. */
123         private I18nLabel fileSizeLabel;
124
125         /** The “file size” text field. */
126         private JTextField fileSizeTextField;
127
128         /**
129          * Creates a new file manager.
130          *
131          * @param swingInterface
132          *            The Swing interface
133          * @param project
134          *            The project whose files to manage
135          */
136         public FileManager(SwingInterface swingInterface, Project project) {
137                 super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true);
138                 logger.log(Level.FINEST, "project: " + project);
139                 this.swingInterface = swingInterface;
140                 this.project = project;
141                 fileTreeRoot = new SortableTreeNode(project.getName());
142                 fileTreeModel = new DefaultTreeModel(fileTreeRoot);
143                 fileCellRenderer = new FileCellRenderer();
144                 initActions();
145                 initComponents();
146                 pack();
147                 SwingUtils.center(this);
148         }
149
150         //
151         // ACTIONS
152         //
153
154         /**
155          * @see java.awt.Component#setVisible(boolean)
156          */
157         @Override
158         public void setVisible(boolean visible) {
159                 if (visible) {
160                         initiateFileScan();
161                 }
162                 super.setVisible(visible);
163         }
164
165         //
166         // PRIVATE METHODS
167         //
168
169         /**
170          * Initializes all actions.
171          */
172         private void initActions() {
173                 closeAction = new I18nAction("fileManager.button.close") {
174
175                         /**
176                          * {@inheritDoc}
177                          */
178                         public void actionPerformed(ActionEvent e) {
179                                 setVisible(false);
180                         }
181                 };
182                 rescanAction = new I18nAction("fileManager.button.rescan") {
183
184                         /**
185                          * {@inheritDoc}
186                          */
187                         @SuppressWarnings("synthetic-access")
188                         public void actionPerformed(ActionEvent actionEvent) {
189                                 initiateFileScan();
190                         }
191                 };
192         }
193
194         /**
195          * Initializes all components.
196          */
197         private void initComponents() {
198                 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
199                 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
200
201                 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
202                 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
203
204                 setContentPane(contentPanel);
205         }
206
207         /**
208          * Creates the main panel with the file tree and the file properties.
209          *
210          * @return The mail panel
211          */
212         private Component createFileManagerPanel() {
213                 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
214
215                 /* file tree panel */
216                 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
217                 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
218
219                 fileTree = new JTree(fileTreeModel);
220                 fileTree.setShowsRootHandles(false);
221                 fileTree.addTreeSelectionListener(this);
222                 fileTree.setCellRenderer(fileCellRenderer);
223                 fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER);
224                 fileScrollPane.setPreferredSize(new Dimension(250, 400));
225
226                 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
227                 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
228                 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
229                 projectFilesLabelPanel.add(projectFilesLabel);
230
231                 /* the right panel */
232                 JPanel rightPanel = new JPanel(new BorderLayout(12, 12));
233                 fileManagerPanel.add(rightPanel, BorderLayout.CENTER);
234
235                 /* properties panel */
236                 JPanel propertiesPanel = new JPanel(new GridBagLayout());
237                 rightPanel.add(propertiesPanel, BorderLayout.CENTER);
238                 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
239
240                 filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties");
241                 filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD));
242                 propertiesPanel.add(filePropertiesLabel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
243
244                 filePathLabel = new I18nLabel("fileManager.label.filePath");
245                 filePathTextField = new JTextField();
246                 filePathTextField.setEditable(false);
247                 propertiesPanel.add(filePathLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
248                 propertiesPanel.add(filePathTextField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
249
250                 fileNameLabel = new I18nLabel("fileManager.label.fileName");
251                 fileNameTextField = new JTextField();
252                 fileNameTextField.setEditable(false);
253                 propertiesPanel.add(fileNameLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
254                 propertiesPanel.add(fileNameTextField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
255
256                 fileSizeLabel = new I18nLabel("fileManager.label.fileSize");
257                 fileSizeTextField = new JTextField();
258                 fileSizeTextField.setEditable(false);
259                 propertiesPanel.add(fileSizeLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
260                 propertiesPanel.add(fileSizeTextField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
261
262                 /* glue panel. */
263                 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 3, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
264
265                 /* action button panel */
266                 JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12));
267                 rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END);
268                 actionButtonPanel.setBorder(BorderFactory.createEtchedBorder());
269
270                 JButton rescanButton = new JButton(rescanAction);
271                 actionButtonPanel.add(rescanButton);
272
273                 return fileManagerPanel;
274         }
275
276         /**
277          * Creates the button panel.
278          *
279          * @return The button panel
280          */
281         private Component createButtonPanel() {
282                 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
283
284                 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
285                 JButton closeButton = new JButton(closeAction);
286                 buttonPanel.add(closeButton);
287
288                 getRootPane().setDefaultButton(closeButton);
289                 return buttonPanel;
290         }
291
292         /**
293          * Initiates a file scan.
294          */
295         private void initiateFileScan() {
296                 swingInterface.getThreadPool().execute(new Runnable() {
297
298                         /**
299                          * @see java.lang.Runnable#run()
300                          */
301                         @SuppressWarnings("synthetic-access")
302                         public void run() {
303                                 fileTree.setEnabled(false);
304                                 rescanAction.setEnabled(false);
305                                 String basePath = project.getBasePath();
306                                 File basePathDirectory = new File(basePath);
307                                 fileTreeRoot.removeAll();
308                                 hiddenFiles.clear();
309                                 if (!basePathDirectory.exists() || !basePathDirectory.isDirectory()) {
310                                         /* TODO - i18n */
311                                         JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
312                                 } else {
313                                         scanDirectory(fileTreeRoot, basePathDirectory, "", hiddenFiles);
314                                 }
315                                 fileTreeModel.reload();
316                                 // fileScrollPane.revalidate();
317                                 rescanAction.setEnabled(true);
318                                 fileTree.setEnabled(true);
319                         }
320
321                         private void scanDirectory(SortableTreeNode rootNode, File directory, String currentDirectory, List<String> hiddenFiles) {
322                                 for (File file : directory.listFiles()) {
323                                         String fileName = file.getName();
324                                         SortableTreeNode fileNode = new SortableTreeNode(fileName);
325                                         rootNode.add(fileNode);
326                                         if (file.isFile() && file.isHidden()) {
327                                                 hiddenFiles.add((currentDirectory + File.separator + fileName).substring(1));
328                                         }
329                                         if (file.isDirectory()) {
330                                                 scanDirectory(fileNode, file, currentDirectory + File.separator + fileName, hiddenFiles);
331                                         }
332                                 }
333                                 rootNode.sort();
334                         }
335
336                 });
337         }
338
339         /**
340          * Returns the complete path for the given node.
341          *
342          * @param treeNode
343          *            The node
344          * @return The complete path for the node, or the empty string (“”) for the
345          *         root node
346          */
347         private String getPathForNode(TreeNode treeNode) {
348                 TreeNode[] pathToNode = fileTreeModel.getPathToRoot(treeNode);
349                 StringBuilder completePathBuilder = new StringBuilder();
350                 boolean first = true;
351                 for (TreeNode nodePathNode : pathToNode) {
352                         if (first) {
353                                 first = false;
354                                 continue;
355                         }
356                         if (!(nodePathNode instanceof SortableTreeNode)) {
357                                 logger.log(Level.WARNING, "nodePathNode is not a SortableTreeNode!");
358                                 continue;
359                         }
360                         completePathBuilder.append(File.separatorChar).append(((SortableTreeNode) nodePathNode).getUserObject());
361                 }
362                 String completePath = (completePathBuilder.length() == 0) ? "" : completePathBuilder.substring(1);
363                 return completePath;
364         }
365
366         //
367         // INTERFACE I18nable
368         //
369
370         /**
371          * {@inheritDoc}
372          */
373         public void updateI18n() {
374                 setTitle(I18n.get("fileManager.title", project.getName()));
375                 projectFilesLabel.updateI18n();
376                 filePropertiesLabel.updateI18n();
377                 filePathLabel.updateI18n();
378         }
379
380         //
381         // INTERFACE TreeSelectionListener
382         //
383
384         /**
385          * {@inheritDoc}
386          */
387         public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
388                 TreePath[] selectedPaths = fileTree.getSelectionPaths();
389                 if ((selectedPaths != null) && (selectedPaths.length == 1)) {
390                         Object lastPathComponent = selectedPaths[0].getLastPathComponent();
391                         if (!(lastPathComponent instanceof SortableTreeNode)) {
392                                 logger.log(Level.WARNING, "lastPathComponent is not a SortableTreeNode!");
393                                 return;
394                         }
395                         SortableTreeNode node = (SortableTreeNode) lastPathComponent;
396                         String completePath = getPathForNode(node);
397                         int lastSeparator = completePath.lastIndexOf(File.separatorChar);
398                         String filePath = "";
399                         String fileName;
400                         if (lastSeparator == -1) {
401                                 fileName = completePath;
402                         } else {
403                                 filePath = completePath.substring(0, lastSeparator);
404                                 fileName = completePath.substring(lastSeparator + 1);
405                         }
406                         filePathTextField.setText(filePath);
407                         fileNameTextField.setText(fileName);
408                         return;
409                 }
410                 filePathTextField.setText("");
411                 fileNameTextField.setText("");
412         }
413
414         //
415         // INTERFACE ActionListener
416         //
417
418         /**
419          * {@inheritDoc}
420          */
421         public void actionPerformed(ActionEvent actionEvent) {
422                 /* TODO */
423         }
424
425         /**
426          * Tree cell renderer that takes care of certain display properties for
427          * project-specific stuff.
428          *
429          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
430          */
431         private class FileCellRenderer extends DefaultTreeCellRenderer {
432
433                 /**
434                  * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
435                  *      java.lang.Object, boolean, boolean, boolean, int, boolean)
436                  */
437                 @Override
438                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
439                         Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
440                         if (!(superCellRenderer instanceof JLabel)) {
441                                 logger.log(Level.WARNING, "superCellRenderer is not a JLabel!");
442                                 return superCellRenderer;
443                         }
444                         if (!(value instanceof SortableTreeNode)) {
445                                 logger.log(Level.WARNING, "value is not a SortableTreeNode!");
446                                 return superCellRenderer;
447                         }
448                         SortableTreeNode node = (SortableTreeNode) value;
449                         TreeNode[] pathToRoot = fileTreeModel.getPathToRoot(node);
450                         if (pathToRoot.length > 1) {
451                                 String completePath = getPathForNode(node);
452                                 if (project.getDefaultFile().equals(completePath)) {
453                                         superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
454                                 } else {
455                                         if (hiddenFiles.contains(completePath)) {
456                                                 /* fade hidden files’ font */
457                                                 Color foreground = superCellRenderer.getForeground();
458                                                 Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor();
459                                                 Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2);
460                                                 superCellRenderer.setForeground(averageColor);
461                                         } else {
462                                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN));
463                                         }
464                                 }
465                         } else {
466                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
467                         }
468                         return superCellRenderer;
469                 }
470
471         }
472
473 }