X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fnet%2Fpterodactylus%2Fjsite%2Fgui%2FFileManager.java;h=953e05dc894ee4e6f5c67510ba4d0b6cda743243;hb=a5fcf3cbb126fe4115ff32d5adc0026f2fcf9e7f;hp=e4c8570549a55ed612c5edb1b3063cd4f60edc32;hpb=086aff15fcd35b2dfed9d64b24e5b7dacd8fb684;p=jSite2.git diff --git a/src/net/pterodactylus/jsite/gui/FileManager.java b/src/net/pterodactylus/jsite/gui/FileManager.java index e4c8570..953e05d 100644 --- a/src/net/pterodactylus/jsite/gui/FileManager.java +++ b/src/net/pterodactylus/jsite/gui/FileManager.java @@ -20,101 +20,204 @@ package net.pterodactylus.jsite.gui; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; +import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.AbstractButton; +import javax.swing.Action; import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; -import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSplitPane; +import javax.swing.JTextField; import javax.swing.JTree; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; +import net.pterodactylus.jsite.core.FileOverride; +import net.pterodactylus.jsite.core.Project; +import net.pterodactylus.jsite.core.ProjectFile; import net.pterodactylus.jsite.i18n.I18n; import net.pterodactylus.jsite.i18n.I18nable; import net.pterodactylus.jsite.i18n.gui.I18nAction; import net.pterodactylus.jsite.i18n.gui.I18nLabel; -import net.pterodactylus.jsite.project.Entry; -import net.pterodactylus.jsite.project.Project; -import net.pterodactylus.util.data.Node; -import net.pterodactylus.util.data.Tree; +import net.pterodactylus.jsite.i18n.gui.I18nMenu; import net.pterodactylus.util.io.MimeTypes; import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.swing.SwingUtils; /** * Manages physical and virtual files in a project. - * + * * @author David ‘Bombe’ Roden <bombe@freenetproject.org> */ -public class FileManager extends JDialog implements I18nable, ActionListener, TreeSelectionListener { +public class FileManager extends JDialog implements I18nable, TreeSelectionListener, MouseListener { /** Logger. */ private static final Logger logger = Logging.getLogger(FileManager.class.getName()); + /** The Swing interface. */ + private final SwingInterface swingInterface; + /** The project whose files to manage. */ private final Project project; /** The tree model for the project files. */ - private final FileTreeModel fileTreeModel; + private final ProjectFileTreeModel fileTreeModel; + + /** The tree cell renderer. */ + private final FileCellRenderer fileCellRenderer; + + /** The “rescan” action. */ + private I18nAction rescanAction; /** The “close” action. */ private I18nAction closeAction; + /** The “set default file” action. */ + private I18nAction setDefaultFileAction; + /** The “project files” label. */ private I18nLabel projectFilesLabel; /** The tree that shows the files. */ private JTree fileTree; - /** The “insert” action. */ - private I18nAction insertAction; + /** The scroll pane that holds the file tree. */ + private JScrollPane fileScrollPane; + + /** The “file properties” label. */ + private I18nLabel filePropertiesLabel; + + /** The “file path” label. */ + private I18nLabel filePathLabel; + + /** The “file path” textfield. */ + private JTextField filePathTextField; + + /** The “file name” label. */ + private I18nLabel fileNameLabel; + + /** The “file name” textfield. */ + private JTextField fileNameTextField; + + /** The “file size” label. */ + private I18nLabel fileSizeLabel; + + /** The “file size” text field. */ + private JTextField fileSizeTextField; + + /** The “insert settings” label. */ + private I18nLabel insertSettingsLabel; + + /** The “insert default” label. */ + private I18nLabel insertDefaultLabel; + + /** The “insert default” checkbox. */ + private JCheckBox insertDefaultCheckBox; + + /** The “override insert default” action. */ + private I18nAction overrideInsertDefaultAction; + + /** The “override insert default” checkbox. */ + private JCheckBox overrideInsertDefaultCheckBox; + + /** The “override insert” action. */ + private Action insertOverrideAction; + + /** The “override insert” checkbox. */ + private JCheckBox insertOverrideCheckBox; + + /** The “content type settings” label. */ + private I18nLabel contentTypeSettingsLabel; + + /** The “content type default” label. */ + private I18nLabel contentTypeDefaultLabel; - /** The “insert” checkbox. */ - private JCheckBox insertCheckBox; + /** The “content type default” text field. */ + private JTextField contentTypeDefaultTextField; - /** The “use custom mime type” action. */ - private I18nAction useCustomMimeTypeAction; + /** The “override content type default” action. */ + private I18nAction overrideContentTypeDefaultAction; - /** The “use custom mime type” checkbox. */ - private JCheckBox useCustomMimeTypeCheckBox; + /** The “override content type default” checkbox. */ + private JCheckBox overrideContentTypeDefaultCheckBox; - /** The “mime type” combo box. */ - private JComboBox mimeTypeComboBox; + /** The “content type override” action. */ + private Action contentTypeOverrideAction; + + /** The “content type override” combo box. */ + private JComboBox contentTypeOverrideComboBox; + + /** The context menu for the tree. */ + private JPopupMenu treeContextMenu; + + /** The “apply override” menu. */ + private I18nMenu overrideMenu; + + /** The “apply insert override” action. */ + private I18nAction applyInsertOverrideAction; + + /** The “apply mime type override” action. */ + private I18nAction applyMimeTypeOverrideAction; + + /** The “remove override” action. */ + private I18nAction removeOverrideAction; /** * Creates a new file manager. - * - * @param parent - * The parent frame + * + * @param swingInterface + * The Swing interface * @param project * The project whose files to manage */ - public FileManager(JFrame parent, Project project) { - super(parent, I18n.get("fileManager.title", project.getName()), true); + public FileManager(SwingInterface swingInterface, Project project) { + super(swingInterface.getMainWindow(), I18n.get("fileManager.title", project.getName()), true); logger.log(Level.FINEST, "project: " + project); + this.swingInterface = swingInterface; this.project = project; - fileTreeModel = new FileTreeModel(); + fileTreeModel = new ProjectFileTreeModel(); + project.addPropertyChangeListener(fileTreeModel); + fileCellRenderer = new FileCellRenderer(); initActions(); initComponents(); pack(); @@ -122,6 +225,21 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr } // + // ACTIONS + // + + /** + * @see java.awt.Component#setVisible(boolean) + */ + @Override + public void setVisible(boolean visible) { + if (visible) { + initiateFileScan(); + } + super.setVisible(visible); + } + + // // PRIVATE METHODS // @@ -134,47 +252,219 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** * {@inheritDoc} */ + @SuppressWarnings("synthetic-access") public void actionPerformed(ActionEvent e) { - setVisible(false); + String defaultFile = project.getDefaultFile(); + if ((defaultFile == null) || (defaultFile.length() == 0)) { + int selectedOption = JOptionPane.showOptionDialog(FileManager.this, I18n.get("fileManager.dialog.noDefaultFileSet.message"), I18n.get("fileManager.dialog.noDefaultFileSet.title"), 0, JOptionPane.WARNING_MESSAGE, null, new String[] { I18n.get("general.button.continue.name"), I18n.get("general.button.cancel.name") }, I18n.get("general.button.cancel.name")); + if (selectedOption == 1) { + return; + } + } else { + ProjectFile projectFile = project.getFile(defaultFile); + if (projectFile == null) { + /* TODO - i18n */ + JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); + return; + } + setVisible(false); + } } }; - insertAction = new I18nAction("fileManager.checkbox.insertFile") { + rescanAction = new I18nAction("fileManager.button.rescan") { /** * {@inheritDoc} */ @SuppressWarnings("synthetic-access") public void actionPerformed(ActionEvent actionEvent) { - useCustomMimeTypeAction.setEnabled(insertCheckBox.isSelected()); - mimeTypeComboBox.setEnabled(insertCheckBox.isSelected()); - for (Entry entry: getSelectedEntries()) { - entry.setInsert(insertCheckBox.isSelected()); + initiateFileScan(); + } + }; + setDefaultFileAction = new I18nAction("fileManager.menu.item.setDefaultFile") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + TreePath selectedPath = fileTree.getSelectionPath(); + if (selectedPath == null) { + logger.log(Level.WARNING, "nothing selected!"); + return; } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent(); + if (isHidden(projectFileWrapper)) { + /* TODO - i18n */ + JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); + return; + } + if (projectFileWrapper.getProjectFile().isDirectory()) { + /* TODO - i18n */ + JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); + return; + } + String completePath = projectFileWrapper.getProjectFile().getCompletePath(); + project.setDefaultFile(completePath); + fileTree.setShowsRootHandles(false); + fileTree.repaint(); } }; - insertAction.setEnabled(false); - useCustomMimeTypeAction = new I18nAction("fileManager.checkbox.useCustomMimeType") { + applyInsertOverrideAction = new I18nAction("fileManager.menu.item.applyInsertOverride") { /** * {@inheritDoc} */ @SuppressWarnings("synthetic-access") public void actionPerformed(ActionEvent actionEvent) { - mimeTypeComboBox.setEnabled(useCustomMimeTypeCheckBox.isSelected()); - if (!useCustomMimeTypeCheckBox.isSelected()) { - for (Entry entry: getSelectedEntries()) { - entry.restoreDefaultContentType(); + applyInsertOverride(); + } + }; + applyMimeTypeOverrideAction = new I18nAction("fileManager.menu.item.applyMimeTypeOverride") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + applyMimeTypeOverride(); + } + }; + removeOverrideAction = new I18nAction("fileManager.menu.item.removeOverride") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + removeOverride(); + } + }; + overrideInsertDefaultAction = new I18nAction("fileManager.checkbox.overrideInsertDefault") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + boolean overrideInsert = overrideInsertDefaultCheckBox.isSelected(); + insertOverrideAction.setEnabled(overrideInsert); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (overrideInsert) { + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + fileOverride.setInsert(!projectFile.isHidden()); + } + } else { + fileOverride.setInsert(null); + if (fileOverride.isEmpty()) { + project.removeFileOverride(projectFile); } } + fileTree.setShowsRootHandles(false); + fileTree.repaint(); } }; - useCustomMimeTypeAction.setEnabled(false); + overrideInsertDefaultAction.setEnabled(false); + insertOverrideAction = new AbstractAction() { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + boolean insertOverride = insertOverrideCheckBox.isSelected(); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + fileOverride.setInsert(insertOverride); + fileTree.setShowsRootHandles(false); + fileTree.repaint(); + } + }; + insertOverrideAction.setEnabled(false); + overrideContentTypeDefaultAction = new I18nAction("fileManager.checkbox.overrideContentTypeDefault") { + + /** + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + boolean contentTypeOverrideEnabled = overrideContentTypeDefaultCheckBox.isSelected(); + contentTypeOverrideComboBox.setEnabled(contentTypeOverrideEnabled); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (contentTypeOverrideEnabled) { + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + } + String projectFileName = projectFile.getName(); + String mimeType = MimeTypes.getMimeType(getFileExtension(projectFileName)); + fileOverride.setContentType(mimeType); + fillComboBox(projectFileName); + contentTypeOverrideComboBox.setSelectedItem(mimeType); + } else { + fileOverride.setContentType(null); + if (fileOverride.isEmpty()) { + project.removeFileOverride(projectFile); + } + } + } + }; + overrideContentTypeDefaultAction.setEnabled(false); + contentTypeOverrideAction = new AbstractAction() { + + /** + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + if (!contentTypeOverrideComboBox.isEnabled()) { + return; + } + String selectedContentType = (String) contentTypeOverrideComboBox.getSelectedItem(); + logger.log(Level.FINEST, "selectedContentType: " + selectedContentType); + if ("--".equals(selectedContentType)) { + return; + } + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + for (ProjectFileWrapper projectFileWrapper : selectedProjectFileWrappers) { + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + } + fileOverride.setContentType(selectedContentType); + } + } + }; + contentTypeOverrideAction.setEnabled(false); } /** * Initializes all components. */ private void initComponents() { + treeContextMenu = new JPopupMenu(); + treeContextMenu.add(setDefaultFileAction); + + overrideMenu = new I18nMenu("fileManager.menu.override"); + treeContextMenu.add(overrideMenu); + + overrideMenu.add(applyInsertOverrideAction); + overrideMenu.add(applyMimeTypeOverrideAction); + overrideMenu.addSeparator(); + overrideMenu.add(removeOverrideAction); + JPanel contentPanel = new JPanel(new BorderLayout(12, 12)); contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); @@ -186,48 +476,114 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** * Creates the main panel with the file tree and the file properties. - * + * * @return The mail panel */ private Component createFileManagerPanel() { - JPanel fileManagerPanel = new JPanel(new BorderLayout(12, 12)); + JSplitPane fileManagerPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true); + fileManagerPanel.setBorder(null); + /* file tree panel */ JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12)); - fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START); + fileTreePanel.setBorder(new EmptyBorder(0, 0, 0, 12)); + fileManagerPanel.setLeftComponent(fileTreePanel); fileTree = new JTree(fileTreeModel); fileTree.setShowsRootHandles(false); fileTree.addTreeSelectionListener(this); - fileTreePanel.add(new JScrollPane(fileTree), BorderLayout.CENTER); + fileTree.addMouseListener(this); + fileTree.setCellRenderer(fileCellRenderer); + fileTreePanel.add(fileScrollPane = new JScrollPane(fileTree), BorderLayout.CENTER); + fileScrollPane.setPreferredSize(new Dimension(200, 350)); projectFilesLabel = new I18nLabel("fileManager.label.projectFiles", fileTree); JPanel projectFilesLabelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); fileTreePanel.add(projectFilesLabelPanel, BorderLayout.NORTH); projectFilesLabelPanel.add(projectFilesLabel); + /* the right panel */ + JPanel rightPanel = new JPanel(new BorderLayout(12, 12)); + rightPanel.setBorder(new EmptyBorder(0, 12, 0, 0)); + fileManagerPanel.setRightComponent(rightPanel); + + /* properties panel */ JPanel propertiesPanel = new JPanel(new GridBagLayout()); - fileManagerPanel.add(propertiesPanel, BorderLayout.CENTER); + rightPanel.add(propertiesPanel, BorderLayout.CENTER); propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12))); - - insertCheckBox = new JCheckBox(insertAction); - 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)); - - useCustomMimeTypeCheckBox = new JCheckBox(useCustomMimeTypeAction); - List allMimeTypes = MimeTypes.getAllMimeTypes(); - mimeTypeComboBox = new JComboBox(allMimeTypes.toArray(new String[0])); - mimeTypeComboBox.setEnabled(false); - mimeTypeComboBox.addActionListener(this); - propertiesPanel.add(useCustomMimeTypeCheckBox, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(6, 0, 0, 0), 0, 0)); - 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)); - - 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)); + propertiesPanel.setPreferredSize(new Dimension(500, 350)); + + filePropertiesLabel = new I18nLabel("fileManager.label.fileProperties"); + filePropertiesLabel.setFont(filePropertiesLabel.getFont().deriveFont(Font.BOLD)); + 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)); + + filePathLabel = new I18nLabel("fileManager.label.filePath"); + filePathTextField = new JTextField(); + filePathTextField.setEditable(false); + 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)); + 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)); + + fileNameLabel = new I18nLabel("fileManager.label.fileName"); + fileNameTextField = new JTextField(); + fileNameTextField.setEditable(false); + 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)); + 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)); + + fileSizeLabel = new I18nLabel("fileManager.label.fileSize"); + fileSizeTextField = new JTextField(); + fileSizeTextField.setEditable(false); + 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)); + 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)); + + /* file settings. */ + insertSettingsLabel = new I18nLabel("fileManager.label.insertSetting"); + insertSettingsLabel.setFont(insertSettingsLabel.getFont().deriveFont(Font.BOLD)); + 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)); + + insertDefaultLabel = new I18nLabel("fileManager.label.insertDefault"); + 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)); + insertDefaultCheckBox = new JCheckBox(); + insertDefaultCheckBox.setEnabled(false); + 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)); + overrideInsertDefaultCheckBox = new JCheckBox(overrideInsertDefaultAction); + 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)); + insertOverrideCheckBox = new JCheckBox(insertOverrideAction); + 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)); + + /* content type settings. */ + contentTypeSettingsLabel = new I18nLabel("fileManager.label.contentTypeSetting"); + contentTypeSettingsLabel.setFont(contentTypeSettingsLabel.getFont().deriveFont(Font.BOLD)); + 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)); + + contentTypeDefaultLabel = new I18nLabel("fileManager.label.contentTypeDefault"); + 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)); + contentTypeDefaultTextField = new JTextField(); + contentTypeDefaultTextField.setEditable(false); + 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)); + overrideContentTypeDefaultCheckBox = new JCheckBox(overrideContentTypeDefaultAction); + 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)); + contentTypeOverrideComboBox = new JComboBox(new DefaultComboBoxModel()); + contentTypeOverrideComboBox.setRenderer(new MimeTypeComboBoxRenderer()); + contentTypeOverrideComboBox.addActionListener(contentTypeOverrideAction); + contentTypeOverrideComboBox.setEnabled(false); + 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)); + + /* glue panel. */ + 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)); + + /* action button panel */ + JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12)); + rightPanel.add(actionButtonPanel, BorderLayout.PAGE_END); + actionButtonPanel.setBorder(BorderFactory.createEtchedBorder()); + + JButton rescanButton = new JButton(rescanAction); + actionButtonPanel.add(rescanButton); return fileManagerPanel; } /** * Creates the button panel. - * + * * @return The button panel */ private Component createButtonPanel() { @@ -242,20 +598,235 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr } /** - * Returns a list of all selected entries. - * - * @return The selected entries + * Initiates a file scan. + */ + private void initiateFileScan() { + swingInterface.getThreadPool().execute(new Runnable() { + + /** + * @see java.lang.Runnable#run() + */ + @SuppressWarnings("synthetic-access") + public void run() { + fileTree.setEnabled(false); + rescanAction.setEnabled(false); + ProjectFile baseProjectFile = project.getBaseFile(); + if (baseProjectFile != null) { + fileTreeModel.setBaseProjectFile(baseProjectFile); + } + // fileScrollPane.revalidate(); + rescanAction.setEnabled(true); + fileTree.setEnabled(true); + } + + }); + } + + /** + * Checks whether the given mouse event is a popup trigger and occured over + * a file. If so, the context menu is shown. + * + * @param mouseEvent + * The mouse event to check */ - private List getSelectedEntries() { + private void maybeShowContextMenu(MouseEvent mouseEvent) { + if (!mouseEvent.isPopupTrigger()) { + return; + } + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(false); + TreePath clickedPath = fileTree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); + ProjectFileWrapper clickedProjectFileWrapper = (ProjectFileWrapper) clickedPath.getLastPathComponent(); + if (!selectedProjectFileWrappers.contains(clickedProjectFileWrapper)) { + fileTree.setSelectionPath(clickedPath); + selectedProjectFileWrappers = getSelectedProjectFileWrappers(false); + } + logger.log(Level.FINEST, "selectedProjectFileWrappers.size(): " + selectedProjectFileWrappers.size()); + if (selectedProjectFileWrappers.size() == 1) { + ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + setDefaultFileAction.setEnabled(!isHidden(projectFileWrapper) && projectFile.isFile() && !projectFile.getCompletePath().equals(project.getDefaultFile())); + } else { + setDefaultFileAction.setEnabled(false); + } + overrideMenu.setVisible(selectedProjectFileWrappers.size() > 1); + treeContextMenu.show(fileTree, mouseEvent.getX(), mouseEvent.getY()); + } + + /** + * Finds whether the {@link ProjectFile} given by + * projectFileWrapper is hidden. + * + * @param projectFileWrapper + * The wrapped project file + * @return true if the file is hidden and should not be + * inserted, false otherwise + */ + private boolean isHidden(ProjectFileWrapper projectFileWrapper) { + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverrides().get(projectFile.getCompletePath()); + logger.log(Level.FINEST, "fileOverride: " + fileOverride); + return ((fileOverride == null) && projectFile.isHidden()) || ((fileOverride != null) && (fileOverride.isInsert() == null) && projectFile.isHidden()) || ((fileOverride != null) && (fileOverride.isInsert() != null) && (Boolean.FALSE.equals(fileOverride.isInsert()))); + } + + /** + * Opens the “apply insert override” dialog and lets the user apply an + * override for the “insert” setting for multiple files. + */ + private void applyInsertOverride() { + JCheckBox insertCheckBox = new JCheckBox(I18n.get("fileManager.menu.item.insert.name")); + String okString = I18n.get("general.button.okay.name"); + String cancelString = I18n.get("general.button.cancel.name"); + 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); + logger.log(Level.FINEST, "choice: " + choice); + if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) { + return; + } + logger.log(Level.INFO, "selected insert override: " + insertCheckBox.isSelected()); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) { + ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + } + fileOverride.setInsert(insertCheckBox.isSelected()); + } + /* HACK - Swing sucks a bit */ + fileTree.setShowsRootHandles(false); + } + + /** + * Opens the “apply mime type override” dialog and lets the user apply an + * override for the “mime type” setting for multiple files. + */ + private void applyMimeTypeOverride() { + List allMimeTypes = MimeTypes.getAllMimeTypes(); + allMimeTypes.add(0, null); + JComboBox mimeTypeComboBox = new JComboBox(allMimeTypes.toArray()); + String okString = I18n.get("general.button.okay.name"); + String cancelString = I18n.get("general.button.cancel.name"); + 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); + if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) { + return; + } + logger.log(Level.FINE, "selected mime type: " + mimeTypeComboBox.getSelectedItem()); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) { + ProjectFile projectFile = selectedProjectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + } + fileOverride.setContentType((String) mimeTypeComboBox.getSelectedItem()); + } + } + + /** + * Removes the overrides of all selected files. + */ + private void removeOverride() { + String continueString = I18n.get("general.button.continue.name"); + String cancelString = I18n.get("general.button.cancel.name"); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + 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); + if ((choice == JOptionPane.CLOSED_OPTION) || (choice == 1)) { + return; + } + for (ProjectFileWrapper selectedProjectFileWrapper : selectedProjectFileWrappers) { + project.removeFileOverride(selectedProjectFileWrapper.getProjectFile()); + } + } + + /** + * Returns all currently selected {@link ProjectFileWrapper}s. + * + * @param filesOnly + * true to return only selected files, + * false to include directories + * @return All selected project file wrappers + */ + private List getSelectedProjectFileWrappers(boolean filesOnly) { + List selectedProjectFileWrappers = new ArrayList(); TreePath[] selectedPaths = fileTree.getSelectionPaths(); - List entries = new ArrayList(); - for (TreePath selectedPath: selectedPaths) { - Entry entry = ((FileTreePath) selectedPath.getLastPathComponent()).getFileEntry(); - if (entry != null) { - entries.add(entry); + if (selectedPaths != null) { + for (TreePath selectedPath : selectedPaths) { + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent(); + if (filesOnly && !projectFileWrapper.getProjectFile().isFile()) { + continue; + } + selectedProjectFileWrappers.add(projectFileWrapper); } } - return entries; + return selectedProjectFileWrappers; + } + + /** + * Sets the given action’s enabled state to the given enabled state if the + * action’s current enabled state is not the given enabled state. + * + * @param action + * The action to set the enabled state on + * @param enabled + * The new enabled state of the action + */ + private void setEnabled(Action action, boolean enabled) { + if (action.isEnabled() != enabled) { + action.setEnabled(enabled); + } + } + + /** + * Sets the given button’s selected state to the given selected state if the + * button’s current selected state is not the given selected state. + * + * @param button + * The button to set the selected state on + * @param selected + * The new selected state of the button + */ + private void setSelected(AbstractButton button, boolean selected) { + if (button.isSelected() != selected) { + button.setSelected(selected); + } + } + + /** + * Returns the extension of the given filename. If the file name does not + * have an extension, the name of the file (without any path components) is + * returned. + * + * @param fileName + * The name of the file + * @return The extension of the file + */ + private String getFileExtension(String fileName) { + String lastComponent = fileName.substring(fileName.lastIndexOf(File.separator) + 1); + return lastComponent.substring(lastComponent.lastIndexOf('.') + 1); + } + + /** + * Repopulates the content type combo box with all content types, putting + * the most probably ones to the front of the list. + * + * @param fileName + * The name of the file + */ + private void fillComboBox(String fileName) { + String fileExtension = getFileExtension(fileName); + List allMimeTypes = MimeTypes.getAllMimeTypes(); + List eligibleMimeTypes = MimeTypes.getMimeTypes(fileExtension); + for (String mimeType : eligibleMimeTypes) { + allMimeTypes.remove(mimeType); + allMimeTypes.add(eligibleMimeTypes.indexOf(mimeType), mimeType); + } + allMimeTypes.add(eligibleMimeTypes.size(), "--"); + DefaultComboBoxModel contentTypeOverrideComboBoxModel = (DefaultComboBoxModel) contentTypeOverrideComboBox.getModel(); + contentTypeOverrideComboBoxModel.removeAllElements(); + for (String mimeType : allMimeTypes) { + contentTypeOverrideComboBoxModel.addElement(mimeType); + } } // @@ -267,6 +838,9 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr */ public void updateI18n() { setTitle(I18n.get("fileManager.title", project.getName())); + projectFilesLabel.updateI18n(); + filePropertiesLabel.updateI18n(); + filePathLabel.updateI18n(); } // @@ -277,63 +851,201 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr * {@inheritDoc} */ public void valueChanged(TreeSelectionEvent treeSelectionEvent) { - TreePath[] selectedPaths = fileTree.getSelectionPaths(); - if (selectedPaths.length == 1) { - Entry fileEntry = ((FileTreePath) selectedPaths[0].getLastPathComponent()).getFileEntry(); - if (fileEntry == null) { - /* some directory node selected. */ - insertAction.setEnabled(false); - insertCheckBox.setSelected(false); - useCustomMimeTypeAction.setEnabled(false); - useCustomMimeTypeCheckBox.setSelected(false); - mimeTypeComboBox.setEnabled(false); - } else { - String contentType = fileEntry.getContentType(); - insertAction.setEnabled(true); - insertCheckBox.setSelected(fileEntry.isInsert()); - useCustomMimeTypeAction.setEnabled(fileEntry.isInsert()); - useCustomMimeTypeCheckBox.setSelected(!fileEntry.isDefaultContentType()); - mimeTypeComboBox.setEnabled(fileEntry.isDefaultContentType()); - mimeTypeComboBox.setSelectedItem(contentType); + List selectedProjectFileWrappers = getSelectedProjectFileWrappers(true); + String filePathText = ""; + String fileNameText = ""; + String fileSizeText = ""; + boolean insertDefaultSelected = false; + boolean overrideInsertEnabled = false; + boolean overrideInsertSelected = false; + boolean insertOverrideEnabled = false; + boolean insertOverrideSelected = false; + String defaultContentType = ""; + boolean overrideContentTypeEnabled = false; + boolean overrideContentTypeSelected = false; + boolean contentTypeOverrideEnabled = false; + String contentTypeOverride = "--"; + if (selectedProjectFileWrappers.size() == 1) { + ProjectFileWrapper projectFileWrapper = selectedProjectFileWrappers.get(0); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + if (projectFile.isFile()) { + String completePath = projectFile.getCompletePath(); + int lastSeparator = completePath.lastIndexOf(File.separatorChar); + if (lastSeparator != -1) { + filePathText = completePath.substring(0, lastSeparator); + } + fileNameText = projectFile.getName(); + fileSizeText = String.valueOf(projectFile.getSize()); + insertDefaultSelected = !projectFile.isHidden(); + overrideInsertEnabled = true; + overrideContentTypeEnabled = true; + defaultContentType = MimeTypes.getMimeType(getFileExtension(projectFile.getName())); + FileOverride fileOverride = project.getFileOverride(projectFile); + if (fileOverride != null) { + Boolean overrideInsert = fileOverride.isInsert(); + overrideInsertSelected = overrideInsert != null; + insertOverrideEnabled = overrideInsertSelected; + insertOverrideSelected = overrideInsertSelected ? overrideInsert : !projectFile.isHidden(); + String contentType = fileOverride.getContentType(); + if (contentType != null) { + contentTypeOverride = contentType; + overrideContentTypeSelected = true; + contentTypeOverrideEnabled = true; + } + } + fillComboBox(projectFile.getName()); } + } else if (selectedProjectFileWrappers.size() > 1) { + /* TODO */ } + filePathTextField.setText(filePathText); + fileNameTextField.setText(fileNameText); + fileSizeTextField.setText(fileSizeText); + setSelected(insertDefaultCheckBox, insertDefaultSelected); + setEnabled(overrideInsertDefaultAction, overrideInsertEnabled); + setSelected(overrideInsertDefaultCheckBox, overrideInsertSelected); + setEnabled(insertOverrideAction, insertOverrideEnabled); + setSelected(insertOverrideCheckBox, insertOverrideSelected); + contentTypeDefaultTextField.setText(defaultContentType); + overrideContentTypeDefaultAction.setEnabled(overrideContentTypeEnabled); + overrideContentTypeDefaultCheckBox.setSelected(overrideContentTypeSelected); + contentTypeOverrideComboBox.setEnabled(contentTypeOverrideEnabled); + contentTypeOverrideComboBox.setSelectedItem(contentTypeOverride); } // - // INTERFACE ActionListener + // INTERFACE MouseListener // /** * {@inheritDoc} */ - public void actionPerformed(ActionEvent actionEvent) { - if (actionEvent.getSource() == mimeTypeComboBox) { - String contentType = (String) mimeTypeComboBox.getSelectedItem(); - for (Entry entry: getSelectedEntries()) { - entry.setContentType(contentType); + public void mouseClicked(MouseEvent mouseEvent) { + maybeShowContextMenu(mouseEvent); + } + + /** + * {@inheritDoc} + */ + public void mouseEntered(MouseEvent mouseEvent) { + /* ignore. */ + } + + /** + * {@inheritDoc} + */ + public void mouseExited(MouseEvent mouseEvent) { + /* ignore. */ + } + + /** + * {@inheritDoc} + */ + public void mousePressed(MouseEvent mouseEvent) { + maybeShowContextMenu(mouseEvent); + } + + /** + * {@inheritDoc} + */ + public void mouseReleased(MouseEvent mouseEvent) { + maybeShowContextMenu(mouseEvent); + } + + /** + * Tree cell renderer that takes care of certain display properties for + * project-specific stuff. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private class FileCellRenderer extends DefaultTreeCellRenderer { + + /** + * Empty constructor. + */ + FileCellRenderer() { + /* do nothing. */ + } + + /** + * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, + * java.lang.Object, boolean, boolean, boolean, int, boolean) + */ + @SuppressWarnings("synthetic-access") + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component superCellRenderer = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + if (!(superCellRenderer instanceof JLabel)) { + logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!"); + return superCellRenderer; + } + if (!(value instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!"); + return superCellRenderer; + } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + FileOverride fileOverride = project.getFileOverride(projectFile); + String completePath = projectFile.getCompletePath(); + boolean paintBold = false; + boolean paintHalfColor = false; + boolean showFile = !projectFile.isHidden(); + if (fileOverride != null) { + Boolean insertOverride = fileOverride.isInsert(); + if (insertOverride != null) { + showFile = insertOverride; + } + } + if (projectFile.isFile() && !showFile) { + paintHalfColor = true; + } else if (completePath.equals(project.getDefaultFile())) { + paintBold = true; + } else if (projectFile.getParents().size() == 1) { + paintBold = true; + } + if (paintHalfColor) { + /* TODO - cache colors */ + Color foreground = superCellRenderer.getForeground(); + Color background = selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor(); + Color averageColor = new Color((foreground.getRed() + background.getRed()) / 2, (foreground.getGreen() + background.getGreen()) / 2, (foreground.getBlue() + background.getBlue()) / 2); + superCellRenderer.setForeground(averageColor); + } else { + superCellRenderer.setForeground(selected ? getTextSelectionColor() : getTextNonSelectionColor()); + } + if (paintBold) { + superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD)); + } else { + superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN)); } - useCustomMimeTypeCheckBox.setSelected(!getSelectedEntries().get(0).isDefaultContentType()); + return superCellRenderer; } + } /** - * Model for the tree of files. - * + * TreeModel that is based on {@link Project#getBaseFile()}. + * * @author David ‘Bombe’ Roden <bombe@freenetproject.org> */ - private class FileTreeModel implements TreeModel, PropertyChangeListener { + private class ProjectFileTreeModel implements TreeModel, PropertyChangeListener { /** Tree model listeners. */ - private final List treeModelListeners = new ArrayList(); + private final List treeModelListeners = Collections.synchronizedList(new ArrayList()); + + /** The base project file. */ + private ProjectFile baseProjectFile; + + /** Maps path names to project files. */ + private final Map pathProjectFiles = Collections.synchronizedMap(new HashMap()); - /** The tree of files. */ - private final Tree fileTreePathTree = new Tree(); + /** Maps project files to wrappers. */ + private final Map projectFileWrappers = Collections.synchronizedMap(new HashMap()); /** - * Creates a new file tree model. + * Empty constructor. */ - FileTreeModel() { - buildTree(); + ProjectFileTreeModel() { + /* do nothing. */ } // @@ -354,206 +1066,159 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr treeModelListeners.remove(treeModelListener); } - // - // ACCESSORS - // - - /** - * {@inheritDoc} - */ - @SuppressWarnings("synthetic-access") - public Object getChild(Object parent, int index) { - logger.log(Level.FINEST, "getChild(" + parent + ", " + index + ")"); - Node parentNode = findNode(parent); - if (parentNode != null) { - return parentNode.getChild(index).getElement(); - } - return null; - } - /** - * {@inheritDoc} + * Notifies all listeners that a node has changed. + * + * @param changedProjectFileWrapper + * The wrapper around the changed project file */ - @SuppressWarnings("synthetic-access") - public int getChildCount(Object parent) { - logger.log(Level.FINEST, "getChildCount(" + parent + ")"); - Node parentNode = findNode(parent); - if (parentNode != null) { - logger.log(Level.FINEST, "getChildCount(" + parent + "): " + parentNode.size()); - return parentNode.size(); + protected void fireTreeNodesChanged(ProjectFileWrapper changedProjectFileWrapper) { + ProjectFile changedProjectFile = changedProjectFileWrapper.getProjectFile(); + ProjectFile changedProjectFileParent = changedProjectFile.getParent(); + ProjectFile currentProjectFileParent = changedProjectFile; + List parentProjectFileWrappers = new ArrayList(); + do { + parentProjectFileWrappers.add(0, projectFileWrappers.get(currentProjectFileParent)); + currentProjectFileParent = currentProjectFileParent.getParent(); + } while (currentProjectFileParent != null); + TreeModelEvent treeModelEvent = new TreeModelEvent(this, parentProjectFileWrappers.toArray(), new int[] { getIndexOfChild(projectFileWrappers.get(changedProjectFileParent), changedProjectFileWrapper) }, new Object[] { changedProjectFileWrapper }); + for (TreeModelListener treeModelListener : treeModelListeners) { + treeModelListener.treeNodesChanged(treeModelEvent); } - return -1; } /** - * {@inheritDoc} + * Notifies all listeners that the tree structure has changed + * significantly. + * + * @see TreeModelListener#treeStructureChanged(TreeModelEvent) + * @param newRootNode */ - @SuppressWarnings("synthetic-access") - public int getIndexOfChild(Object parent, Object child) { - logger.log(Level.FINEST, "getIndexOfChild(" + parent + ", " + child + ")"); - Node parentNode = findNode(parent); - if (parentNode != null) { - return parentNode.getIndexOfChild((FileTreePath) child); + protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) { + for (TreeModelListener treeModelListener : treeModelListeners) { + treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode })); } - return -1; } - /** - * {@inheritDoc} - */ - @SuppressWarnings("synthetic-access") - public Object getRoot() { - logger.log(Level.FINEST, "getRoot()"); - return fileTreePathTree.getRootNode().getChild(0).getElement(); - } + // + // ACCESSORS + // /** - * {@inheritDoc} + * Sets the new base project file. This causes the model to reload. + * + * @param baseProjectFile + * The new base project file */ @SuppressWarnings("synthetic-access") - public boolean isLeaf(Object node) { - logger.log(Level.FINEST, "isLeaf(" + node + ")"); - Node parentNode = findNode(node); - if (parentNode != null) { - return parentNode.size() == 0; - } - return true; + public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) { + this.baseProjectFile = baseProjectFile; + projectFileWrappers.clear(); + pathProjectFiles.clear(); + createWrappers(baseProjectFile); + projectFileWrappers.get(baseProjectFile).setNameOverride(project.getName()); + fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile)); } // - // ACTIONS + // PRIVATE METHODS // /** - * {@inheritDoc} + * Creates {@link ProjectFileWrapper}s for all files below the given + * project file. + * + * @param projectFile + * The base project file for all project files to create + * wrappers for */ - public void valueForPathChanged(TreePath path, Object newValue) { - /* TODO - implement */ + private void createWrappers(ProjectFile projectFile) { + projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile)); + pathProjectFiles.put(projectFile.getCompletePath(), projectFile); + for (ProjectFile projectFileChild : projectFile.getFiles()) { + if (projectFileChild.isDirectory()) { + createWrappers(projectFileChild); + } + projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild)); + pathProjectFiles.put(projectFileChild.getCompletePath(), projectFileChild); + } } // - // PRIVATE METHODS + // INTERFACE TreeModel // /** - * Finds the node for the given object. This method is quite necessary - * because the element for the root node of the JTree is - * null - * - * @param node - * The element whose node to return - * @return The node, or null if no node could be found + * {@inheritDoc} */ - private Node findNode(Object node) { - if (node == null) { - return fileTreePathTree.getRootNode().getChild(0); - } - return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node); + public Object getRoot() { + return projectFileWrappers.get(baseProjectFile); } /** - * Builds the tree from the project’s file entries. + * {@inheritDoc} */ @SuppressWarnings("synthetic-access") - private void buildTree() { - Tree pathTree = new Tree(); - Node pathRootNode = pathTree.getRootNode().addChild(File.separator); - logger.log(Level.FINEST, "project: " + project); - buildTree(pathRootNode, project.getBasePathEntries()); - buildTree(pathRootNode, project.getVirtualEntries()); - /* now convert to a tree suitable for the JTree. */ - Node fileTreePathRootNode = fileTreePathTree.getRootNode(); - fileTreePathRootNode.removeAllChildren(); - convertTree(File.separator, pathRootNode, fileTreePathRootNode.addChild(new FileTreePath(File.separator, project.getName()))); - /* TODO - now add entries to all file tree path tree nodes. */ - addEntries(fileTreePathRootNode.getChild(0), project.getVirtualEntries()); - addEntries(fileTreePathRootNode.getChild(0), project.getBasePathEntries()); + public Object getChild(Object parent, int index) { + if (!(parent instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!"); + return null; + } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + return projectFileWrappers.get(projectFile.getFiles().get(index)); } /** - * Traverses the tree and assigned {@link Entry}s to every file tree - * path whose name matchtes the name of an Entry. - * - * @param fileTreePathRootNode - * The root node of the tree to walk - * @param entries - * The list of entries + * {@inheritDoc} */ - private void addEntries(Node fileTreePathRootNode, List entries) { - for (Entry entry: entries) { - String completeEntryName = File.separatorChar + entry.getName(); - FileTreePath fileTreePath = getFileTreePath(fileTreePathRootNode, completeEntryName); - if (fileTreePath != null) { - fileTreePath.setFileEntry(entry); - } + @SuppressWarnings("synthetic-access") + public int getChildCount(Object parent) { + if (!(parent instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!"); + return -1; } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + return projectFile.getFiles().size(); } /** - * Find the {@link FileTreePath} below the given node that has the given - * file path. - * - * @param fileTreePathNode - * The node to start searching at - * @param filePath - * The path to search - * @return The file tree path with the matching file path, or - * null if these is no such file tree path + * {@inheritDoc} */ - private FileTreePath getFileTreePath(Node fileTreePathNode, String filePath) { - for (Node child: fileTreePathNode) { - if (child.getElement().getFilePath().equals(filePath)) { - return child.getElement(); - } - FileTreePath fileTreePath = getFileTreePath(child, filePath); - if (fileTreePath != null) { - return fileTreePath; - } + @SuppressWarnings("synthetic-access") + public int getIndexOfChild(Object parent, Object child) { + if (!(parent instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!"); + return -1; } - return null; + if (!(child instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!"); + return -1; + } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile()); } /** - * Traverses the tree of path nodes and converts all paths to - * {@link FileTreePath} objects, suitable for the JTree. - * - * @param completePath - * The base path of the current root node - * @param pathRootNode - * The root node of the path tree - * @param fileTreePathRootNode - * The root node of the file tree path tree. + * {@inheritDoc} */ - private void convertTree(String completePath, Node pathRootNode, Node fileTreePathRootNode) { - for (Node pathChild: pathRootNode) { - String currentFilePath = completePath + pathChild.getElement(); - Node newNode = fileTreePathRootNode.addChild(new FileTreePath(currentFilePath)); - convertTree(currentFilePath, pathChild, newNode); + @SuppressWarnings("synthetic-access") + public boolean isLeaf(Object node) { + if (!(node instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!"); + return true; } - fileTreePathRootNode.sortChildren(); + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node; + return projectFileWrapper.getProjectFile().isFile(); } /** - * Builds a tree matching the directory structure of the given entries. - * - * @param pathRootNode - * The root node of the tree - * @param entries - * The entries + * {@inheritDoc} */ - private void buildTree(Node pathRootNode, List entries) { - for (Entry basePathEntry: entries) { - String entryName = basePathEntry.getName(); - String[] directories = entryName.split("\\" + File.separator); - Node currentPathNode = pathRootNode; - for (String directory: directories) { - if (!currentPathNode.hasChild(directory)) { - currentPathNode = currentPathNode.addChild(directory); - } else { - currentPathNode = currentPathNode.getChild(directory); - } - } - } + public void valueForPathChanged(TreePath path, Object newValue) { + /* ignore, items will not be modified in tree. */ } // @@ -561,146 +1226,116 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr // /** - * {@inheritDoc} + * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) */ + @SuppressWarnings("synthetic-access") public void propertyChange(PropertyChangeEvent propertyChangeEvent) { - if (propertyChangeEvent.getSource() instanceof Project) { - if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) { - buildTree(); + if (Project.PROPERTY_DEFAULT_FILE.equals(propertyChangeEvent.getPropertyName())) { + if (propertyChangeEvent.getOldValue() != null) { + String oldCompletePath = (String) propertyChangeEvent.getOldValue(); + logger.log(Level.FINEST, "oldCompletePath: " + oldCompletePath); + ProjectFile oldProjectFile = pathProjectFiles.get(oldCompletePath); + logger.log(Level.FINEST, "oldProjectFile: " + oldProjectFile); + ProjectFileWrapper oldProjectFileWrapper = projectFileWrappers.get(oldProjectFile); + logger.log(Level.FINEST, "oldProjectFileWrapper: " + oldProjectFileWrapper); + fireTreeNodesChanged(oldProjectFileWrapper); } + String newCompletePath = (String) propertyChangeEvent.getNewValue(); + ProjectFile newProjectFile = pathProjectFiles.get(newCompletePath); + ProjectFileWrapper newProjectFileWrapper = projectFileWrappers.get(newProjectFile); + System.out.println("newProjectFileWrapper: " + newProjectFileWrapper); + fireTreeNodesChanged(newProjectFileWrapper); + /* HACK - swing sucks a bit. */ + fileTree.setShowsRootHandles(false); } } } /** - * Container that is used to back the {@link FileTreeModel}. Each - * FileTreePath contains a complete path name, a filename, and the - * associated {@link Entry}, if any. - * + * Wrapper around a {@link ProjectFile} that overwrites + * {@link Object#toString()} to return the project file’s name. + * * @author David ‘Bombe’ Roden <bombe@freenetproject.org> */ - private static class FileTreePath implements Comparable { - - /** The complete file path. */ - private final String filePath; + private static class ProjectFileWrapper { - /** The file name. */ - private final String fileName; + /** The wrapped project file. */ + private final ProjectFile projectFile; - /** The file entry, if any. */ - private Entry fileEntry; + /** The override name. */ + private String nameOverride; /** - * Creates a new file tree path with an auto-detected file name. The - * file name is everything after the last separator in the complete - * path, or the complete path itself if it does not contain any - * separators. - * - * @param filePath - * The complete file path + * Creates a new wrapper around a project file. + * + * @param projectFile + * The project file to wrap */ - public FileTreePath(String filePath) { - this(filePath, null); + public ProjectFileWrapper(ProjectFile projectFile) { + this.projectFile = projectFile; } /** - * Creates a new file tree path with the given file path and file name. - * - * @param filePath - * The complete file path - * @param fileName - * The file name + * Returns the wrapped project file. + * + * @return The wrapped project file */ - public FileTreePath(String filePath, String fileName) { - this.filePath = filePath; - if (fileName == null) { - if (filePath.indexOf(File.separatorChar) != -1) { - this.fileName = filePath.substring(filePath.lastIndexOf(File.separatorChar) + 1); - } else { - this.fileName = filePath; - } - } else { - this.fileName = fileName; - } + public ProjectFile getProjectFile() { + return projectFile; } /** - * Returns the complete file path. - * - * @return The file path + * Sets the name override. If the name override is not null + * it will be shown insted of the project file’s name. + * + * @param nameOverride + * The name override */ - public String getFilePath() { - return filePath; + void setNameOverride(String nameOverride) { + this.nameOverride = nameOverride; } /** - * Returns the file name, i.e. everything after the last - * {@link File#separatorChar}. - * - * @return The file name + * {@inheritDoc} */ - public String getFileName() { - return fileName; + @Override + public String toString() { + return (nameOverride != null) ? nameOverride : projectFile.getName(); } - /** - * Returns the file entry associated with this path, if any. - * - * @return The file entry associated with this path, or - * null if this path denotes a directory - */ - public Entry getFileEntry() { - return fileEntry; - } + } - /** - * Sets the entry associated with this path. - * - * @param fileEntry - * The entry - */ - public void setFileEntry(Entry fileEntry) { - this.fileEntry = fileEntry; - } + /** + * A cell renderer for combo boxes that converts the string “--” to a + * separator. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private class MimeTypeComboBoxRenderer extends DefaultListCellRenderer { - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if ((object == null) || !(object instanceof FileTreePath)) { - return false; - } - FileTreePath fileTreePath = (FileTreePath) object; - return fileTreePath.filePath.equals(filePath); - } + /** The separator component. */ + private final JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL); /** - * {@inheritDoc} + * Empty constructor. */ - @Override - public int hashCode() { - return filePath.hashCode(); + MimeTypeComboBoxRenderer() { + /* do nothing. */ } /** * {@inheritDoc} */ @Override - public String toString() { - return fileName; - } - - // - // INTERFACE Comparable - // - - /** - * {@inheritDoc} - */ - public int compareTo(FileTreePath otherFileTreePath) { - return filePath.compareTo(otherFileTreePath.filePath); + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if ("--".equals(value)) { + return separator; + } + if (value == null) { + return super.getListCellRendererComponent(list, "\u00a0", index, isSelected, cellHasFocus); + } + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } }