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.MouseEvent;
33 import java.awt.event.MouseListener;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyChangeListener;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.List;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
45 import javax.swing.AbstractAction;
46 import javax.swing.AbstractButton;
47 import javax.swing.Action;
48 import javax.swing.BorderFactory;
49 import javax.swing.ComboBoxModel;
50 import javax.swing.JButton;
51 import javax.swing.JCheckBox;
52 import javax.swing.JComboBox;
53 import javax.swing.JDialog;
54 import javax.swing.JLabel;
55 import javax.swing.JOptionPane;
56 import javax.swing.JPanel;
57 import javax.swing.JPopupMenu;
58 import javax.swing.JScrollPane;
59 import javax.swing.JTextField;
60 import javax.swing.JTree;
61 import javax.swing.event.ListDataListener;
62 import javax.swing.event.TreeModelEvent;
63 import javax.swing.event.TreeModelListener;
64 import javax.swing.event.TreeSelectionEvent;
65 import javax.swing.event.TreeSelectionListener;
66 import javax.swing.tree.DefaultTreeCellRenderer;
67 import javax.swing.tree.TreeModel;
68 import javax.swing.tree.TreePath;
70 import net.pterodactylus.jsite.i18n.I18n;
71 import net.pterodactylus.jsite.i18n.I18nable;
72 import net.pterodactylus.jsite.i18n.gui.I18nAction;
73 import net.pterodactylus.jsite.i18n.gui.I18nLabel;
74 import net.pterodactylus.jsite.i18n.gui.I18nMenu;
75 import net.pterodactylus.jsite.project.FileOverride;
76 import net.pterodactylus.jsite.project.Project;
77 import net.pterodactylus.jsite.project.ProjectFile;
78 import net.pterodactylus.util.io.MimeTypes;
79 import net.pterodactylus.util.logging.Logging;
80 import net.pterodactylus.util.swing.SwingUtils;
83 * Manages physical and virtual files in a project.
85 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
87 public class FileManager extends JDialog implements I18nable, TreeSelectionListener, MouseListener {
90 private static final Logger logger = Logging.getLogger(FileManager.class.getName());
92 /** The Swing interface. */
93 private final SwingInterface swingInterface;
95 /** The project whose files to manage. */
96 private final Project project;
98 /** The tree model for the project files. */
99 private final ProjectFileTreeModel fileTreeModel;
101 /** The tree cell renderer. */
102 private final FileCellRenderer fileCellRenderer;
104 /** The “rescan” action. */
105 private I18nAction rescanAction;
107 /** The “close” action. */
108 private I18nAction closeAction;
110 /** The “set default file” action. */
111 private I18nAction setDefaultFileAction;
113 /** The “project files” label. */
114 private I18nLabel projectFilesLabel;
116 /** The tree that shows the files. */
117 private JTree fileTree;
119 /** The scroll pane that holds the file tree. */
120 private JScrollPane fileScrollPane;
122 /** The “file properties” label. */
123 private I18nLabel filePropertiesLabel;
125 /** The “file path” label. */
126 private I18nLabel filePathLabel;
128 /** The “file path” textfield. */
129 private JTextField filePathTextField;
131 /** The “file name” label. */
132 private I18nLabel fileNameLabel;
134 /** The “file name” textfield. */
135 private JTextField fileNameTextField;
137 /** The “file size” label. */
138 private I18nLabel fileSizeLabel;
140 /** The “file size” text field. */
141 private JTextField fileSizeTextField;
143 /** The “insert settings” label. */
144 private I18nLabel insertSettingsLabel;
146 /** The “insert default” label. */
147 private I18nLabel insertDefaultLabel;
149 /** The “insert default” checkbox. */
150 private JCheckBox insertDefaultCheckBox;
152 /** The “override insert default” action. */
153 private I18nAction overrideInsertDefaultAction;
155 /** The “override insert default” checkbox. */
156 private JCheckBox overrideInsertDefaultCheckBox;
158 /** The “override insert” action. */
159 private Action insertOverrideAction;
161 /** The “override insert” checkbox. */
162 private JCheckBox insertOverrideCheckBox;
164 /** The “content type settings” label. */
165 private I18nLabel contentTypeSettingsLabel;
167 /** The “content type default” label. */
168 private I18nLabel contentTypeDefaultLabel;
170 /** The “content type default” text field. */
171 private JTextField contentTypeDefaultTextField;
173 /** The “override content type default” action. */
174 private I18nAction overrideContentTypeDefaultAction;
176 /** The “override content type default” checkbox. */
177 private JCheckBox overrideContentTypeDefaultCheckBox;
179 /** The “content type override” action. */
180 private Action contentTypeOverrideAction;
182 /** The “content type override” combo box. */
183 private JComboBox contentTypeOverrideComboBox;
185 /** The context menu for the tree. */
186 private JPopupMenu treeContextMenu;
188 /** The “apply override” menu. */
189 private I18nMenu overrideMenu;
191 /** The “apply insert override” action. */
192 private I18nAction applyInsertOverrideAction;
194 /** The “apply mime type override” action. */
195 private I18nAction applyMimeTypeOverrideAction;
197 /** The “remove override” action. */
198 private I18nAction removeOverrideAction;
201 * Creates a new file manager.
203 * @param swingInterface
204 * The Swing interface
206 * The project whose files to manage
208 public FileManager(SwingInterface swingInterface, Project project) {
209 super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true);
210 logger.log(Level.FINEST, "project: " + project);
211 this.swingInterface = swingInterface;
212 this.project = project;
213 fileTreeModel = new ProjectFileTreeModel();
214 project.addPropertyChangeListener(fileTreeModel);
215 fileCellRenderer = new FileCellRenderer();
219 SwingUtils.center(this);
227 * @see java.awt.Component#setVisible(boolean)
230 public void setVisible(boolean visible) {
234 super.setVisible(visible);
242 * Initializes all actions.
244 private void initActions() {
245 closeAction = new I18nAction("fileManager.button.close") {
250 @SuppressWarnings("synthetic-access")
251 public void actionPerformed(ActionEvent e) {
252 String defaultFile = project.getDefaultFile();
253 if ((defaultFile == null) || (defaultFile.length() == 0)) {
255 JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
258 ProjectFile projectFile = project.getFile(defaultFile);
259 if (projectFile == null) {
260 JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
266 rescanAction = new I18nAction("fileManager.button.rescan") {
271 @SuppressWarnings("synthetic-access")
272 public void actionPerformed(ActionEvent actionEvent) {
276 setDefaultFileAction = new I18nAction("fileManager.menu.item.setDefaultFile") {
281 @SuppressWarnings("synthetic-access")
282 public void actionPerformed(ActionEvent actionEvent) {
283 TreePath selectedPath = fileTree.getSelectionPath();
284 if (selectedPath == null) {
285 logger.log(Level.WARNING, "nothing selected!");
288 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent();
289 if (isHidden(projectFileWrapper)) {
291 JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
294 if (projectFileWrapper.getProjectFile().isDirectory()) {
296 JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
299 String completePath = projectFileWrapper.getProjectFile().getCompletePath();
300 project.setDefaultFile(completePath);
301 fileTree.setShowsRootHandles(false);
305 applyInsertOverrideAction = new I18nAction("fileManager.menu.item.applyInsertOverride") {
310 @SuppressWarnings("synthetic-access")
311 public void actionPerformed(ActionEvent actionEvent) {
312 applyInsertOverride();
315 applyMimeTypeOverrideAction = new I18nAction("fileManager.menu.item.applyMimeTypeOverride") {
320 @SuppressWarnings("synthetic-access")
321 public void actionPerformed(ActionEvent actionEvent) {
322 applyMimeTypeOverride();
325 removeOverrideAction = new I18nAction("fileManager.menu.item.removeOverride") {
330 @SuppressWarnings("synthetic-access")
331 public void actionPerformed(ActionEvent actionEvent) {
335 overrideInsertDefaultAction = new I18nAction("fileManager.checkbox.overrideInsertDefault") {
340 @SuppressWarnings("synthetic-access")
341 public void actionPerformed(ActionEvent actionEvent) {
342 boolean overrideInsert = overrideInsertDefaultCheckBox.isSelected();
343 insertOverrideAction.setEnabled(overrideInsert);
344 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
345 ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0);
346 ProjectFile projectFile = projectFileWrapper.getProjectFile();
347 FileOverride fileOverride = project.getFileOverride(projectFile);
348 if (overrideInsert) {
349 if (fileOverride == null) {
350 fileOverride = new FileOverride();
351 project.addFileOverride(projectFile, fileOverride);
352 fileOverride.setInsert(!projectFile.isHidden());
355 fileOverride.setInsert(null);
356 if (fileOverride.isEmpty()) {
357 project.removeFileOverride(projectFile);
362 overrideInsertDefaultAction.setEnabled(false);
363 insertOverrideAction = new AbstractAction() {
368 public void actionPerformed(ActionEvent actionEvent) {
369 boolean insertOverride = insertOverrideCheckBox.isSelected();
370 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
371 ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0);
372 ProjectFile projectFile = projectFileWrapper.getProjectFile();
373 FileOverride fileOverride = project.getFileOverride(projectFile);
374 fileOverride.setInsert(insertOverride);
375 fileTree.setShowsRootHandles(false);
379 insertOverrideAction.setEnabled(false);
380 overrideContentTypeDefaultAction = new I18nAction("fileManager.checkbox.overrideContentTypeDefault") {
383 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
385 public void actionPerformed(ActionEvent actionEvent) {
389 overrideContentTypeDefaultAction.setEnabled(false);
390 contentTypeOverrideAction = new AbstractAction() {
393 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
395 public void actionPerformed(ActionEvent actionEvent) {
399 contentTypeOverrideAction.setEnabled(false);
403 * Initializes all components.
405 private void initComponents() {
406 treeContextMenu = new JPopupMenu();
407 treeContextMenu.add(setDefaultFileAction);
409 overrideMenu = new I18nMenu("fileManager.menu.override");
410 treeContextMenu.add(overrideMenu);
412 overrideMenu.add(applyInsertOverrideAction);
413 overrideMenu.add(applyMimeTypeOverrideAction);
414 overrideMenu.addSeparator();
415 overrideMenu.add(removeOverrideAction);
417 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
418 contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
420 contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER);
421 contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END);
423 setContentPane(contentPanel);
427 * Creates the main panel with the file tree and the file properties.
429 * @return The mail panel
431 private Component createFileManagerPanel() {
432 JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12));
434 /* file tree panel */
435 JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12));
436 fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START);
438 fileTree = new JTree(fileTreeModel);
439 fileTree.setShowsRootHandles(false);
440 fileTree.addTreeSelectionListener(this);
441 fileTree.addMouseListener(this);
442 fileTree.setCellRenderer(fileCellRenderer);
443 fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER);
444 fileScrollPane.setPreferredSize(new Dimension(200, 350));
446 projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree);
447 JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
448 fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH);
449 projectFilesLabelPanel.add(projectFilesLabel);
451 /* the right panel */
452 JPanel rightPanel = new JPanel(new BorderLayout(12, 12));
453 fileManagerPanel.add(rightPanel, BorderLayout.CENTER);
455 /* properties panel */
456 JPanel propertiesPanel = new JPanel(new GridBagLayout());
457 rightPanel.add(propertiesPanel, BorderLayout.CENTER);
458 propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12)));
459 propertiesPanel.setPreferredSize(new Dimension(500, 350));
461 filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties");
462 filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD));
463 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));
465 filePathLabel = new I18nLabel("fileManager.label.filePath");
466 filePathTextField = new JTextField();
467 filePathTextField.setEditable(false);
468 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));
469 propertiesPanel.add(filePathTextField, new GridBagConstraints(1, 1, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
471 fileNameLabel = new I18nLabel("fileManager.label.fileName");
472 fileNameTextField = new JTextField();
473 fileNameTextField.setEditable(false);
474 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));
475 propertiesPanel.add(fileNameTextField, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
477 fileSizeLabel = new I18nLabel("fileManager.label.fileSize");
478 fileSizeTextField = new JTextField();
479 fileSizeTextField.setEditable(false);
480 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));
481 propertiesPanel.add(fileSizeTextField, new GridBagConstraints(1, 3, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
484 insertSettingsLabel = new I18nLabel("fileManager.label.insertSetting");
485 insertSettingsLabel.setFont(insertSettingsLabel.getFont().deriveFont(Font.BOLD));
486 propertiesPanel.add(insertSettingsLabel, new GridBagConstraints(0, 4, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(24, 0, 0, 0), 0, 0));
488 insertDefaultLabel = new I18nLabel("fileManager.label.insertDefault");
489 propertiesPanel.add(insertDefaultLabel, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
490 insertDefaultCheckBox = new JCheckBox();
491 insertDefaultCheckBox.setEnabled(false);
492 propertiesPanel.add(insertDefaultCheckBox, new GridBagConstraints(1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
493 overrideInsertDefaultCheckBox = new JCheckBox(overrideInsertDefaultAction);
494 propertiesPanel.add(overrideInsertDefaultCheckBox, new GridBagConstraints(0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
495 insertOverrideCheckBox = new JCheckBox(insertOverrideAction);
496 propertiesPanel.add(insertOverrideCheckBox, new GridBagConstraints(1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
498 /* content type settings. */
499 contentTypeSettingsLabel = new I18nLabel("fileManager.label.contentTypeSetting");
500 contentTypeSettingsLabel.setFont(contentTypeSettingsLabel.getFont().deriveFont(Font.BOLD));
501 propertiesPanel.add(contentTypeSettingsLabel, new GridBagConstraints(0, 7, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(24, 0, 0, 0), 0, 0));
503 contentTypeDefaultLabel = new I18nLabel("fileManager.label.contentTypeDefault");
504 propertiesPanel.add(contentTypeDefaultLabel, new GridBagConstraints(0, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
505 contentTypeDefaultTextField = new JTextField();
506 contentTypeDefaultTextField.setEditable(false);
507 propertiesPanel.add(contentTypeDefaultTextField, new GridBagConstraints(1, 8, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
508 overrideContentTypeDefaultCheckBox = new JCheckBox(overrideContentTypeDefaultAction);
509 propertiesPanel.add(overrideContentTypeDefaultCheckBox, new GridBagConstraints(0, 9, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0));
510 contentTypeOverrideComboBox = new JComboBox();
511 contentTypeOverrideComboBox.addActionListener(contentTypeOverrideAction);
512 propertiesPanel.add(contentTypeOverrideComboBox, new GridBagConstraints(1, 9, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0));
515 propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 10, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
517 /* action button panel */
518 JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12));
519 rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END);
520 actionButtonPanel.setBorder(BorderFactory.createEtchedBorder());
522 JButton rescanButton = new JButton(rescanAction);
523 actionButtonPanel.add(rescanButton);
525 return fileManagerPanel;
529 * Creates the button panel.
531 * @return The button panel
533 private Component createButtonPanel() {
534 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12));
536 buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12));
537 JButton closeButton = new JButton(closeAction);
538 buttonPanel.add(closeButton);
540 getRootPane().setDefaultButton(closeButton);
545 * Initiates a file scan.
547 private void initiateFileScan() {
548 swingInterface.getThreadPool().execute(new Runnable() {
551 * @see java.lang.Runnable#run()
553 @SuppressWarnings("synthetic-access")
555 fileTree.setEnabled(false);
556 rescanAction.setEnabled(false);
557 ProjectFile baseProjectFile = project.getBaseFile();
558 if (baseProjectFile != null) {
559 fileTreeModel.setBaseProjectFile(baseProjectFile);
561 // fileScrollPane.revalidate();
562 rescanAction.setEnabled(true);
563 fileTree.setEnabled(true);
570 * Checks whether the given mouse event is a popup trigger and occured over
571 * a file. If so, the context menu is shown.
574 * The mouse event to check
576 private void maybeShowContextMenu(MouseEvent mouseEvent) {
577 if (!mouseEvent.isPopupTrigger()) {
580 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(false);
581 TreePath clickedPath = fileTree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
582 ProjectFileWrapper clickedProjectFileWrapper = (ProjectFileWrapper) clickedPath.getLastPathComponent();
583 if (!selectedProjectFileWrappers.contains(clickedProjectFileWrapper)) {
584 fileTree.setSelectionPath(clickedPath);
585 selectedProjectFileWrappers = getSelectedProjectFileWrappers(false);
587 logger.log(Level.FINEST, "selectedProjectFileWrappers.size(): " + selectedProjectFileWrappers.size());
588 if (selectedProjectFileWrappers.size() == 1) {
589 ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0);
590 ProjectFile projectFile = projectFileWrapper.getProjectFile();
591 setDefaultFileAction.setEnabled(!isHidden(projectFileWrapper) && projectFile.isFile() && !projectFile.getCompletePath().equals(project.getDefaultFile()));
593 setDefaultFileAction.setEnabled(false);
595 overrideMenu.setVisible(selectedProjectFileWrappers.size() > 1);
596 treeContextMenu.show(fileTree, mouseEvent.getX(), mouseEvent.getY());
600 * Finds whether the {@link ProjectFile} given by
601 * <code>projectFileWrapper</code> is hidden.
603 * @param projectFileWrapper
604 * The wrapped project file
605 * @return <code>true</code> if the file is hidden and should not be
606 * inserted, <code>false</code> otherwise
608 private boolean isHidden(ProjectFileWrapper projectFileWrapper) {
609 ProjectFile projectFile = projectFileWrapper.getProjectFile();
610 FileOverride fileOverride = project.getFileOverrides().get(projectFile.getCompletePath());
611 logger.log(Level.FINEST, "fileOverride: " + fileOverride);
612 return ((fileOverride == null) && projectFile.isHidden()) || ((fileOverride != null) && (fileOverride.isInsert() == null) && projectFile.isHidden()) || ((fileOverride != null) && (fileOverride.isInsert() != null) && (Boolean.FALSE.equals(fileOverride.isInsert())));
616 * Opens the “apply insert override” dialog and lets the user apply an
617 * override for the “insert” setting for multiple files.
619 private void applyInsertOverride() {
620 JCheckBox insertCheckBox = new JCheckBox(I18n.get("fileManager.menu.item.insert.name"));
621 String okString = I18n.get("general.button.okay.name");
622 String cancelString = I18n.get("general.button.cancel.name");
623 int choice = JOptionPane.showOptionDialog(this, new Object[] { I18n.get("fileManager.dialog.insertOverride.message"), insertCheckBox }, I18n.get("fileManager.dialog.insertOverride.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { okString, cancelString }, okString);
624 logger.log(Level.FINEST, "choice: " + choice);
625 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
628 logger.log(Level.INFO, "selected insert override: " + insertCheckBox.isSelected());
629 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
630 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
631 ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile();
632 FileOverride fileOverride = project.getFileOverride(projectFile);
633 if (fileOverride == null) {
634 fileOverride = new FileOverride();
635 project.addFileOverride(projectFile, fileOverride);
637 fileOverride.setInsert(insertCheckBox.isSelected());
639 /* HACK - Swing sucks a bit */
640 fileTree.setShowsRootHandles(false);
644 * Opens the “apply mime type override” dialog and lets the user apply an
645 * override for the “mime type” setting for multiple files.
647 private void applyMimeTypeOverride() {
648 List<String> allMimeTypes = MimeTypes.getAllMimeTypes();
649 allMimeTypes.add(0, null);
650 JComboBox mimeTypeComboBox = new JComboBox(allMimeTypes.toArray());
651 String okString = I18n.get("general.button.okay.name");
652 String cancelString = I18n.get("general.button.cancel.name");
653 int choice = JOptionPane.showOptionDialog(this, new Object[] { I18n.get("fileManager.dialog.mimeTypeOverride.message"), mimeTypeComboBox }, I18n.get("fileManager.dialog.mimeTypeOverride.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { okString, cancelString }, okString);
654 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
657 logger.log(Level.FINE, "selected mime type: " + mimeTypeComboBox.getSelectedItem());
658 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
659 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
660 ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile();
661 FileOverride fileOverride = project.getFileOverride(projectFile);
662 if (fileOverride == null) {
663 fileOverride = new FileOverride();
664 project.addFileOverride(projectFile, fileOverride);
666 fileOverride.setContentType((String) mimeTypeComboBox.getSelectedItem());
671 * Removes the overrides of all selected files.
673 private void removeOverride() {
674 String continueString = I18n.get("general.button.continue.name");
675 String cancelString = I18n.get("general.button.cancel.name");
676 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
677 int choice = JOptionPane.showOptionDialog(this, I18n.get("fileManager.dialog.removeOverride.message", selectedProjectFileWrappers.size()), I18n.get("fileManager.dialog.removeOverride.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new Object[] { continueString, cancelString }, continueString);
678 if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) {
681 for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) {
682 project.removeFileOverride(selectedProjectFileWrapper.getProjectFile());
687 * Returns all currently selected {@link ProjectFileWrapper}s.
690 * <code>true</code> to return only selected files,
691 * <code>false</code> to include directories
692 * @return All selected project file wrappers
694 private List<ProjectFileWrapper> getSelectedProjectFileWrappers(boolean filesOnly) {
695 List<ProjectFileWrapper> selectedProjectFileWrappers = new ArrayList<ProjectFileWrapper>();
696 TreePath[] selectedPaths = fileTree.getSelectionPaths();
697 if (selectedPaths != null) {
698 for (TreePath selectedPath : selectedPaths) {
699 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent();
700 if (filesOnly && !projectFileWrapper.getProjectFile().isFile()) {
703 selectedProjectFileWrappers.add(projectFileWrapper);
706 return selectedProjectFileWrappers;
710 * Sets the given action’s enabled state to the given enabled state if the
711 * action’s current enabled state is not the given enabled state.
714 * The action to set the enabled state on
716 * The new enabled state of the action
718 private void setEnabled(Action action, boolean enabled) {
719 if (action.isEnabled() != enabled) {
720 action.setEnabled(enabled);
725 * Sets the given button’s selected state to the given selected state if the
726 * button’s current selected state is not the given selected state.
729 * The button to set the selected state on
731 * The new selected state of the button
733 private void setSelected(AbstractButton button, boolean selected) {
734 if (button.isSelected() != selected) {
735 button.setSelected(selected);
740 * Returns the extension of the given filename. If the file name does not
741 * have an extension, the name of the file (without any path components) is
745 * The name of the file
746 * @return The extension of the file
748 private String getFileExtension(String fileName) {
749 String lastComponent = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
750 return lastComponent.substring(lastComponent.lastIndexOf('.') + 1);
753 private void fillComboBox(String fileName) {
754 String fileExtension = getFileExtension(fileName);
755 List<String> allMimeTypes = MimeTypes.getAllMimeTypes();
756 List<String> eligibleMimeTypes = MimeTypes.getMimeTypes(fileExtension);
757 for (String mimeType: eligibleMimeTypes) {
758 allMimeTypes.remove(mimeType);
759 allMimeTypes.add(eligibleMimeTypes.indexOf(mimeType), mimeType);
761 allMimeTypes.add(eligibleMimeTypes.size(), "--");
762 allMimeTypes.add(0, null);
763 contentTypeOverrideComboBox.set
767 // INTERFACE I18nable
773 public void updateI18n() {
774 setTitle(I18n.get("fileManager.title", project.getName()));
775 projectFilesLabel.updateI18n();
776 filePropertiesLabel.updateI18n();
777 filePathLabel.updateI18n();
781 // INTERFACE TreeSelectionListener
787 public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
788 List<ProjectFileWrapper> selectedProjectFileWrappers = getSelectedProjectFileWrappers(true);
789 String filePathText = "";
790 String fileNameText = "";
791 String fileSizeText = "";
792 boolean insertDefaultSelected = false;
793 boolean overrideInsertEnabled = false;
794 boolean overrideInsertSelected = false;
795 boolean insertOverrideEnabled = false;
796 boolean insertOverrideSelected = false;
797 String defaultContentType = MimeTypes.DEFAULT_CONTENT_TYPE;
798 if (selectedProjectFileWrappers.size() == 1) {
799 ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0);
800 ProjectFile projectFile = projectFileWrapper.getProjectFile();
801 if (projectFile.isFile()) {
802 String completePath = projectFile.getCompletePath();
803 int lastSeparator = completePath.lastIndexOf(File.separatorChar);
804 if (lastSeparator != -1) {
805 filePathText = completePath.substring(0, lastSeparator);
807 fileNameText = projectFile.getName();
808 fileSizeText = String.valueOf(projectFile.getSize());
809 insertDefaultSelected = !projectFile.isHidden();
810 overrideInsertEnabled = true;
811 defaultContentType = MimeTypes.getMimeType(getFileExtension(projectFile.getName()));
812 FileOverride fileOverride = project.getFileOverride(projectFile);
813 if (fileOverride != null) {
814 Boolean overrideInsert = fileOverride.isInsert();
815 overrideInsertSelected = overrideInsert != null;
816 insertOverrideEnabled = overrideInsertSelected;
817 insertOverrideSelected = overrideInsertSelected ? overrideInsert : !projectFile.isHidden();
820 } else if (selectedProjectFileWrappers.size() > 1) {
823 filePathTextField.setText(filePathText);
824 fileNameTextField.setText(fileNameText);
825 fileSizeTextField.setText(fileSizeText);
826 setSelected(insertDefaultCheckBox, insertDefaultSelected);
827 setEnabled(overrideInsertDefaultAction, overrideInsertEnabled);
828 setSelected(overrideInsertDefaultCheckBox, overrideInsertSelected);
829 setEnabled(insertOverrideAction, insertOverrideEnabled);
830 setSelected(insertOverrideCheckBox, insertOverrideSelected);
831 contentTypeDefaultTextField.setText(defaultContentType);
835 // INTERFACE MouseListener
841 public void mouseClicked(MouseEvent mouseEvent) {
842 maybeShowContextMenu(mouseEvent);
848 public void mouseEntered(MouseEvent mouseEvent) {
855 public void mouseExited(MouseEvent mouseEvent) {
862 public void mousePressed(MouseEvent mouseEvent) {
863 maybeShowContextMenu(mouseEvent);
869 public void mouseReleased(MouseEvent mouseEvent) {
870 maybeShowContextMenu(mouseEvent);
874 * Tree cell renderer that takes care of certain display properties for
875 * project-specific stuff.
877 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
879 private class FileCellRenderer extends DefaultTreeCellRenderer {
889 * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
890 * java.lang.Object, boolean, boolean, boolean, int, boolean)
892 @SuppressWarnings("synthetic-access")
894 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
895 Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
896 if (!(superCellRenderer instanceof JLabel)) {
897 logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!");
898 return superCellRenderer;
900 if (!(value instanceof ProjectFileWrapper)) {
901 logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!");
902 return superCellRenderer;
904 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value;
905 ProjectFile projectFile = projectFileWrapper.getProjectFile();
906 FileOverride fileOverride = project.getFileOverride(projectFile);
907 String completePath = projectFile.getCompletePath();
908 boolean paintBold = false;
909 boolean paintHalfColor = false;
910 boolean showFile = !projectFile.isHidden();
911 if (fileOverride != null) {
912 Boolean insertOverride = fileOverride.isInsert();
913 if (insertOverride != null) {
914 showFile = insertOverride;
917 if (projectFile.isFile() && !showFile) {
918 paintHalfColor = true;
919 } else if (completePath.equals(project.getDefaultFile())) {
921 } else if (projectFile.getParents().size() == 1) {
924 if (paintHalfColor) {
925 /* TODO - cache colors */
926 Color foreground = superCellRenderer.getForeground();
927 Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor();
928 Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2);
929 superCellRenderer.setForeground(averageColor);
931 superCellRenderer.setForeground(selected ? getTextSelectionColor() : getTextNonSelectionColor());
934 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD));
936 superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN));
938 return superCellRenderer;
944 * TreeModel that is based on {@link Project#getBaseFile()}.
946 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
948 private class ProjectFileTreeModel implements TreeModel, PropertyChangeListener {
950 /** Tree model listeners. */
951 private final List<TreeModelListener> treeModelListeners = Collections.synchronizedList(new ArrayList<TreeModelListener>());
953 /** The base project file. */
954 private ProjectFile baseProjectFile;
956 /** Maps path names to project files. */
957 private final Map<String, ProjectFile> pathProjectFiles = Collections.synchronizedMap(new HashMap<String, ProjectFile>());
959 /** Maps project files to wrappers. */
960 private final Map<ProjectFile, ProjectFileWrapper> projectFileWrappers = Collections.synchronizedMap(new HashMap<ProjectFile, ProjectFileWrapper>());
965 ProjectFileTreeModel() {
976 public void addTreeModelListener(TreeModelListener treeModelListener) {
977 treeModelListeners.add(treeModelListener);
983 public void removeTreeModelListener(TreeModelListener treeModelListener) {
984 treeModelListeners.remove(treeModelListener);
988 * Notifies all listeners that a node has changed.
990 * @param changedProjectFileWrapper
991 * The wrapper around the changed project file
993 protected void fireTreeNodesChanged(ProjectFileWrapper changedProjectFileWrapper) {
994 ProjectFile changedProjectFile = changedProjectFileWrapper.getProjectFile();
995 ProjectFile changedProjectFileParent = changedProjectFile.getParent();
996 ProjectFile currentProjectFileParent = changedProjectFile;
997 List<ProjectFileWrapper> parentProjectFileWrappers = new ArrayList<ProjectFileWrapper>();
999 parentProjectFileWrappers.add(0, projectFileWrappers.get(currentProjectFileParent));
1000 currentProjectFileParent = currentProjectFileParent.getParent();
1001 } while (currentProjectFileParent != null);
1002 TreeModelEvent treeModelEvent = new TreeModelEvent(this, parentProjectFileWrappers.toArray(), new int[] { getIndexOfChild(projectFileWrappers.get(changedProjectFileParent), changedProjectFileWrapper) }, new Object[] { changedProjectFileWrapper });
1003 for (TreeModelListener treeModelListener : treeModelListeners) {
1004 treeModelListener.treeNodesChanged(treeModelEvent);
1009 * Notifies all listeners that the tree structure has changed
1012 * @see TreeModelListener#treeStructureChanged(TreeModelEvent)
1013 * @param newRootNode
1015 protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) {
1016 for (TreeModelListener treeModelListener : treeModelListeners) {
1017 treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode }));
1026 * Sets the new base project file. This causes the model to reload.
1028 * @param baseProjectFile
1029 * The new base project file
1031 @SuppressWarnings("synthetic-access")
1032 public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) {
1033 this.baseProjectFile = baseProjectFile;
1034 projectFileWrappers.clear();
1035 pathProjectFiles.clear();
1036 createWrappers(baseProjectFile);
1037 projectFileWrappers.get(baseProjectFile).setNameOverride(project.getName());
1038 fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile));
1046 * Creates {@link ProjectFileWrapper}s for all files below the given
1049 * @param projectFile
1050 * The base project file for all project files to create
1053 private void createWrappers(ProjectFile projectFile) {
1054 projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile));
1055 pathProjectFiles.put(projectFile.getCompletePath(), projectFile);
1056 for (ProjectFile projectFileChild : projectFile.getFiles()) {
1057 if (projectFileChild.isDirectory()) {
1058 createWrappers(projectFileChild);
1060 projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild));
1061 pathProjectFiles.put(projectFileChild.getCompletePath(), projectFileChild);
1066 // INTERFACE TreeModel
1072 public Object getRoot() {
1073 return projectFileWrappers.get(baseProjectFile);
1079 @SuppressWarnings("synthetic-access")
1080 public Object getChild(Object parent, int index) {
1081 if (!(parent instanceof ProjectFileWrapper)) {
1082 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
1085 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
1086 ProjectFile projectFile = projectFileWrapper.getProjectFile();
1087 return projectFileWrappers.get(projectFile.getFiles().get(index));
1093 @SuppressWarnings("synthetic-access")
1094 public int getChildCount(Object parent) {
1095 if (!(parent instanceof ProjectFileWrapper)) {
1096 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
1099 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
1100 ProjectFile projectFile = projectFileWrapper.getProjectFile();
1101 return projectFile.getFiles().size();
1107 @SuppressWarnings("synthetic-access")
1108 public int getIndexOfChild(Object parent, Object child) {
1109 if (!(parent instanceof ProjectFileWrapper)) {
1110 logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
1113 if (!(child instanceof ProjectFileWrapper)) {
1114 logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!");
1117 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
1118 ProjectFile projectFile = projectFileWrapper.getProjectFile();
1119 return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile());
1125 @SuppressWarnings("synthetic-access")
1126 public boolean isLeaf(Object node) {
1127 if (!(node instanceof ProjectFileWrapper)) {
1128 logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!");
1131 ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node;
1132 return projectFileWrapper.getProjectFile().isFile();
1138 public void valueForPathChanged(TreePath path, Object newValue) {
1139 /* ignore, items will not be modified in tree. */
1143 // INTERFACE PropertyChangeListener
1147 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
1149 @SuppressWarnings("synthetic-access")
1150 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
1151 if (Project.PROPERTY_DEFAULT_FILE.equals(propertyChangeEvent.getPropertyName())) {
1152 if (propertyChangeEvent.getOldValue() != null) {
1153 String oldCompletePath = (String) propertyChangeEvent.getOldValue();
1154 logger.log(Level.FINEST, "oldCompletePath: " + oldCompletePath);
1155 ProjectFile oldProjectFile = pathProjectFiles.get(oldCompletePath);
1156 logger.log(Level.FINEST, "oldProjectFile: " + oldProjectFile);
1157 ProjectFileWrapper oldProjectFileWrapper = projectFileWrappers.get(oldProjectFile);
1158 logger.log(Level.FINEST, "oldProjectFileWrapper: " + oldProjectFileWrapper);
1159 fireTreeNodesChanged(oldProjectFileWrapper);
1161 String newCompletePath = (String) propertyChangeEvent.getNewValue();
1162 ProjectFile newProjectFile = pathProjectFiles.get(newCompletePath);
1163 ProjectFileWrapper newProjectFileWrapper = projectFileWrappers.get(newProjectFile);
1164 System.out.println("newProjectFileWrapper: " + newProjectFileWrapper);
1165 fireTreeNodesChanged(newProjectFileWrapper);
1166 /* HACK - swing sucks a bit. */
1167 fileTree.setShowsRootHandles(false);
1174 * Wrapper around a {@link ProjectFile} that overwrites
1175 * {@link Object#toString()} to return the project file’s name.
1177 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
1179 private static class ProjectFileWrapper {
1181 /** The wrapped project file. */
1182 private final ProjectFile projectFile;
1184 /** The override name. */
1185 private String nameOverride;
1188 * Creates a new wrapper around a project file.
1190 * @param projectFile
1191 * The project file to wrap
1193 public ProjectFileWrapper(ProjectFile projectFile) {
1194 this.projectFile = projectFile;
1198 * Returns the wrapped project file.
1200 * @return The wrapped project file
1202 public ProjectFile getProjectFile() {
1207 * Sets the name override. If the name override is not <code>null</code>
1208 * it will be shown insted of the project file’s name.
1210 * @param nameOverride
1213 void setNameOverride(String nameOverride) {
1214 this.nameOverride = nameOverride;
1221 public String toString() {
1222 return (nameOverride != null) ? nameOverride : projectFile.getName();
1227 private class MimeTypeListModel implements ComboBoxModel {
1229 private List<ListDataListener> listDataListeners = new ArrayList<ListDataListener>();
1230 private Object selectedItem;
1233 * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
1235 public void addListDataListener(ListDataListener listDataListener) {
1236 listDataListeners.add(listDataListener);
1240 * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
1242 public void removeListDataListener(ListDataListener listDataListener) {
1243 listDataListeners.remove(listDataListener);
1247 * @see javax.swing.ComboBoxModel#getSelectedItem()
1249 public Object getSelectedItem() {
1250 return selectedItem;
1254 * @see javax.swing.ComboBoxModel#setSelectedItem(java.lang.Object)
1256 public void setSelectedItem(Object anItem) {
1257 selectedItem = anItem;
1261 * @see javax.swing.ListModel#getElementAt(int)
1263 public Object getElementAt(int index) {
1264 // TODO Auto-generated method stub
1269 * @see javax.swing.ListModel#getSize()
1271 public int getSize() {
1272 // TODO Auto-generated method stub