2 * jSite2 - FileManager.java -
3 * Copyright © 2008 David Roden
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.
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.
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.
20 package net.pterodactylus.jsite.gui;
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;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
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;
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;
62 * Manages physical and virtual files in a project.
64 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
66 public class FileManager extends JDialog implements I18nable {
69 private static final Logger logger = Logging.getLogger(FileManager.class.getName());
71 /** The project whose files to manage. */
72 private final Project project;
74 /** The tree model for the project files. */
75 private final FileTreeModel fileTreeModel;
77 /** The “close” action. */
78 private I18nAction closeAction;
80 /** The tree that shows the files. */
81 private JTree fileTree;
83 /** The “mime type” label. */
84 private I18nLabel mimeTypeLabel;
86 /** The “mime type” combo box. */
87 private JComboBox mimeTypeComboBox;
90 * Creates a new file manager.
95 * The project whose files to manage
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();
105 SwingUtils.center(this);
113 * Initializes all actions.
115 private void initActions() {
116 closeAction = new I18nAction("fileManager.button.close") {
121 public void actionPerformed(ActionEvent e) {
128 * Initializes all components.
130 private void initComponents() {
131 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
132 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
134 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
135 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
137 setContentPane(contentPanel);
141 * Creates the main panel with the file tree and the file properties.
143 * @return The mail panel
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)));
149 fileTree = new JTree(fileTreeModel);
150 fileManagerPanel.add(new JScrollPane(fileTree), BorderLayout.LINE_START);
151 fileTree.setShowsRootHandles(false);
153 JPanel propertiesPanel = new JPanel(new GridBagLayout());
154 fileManagerPanel.add(propertiesPanel, BorderLayout.CENTER);
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));
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));
163 return fileManagerPanel;
167 * Creates the button panel.
169 * @return The button panel
171 private Component createButtonPanel() {
172 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
174 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
175 JButton closeButton = new JButton(closeAction);
176 buttonPanel.add(closeButton);
178 getRootPane().setDefaultButton(closeButton);
183 // INTERFACE I18nable
189 public void updateI18n() {
190 setTitle(I18n.get("fileManager.title", project.getName()));
194 * Model for the tree of files.
196 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
198 private class FileTreeModel implements TreeModel, PropertyChangeListener {
200 /** Tree model listeners. */
201 private final List<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
203 /** The tree of files. */
204 private final Tree<FileTreePath> fileTreePathTree = new Tree<FileTreePath>();
207 * Creates a new file tree model.
220 public void addTreeModelListener(TreeModelListener treeModelListener) {
221 treeModelListeners.add(treeModelListener);
227 public void removeTreeModelListener(TreeModelListener treeModelListener) {
228 treeModelListeners.remove(treeModelListener);
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();
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();
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);
278 @SuppressWarnings("synthetic-access")
279 public Object getRoot() {
280 logger.log(Level.FINEST, "getRoot()");
281 return fileTreePathTree.getRootNode().getChild(0).getElement();
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;
304 public void valueForPathChanged(TreePath path, Object newValue) {
305 /* TODO - implement */
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
318 * The element whose node to return
319 * @return The node, or <code>null</code> if no node could be found
321 private Node<FileTreePath> findNode(Object node) {
323 return fileTreePathTree.getRootNode().getChild(0);
325 return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node);
329 * Builds the tree from the project’s file entries.
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. */
346 * Traverses the tree of path nodes and converts all paths to
347 * {@link FileTreePath} objects, suitable for the JTree.
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.
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);
362 fileTreePathRootNode.sortChildren();
366 * Builds a tree matching the directory structure of the given entries.
368 * @param pathRootNode
369 * The root node of the tree
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);
382 currentPathNode = currentPathNode.getChild(directory);
389 // INTERFACE PropertyChangeListener
395 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
396 if (propertyChangeEvent.getSource() instanceof Project) {
397 if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) {
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.
410 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
412 private static class FileTreePath implements Comparable<FileTreePath> {
414 /** The complete file path. */
415 private final String filePath;
417 /** The file name. */
418 private final String fileName;
420 /** The file entry, if any. */
421 private Entry fileEntry;
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
430 * The complete file path
432 public FileTreePath(String filePath) {
433 this(filePath, null);
437 * Creates a new file tree path with the given file path and file name.
440 * The complete file path
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);
450 this.fileName = filePath;
453 this.fileName = fileName;
458 * Returns the complete file path.
460 * @return The file path
462 public String getFilePath() {
467 * Returns the file name, i.e. everything after the last
468 * {@link File#separatorChar}.
470 * @return The file name
472 public String getFileName() {
477 * Returns the file entry associated with this path, if any.
479 * @return The file entry associated with this path, or
480 * <code>null</code> if this path denotes a directory
482 public Entry getFileEntry() {
487 * Sets the entry associated with this path.
492 public void setFileEntry(Entry fileEntry) {
493 this.fileEntry = fileEntry;
500 public boolean equals(Object object) {
501 if ((object == null) || !(object instanceof FileTreePath)) {
504 FileTreePath fileTreePath = (FileTreePath) object;
505 return fileTreePath.filePath.equals(filePath);
512 public int hashCode() {
513 return filePath.hashCode();
520 public String toString() {
525 // INTERFACE Comparable
531 public int compareTo(FileTreePath otherFileTreePath) {
532 return filePath.compareTo(otherFileTreePath.filePath);