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