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