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