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.event.ActionEvent;
26 import java.beans.PropertyChangeEvent;
27 import java.beans.PropertyChangeListener;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
34 import javax.swing.BorderFactory;
35 import javax.swing.JButton;
36 import javax.swing.JDialog;
37 import javax.swing.JFrame;
38 import javax.swing.JPanel;
39 import javax.swing.JScrollPane;
40 import javax.swing.JTree;
41 import javax.swing.event.TreeModelListener;
42 import javax.swing.tree.TreeModel;
43 import javax.swing.tree.TreePath;
45 import net.pterodactylus.jsite.i18n.I18n;
46 import net.pterodactylus.jsite.i18n.I18nable;
47 import net.pterodactylus.jsite.i18n.gui.I18nAction;
48 import net.pterodactylus.jsite.project.Entry;
49 import net.pterodactylus.jsite.project.Project;
50 import net.pterodactylus.util.data.Node;
51 import net.pterodactylus.util.data.Tree;
52 import net.pterodactylus.util.logging.Logging;
53 import net.pterodactylus.util.swing.SwingUtils;
56 * Manages physical and virtual files in a project.
58 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
60 public class FileManager extends JDialog implements I18nable {
63 private static final Logger logger = Logging.getLogger(FileManager.class.getName());
65 /** The project whose files to manage. */
66 private final Project project;
68 /** The tree model for the project files. */
69 private final FileTreeModel fileTreeModel;
71 /** The “close” action. */
72 private I18nAction closeAction;
74 /** The tree that shows the files. */
75 private JTree fileTree;
78 * Creates a new file manager.
83 * The project whose files to manage
85 public FileManager(JFrame parent, Project project) {
86 super(parent, I18n.get("fileManager.title", project.getName()), true);
87 logger.log(Level.FINEST, "project: " + project);
88 this.project = project;
89 fileTreeModel = new FileTreeModel();
92 SwingUtils.repackCentered(this);
100 * Initializes all actions.
102 private void initActions() {
103 closeAction = new I18nAction("fileManager.button.close") {
108 public void actionPerformed(ActionEvent e) {
115 * Initializes all components.
117 private void initComponents() {
118 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
119 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
121 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
122 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
124 setContentPane(contentPanel);
128 * Creates the main panel with the file tree and the file properties.
130 * @return The mail panel
132 private Component createFileManagerPanel() {
133 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
134 fileManagerPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
136 fileTree = new JTree(fileTreeModel);
137 fileTree.setShowsRootHandles(false);
138 fileManagerPanel.add(new JScrollPane(fileTree), BorderLayout.CENTER);
140 return fileManagerPanel;
144 * Creates the button panel.
146 * @return The button panel
148 private Component createButtonPanel() {
149 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
151 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
152 JButton closeButton = new JButton(closeAction);
153 buttonPanel.add(closeButton);
155 getRootPane().setDefaultButton(closeButton);
160 // INTERFACE I18nable
166 public void updateI18n() {
167 setTitle(I18n.get("fileManager.title", project.getName()));
171 * Model for the tree of files.
173 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
175 private class FileTreeModel implements TreeModel, PropertyChangeListener {
177 /** Tree model listeners. */
178 private final List<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
180 /** The tree of files. */
181 private final Tree<FileTreePath> fileTreePathTree = new Tree<FileTreePath>();
184 * Creates a new file tree model.
197 public void addTreeModelListener(TreeModelListener treeModelListener) {
198 treeModelListeners.add(treeModelListener);
204 public void removeTreeModelListener(TreeModelListener treeModelListener) {
205 treeModelListeners.remove(treeModelListener);
215 @SuppressWarnings("synthetic-access")
216 public Object getChild(Object parent, int index) {
217 logger.log(Level.FINEST, "getChild(" + parent + ", " + index + ")");
218 Node<FileTreePath> parentNode = findNode(parent);
219 if (parentNode != null) {
220 return parentNode.getChild(index).getElement();
228 @SuppressWarnings("synthetic-access")
229 public int getChildCount(Object parent) {
230 logger.log(Level.FINEST, "getChildCount(" + parent + ")");
231 Node<FileTreePath> parentNode = findNode(parent);
232 if (parentNode != null) {
233 logger.log(Level.FINEST, "getChildCount(" + parent + "): " + parentNode.size());
234 return parentNode.size();
242 @SuppressWarnings("synthetic-access")
243 public int getIndexOfChild(Object parent, Object child) {
244 logger.log(Level.FINEST, "getIndexOfChild(" + parent + ", " + child + ")");
245 Node<FileTreePath> parentNode = findNode(parent);
246 if (parentNode != null) {
247 return parentNode.getIndexOfChild((FileTreePath) child);
255 @SuppressWarnings("synthetic-access")
256 public Object getRoot() {
257 logger.log(Level.FINEST, "getRoot()");
258 return fileTreePathTree.getRootNode().getChild(0).getElement();
264 @SuppressWarnings("synthetic-access")
265 public boolean isLeaf(Object node) {
266 logger.log(Level.FINEST, "isLeaf(" + node + ")");
267 Node<FileTreePath> parentNode = findNode(node);
268 if (parentNode != null) {
269 return parentNode.size() == 0;
281 public void valueForPathChanged(TreePath path, Object newValue) {
282 /* TODO - implement */
290 * Finds the node for the given object. This method is quite necessary
291 * because the element for the root node of the JTree is
295 * The element whose node to return
296 * @return The node, or <code>null</code> if no node could be found
298 private Node<FileTreePath> findNode(Object node) {
300 return fileTreePathTree.getRootNode().getChild(0);
302 return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node);
306 * Builds the tree from the project’s file entries.
308 @SuppressWarnings("synthetic-access")
309 private void buildTree() {
310 Tree<String> pathTree = new Tree<String>();
311 Node<String> pathRootNode = pathTree.getRootNode().addChild(File.separator);
312 logger.log(Level.FINEST, "project: " + project);
313 buildTree(pathRootNode, project.getBasePathEntries());
314 buildTree(pathRootNode, project.getVirtualEntries());
315 /* now convert to a tree suitable for the JTree. */
316 Node<FileTreePath> fileTreePathRootNode = fileTreePathTree.getRootNode();
317 fileTreePathRootNode.removeAllChildren();
318 convertTree(File.separator, pathRootNode, fileTreePathRootNode.addChild(new FileTreePath(File.separator, project.getName())));
319 /* TODO - now add entries to all file tree path tree nodes. */
323 * Traverses the tree of path nodes and converts all paths to
324 * {@link FileTreePath} objects, suitable for the JTree.
326 * @param completePath
327 * The base path of the current root node
328 * @param pathRootNode
329 * The root node of the path tree
330 * @param fileTreePathRootNode
331 * The root node of the file tree path tree.
333 private void convertTree(String completePath, Node<String> pathRootNode, Node<FileTreePath> fileTreePathRootNode) {
334 for (Node<String> pathChild: pathRootNode) {
335 String currentFilePath = completePath + pathChild.getElement();
336 Node<FileTreePath> newNode = fileTreePathRootNode.addChild(new FileTreePath(currentFilePath));
337 convertTree(currentFilePath, pathChild, newNode);
339 fileTreePathRootNode.sortChildren();
343 * Builds a tree matching the directory structure of the given entries.
345 * @param pathRootNode
346 * The root node of the tree
350 private void buildTree(Node<String> pathRootNode, List<Entry> entries) {
351 for (Entry basePathEntry: entries) {
352 String entryName = basePathEntry.getName();
353 String[] directories = entryName.split("\\" + File.separator);
354 Node<String> currentPathNode = pathRootNode;
355 for (String directory: directories) {
356 if (!currentPathNode.hasChild(directory)) {
357 currentPathNode = currentPathNode.addChild(directory);
359 currentPathNode = currentPathNode.getChild(directory);
366 // INTERFACE PropertyChangeListener
372 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
373 if (propertyChangeEvent.getSource() instanceof Project) {
374 if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) {
383 * Container that is used to back the {@link FileTreeModel}. Each
384 * FileTreePath contains a complete path name, a filename, and the
385 * associated {@link Entry}, if any.
387 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
389 private static class FileTreePath implements Comparable<FileTreePath> {
391 /** The complete file path. */
392 private final String filePath;
394 /** The file name. */
395 private final String fileName;
397 /** The file entry, if any. */
398 private Entry fileEntry;
401 * Creates a new file tree path with an auto-detected file name. The
402 * file name is everything after the last separator in the complete
403 * path, or the complete path itself if it does not contain any
407 * The complete file path
409 public FileTreePath(String filePath) {
410 this(filePath, null);
414 * Creates a new file tree path with the given file path and file name.
417 * The complete file path
421 public FileTreePath(String filePath, String fileName) {
422 this.filePath = filePath;
423 if (fileName == null) {
424 if (filePath.indexOf(File.separatorChar) != -1) {
425 this.fileName = filePath.substring(filePath.lastIndexOf(File.separatorChar) + 1);
427 this.fileName = filePath;
430 this.fileName = fileName;
435 * Returns the complete file path.
437 * @return The file path
439 public String getFilePath() {
444 * Returns the file name, i.e. everything after the last
445 * {@link File#separatorChar}.
447 * @return The file name
449 public String getFileName() {
454 * Returns the file entry associated with this path, if any.
456 * @return The file entry associated with this path, or
457 * <code>null</code> if this path denotes a directory
459 public Entry getFileEntry() {
464 * Sets the entry associated with this path.
469 public void setFileEntry(Entry fileEntry) {
470 this.fileEntry = fileEntry;
477 public boolean equals(Object object) {
478 if ((object == null) || !(object instanceof FileTreePath)) {
481 FileTreePath fileTreePath = (FileTreePath) object;
482 return fileTreePath.filePath.equals(filePath);
489 public int hashCode() {
490 return filePath.hashCode();
497 public String toString() {
502 // INTERFACE Comparable
508 public int compareTo(FileTreePath otherFileTreePath) {
509 return filePath.compareTo(otherFileTreePath.filePath);