add context menu
[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.Point;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.MouseEvent;
35 import java.awt.event.MouseListener;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.logging.Level;
42 import java.util.logging.Logger;
43
44 import javax.swing.BorderFactory;
45 import javax.swing.JButton;
46 import javax.swing.JCheckBoxMenuItem;
47 import javax.swing.JDialog;
48 import javax.swing.JLabel;
49 import javax.swing.JPanel;
50 import javax.swing.JPopupMenu;
51 import javax.swing.JScrollPane;
52 import javax.swing.JTextField;
53 import javax.swing.JTree;
54 import javax.swing.event.TreeModelEvent;
55 import javax.swing.event.TreeModelListener;
56 import javax.swing.event.TreeSelectionEvent;
57 import javax.swing.event.TreeSelectionListener;
58 import javax.swing.tree.DefaultTreeCellRenderer;
59 import javax.swing.tree.TreeModel;
60 import javax.swing.tree.TreePath;
61
62 import net.pterodactylus.jsite.i18n.I18n;
63 import net.pterodactylus.jsite.i18n.I18nable;
64 import net.pterodactylus.jsite.i18n.gui.I18nAction;
65 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
66 import net.pterodactylus.jsite.project.Project;
67 import net.pterodactylus.jsite.project.ProjectFile;
68 import net.pterodactylus.util.logging.Logging;
69 import net.pterodactylus.util.swing.SwingUtils;
70
71 /**
72  * Manages physical and virtual files in a project.
73  * 
74  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
75  */
76 public class FileManager extends JDialog implements I18nable, ActionListener, TreeSelectionListener, MouseListener {
77
78         /** Logger. */
79         private static final Logger logger = Logging.getLogger(FileManager.class.getName());
80
81         /** The Swing interface. */
82         private final SwingInterface swingInterface;
83
84         /** The project whose files to manage. */
85         private final Project project;
86
87         /** The tree model for the project files. */
88         private final ProjectFileTreeModel fileTreeModel;
89
90         /** The tree cell renderer. */
91         private final FileCellRenderer fileCellRenderer;
92
93         /** The “rescan” action. */
94         private I18nAction rescanAction;
95
96         /** The “close” action. */
97         private I18nAction closeAction;
98
99         /** The “set default file” action. */
100         private I18nAction setDefaultFileAction;
101
102         /** The “insert” action. */
103         private I18nAction insertAction;
104
105         /** The “project files” label. */
106         private I18nLabel projectFilesLabel;
107
108         /** The tree that shows the files. */
109         private JTree fileTree;
110
111         /** The scroll pane that holds the file tree. */
112         private JScrollPane fileScrollPane;
113
114         /** The “file properties” label. */
115         private I18nLabel filePropertiesLabel;
116
117         /** The “file path” label. */
118         private I18nLabel filePathLabel;
119
120         /** The “file path” textfield. */
121         private JTextField filePathTextField;
122
123         /** The “file name” label. */
124         private I18nLabel fileNameLabel;
125
126         /** The “file name” textfield. */
127         private JTextField fileNameTextField;
128
129         /** The “file size” label. */
130         private I18nLabel fileSizeLabel;
131
132         /** The “file size” text field. */
133         private JTextField fileSizeTextField;
134
135         /** The context menu for the tree. */
136         private JPopupMenu treeContextMenu;
137
138         /** The “insert” checkbox. */
139         private JCheckBoxMenuItem insertCheckBoxMenuItem;
140
141         /**
142          * Creates a new file manager.
143          * 
144          * @param swingInterface
145          *            The Swing interface
146          * @param project
147          *            The project whose files to manage
148          */
149         public FileManager(SwingInterface swingInterface, Project project) {
150                 super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true);
151                 logger.log(Level.FINEST, "project: " + project);
152                 this.swingInterface = swingInterface;
153                 this.project = project;
154                 fileTreeModel = new ProjectFileTreeModel();
155                 fileCellRenderer = new FileCellRenderer();
156                 initActions();
157                 initComponents();
158                 pack();
159                 SwingUtils.center(this);
160         }
161
162         //
163         // ACTIONS
164         //
165
166         /**
167          * @see java.awt.Component#setVisible(boolean)
168          */
169         @Override
170         public void setVisible(boolean visible) {
171                 if (visible) {
172                         initiateFileScan();
173                 }
174                 super.setVisible(visible);
175         }
176
177         //
178         // PRIVATE METHODS
179         //
180
181         /**
182          * Initializes all actions.
183          */
184         private void initActions() {
185                 closeAction = new I18nAction("fileManager.button.close") {
186
187                         /**
188                          * {@inheritDoc}
189                          */
190                         public void actionPerformed(ActionEvent e) {
191                                 setVisible(false);
192                         }
193                 };
194                 rescanAction = new I18nAction("fileManager.button.rescan") {
195
196                         /**
197                          * {@inheritDoc}
198                          */
199                         @SuppressWarnings("synthetic-access")
200                         public void actionPerformed(ActionEvent actionEvent) {
201                                 initiateFileScan();
202                         }
203                 };
204                 setDefaultFileAction = new I18nAction("fileManager.menu.item.setDefaultFile") {
205
206                         /**
207                          * {@inheritDoc}
208                          */
209                         public void actionPerformed(ActionEvent e) {
210                                 /* TODO */
211                         }
212                 };
213                 insertAction = new I18nAction("fileManager.menu.item.insert") {
214
215                         /**
216                          * {@inheritDoc}
217                          */
218                         public void actionPerformed(ActionEvent e) {
219                                 /* TODO */
220                         }
221                 };
222         }
223
224         /**
225          * Initializes all components.
226          */
227         private void initComponents() {
228                 treeContextMenu = new JPopupMenu();
229                 treeContextMenu.add(setDefaultFileAction);
230                 insertCheckBoxMenuItem = new JCheckBoxMenuItem(insertAction);
231                 treeContextMenu.add(insertCheckBoxMenuItem);
232
233                 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
234                 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
235
236                 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
237                 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
238
239                 setContentPane(contentPanel);
240         }
241
242         /**
243          * Creates the main panel with the file tree and the file properties.
244          * 
245          * @return The mail panel
246          */
247         private Component createFileManagerPanel() {
248                 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
249
250                 /* file tree panel */
251                 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
252                 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
253
254                 fileTree = new JTree(fileTreeModel);
255                 fileTree.setShowsRootHandles(false);
256                 fileTree.addTreeSelectionListener(this);
257                 fileTree.addMouseListener(this);
258                 fileTree.setCellRenderer(fileCellRenderer);
259                 fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER);
260                 fileScrollPane.setPreferredSize(new Dimension(250, 400));
261
262                 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
263                 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
264                 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
265                 projectFilesLabelPanel.add(projectFilesLabel);
266
267                 /* the right panel */
268                 JPanel rightPanel = new JPanel(new BorderLayout(12, 12));
269                 fileManagerPanel.add(rightPanel, BorderLayout.CENTER);
270
271                 /* properties panel */
272                 JPanel propertiesPanel = new JPanel(new GridBagLayout());
273                 rightPanel.add(propertiesPanel, BorderLayout.CENTER);
274                 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
275
276                 filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties");
277                 filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD));
278                 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));
279
280                 filePathLabel = new I18nLabel("fileManager.label.filePath");
281                 filePathTextField = new JTextField();
282                 filePathTextField.setEditable(false);
283                 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));
284                 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));
285
286                 fileNameLabel = new I18nLabel("fileManager.label.fileName");
287                 fileNameTextField = new JTextField();
288                 fileNameTextField.setEditable(false);
289                 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));
290                 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));
291
292                 fileSizeLabel = new I18nLabel("fileManager.label.fileSize");
293                 fileSizeTextField = new JTextField();
294                 fileSizeTextField.setEditable(false);
295                 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));
296                 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));
297
298                 /* glue panel. */
299                 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 4, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
300
301                 /* action button panel */
302                 JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12));
303                 rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END);
304                 actionButtonPanel.setBorder(BorderFactory.createEtchedBorder());
305
306                 JButton rescanButton = new JButton(rescanAction);
307                 actionButtonPanel.add(rescanButton);
308
309                 return fileManagerPanel;
310         }
311
312         /**
313          * Creates the button panel.
314          * 
315          * @return The button panel
316          */
317         private Component createButtonPanel() {
318                 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
319
320                 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
321                 JButton closeButton = new JButton(closeAction);
322                 buttonPanel.add(closeButton);
323
324                 getRootPane().setDefaultButton(closeButton);
325                 return buttonPanel;
326         }
327
328         /**
329          * Initiates a file scan.
330          */
331         private void initiateFileScan() {
332                 swingInterface.getThreadPool().execute(new Runnable() {
333
334                         /**
335                          * @see java.lang.Runnable#run()
336                          */
337                         @SuppressWarnings("synthetic-access")
338                         public void run() {
339                                 fileTree.setEnabled(false);
340                                 rescanAction.setEnabled(false);
341                                 ProjectFile baseProjectFile = project.getBaseFile();
342                                 if (baseProjectFile != null) {
343                                         fileTreeModel.setBaseProjectFile(baseProjectFile);
344                                 }
345                                 // fileScrollPane.revalidate();
346                                 rescanAction.setEnabled(true);
347                                 fileTree.setEnabled(true);
348                         }
349
350                 });
351         }
352
353         /**
354          * Checks whether the given mouse event is a popup trigger and occured over
355          * a file. If so, the context menu is shown.
356          * 
357          * @param mouseEvent
358          *            The mouse event to check
359          */
360         private void maybeShowContextMenu(MouseEvent mouseEvent) {
361                 if (!mouseEvent.isPopupTrigger()) {
362                         return;
363                 }
364                 Point eventLocation = mouseEvent.getPoint();
365                 TreePath clickedPath = fileTree.getPathForLocation(eventLocation.x, eventLocation.y);
366                 if (clickedPath == null) {
367                         return;
368                 }
369                 fileTree.setSelectionPath(clickedPath);
370                 treeContextMenu.show(fileTree, eventLocation.x, eventLocation.y);
371         }
372
373         //
374         // INTERFACE I18nable
375         //
376
377         /**
378          * {@inheritDoc}
379          */
380         public void updateI18n() {
381                 setTitle(I18n.get("fileManager.title", project.getName()));
382                 projectFilesLabel.updateI18n();
383                 filePropertiesLabel.updateI18n();
384                 filePathLabel.updateI18n();
385         }
386
387         //
388         // INTERFACE TreeSelectionListener
389         //
390
391         /**
392          * {@inheritDoc}
393          */
394         public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
395                 TreePath[] selectedPaths = fileTree.getSelectionPaths();
396                 if (selectedPaths.length == 1) {
397                         Object lastPathComponent = selectedPaths[0].getLastPathComponent();
398                         if (!(lastPathComponent instanceof ProjectFileWrapper)) {
399                                 logger.log(Level.SEVERE, "lastPathComponent is not a ProjectFileWrapper!");
400                                 return;
401                         }
402                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) lastPathComponent;
403                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
404                         if (projectFile.isFile()) {
405                                 fileNameTextField.setText(projectFile.getName());
406                                 fileSizeTextField.setText(String.valueOf(projectFile.getSize()));
407                         }
408                 }
409         }
410
411         //
412         // INTERFACE ActionListener
413         //
414
415         /**
416          * {@inheritDoc}
417          */
418         public void actionPerformed(ActionEvent actionEvent) {
419                 /* TODO */
420         }
421
422         //
423         // INTERFACE MouseListener
424         //
425
426         /**
427          * {@inheritDoc}
428          */
429         public void mouseClicked(MouseEvent mouseEvent) {
430                 maybeShowContextMenu(mouseEvent);
431         }
432
433         /**
434          * {@inheritDoc}
435          */
436         public void mouseEntered(MouseEvent mouseEvent) {
437                 /* ignore. */
438         }
439
440         /**
441          * {@inheritDoc}
442          */
443         public void mouseExited(MouseEvent mouseEvent) {
444                 /* ignore. */
445         }
446
447         /**
448          * {@inheritDoc}
449          */
450         public void mousePressed(MouseEvent mouseEvent) {
451                 maybeShowContextMenu(mouseEvent);
452         }
453
454         /**
455          * {@inheritDoc}
456          */
457         public void mouseReleased(MouseEvent mouseEvent) {
458                 maybeShowContextMenu(mouseEvent);
459         }
460
461         /**
462          * Tree cell renderer that takes care of certain display properties for
463          * project-specific stuff.
464          * 
465          * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
466          */
467         private class FileCellRenderer extends DefaultTreeCellRenderer {
468
469                 /**
470                  * Empty constructor.
471                  */
472                 FileCellRenderer() {
473                         /* do nothing. */
474                 }
475
476                 /**
477                  * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
478                  *      java.lang.Object, boolean, boolean, boolean, int, boolean)
479                  */
480                 @SuppressWarnings("synthetic-access")
481                 @Override
482                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
483                         Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
484                         if (!(superCellRenderer instanceof JLabel)) {
485                                 logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!");
486                                 return superCellRenderer;
487                         }
488                         if (!(value instanceof ProjectFileWrapper)) {
489                                 logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!");
490                                 return superCellRenderer;
491                         }
492                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value;
493                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
494                         String completePath = projectFile.getCompletePath();
495                         if (projectFile.isHidden()) {
496                                 /* TODO - check override */
497                                 Color foreground = superCellRenderer.getForeground();
498                                 Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor();
499                                 Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2);
500                                 superCellRenderer.setForeground(averageColor);
501                         } else if (completePath.equals(project.getDefaultFile())) {
502                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
503                         } else if (projectFile.getParents().size() == 1) {
504                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
505                         } else {
506                                 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN));
507                         }
508                         return superCellRenderer;
509                 }
510
511         }
512
513         /**
514          * TreeModel that is based on {@link Project#getBaseFile()}.
515          * 
516          * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
517          */
518         private class ProjectFileTreeModel implements TreeModel {
519
520                 /** Tree model listeners. */
521                 private final List<TreeModelListener> treeModelListeners = Collections.synchronizedList(new ArrayList<TreeModelListener>());
522
523                 /** The base project file. */
524                 private ProjectFile baseProjectFile;
525
526                 /** Maps project files to wrappers. */
527                 private final Map<ProjectFile, ProjectFileWrapper> projectFileWrappers = Collections.synchronizedMap(new HashMap<ProjectFile, ProjectFileWrapper>());
528
529                 /**
530                  * Empty constructor.
531                  */
532                 ProjectFileTreeModel() {
533                         /* do nothing. */
534                 }
535
536                 //
537                 // EVENT MANAGEMENT
538                 //
539
540                 /**
541                  * {@inheritDoc}
542                  */
543                 public void addTreeModelListener(TreeModelListener treeModelListener) {
544                         treeModelListeners.add(treeModelListener);
545                 }
546
547                 /**
548                  * {@inheritDoc}
549                  */
550                 public void removeTreeModelListener(TreeModelListener treeModelListener) {
551                         treeModelListeners.remove(treeModelListener);
552                 }
553
554                 /**
555                  * Notifies all listeners that the tree structure has changed
556                  * significantly.
557                  * 
558                  * @see TreeModelListener#treeStructureChanged(TreeModelEvent)
559                  * @param newRootNode
560                  */
561                 protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) {
562                         for (TreeModelListener treeModelListener: treeModelListeners) {
563                                 treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode }));
564                         }
565                 }
566
567                 //
568                 // ACCESSORS
569                 //
570
571                 /**
572                  * Sets the new base project file. This causes the model to reload.
573                  * 
574                  * @param baseProjectFile
575                  *            The new base project file
576                  */
577                 @SuppressWarnings("synthetic-access")
578                 public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) {
579                         this.baseProjectFile = baseProjectFile;
580                         projectFileWrappers.clear();
581                         createWrappers(baseProjectFile);
582                         projectFileWrappers.get(baseProjectFile).setNameOverride(project.getName());
583                         fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile));
584                 }
585
586                 //
587                 // PRIVATE METHODS
588                 //
589
590                 /**
591                  * Creates {@link ProjectFileWrapper}s for all files below the given
592                  * project file.
593                  * 
594                  * @param projectFile
595                  *            The base project file for all project files to create
596                  *            wrappers for
597                  */
598                 private void createWrappers(ProjectFile projectFile) {
599                         projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile));
600                         for (ProjectFile projectFileChild: projectFile.getFiles()) {
601                                 if (projectFileChild.isDirectory()) {
602                                         createWrappers(projectFileChild);
603                                 }
604                                 projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild));
605                         }
606                 }
607
608                 //
609                 // INTERFACE TreeModel
610                 //
611
612                 /**
613                  * {@inheritDoc}
614                  */
615                 public Object getRoot() {
616                         return projectFileWrappers.get(baseProjectFile);
617                 }
618
619                 /**
620                  * {@inheritDoc}
621                  */
622                 @SuppressWarnings("synthetic-access")
623                 public Object getChild(Object parent, int index) {
624                         if (!(parent instanceof ProjectFileWrapper)) {
625                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
626                                 return null;
627                         }
628                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
629                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
630                         return projectFileWrappers.get(projectFile.getFiles().get(index));
631                 }
632
633                 /**
634                  * {@inheritDoc}
635                  */
636                 @SuppressWarnings("synthetic-access")
637                 public int getChildCount(Object parent) {
638                         if (!(parent instanceof ProjectFileWrapper)) {
639                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
640                                 return -1;
641                         }
642                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
643                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
644                         return projectFile.getFiles().size();
645                 }
646
647                 /**
648                  * {@inheritDoc}
649                  */
650                 @SuppressWarnings("synthetic-access")
651                 public int getIndexOfChild(Object parent, Object child) {
652                         if (!(parent instanceof ProjectFileWrapper)) {
653                                 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
654                                 return -1;
655                         }
656                         if (!(child instanceof ProjectFileWrapper)) {
657                                 logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!");
658                                 return -1;
659                         }
660                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
661                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
662                         return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile());
663                 }
664
665                 /**
666                  * {@inheritDoc}
667                  */
668                 @SuppressWarnings("synthetic-access")
669                 public boolean isLeaf(Object node) {
670                         if (!(node instanceof ProjectFileWrapper)) {
671                                 logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!");
672                                 return true;
673                         }
674                         ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node;
675                         ProjectFile projectFile = projectFileWrapper.getProjectFile();
676                         return projectFile.getFiles().isEmpty();
677                 }
678
679                 /**
680                  * {@inheritDoc}
681                  */
682                 public void valueForPathChanged(TreePath path, Object newValue) {
683                         /* ignore, items will not be modified in tree. */
684                 }
685
686         }
687
688         /**
689          * Wrapper around a {@link ProjectFile} that overwrites
690          * {@link Object#toString()} to return the project file’s name.
691          * 
692          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
693          */
694         private static class ProjectFileWrapper {
695
696                 /** The wrapped project file. */
697                 private final ProjectFile projectFile;
698
699                 /** The override name. */
700                 private String nameOverride;
701
702                 /**
703                  * Creates a new wrapper around a project file.
704                  * 
705                  * @param projectFile
706                  *            The project file to wrap
707                  */
708                 public ProjectFileWrapper(ProjectFile projectFile) {
709                         this.projectFile = projectFile;
710                 }
711
712                 /**
713                  * Returns the wrapped project file.
714                  * 
715                  * @return The wrapped project file
716                  */
717                 public ProjectFile getProjectFile() {
718                         return projectFile;
719                 }
720
721                 /**
722                  * Sets the name override. If the name override is not <code>null</code>
723                  * it will be shown insted of the project file’s name.
724                  * 
725                  * @param nameOverride
726                  *            The name override
727                  */
728                 void setNameOverride(String nameOverride) {
729                         this.nameOverride = nameOverride;
730                 }
731
732                 /**
733                  * {@inheritDoc}
734                  */
735                 @Override
736                 public String toString() {
737                         return (nameOverride != null) ? nameOverride : projectFile.getName();
738                 }
739
740         }
741
742 }