e9d6f3fad3fdddf74db8f44c2bd2ce8e7a41bc60
[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.awt.event.MouseEvent;
34 import java.awt.event.MouseListener;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
37 import java.io.File;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.logging.Level;
44 import java.util.logging.Logger;
45
46 import javax.swing.BorderFactory;
47 import javax.swing.JButton;
48 import javax.swing.JCheckBox;
49 import javax.swing.JComboBox;
50 import javax.swing.JDialog;
51 import javax.swing.JLabel;
52 import javax.swing.JOptionPane;
53 import javax.swing.JPanel;
54 import javax.swing.JPopupMenu;
55 import javax.swing.JScrollPane;
56 import javax.swing.JTextField;
57 import javax.swing.JTree;
58 import javax.swing.event.TreeModelEvent;
59 import javax.swing.event.TreeModelListener;
60 import javax.swing.event.TreeSelectionEvent;
61 import javax.swing.event.TreeSelectionListener;
62 import javax.swing.tree.DefaultTreeCellRenderer;
63 import javax.swing.tree.TreeModel;
64 import javax.swing.tree.TreePath;
65
66 import net.pterodactylus.jsite.i18n.I18n;
67 import net.pterodactylus.jsite.i18n.I18nable;
68 import net.pterodactylus.jsite.i18n.gui.I18nAction;
69 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
70 import net.pterodactylus.jsite.i18n.gui.I18nMenu;
71 import net.pterodactylus.jsite.project.FileOverride;
72 import net.pterodactylus.jsite.project.Project;
73 import net.pterodactylus.jsite.project.ProjectFile;
74 import net.pterodactylus.util.io.MimeTypes;
75 import net.pterodactylus.util.logging.Logging;
76 import net.pterodactylus.util.swing.SwingUtils;
77
78 /**
79  * Manages physical and virtual files in a project.
80  * 
81  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
82  */
83 public class FileManager extends JDialog implements I18nable, ActionListener, TreeSelectionListener, MouseListener {
84
85         /** Logger. */
86         private static final Logger logger = Logging.getLogger(FileManager.class.getName());
87
88         /** The Swing interface. */
89         private final SwingInterface swingInterface;
90
91         /** The project whose files to manage. */
92         private final Project project;
93
94         /** The tree model for the project files. */
95         private final ProjectFileTreeModel fileTreeModel;
96
97         /** The tree cell renderer. */
98         private final FileCellRenderer fileCellRenderer;
99
100         /** The “rescan” action. */
101         private I18nAction rescanAction;
102
103         /** The “close” action. */
104         private I18nAction closeAction;
105
106         /** The “set default file” action. */
107         private I18nAction setDefaultFileAction;
108
109         /** The “project files” label. */
110         private I18nLabel projectFilesLabel;
111
112         /** The tree that shows the files. */
113         private JTree fileTree;
114
115         /** The scroll pane that holds the file tree. */
116         private JScrollPane fileScrollPane;
117
118         /** The “file properties” label. */
119         private I18nLabel filePropertiesLabel;
120
121         /** The “file path” label. */
122         private I18nLabel filePathLabel;
123
124         /** The “file path” textfield. */
125         private JTextField filePathTextField;
126
127         /** The “file name” label. */
128         private I18nLabel fileNameLabel;
129
130         /** The “file name” textfield. */
131         private JTextField fileNameTextField;
132
133         /** The “file size” label. */
134         private I18nLabel fileSizeLabel;
135
136         /** The “file size” text field. */
137         private JTextField fileSizeTextField;
138
139         /** The “override” label. */
140         private I18nLabel fileSettingsLabel;
141
142         /** The context menu for the tree. */
143         private JPopupMenu treeContextMenu;
144
145         /** The “apply override” menu. */
146         private I18nMenu overrideMenu;
147
148         /** The “apply insert override” action. */
149         private I18nAction applyInsertOverrideAction;
150
151         /** The “apply mime type override” action. */
152         private I18nAction applyMimeTypeOverrideAction;
153
154         /** The “remove override” action. */
155         private I18nAction removeOverrideAction;
156
157         /**
158          * Creates a new file manager.
159          * 
160          * @param swingInterface
161          *            The Swing interface
162          * @param project
163          *            The project whose files to manage
164          */
165         public FileManager(SwingInterface swingInterface, Project project) {
166                 super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true);
167                 logger.log(Level.FINEST, "project: " + project);
168                 this.swingInterface = swingInterface;
169                 this.project = project;
170                 fileTreeModel = new ProjectFileTreeModel();
171                 project.addPropertyChangeListener(fileTreeModel);
172                 fileCellRenderer = new FileCellRenderer();
173                 initActions();
174                 initComponents();
175                 pack();
176                 SwingUtils.center(this);
177         }
178
179         //
180         // ACTIONS
181         //
182
183         /**
184          * @see java.awt.Component#setVisible(boolean)
185          */
186         @Override
187         public void setVisible(boolean visible) {
188                 if (visible) {
189                         initiateFileScan();
190                 }
191                 super.setVisible(visible);
192         }
193
194         //
195         // PRIVATE METHODS
196         //
197
198         /**
199          * Initializes all actions.
200          */
201         private void initActions() {
202                 closeAction = new I18nAction("fileManager.button.close") {
203
204                         /**
205                          * {@inheritDoc}
206                          */
207                         @SuppressWarnings("synthetic-access")
208                         public void actionPerformed(ActionEvent e) {
209                                 String defaultFile = project.getDefaultFile();
210                                 if ((defaultFile == null) || (defaultFile.length() == 0)) {
211                                         /* TODO - i18n */
212                                         JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
213                                         return;
214                                 }
215                                 ProjectFile projectFile = project.getFile(defaultFile);
216                                 if (projectFile == null) {
217                                         JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
218                                         return;
219                                 }
220                                 setVisible(false);
221                         }
222                 };
223                 rescanAction = new I18nAction("fileManager.button.rescan") {
224
225                         /**
226                          * {@inheritDoc}
227                          */
228                         @SuppressWarnings("synthetic-access")
229                         public void actionPerformed(ActionEvent actionEvent) {
230                                 initiateFileScan();
231                         }
232                 };
233                 setDefaultFileAction = new I18nAction("fileManager.menu.item.setDefaultFile") {
234
235                         /**
236                          * {@inheritDoc}
237                          */
238                         @SuppressWarnings("synthetic-access")
239                         public void actionPerformed(ActionEvent actionEvent) {
240                                 TreePath selectedPath = fileTree.getSelectionPath();
241                                 if (selectedPath == null) {
242                                         logger.log(Level.WARNING, "nothing selected!");
243                                         return;
244                                 }
245                                 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent();
246                                 if (isHidden(projectFileWrapper)) {
247                                         /* TODO - i18n */
248                                         JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
249                                         return;
250                                 }
251                                 if (projectFileWrapper.getProjectFile().isDirectory()) {
252                                         /* TODO - i18n */
253                                         JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
254                                         return;
255                                 }
256                                 String completePath = projectFileWrapper.getProjectFile().getCompletePath();
257                                 project.setDefaultFile(completePath);
258                         }
259                 };
260                 applyInsertOverrideAction = new I18nAction("fileManager.menu.item.applyInsertOverride") {
261
262                         /**
263                          * {@inheritDoc}
264                          */
265                         @SuppressWarnings("synthetic-access")
266                         public void actionPerformed(ActionEvent actionEvent) {
267                                 applyInsertOverride();
268                         }
269                 };
270                 applyMimeTypeOverrideAction = new I18nAction("fileManager.menu.item.applyMimeTypeOverride") {
271
272                         /**
273                          * {@inheritDoc}
274                          */
275                         @SuppressWarnings("synthetic-access")
276                         public void actionPerformed(ActionEvent actionEvent) {
277                                 applyMimeTypeOverride();
278                         }
279                 };
280                 removeOverrideAction = new I18nAction("fileManager.menu.item.removeOverride") {
281
282                         /**
283                          * {@inheritDoc}
284                          */
285                         @SuppressWarnings("synthetic-access")
286                         public void actionPerformed(ActionEvent actionEvent) {
287                                 removeOverride();
288                         }
289                 };
290         }
291
292         /**
293          * Initializes all components.
294          */
295         private void initComponents() {
296                 treeContextMenu = new JPopupMenu();
297                 treeContextMenu.add(setDefaultFileAction);
298
299                 overrideMenu = new I18nMenu("fileManager.menu.override");
300                 treeContextMenu.add(overrideMenu);
301
302                 overrideMenu.add(applyInsertOverrideAction);
303                 overrideMenu.add(applyMimeTypeOverrideAction);
304                 overrideMenu.addSeparator();
305                 overrideMenu.add(removeOverrideAction);
306
307                 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
308                 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
309
310                 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
311                 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
312
313                 setContentPane(contentPanel);
314         }
315
316         /**
317          * Creates the main panel with the file tree and the file properties.
318          * 
319          * @return The mail panel
320          */
321         private Component createFileManagerPanel() {
322                 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
323
324                 /* file tree panel */
325                 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
326                 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
327
328                 fileTree = new JTree(fileTreeModel);
329                 fileTree.setShowsRootHandles(false);
330                 fileTree.addTreeSelectionListener(this);
331                 fileTree.addMouseListener(this);
332                 fileTree.setCellRenderer(fileCellRenderer);
333                 fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER);
334                 fileScrollPane.setPreferredSize(new Dimension(200, 350));
335
336                 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
337                 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
338                 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
339                 projectFilesLabelPanel.add(projectFilesLabel);
340
341                 /* the right panel */
342                 JPanel rightPanel = new JPanel(new BorderLayout(12, 12));
343                 fileManagerPanel.add(rightPanel, BorderLayout.CENTER);
344
345                 /* properties panel */
346                 JPanel propertiesPanel = new JPanel(new GridBagLayout());
347                 rightPanel.add(propertiesPanel, BorderLayout.CENTER);
348                 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
349                 propertiesPanel.setPreferredSize(new Dimension(400, 350));
350
351                 filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties");
352                 filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD));
353                 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));
354
355                 filePathLabel = new I18nLabel("fileManager.label.filePath");
356                 filePathTextField = new JTextField();
357                 filePathTextField.setEditable(false);
358                 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));
359                 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));
360
361                 fileNameLabel = new I18nLabel("fileManager.label.fileName");
362                 fileNameTextField = new JTextField();
363                 fileNameTextField.setEditable(false);
364                 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));
365                 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));
366
367                 fileSizeLabel = new I18nLabel("fileManager.label.fileSize");
368                 fileSizeTextField = new JTextField();
369                 fileSizeTextField.setEditable(false);
370                 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));
371                 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));
372
373                 /* override settings. */
374                 fileSettingsLabel = new I18nLabel("fileManager.label.fileSettings");
375                 fileSettingsLabel.setFont(fileSettingsLabel.getFont().deriveFont(Font.BOLD));
376                 propertiesPanel.add(fileSettingsLabel, new GridBagConstraints(0, 4, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(24, 0, 0, 0), 0, 0));
377
378                 /* glue panel. */
379                 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 7, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
380
381                 /* action button panel */
382                 JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12));
383                 rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END);
384                 actionButtonPanel.setBorder(BorderFactory.createEtchedBorder());
385
386                 JButton rescanButton = new JButton(rescanAction);
387                 actionButtonPanel.add(rescanButton);
388
389                 return fileManagerPanel;
390         }
391
392         /**
393          * Creates the button panel.
394          * 
395          * @return The button panel
396          */
397         private Component createButtonPanel() {
398                 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
399
400                 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
401                 JButton closeButton = new JButton(closeAction);
402                 buttonPanel.add(closeButton);
403
404                 getRootPane().setDefaultButton(closeButton);
405                 return buttonPanel;
406         }
407
408         /**
409          * Initiates a file scan.
410          */
411         private void initiateFileScan() {
412                 swingInterface.getThreadPool().execute(new Runnable() {
413
414                         /**
415                          * @see java.lang.Runnable#run()
416                          */
417                         @SuppressWarnings("synthetic-access")
418                         public void run() {
419                                 fileTree.setEnabled(false);
420                                 rescanAction.setEnabled(false);
421                                 ProjectFile baseProjectFile = project.getBaseFile();
422                                 if (baseProjectFile != null) {
423                                         fileTreeModel.setBaseProjectFile(baseProjectFile);
424                                 }
425                                 // fileScrollPane.revalidate();
426                                 rescanAction.setEnabled(true);
427                                 fileTree.setEnabled(true);
428                         }
429
430                 });
431         }
432
433         /**
434          * Checks whether the given mouse event is a popup trigger and occured over
435          * a file. If so, the context menu is shown.
436          * 
437          * @param mouseEvent
438          *            The mouse event to check
439          */
440         private void maybeShowContextMenu(MouseEvent mouseEvent) {
441                 if (!mouseEvent.isPopupTrigger()) {
442                         return;
443                 }
444                 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(false);
445                 TreePath clickedPath = fileTree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
446                 ProjectFileWrapper clickedProjectFileWrapper = (ProjectFileWrapper) clickedPath.getLastPathComponent();
447                 if (!selectedProjectFileWrappers.contains(clickedProjectFileWrapper)) {
448                         fileTree.setSelectionPath(clickedPath);
449                 }
450                 if (selectedProjectFileWrappers.size() == 1) {
451                         ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0);
452                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
453                         setDefaultFileAction.setEnabled(!isHidden(projectFileWrapper) && projectFile.isFile() && !projectFile.getCompletePath().equals(project.getDefaultFile()));
454                 } else {
455                         setDefaultFileAction.setEnabled(false);
456                 }
457                 overrideMenu.setVisible(selectedProjectFileWrappers.size() > 1);
458                 treeContextMenu.show(fileTree, mouseEvent.getX(), mouseEvent.getY());
459         }
460
461         /**
462          * Finds whether the {@link ProjectFile} given by
463          * <code>projectFileWrapper</code> is hidden.
464          * 
465          * @param projectFileWrapper
466          *            The wrapped project file
467          * @return <code>true</code> if the file is hidden and should not be
468          *         inserted, <code>false</code> otherwise
469          */
470         private boolean isHidden(ProjectFileWrapper projectFileWrapper) {
471                 ProjectFile projectFile = projectFileWrapper.getProjectFile();
472                 FileOverride fileOverride = project.getFileOverrides().get(projectFile.getCompletePath());
473                 logger.log(Level.FINEST, "fileOverride: " + fileOverride);
474                 return ((fileOverride == null) && projectFile.isHidden()) || ((fileOverride != null) && (Boolean.TRUE.equals(fileOverride.isInsert())));
475         }
476
477         /**
478          * Opens the “apply insert override” dialog and lets the user apply an
479          * override for the “insert” setting for multiple files.
480          */
481         private void applyInsertOverride() {
482                 JCheckBox insertCheckBox = new JCheckBox(I18n.get("fileManager.checkbox.insertFile.name"));
483                 String okString = I18n.get("general.button.okay.name");
484                 String cancelString = I18n.get("general.button.cancel.name");
485                 int choice = JOptionPane.showOptionDialog(this, new Object[] { I18n.get("fileManager.dialog.insertOverride.message"), insertCheckBox }, I18n.get("fileManager.dialog.insertOverride.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { okString, cancelString }, okString);
486                 logger.log(Level.FINEST, "choice: " + choice);
487                 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
488                         return;
489                 }
490                 logger.log(Level.INFO, "selected insert override: " + insertCheckBox.isSelected());
491                 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
492                 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
493                         ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile();
494                         FileOverride fileOverride = project.getFileOverride(projectFile);
495                         if (fileOverride == null) {
496                                 fileOverride = new FileOverride();
497                                 project.addFileOverride(projectFile, fileOverride);
498                         }
499                         fileOverride.setInsert(insertCheckBox.isSelected());
500                 }
501         }
502
503         /**
504          * Opens the “apply mime type override” dialog and lets the user apply an
505          * override for the “mime type” setting for multiple files.
506          */
507         private void applyMimeTypeOverride() {
508                 List<String> allMimeTypes = MimeTypes.getAllMimeTypes();
509                 allMimeTypes.add(0, null);
510                 JComboBox mimeTypeComboBox = new JComboBox(allMimeTypes.toArray());
511                 String okString = I18n.get("general.button.okay.name");
512                 String cancelString = I18n.get("general.button.cancel.name");
513                 int choice = JOptionPane.showOptionDialog(this, new Object[] { I18n.get("fileManager.dialog.mimeTypeOverride.message"), mimeTypeComboBox }, I18n.get("fileManager.dialog.mimeTypeOverride.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { okString, cancelString }, okString);
514                 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
515                         return;
516                 }
517                 logger.log(Level.FINE, "selected mime type: " + mimeTypeComboBox.getSelectedItem());
518                 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
519                 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
520                         ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile();
521                         FileOverride fileOverride = project.getFileOverride(projectFile);
522                         if (fileOverride == null) {
523                                 fileOverride = new FileOverride();
524                                 project.addFileOverride(projectFile, fileOverride);
525                         }
526                         fileOverride.setContentType((String) mimeTypeComboBox.getSelectedItem());
527                 }
528         }
529
530         /**
531          * Removes the overrides of all selected files.
532          */
533         private void removeOverride() {
534                 String continueString = I18n.get("general.button.continue.name");
535                 String cancelString = I18n.get("general.button.cancel.name");
536                 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
537                 int choice = JOptionPane.showOptionDialog(this, I18n.get("fileManager.dialog.removeOverride.message", selectedProjectFileWrappers.size()), I18n.get("fileManager.dialog.removeOverride.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new Object[] { continueString, cancelString }, continueString);
538                 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
539                         return;
540                 }
541                 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
542                         project.removeFileOverride(selectedProjectFileWrapper.getProjectFile());
543                 }
544         }
545
546         /**
547          * Returns all currently selected {@link ProjectFileWrapper}s.
548          * 
549          * @param filesOnly
550          *            <code>true</code> to return only selected files,
551          *            <code>false</code> to include directories
552          * @return All selected project file wrappers
553          */
554         private List<ProjectFileWrapper> getSelectedProjectFileWrappers(boolean filesOnly) {
555                 List<ProjectFileWrapper> selectedProjectFileWrappers = new ArrayList<ProjectFileWrapper>();
556                 TreePath[] selectedPaths = fileTree.getSelectionPaths();
557                 if (selectedPaths != null) {
558                         for (TreePath selectedPath : selectedPaths) {
559                                 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent();
560                                 if (filesOnly && !projectFileWrapper.getProjectFile().isFile()) {
561                                         continue;
562                                 }
563                                 selectedProjectFileWrappers.add(projectFileWrapper);
564                         }
565                 }
566                 return selectedProjectFileWrappers;
567         }
568
569         //
570         // INTERFACE I18nable
571         //
572
573         /**
574          * {@inheritDoc}
575          */
576         public void updateI18n() {
577                 setTitle(I18n.get("fileManager.title", project.getName()));
578                 projectFilesLabel.updateI18n();
579                 filePropertiesLabel.updateI18n();
580                 filePathLabel.updateI18n();
581         }
582
583         //
584         // INTERFACE TreeSelectionListener
585         //
586
587         /**
588          * {@inheritDoc}
589          */
590         public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
591                 TreePath[] selectedPaths = fileTree.getSelectionPaths();
592                 filePathTextField.setText("");
593                 fileNameTextField.setText("");
594                 fileSizeTextField.setText("");
595                 if ((selectedPaths != null) && (selectedPaths.length == 1)) {
596                         Object lastPathComponent = selectedPaths[0].getLastPathComponent();
597                         if (!(lastPathComponent instanceof ProjectFileWrapper)) {
598                                 logger.log(Level.SEVERE, "lastPathComponent is not a ProjectFileWrapper!");
599                                 return;
600                         }
601                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) lastPathComponent;
602                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
603                         if (projectFile.isFile()) {
604                                 String completePath = projectFile.getCompletePath();
605                                 int lastSeparator = completePath.lastIndexOf(File.separatorChar);
606                                 if (lastSeparator == -1) {
607                                         filePathTextField.setText("");
608                                 } else {
609                                         filePathTextField.setText(completePath.substring(0, lastSeparator));
610                                 }
611                                 fileNameTextField.setText(projectFile.getName());
612                                 fileSizeTextField.setText(String.valueOf(projectFile.getSize()));
613                         }
614                 } else if ((selectedPaths != null) && (selectedPaths.length > 1)) {
615                         /* TODO */
616                 }
617         }
618
619         //
620         // INTERFACE ActionListener
621         //
622
623         /**
624          * {@inheritDoc}
625          */
626         public void actionPerformed(ActionEvent actionEvent) {
627                 /* TODO */
628         }
629
630         //
631         // INTERFACE MouseListener
632         //
633
634         /**
635          * {@inheritDoc}
636          */
637         public void mouseClicked(MouseEvent mouseEvent) {
638                 maybeShowContextMenu(mouseEvent);
639         }
640
641         /**
642          * {@inheritDoc}
643          */
644         public void mouseEntered(MouseEvent mouseEvent) {
645                 /* ignore. */
646         }
647
648         /**
649          * {@inheritDoc}
650          */
651         public void mouseExited(MouseEvent mouseEvent) {
652                 /* ignore. */
653         }
654
655         /**
656          * {@inheritDoc}
657          */
658         public void mousePressed(MouseEvent mouseEvent) {
659                 maybeShowContextMenu(mouseEvent);
660         }
661
662         /**
663          * {@inheritDoc}
664          */
665         public void mouseReleased(MouseEvent mouseEvent) {
666                 maybeShowContextMenu(mouseEvent);
667         }
668
669         /**
670          * Tree cell renderer that takes care of certain display properties for
671          * project-specific stuff.
672          * 
673          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
674          */
675         private class FileCellRenderer extends DefaultTreeCellRenderer {
676
677                 /**
678                  * Empty constructor.
679                  */
680                 FileCellRenderer() {
681                         /* do nothing. */
682                 }
683
684                 /**
685                  * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
686                  *      java.lang.Object, boolean, boolean, boolean, int, boolean)
687                  */
688                 @SuppressWarnings("synthetic-access")
689                 @Override
690                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
691                         Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
692                         if (!(superCellRenderer instanceof JLabel)) {
693                                 logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!");
694                                 return superCellRenderer;
695                         }
696                         if (!(value instanceof ProjectFileWrapper)) {
697                                 logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!");
698                                 return superCellRenderer;
699                         }
700                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value;
701                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
702                         String completePath = projectFile.getCompletePath();
703                         boolean paintBold = false;
704                         boolean paintHalfColor = false;
705                         if (projectFile.isFile() && projectFile.isHidden()) {
706                                 /* TODO - check override */
707                                 paintHalfColor = true;
708                         } else if (completePath.equals(project.getDefaultFile())) {
709                                 paintBold = true;
710                         } else if (projectFile.getParents().size() == 1) {
711                                 paintBold = true;
712                         }
713                         if (paintHalfColor) {
714                                 /* TODO - cache colors */
715                                 Color foreground = superCellRenderer.getForeground();
716                                 Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor();
717                                 Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2);
718                                 superCellRenderer.setForeground(averageColor);
719                         } else {
720                                 superCellRenderer.setForeground(selected ? getTextSelectionColor() : getTextNonSelectionColor());
721                         }
722                         if (paintBold) {
723                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
724                         } else {
725                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN));
726                         }
727                         return superCellRenderer;
728                 }
729
730         }
731
732         /**
733          * TreeModel that is based on {@link Project#getBaseFile()}.
734          * 
735          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
736          */
737         private class ProjectFileTreeModel implements TreeModel, PropertyChangeListener {
738
739                 /** Tree model listeners. */
740                 private final List<TreeModelListener> treeModelListeners = Collections.synchronizedList(new ArrayList<TreeModelListener>());
741
742                 /** The base project file. */
743                 private ProjectFile baseProjectFile;
744
745                 /** Maps path names to project files. */
746                 private final Map<String, ProjectFile> pathProjectFiles = Collections.synchronizedMap(new HashMap<String, ProjectFile>());
747
748                 /** Maps project files to wrappers. */
749                 private final Map<ProjectFile, ProjectFileWrapper> projectFileWrappers = Collections.synchronizedMap(new HashMap<ProjectFile, ProjectFileWrapper>());
750
751                 /**
752                  * Empty constructor.
753                  */
754                 ProjectFileTreeModel() {
755                         /* do nothing. */
756                 }
757
758                 //
759                 // EVENT MANAGEMENT
760                 //
761
762                 /**
763                  * {@inheritDoc}
764                  */
765                 public void addTreeModelListener(TreeModelListener treeModelListener) {
766                         treeModelListeners.add(treeModelListener);
767                 }
768
769                 /**
770                  * {@inheritDoc}
771                  */
772                 public void removeTreeModelListener(TreeModelListener treeModelListener) {
773                         treeModelListeners.remove(treeModelListener);
774                 }
775
776                 /**
777                  * Notifies all listeners that a node has changed.
778                  * 
779                  * @param changedProjectFileWrapper
780                  *            The wrapper around the changed project file
781                  */
782                 protected void fireTreeNodesChanged(ProjectFileWrapper changedProjectFileWrapper) {
783                         ProjectFile changedProjectFile = changedProjectFileWrapper.getProjectFile();
784                         ProjectFile changedProjectFileParent = changedProjectFile.getParent();
785                         ProjectFile currentProjectFileParent = changedProjectFile;
786                         List<ProjectFileWrapper> parentProjectFileWrappers = new ArrayList<ProjectFileWrapper>();
787                         do {
788                                 parentProjectFileWrappers.add(0, projectFileWrappers.get(currentProjectFileParent));
789                                 currentProjectFileParent = currentProjectFileParent.getParent();
790                         } while (currentProjectFileParent != null);
791                         TreeModelEvent treeModelEvent = new TreeModelEvent(this, parentProjectFileWrappers.toArray(), new int[] { getIndexOfChild(projectFileWrappers.get(changedProjectFileParent), changedProjectFileWrapper) }, new Object[] { changedProjectFileWrapper });
792                         for (TreeModelListener treeModelListener : treeModelListeners) {
793                                 treeModelListener.treeNodesChanged(treeModelEvent);
794                         }
795                 }
796
797                 /**
798                  * Notifies all listeners that the tree structure has changed
799                  * significantly.
800                  * 
801                  * @see TreeModelListener#treeStructureChanged(TreeModelEvent)
802                  * @param newRootNode
803                  */
804                 protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) {
805                         for (TreeModelListener treeModelListener : treeModelListeners) {
806                                 treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode }));
807                         }
808                 }
809
810                 //
811                 // ACCESSORS
812                 //
813
814                 /**
815                  * Sets the new base project file. This causes the model to reload.
816                  * 
817                  * @param baseProjectFile
818                  *            The new base project file
819                  */
820                 @SuppressWarnings("synthetic-access")
821                 public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) {
822                         this.baseProjectFile = baseProjectFile;
823                         projectFileWrappers.clear();
824                         pathProjectFiles.clear();
825                         createWrappers(baseProjectFile);
826                         projectFileWrappers.get(baseProjectFile).setNameOverride(project.getName());
827                         fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile));
828                 }
829
830                 //
831                 // PRIVATE METHODS
832                 //
833
834                 /**
835                  * Creates {@link ProjectFileWrapper}s for all files below the given
836                  * project file.
837                  * 
838                  * @param projectFile
839                  *            The base project file for all project files to create
840                  *            wrappers for
841                  */
842                 private void createWrappers(ProjectFile projectFile) {
843                         projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile));
844                         pathProjectFiles.put(projectFile.getCompletePath(), projectFile);
845                         for (ProjectFile projectFileChild : projectFile.getFiles()) {
846                                 if (projectFileChild.isDirectory()) {
847                                         createWrappers(projectFileChild);
848                                 }
849                                 projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild));
850                                 pathProjectFiles.put(projectFileChild.getCompletePath(), projectFileChild);
851                         }
852                 }
853
854                 //
855                 // INTERFACE TreeModel
856                 //
857
858                 /**
859                  * {@inheritDoc}
860                  */
861                 public Object getRoot() {
862                         return projectFileWrappers.get(baseProjectFile);
863                 }
864
865                 /**
866                  * {@inheritDoc}
867                  */
868                 @SuppressWarnings("synthetic-access")
869                 public Object getChild(Object parent, int index) {
870                         if (!(parent instanceof ProjectFileWrapper)) {
871                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
872                                 return null;
873                         }
874                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
875                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
876                         return projectFileWrappers.get(projectFile.getFiles().get(index));
877                 }
878
879                 /**
880                  * {@inheritDoc}
881                  */
882                 @SuppressWarnings("synthetic-access")
883                 public int getChildCount(Object parent) {
884                         if (!(parent instanceof ProjectFileWrapper)) {
885                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
886                                 return -1;
887                         }
888                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
889                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
890                         return projectFile.getFiles().size();
891                 }
892
893                 /**
894                  * {@inheritDoc}
895                  */
896                 @SuppressWarnings("synthetic-access")
897                 public int getIndexOfChild(Object parent, Object child) {
898                         if (!(parent instanceof ProjectFileWrapper)) {
899                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
900                                 return -1;
901                         }
902                         if (!(child instanceof ProjectFileWrapper)) {
903                                 logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!");
904                                 return -1;
905                         }
906                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
907                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
908                         return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile());
909                 }
910
911                 /**
912                  * {@inheritDoc}
913                  */
914                 @SuppressWarnings("synthetic-access")
915                 public boolean isLeaf(Object node) {
916                         if (!(node instanceof ProjectFileWrapper)) {
917                                 logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!");
918                                 return true;
919                         }
920                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node;
921                         return projectFileWrapper.getProjectFile().isFile();
922                 }
923
924                 /**
925                  * {@inheritDoc}
926                  */
927                 public void valueForPathChanged(TreePath path, Object newValue) {
928                         /* ignore, items will not be modified in tree. */
929                 }
930
931                 //
932                 // INTERFACE PropertyChangeListener
933                 //
934
935                 /**
936                  * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
937                  */
938                 @SuppressWarnings("synthetic-access")
939                 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
940                         if (Project.PROPERTY_DEFAULT_FILE.equals(propertyChangeEvent.getPropertyName())) {
941                                 if (propertyChangeEvent.getOldValue() != null) {
942                                         String oldCompletePath = (String) propertyChangeEvent.getOldValue();
943                                         ProjectFile oldProjectFile = pathProjectFiles.get(oldCompletePath);
944                                         ProjectFileWrapper oldProjectFileWrapper = projectFileWrappers.get(oldProjectFile);
945                                         System.out.println("oldProjectFileWrapper: " + oldProjectFileWrapper);
946                                         fireTreeNodesChanged(oldProjectFileWrapper);
947                                 }
948                                 String newCompletePath = (String) propertyChangeEvent.getNewValue();
949                                 ProjectFile newProjectFile = pathProjectFiles.get(newCompletePath);
950                                 ProjectFileWrapper newProjectFileWrapper = projectFileWrappers.get(newProjectFile);
951                                 System.out.println("newProjectFileWrapper: " + newProjectFileWrapper);
952                                 fireTreeNodesChanged(newProjectFileWrapper);
953                                 /* HACK - swing sucks a bit. */
954                                 fileTree.setShowsRootHandles(false);
955                         }
956                 }
957
958         }
959
960         /**
961          * Wrapper around a {@link ProjectFile} that overwrites
962          * {@link Object#toString()} to return the project file’s name.
963          * 
964          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
965          */
966         private static class ProjectFileWrapper {
967
968                 /** The wrapped project file. */
969                 private final ProjectFile projectFile;
970
971                 /** The override name. */
972                 private String nameOverride;
973
974                 /**
975                  * Creates a new wrapper around a project file.
976                  * 
977                  * @param projectFile
978                  *            The project file to wrap
979                  */
980                 public ProjectFileWrapper(ProjectFile projectFile) {
981                         this.projectFile = projectFile;
982                 }
983
984                 /**
985                  * Returns the wrapped project file.
986                  * 
987                  * @return The wrapped project file
988                  */
989                 public ProjectFile getProjectFile() {
990                         return projectFile;
991                 }
992
993                 /**
994                  * Sets the name override. If the name override is not <code>null</code>
995                  * it will be shown insted of the project file’s name.
996                  * 
997                  * @param nameOverride
998                  *            The name override
999                  */
1000                 void setNameOverride(String nameOverride) {
1001                         this.nameOverride = nameOverride;
1002                 }
1003
1004                 /**
1005                  * {@inheritDoc}
1006                  */
1007                 @Override
1008                 public String toString() {
1009                         return (nameOverride != null) ? nameOverride : projectFile.getName();
1010                 }
1011
1012         }
1013
1014 }