add label above project files
[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.Component;
24 import java.awt.FlowLayout;
25 import java.awt.GridBagConstraints;
26 import java.awt.GridBagLayout;
27 import java.awt.Insets;
28 import java.awt.event.ActionEvent;
29 import java.beans.PropertyChangeEvent;
30 import java.beans.PropertyChangeListener;
31 import java.io.File;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
36
37 import javax.swing.BorderFactory;
38 import javax.swing.JButton;
39 import javax.swing.JCheckBox;
40 import javax.swing.JComboBox;
41 import javax.swing.JDialog;
42 import javax.swing.JFrame;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollPane;
45 import javax.swing.JTree;
46 import javax.swing.event.TreeModelListener;
47 import javax.swing.event.TreeSelectionEvent;
48 import javax.swing.event.TreeSelectionListener;
49 import javax.swing.tree.TreeModel;
50 import javax.swing.tree.TreePath;
51
52 import net.pterodactylus.jsite.i18n.I18n;
53 import net.pterodactylus.jsite.i18n.I18nable;
54 import net.pterodactylus.jsite.i18n.gui.I18nAction;
55 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
56 import net.pterodactylus.jsite.project.Entry;
57 import net.pterodactylus.jsite.project.Project;
58 import net.pterodactylus.util.data.Node;
59 import net.pterodactylus.util.data.Tree;
60 import net.pterodactylus.util.io.MimeTypes;
61 import net.pterodactylus.util.logging.Logging;
62 import net.pterodactylus.util.swing.SwingUtils;
63
64 /**
65  * Manages physical and virtual files in a project.
66  * 
67  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
68  */
69 public class FileManager extends JDialog implements I18nable, TreeSelectionListener {
70
71         /** Logger. */
72         private static final Logger logger = Logging.getLogger(FileManager.class.getName());
73
74         /** The project whose files to manage. */
75         private final Project project;
76
77         /** The tree model for the project files. */
78         private final FileTreeModel fileTreeModel;
79
80         /** The “close” action. */
81         private I18nAction closeAction;
82
83         /** The “project files” label. */
84         private I18nLabel projectFilesLabel;
85         
86         /** The tree that shows the files. */
87         private JTree fileTree;
88
89         /** The “insert” action. */
90         private I18nAction insertAction;
91         
92         /** The “insert” checkbox. */
93         private JCheckBox insertCheckBox;
94         
95         /** The “mime type” label. */
96         private I18nLabel mimeTypeLabel;
97
98         /** The “mime type” combo box. */
99         private JComboBox mimeTypeComboBox;
100
101         /**
102          * Creates a new file manager.
103          * 
104          * @param parent
105          *            The parent frame
106          * @param project
107          *            The project whose files to manage
108          */
109         public FileManager(JFrame parent, Project project) {
110                 super(parent, I18n.get("fileManager.title", project.getName()), true);
111                 logger.log(Level.FINEST, "project: " + project);
112                 this.project = project;
113                 fileTreeModel = new FileTreeModel();
114                 initActions();
115                 initComponents();
116                 pack();
117                 SwingUtils.center(this);
118         }
119
120         //
121         // PRIVATE METHODS
122         //
123
124         /**
125          * Initializes all actions.
126          */
127         private void initActions() {
128                 closeAction = new I18nAction("fileManager.button.close") {
129
130                         /**
131                          * {@inheritDoc}
132                          */
133                         public void actionPerformed(ActionEvent e) {
134                                 setVisible(false);
135                         }
136                 };
137                 insertAction = new I18nAction("fileManager.checkbox.insertFile") {
138                         /**
139                          * {@inheritDoc}
140                          */
141                         public void actionPerformed(ActionEvent actionEvent) {
142                                 /* TODO - implements. */
143                         }
144                 };
145         }
146
147         /**
148          * Initializes all components.
149          */
150         private void initComponents() {
151                 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
152                 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
153
154                 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
155                 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
156
157                 setContentPane(contentPanel);
158         }
159
160         /**
161          * Creates the main panel with the file tree and the file properties.
162          * 
163          * @return The mail panel
164          */
165         private Component createFileManagerPanel() {
166                 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
167
168                 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
169                 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
170                 
171                 fileTree = new JTree(fileTreeModel);
172                 fileTree.setShowsRootHandles(false);
173                 fileTreePanel.add(new JScrollPane(fileTree), BorderLayout.CENTER);
174                 
175                 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
176                 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
177                 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
178                 projectFilesLabelPanel.add(projectFilesLabel);
179
180                 JPanel propertiesPanel = new JPanel(new GridBagLayout());
181                 fileManagerPanel.add(propertiesPanel, BorderLayout.CENTER);
182                 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
183
184                 insertCheckBox = new JCheckBox(insertAction);
185                 propertiesPanel.add(insertCheckBox, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
186
187                 List<String> allMimeTypes = MimeTypes.getAllMimeTypes();
188                 allMimeTypes.add(0, "auto-detect");
189                 mimeTypeComboBox = new JComboBox(allMimeTypes.toArray(new String[0]));
190                 mimeTypeLabel = new I18nLabel("fileManager.label.mimeType", mimeTypeComboBox);
191                 propertiesPanel.add(mimeTypeLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(6, 0, 0, 0), 0, 0));
192                 propertiesPanel.add(mimeTypeComboBox, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(6, 6, 0, 0), 0, 0));
193
194                 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
195
196                 return fileManagerPanel;
197         }
198
199         /**
200          * Creates the button panel.
201          * 
202          * @return The button panel
203          */
204         private Component createButtonPanel() {
205                 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
206
207                 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
208                 JButton closeButton = new JButton(closeAction);
209                 buttonPanel.add(closeButton);
210
211                 getRootPane().setDefaultButton(closeButton);
212                 return buttonPanel;
213         }
214
215         //
216         // INTERFACE I18nable
217         //
218
219         /**
220          * {@inheritDoc}
221          */
222         public void updateI18n() {
223                 setTitle(I18n.get("fileManager.title", project.getName()));
224         }
225
226         //
227         // INTERFACE TreeSelectionListener
228         //
229         
230         /**
231          * {@inheritDoc}
232          */
233         public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
234                 /* TODO - implement */
235         }
236
237         /**
238          * Model for the tree of files.
239          * 
240          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
241          */
242         private class FileTreeModel implements TreeModel, PropertyChangeListener {
243
244                 /** Tree model listeners. */
245                 private final List<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
246
247                 /** The tree of files. */
248                 private final Tree<FileTreePath> fileTreePathTree = new Tree<FileTreePath>();
249
250                 /**
251                  * Creates a new file tree model.
252                  */
253                 FileTreeModel() {
254                         buildTree();
255                 }
256
257                 //
258                 // EVENT MANAGEMENT
259                 //
260
261                 /**
262                  * {@inheritDoc}
263                  */
264                 public void addTreeModelListener(TreeModelListener treeModelListener) {
265                         treeModelListeners.add(treeModelListener);
266                 }
267
268                 /**
269                  * {@inheritDoc}
270                  */
271                 public void removeTreeModelListener(TreeModelListener treeModelListener) {
272                         treeModelListeners.remove(treeModelListener);
273                 }
274
275                 //
276                 // ACCESSORS
277                 //
278
279                 /**
280                  * {@inheritDoc}
281                  */
282                 @SuppressWarnings("synthetic-access")
283                 public Object getChild(Object parent, int index) {
284                         logger.log(Level.FINEST, "getChild(" + parent + ", " + index + ")");
285                         Node<FileTreePath> parentNode = findNode(parent);
286                         if (parentNode != null) {
287                                 return parentNode.getChild(index).getElement();
288                         }
289                         return null;
290                 }
291
292                 /**
293                  * {@inheritDoc}
294                  */
295                 @SuppressWarnings("synthetic-access")
296                 public int getChildCount(Object parent) {
297                         logger.log(Level.FINEST, "getChildCount(" + parent + ")");
298                         Node<FileTreePath> parentNode = findNode(parent);
299                         if (parentNode != null) {
300                                 logger.log(Level.FINEST, "getChildCount(" + parent + "): " + parentNode.size());
301                                 return parentNode.size();
302                         }
303                         return -1;
304                 }
305
306                 /**
307                  * {@inheritDoc}
308                  */
309                 @SuppressWarnings("synthetic-access")
310                 public int getIndexOfChild(Object parent, Object child) {
311                         logger.log(Level.FINEST, "getIndexOfChild(" + parent + ", " + child + ")");
312                         Node<FileTreePath> parentNode = findNode(parent);
313                         if (parentNode != null) {
314                                 return parentNode.getIndexOfChild((FileTreePath) child);
315                         }
316                         return -1;
317                 }
318
319                 /**
320                  * {@inheritDoc}
321                  */
322                 @SuppressWarnings("synthetic-access")
323                 public Object getRoot() {
324                         logger.log(Level.FINEST, "getRoot()");
325                         return fileTreePathTree.getRootNode().getChild(0).getElement();
326                 }
327
328                 /**
329                  * {@inheritDoc}
330                  */
331                 @SuppressWarnings("synthetic-access")
332                 public boolean isLeaf(Object node) {
333                         logger.log(Level.FINEST, "isLeaf(" + node + ")");
334                         Node<FileTreePath> parentNode = findNode(node);
335                         if (parentNode != null) {
336                                 return parentNode.size() == 0;
337                         }
338                         return true;
339                 }
340
341                 //
342                 // ACTIONS
343                 //
344
345                 /**
346                  * {@inheritDoc}
347                  */
348                 public void valueForPathChanged(TreePath path, Object newValue) {
349                         /* TODO - implement */
350                 }
351
352                 //
353                 // PRIVATE METHODS
354                 //
355
356                 /**
357                  * Finds the node for the given object. This method is quite necessary
358                  * because the element for the root node of the JTree is
359                  * <code>null</code>
360                  * 
361                  * @param node
362                  *            The element whose node to return
363                  * @return The node, or <code>null</code> if no node could be found
364                  */
365                 private Node<FileTreePath> findNode(Object node) {
366                         if (node == null) {
367                                 return fileTreePathTree.getRootNode().getChild(0);
368                         }
369                         return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node);
370                 }
371
372                 /**
373                  * Builds the tree from the project’s file entries.
374                  */
375                 @SuppressWarnings("synthetic-access")
376                 private void buildTree() {
377                         Tree<String> pathTree = new Tree<String>();
378                         Node<String> pathRootNode = pathTree.getRootNode().addChild(File.separator);
379                         logger.log(Level.FINEST, "project: " + project);
380                         buildTree(pathRootNode, project.getBasePathEntries());
381                         buildTree(pathRootNode, project.getVirtualEntries());
382                         /* now convert to a tree suitable for the JTree. */
383                         Node<FileTreePath> fileTreePathRootNode = fileTreePathTree.getRootNode();
384                         fileTreePathRootNode.removeAllChildren();
385                         convertTree(File.separator, pathRootNode, fileTreePathRootNode.addChild(new FileTreePath(File.separator, project.getName())));
386                         /* TODO - now add entries to all file tree path tree nodes. */
387                 }
388
389                 /**
390                  * Traverses the tree of path nodes and converts all paths to
391                  * {@link FileTreePath} objects, suitable for the JTree.
392                  * 
393                  * @param completePath
394                  *            The base path of the current root node
395                  * @param pathRootNode
396                  *            The root node of the path tree
397                  * @param fileTreePathRootNode
398                  *            The root node of the file tree path tree.
399                  */
400                 private void convertTree(String completePath, Node<String> pathRootNode, Node<FileTreePath> fileTreePathRootNode) {
401                         for (Node<String> pathChild: pathRootNode) {
402                                 String currentFilePath = completePath + pathChild.getElement();
403                                 Node<FileTreePath> newNode = fileTreePathRootNode.addChild(new FileTreePath(currentFilePath));
404                                 convertTree(currentFilePath, pathChild, newNode);
405                         }
406                         fileTreePathRootNode.sortChildren();
407                 }
408
409                 /**
410                  * Builds a tree matching the directory structure of the given entries.
411                  * 
412                  * @param pathRootNode
413                  *            The root node of the tree
414                  * @param entries
415                  *            The entries
416                  */
417                 private void buildTree(Node<String> pathRootNode, List<Entry> entries) {
418                         for (Entry basePathEntry: entries) {
419                                 String entryName = basePathEntry.getName();
420                                 String[] directories = entryName.split("\\" + File.separator);
421                                 Node<String> currentPathNode = pathRootNode;
422                                 for (String directory: directories) {
423                                         if (!currentPathNode.hasChild(directory)) {
424                                                 currentPathNode = currentPathNode.addChild(directory);
425                                         } else {
426                                                 currentPathNode = currentPathNode.getChild(directory);
427                                         }
428                                 }
429                         }
430                 }
431
432                 //
433                 // INTERFACE PropertyChangeListener
434                 //
435
436                 /**
437                  * {@inheritDoc}
438                  */
439                 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
440                         if (propertyChangeEvent.getSource() instanceof Project) {
441                                 if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) {
442                                         buildTree();
443                                 }
444                         }
445                 }
446
447         }
448
449         /**
450          * Container that is used to back the {@link FileTreeModel}. Each
451          * FileTreePath contains a complete path name, a filename, and the
452          * associated {@link Entry}, if any.
453          * 
454          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
455          */
456         private static class FileTreePath implements Comparable<FileTreePath> {
457
458                 /** The complete file path. */
459                 private final String filePath;
460
461                 /** The file name. */
462                 private final String fileName;
463
464                 /** The file entry, if any. */
465                 private Entry fileEntry;
466
467                 /**
468                  * Creates a new file tree path with an auto-detected file name. The
469                  * file name is everything after the last separator in the complete
470                  * path, or the complete path itself if it does not contain any
471                  * separators.
472                  * 
473                  * @param filePath
474                  *            The complete file path
475                  */
476                 public FileTreePath(String filePath) {
477                         this(filePath, null);
478                 }
479
480                 /**
481                  * Creates a new file tree path with the given file path and file name.
482                  * 
483                  * @param filePath
484                  *            The complete file path
485                  * @param fileName
486                  *            The file name
487                  */
488                 public FileTreePath(String filePath, String fileName) {
489                         this.filePath = filePath;
490                         if (fileName == null) {
491                                 if (filePath.indexOf(File.separatorChar) != -1) {
492                                         this.fileName = filePath.substring(filePath.lastIndexOf(File.separatorChar) + 1);
493                                 } else {
494                                         this.fileName = filePath;
495                                 }
496                         } else {
497                                 this.fileName = fileName;
498                         }
499                 }
500
501                 /**
502                  * Returns the complete file path.
503                  * 
504                  * @return The file path
505                  */
506                 public String getFilePath() {
507                         return filePath;
508                 }
509
510                 /**
511                  * Returns the file name, i.e. everything after the last
512                  * {@link File#separatorChar}.
513                  * 
514                  * @return The file name
515                  */
516                 public String getFileName() {
517                         return fileName;
518                 }
519
520                 /**
521                  * Returns the file entry associated with this path, if any.
522                  * 
523                  * @return The file entry associated with this path, or
524                  *         <code>null</code> if this path denotes a directory
525                  */
526                 public Entry getFileEntry() {
527                         return fileEntry;
528                 }
529
530                 /**
531                  * Sets the entry associated with this path.
532                  * 
533                  * @param fileEntry
534                  *            The entry
535                  */
536                 public void setFileEntry(Entry fileEntry) {
537                         this.fileEntry = fileEntry;
538                 }
539
540                 /**
541                  * {@inheritDoc}
542                  */
543                 @Override
544                 public boolean equals(Object object) {
545                         if ((object == null) || !(object instanceof FileTreePath)) {
546                                 return false;
547                         }
548                         FileTreePath fileTreePath = (FileTreePath) object;
549                         return fileTreePath.filePath.equals(filePath);
550                 }
551
552                 /**
553                  * {@inheritDoc}
554                  */
555                 @Override
556                 public int hashCode() {
557                         return filePath.hashCode();
558                 }
559
560                 /**
561                  * {@inheritDoc}
562                  */
563                 @Override
564                 public String toString() {
565                         return fileName;
566                 }
567
568                 //
569                 // INTERFACE Comparable
570                 //
571
572                 /**
573                  * {@inheritDoc}
574                  */
575                 public int compareTo(FileTreePath otherFileTreePath) {
576                         return filePath.compareTo(otherFileTreePath.filePath);
577                 }
578
579         }
580
581 }