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.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;
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;
65 * Manages physical and virtual files in a project.
67 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
69 public class FileManager extends JDialog implements I18nable, TreeSelectionListener {
72 private static final Logger logger = Logging.getLogger(FileManager.class.getName());
74 /** The project whose files to manage. */
75 private final Project project;
77 /** The tree model for the project files. */
78 private final FileTreeModel fileTreeModel;
80 /** The “close” action. */
81 private I18nAction closeAction;
83 /** The “project files” label. */
84 private I18nLabel projectFilesLabel;
86 /** The tree that shows the files. */
87 private JTree fileTree;
89 /** The “insert” action. */
90 private I18nAction insertAction;
92 /** The “insert” checkbox. */
93 private JCheckBox insertCheckBox;
95 /** The “mime type” label. */
96 private I18nLabel mimeTypeLabel;
98 /** The “mime type” combo box. */
99 private JComboBox mimeTypeComboBox;
102 * Creates a new file manager.
107 * The project whose files to manage
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();
117 SwingUtils.center(this);
125 * Initializes all actions.
127 private void initActions() {
128 closeAction = new I18nAction("fileManager.button.close") {
133 public void actionPerformed(ActionEvent e) {
137 insertAction = new I18nAction("fileManager.checkbox.insertFile") {
141 public void actionPerformed(ActionEvent actionEvent) {
142 /* TODO - implements. */
148 * Initializes all components.
150 private void initComponents() {
151 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
152 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
154 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
155 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
157 setContentPane(contentPanel);
161 * Creates the main panel with the file tree and the file properties.
163 * @return The mail panel
165 private Component createFileManagerPanel() {
166 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
168 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
169 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
171 fileTree = new JTree(fileTreeModel);
172 fileTree.setShowsRootHandles(false);
173 fileTreePanel.add(new JScrollPane(fileTree), BorderLayout.CENTER);
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);
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)));
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));
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));
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));
196 return fileManagerPanel;
200 * Creates the button panel.
202 * @return The button panel
204 private Component createButtonPanel() {
205 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
207 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
208 JButton closeButton = new JButton(closeAction);
209 buttonPanel.add(closeButton);
211 getRootPane().setDefaultButton(closeButton);
216 // INTERFACE I18nable
222 public void updateI18n() {
223 setTitle(I18n.get("fileManager.title", project.getName()));
227 // INTERFACE TreeSelectionListener
233 public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
234 /* TODO - implement */
238 * Model for the tree of files.
240 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
242 private class FileTreeModel implements TreeModel, PropertyChangeListener {
244 /** Tree model listeners. */
245 private final List<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
247 /** The tree of files. */
248 private final Tree<FileTreePath> fileTreePathTree = new Tree<FileTreePath>();
251 * Creates a new file tree model.
264 public void addTreeModelListener(TreeModelListener treeModelListener) {
265 treeModelListeners.add(treeModelListener);
271 public void removeTreeModelListener(TreeModelListener treeModelListener) {
272 treeModelListeners.remove(treeModelListener);
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();
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();
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);
322 @SuppressWarnings("synthetic-access")
323 public Object getRoot() {
324 logger.log(Level.FINEST, "getRoot()");
325 return fileTreePathTree.getRootNode().getChild(0).getElement();
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;
348 public void valueForPathChanged(TreePath path, Object newValue) {
349 /* TODO - implement */
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
362 * The element whose node to return
363 * @return The node, or <code>null</code> if no node could be found
365 private Node<FileTreePath> findNode(Object node) {
367 return fileTreePathTree.getRootNode().getChild(0);
369 return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node);
373 * Builds the tree from the project’s file entries.
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. */
390 * Traverses the tree of path nodes and converts all paths to
391 * {@link FileTreePath} objects, suitable for the JTree.
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.
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);
406 fileTreePathRootNode.sortChildren();
410 * Builds a tree matching the directory structure of the given entries.
412 * @param pathRootNode
413 * The root node of the tree
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);
426 currentPathNode = currentPathNode.getChild(directory);
433 // INTERFACE PropertyChangeListener
439 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
440 if (propertyChangeEvent.getSource() instanceof Project) {
441 if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) {
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.
454 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
456 private static class FileTreePath implements Comparable<FileTreePath> {
458 /** The complete file path. */
459 private final String filePath;
461 /** The file name. */
462 private final String fileName;
464 /** The file entry, if any. */
465 private Entry fileEntry;
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
474 * The complete file path
476 public FileTreePath(String filePath) {
477 this(filePath, null);
481 * Creates a new file tree path with the given file path and file name.
484 * The complete file path
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);
494 this.fileName = filePath;
497 this.fileName = fileName;
502 * Returns the complete file path.
504 * @return The file path
506 public String getFilePath() {
511 * Returns the file name, i.e. everything after the last
512 * {@link File#separatorChar}.
514 * @return The file name
516 public String getFileName() {
521 * Returns the file entry associated with this path, if any.
523 * @return The file entry associated with this path, or
524 * <code>null</code> if this path denotes a directory
526 public Entry getFileEntry() {
531 * Sets the entry associated with this path.
536 public void setFileEntry(Entry fileEntry) {
537 this.fileEntry = fileEntry;
544 public boolean equals(Object object) {
545 if ((object == null) || !(object instanceof FileTreePath)) {
548 FileTreePath fileTreePath = (FileTreePath) object;
549 return fileTreePath.filePath.equals(filePath);
556 public int hashCode() {
557 return filePath.hashCode();
564 public String toString() {
569 // INTERFACE Comparable
575 public int compareTo(FileTreePath otherFileTreePath) {
576 return filePath.compareTo(otherFileTreePath.filePath);