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