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.Color;
24 import java.awt.Component;
25 import java.awt.Dimension;
26 import java.awt.FlowLayout;
28 import java.awt.GridBagConstraints;
29 import java.awt.GridBagLayout;
30 import java.awt.Insets;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
41 import javax.swing.BorderFactory;
42 import javax.swing.JButton;
43 import javax.swing.JDialog;
44 import javax.swing.JLabel;
45 import javax.swing.JPanel;
46 import javax.swing.JScrollPane;
47 import javax.swing.JTextField;
48 import javax.swing.JTree;
49 import javax.swing.event.TreeModelEvent;
50 import javax.swing.event.TreeModelListener;
51 import javax.swing.event.TreeSelectionEvent;
52 import javax.swing.event.TreeSelectionListener;
53 import javax.swing.tree.DefaultTreeCellRenderer;
54 import javax.swing.tree.TreeModel;
55 import javax.swing.tree.TreePath;
57 import net.pterodactylus.jsite.i18n.I18n;
58 import net.pterodactylus.jsite.i18n.I18nable;
59 import net.pterodactylus.jsite.i18n.gui.I18nAction;
60 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
61 import net.pterodactylus.jsite.project.Project;
62 import net.pterodactylus.jsite.project.ProjectFile;
63 import net.pterodactylus.util.logging.Logging;
64 import net.pterodactylus.util.swing.SwingUtils;
67 * Manages physical and virtual files in a project.
69 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
71 public class FileManager extends JDialog implements I18nable, ActionListener, TreeSelectionListener {
74 private static final Logger logger = Logging.getLogger(FileManager.class.getName());
76 /** The Swing interface. */
77 private final SwingInterface swingInterface;
79 /** The project whose files to manage. */
80 private final Project project;
82 /** The tree model for the project files. */
83 private final ProjectFileTreeModel fileTreeModel;
85 /** The tree cell renderer. */
86 private final FileCellRenderer fileCellRenderer;
88 /** The “rescan” action. */
89 private I18nAction rescanAction;
91 /** The “close” action. */
92 private I18nAction closeAction;
94 /** The “project files” label. */
95 private I18nLabel projectFilesLabel;
97 /** The tree that shows the files. */
98 private JTree fileTree;
100 /** The scroll pane that holds the file tree. */
101 private JScrollPane fileScrollPane;
103 /** The “file properties” label. */
104 private I18nLabel filePropertiesLabel;
106 /** The “file path” label. */
107 private I18nLabel filePathLabel;
109 /** The “file path” textfield. */
110 private JTextField filePathTextField;
112 /** The “file name” label. */
113 private I18nLabel fileNameLabel;
115 /** The “file name” textfield. */
116 private JTextField fileNameTextField;
118 /** The “file size” label. */
119 private I18nLabel fileSizeLabel;
121 /** The “file size” text field. */
122 private JTextField fileSizeTextField;
125 * Creates a new file manager.
127 * @param swingInterface
128 * The Swing interface
130 * The project whose files to manage
132 public FileManager(SwingInterface swingInterface, Project project) {
133 super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true);
134 logger.log(Level.FINEST, "project: " + project);
135 this.swingInterface = swingInterface;
136 this.project = project;
137 fileTreeModel = new ProjectFileTreeModel();
138 fileCellRenderer = new FileCellRenderer();
142 SwingUtils.center(this);
150 * @see java.awt.Component#setVisible(boolean)
153 public void setVisible(boolean visible) {
157 super.setVisible(visible);
165 * Initializes all actions.
167 private void initActions() {
168 closeAction = new I18nAction("fileManager.button.close") {
173 public void actionPerformed(ActionEvent e) {
177 rescanAction = new I18nAction("fileManager.button.rescan") {
182 @SuppressWarnings("synthetic-access")
183 public void actionPerformed(ActionEvent actionEvent) {
190 * Initializes all components.
192 private void initComponents() {
193 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
194 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
196 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
197 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
199 setContentPane(contentPanel);
203 * Creates the main panel with the file tree and the file properties.
205 * @return The mail panel
207 private Component createFileManagerPanel() {
208 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
210 /* file tree panel */
211 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
212 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
214 fileTree = new JTree(fileTreeModel);
215 fileTree.setShowsRootHandles(false);
216 fileTree.addTreeSelectionListener(this);
217 fileTree.setCellRenderer(fileCellRenderer);
218 fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER);
219 fileScrollPane.setPreferredSize(new Dimension(250, 400));
221 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
222 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
223 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
224 projectFilesLabelPanel.add(projectFilesLabel);
226 /* the right panel */
227 JPanel rightPanel = new JPanel(new BorderLayout(12, 12));
228 fileManagerPanel.add(rightPanel, BorderLayout.CENTER);
230 /* properties panel */
231 JPanel propertiesPanel = new JPanel(new GridBagLayout());
232 rightPanel.add(propertiesPanel, BorderLayout.CENTER);
233 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
235 filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties");
236 filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD));
237 propertiesPanel.add(filePropertiesLabel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
239 filePathLabel = new I18nLabel("fileManager.label.filePath");
240 filePathTextField = new JTextField();
241 filePathTextField.setEditable(false);
242 propertiesPanel.add(filePathLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
243 propertiesPanel.add(filePathTextField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
245 fileNameLabel = new I18nLabel("fileManager.label.fileName");
246 fileNameTextField = new JTextField();
247 fileNameTextField.setEditable(false);
248 propertiesPanel.add(fileNameLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
249 propertiesPanel.add(fileNameTextField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
251 fileSizeLabel = new I18nLabel("fileManager.label.fileSize");
252 fileSizeTextField = new JTextField();
253 fileSizeTextField.setEditable(false);
254 propertiesPanel.add(fileSizeLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
255 propertiesPanel.add(fileSizeTextField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
258 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 4, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
260 /* action button panel */
261 JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12));
262 rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END);
263 actionButtonPanel.setBorder(BorderFactory.createEtchedBorder());
265 JButton rescanButton = new JButton(rescanAction);
266 actionButtonPanel.add(rescanButton);
268 return fileManagerPanel;
272 * Creates the button panel.
274 * @return The button panel
276 private Component createButtonPanel() {
277 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
279 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
280 JButton closeButton = new JButton(closeAction);
281 buttonPanel.add(closeButton);
283 getRootPane().setDefaultButton(closeButton);
288 * Initiates a file scan.
290 private void initiateFileScan() {
291 swingInterface.getThreadPool().execute(new Runnable() {
294 * @see java.lang.Runnable#run()
296 @SuppressWarnings("synthetic-access")
298 fileTree.setEnabled(false);
299 rescanAction.setEnabled(false);
300 ProjectFile baseProjectFile = project.getBaseFile();
301 if (baseProjectFile != null) {
302 fileTreeModel.setBaseProjectFile(baseProjectFile);
304 // fileScrollPane.revalidate();
305 rescanAction.setEnabled(true);
306 fileTree.setEnabled(true);
313 // INTERFACE I18nable
319 public void updateI18n() {
320 setTitle(I18n.get("fileManager.title", project.getName()));
321 projectFilesLabel.updateI18n();
322 filePropertiesLabel.updateI18n();
323 filePathLabel.updateI18n();
327 // INTERFACE TreeSelectionListener
333 public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
338 // INTERFACE ActionListener
344 public void actionPerformed(ActionEvent actionEvent) {
349 * Tree cell renderer that takes care of certain display properties for
350 * project-specific stuff.
352 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
354 private class FileCellRenderer extends DefaultTreeCellRenderer {
364 * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
365 * java.lang.Object, boolean, boolean, boolean, int, boolean)
367 @SuppressWarnings("synthetic-access")
369 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
370 Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
371 if (!(superCellRenderer instanceof JLabel)) {
372 logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!");
373 return superCellRenderer;
375 if (!(value instanceof ProjectFileWrapper)) {
376 logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!");
377 return superCellRenderer;
379 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value;
380 ProjectFile projectFile = projectFileWrapper.getProjectFile();
381 String completePath = projectFile.getCompletePath();
382 if (projectFile.isHidden()) {
383 /* TODO - check override */
384 Color foreground = superCellRenderer.getForeground();
385 Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor();
386 Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2);
387 superCellRenderer.setForeground(averageColor);
388 } else if (completePath.equals(project.getDefaultFile())) {
389 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
391 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN));
393 return superCellRenderer;
399 * TreeModel that is based on {@link Project#getBaseFile()}.
401 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
403 private class ProjectFileTreeModel implements TreeModel {
405 /** Tree model listeners. */
406 private final List<TreeModelListener> treeModelListeners = Collections.synchronizedList(new ArrayList<TreeModelListener>());
408 /** The base project file. */
409 private ProjectFile baseProjectFile;
411 /** Maps project files to wrappers. */
412 private final Map<ProjectFile, ProjectFileWrapper> projectFileWrappers = Collections.synchronizedMap(new HashMap<ProjectFile, ProjectFileWrapper>());
417 ProjectFileTreeModel() {
428 public void addTreeModelListener(TreeModelListener treeModelListener) {
429 treeModelListeners.add(treeModelListener);
435 public void removeTreeModelListener(TreeModelListener treeModelListener) {
436 treeModelListeners.remove(treeModelListener);
440 * Notifies all listeners that the tree structure has changed
443 * @see TreeModelListener#treeStructureChanged(TreeModelEvent)
446 protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) {
447 for (TreeModelListener treeModelListener: treeModelListeners) {
448 treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode }));
457 * Sets the new base project file. This causes the model to reload.
459 * @param baseProjectFile
460 * The new base project file
462 public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) {
463 this.baseProjectFile = baseProjectFile;
464 projectFileWrappers.clear();
465 createWrappers(baseProjectFile);
466 fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile));
474 * Creates {@link ProjectFileWrapper}s for all files below the given
478 * The base project file for all project files to create
481 private void createWrappers(ProjectFile projectFile) {
482 projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile));
483 for (ProjectFile projectFileChild: projectFile.getFiles()) {
484 if (projectFileChild.isDirectory()) {
485 createWrappers(projectFileChild);
487 projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild));
492 // INTERFACE TreeModel
498 public Object getRoot() {
499 return projectFileWrappers.get(baseProjectFile);
505 @SuppressWarnings("synthetic-access")
506 public Object getChild(Object parent, int index) {
507 if (!(parent instanceof ProjectFileWrapper)) {
508 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
511 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
512 ProjectFile projectFile = projectFileWrapper.getProjectFile();
513 return projectFileWrappers.get(projectFile.getFiles().get(index));
519 @SuppressWarnings("synthetic-access")
520 public int getChildCount(Object parent) {
521 if (!(parent instanceof ProjectFileWrapper)) {
522 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
525 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
526 ProjectFile projectFile = projectFileWrapper.getProjectFile();
527 return projectFile.getFiles().size();
533 @SuppressWarnings("synthetic-access")
534 public int getIndexOfChild(Object parent, Object child) {
535 if (!(parent instanceof ProjectFileWrapper)) {
536 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
539 if (!(child instanceof ProjectFileWrapper)) {
540 logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!");
543 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
544 ProjectFile projectFile = projectFileWrapper.getProjectFile();
545 return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile());
551 @SuppressWarnings("synthetic-access")
552 public boolean isLeaf(Object node) {
553 if (!(node instanceof ProjectFileWrapper)) {
554 logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!");
557 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node;
558 ProjectFile projectFile = projectFileWrapper.getProjectFile();
559 return projectFile.getFiles().isEmpty();
565 public void valueForPathChanged(TreePath path, Object newValue) {
566 /* ignore, items will not be modified in tree. */
572 * Wrapper around a {@link ProjectFile} that overwrites
573 * {@link Object#toString()} to return the project file’s name.
575 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
577 private static class ProjectFileWrapper {
579 /** The wrapped project file. */
580 private final ProjectFile projectFile;
583 * Creates a new wrapper around a project file.
586 * The project file to wrap
588 public ProjectFileWrapper(ProjectFile projectFile) {
589 this.projectFile = projectFile;
593 * Returns the wrapped project file.
595 * @return The wrapped project file
597 public ProjectFile getProjectFile() {
605 public String toString() {
606 return projectFile.getName();