From 4fa04bea2bcb171075208b7fe231dffc99c296d9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 30 May 2008 01:57:20 +0200 Subject: [PATCH] finish rework of file manager --- src/net/pterodactylus/jsite/gui/FileManager.java | 372 ++++++++++++++++------- 1 file changed, 255 insertions(+), 117 deletions(-) diff --git a/src/net/pterodactylus/jsite/gui/FileManager.java b/src/net/pterodactylus/jsite/gui/FileManager.java index c961280..b3f772e 100644 --- a/src/net/pterodactylus/jsite/gui/FileManager.java +++ b/src/net/pterodactylus/jsite/gui/FileManager.java @@ -30,9 +30,11 @@ import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -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; @@ -40,16 +42,16 @@ import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTree; +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.DefaultTreeModel; -import javax.swing.tree.TreeNode; +import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import net.pterodactylus.jsite.i18n.I18n; @@ -57,13 +59,13 @@ 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.Project; +import net.pterodactylus.jsite.project.ProjectFile; import net.pterodactylus.util.logging.Logging; -import net.pterodactylus.util.swing.SortableTreeNode; 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 { @@ -77,14 +79,8 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** The project whose files to manage. */ private final Project project; - /** The root of the file tree. */ - private final SortableTreeNode fileTreeRoot; - /** The tree model for the project files. */ - private final DefaultTreeModel fileTreeModel; - - /** Files that are hidden as per {@link File#isHidden()}. */ - private final List hiddenFiles = new ArrayList(); + private final ProjectFileTreeModel fileTreeModel; /** The tree cell renderer. */ private final FileCellRenderer fileCellRenderer; @@ -127,7 +123,7 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** * Creates a new file manager. - * + * * @param swingInterface * The Swing interface * @param project @@ -138,8 +134,7 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr logger.log(Level.FINEST, "project: " + project); this.swingInterface = swingInterface; this.project = project; - fileTreeRoot = new SortableTreeNode(project.getName()); - fileTreeModel = new DefaultTreeModel(fileTreeRoot); + fileTreeModel = new ProjectFileTreeModel(); fileCellRenderer = new FileCellRenderer(); initActions(); initComponents(); @@ -206,7 +201,7 @@ 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() { @@ -260,7 +255,7 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr propertiesPanel.add(fileSizeTextField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0)); /* glue panel. */ - propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 3, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 4, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); /* action button panel */ JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12)); @@ -275,7 +270,7 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** * Creates the button panel. - * + * * @return The button panel */ private Component createButtonPanel() { @@ -302,67 +297,18 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr public void run() { fileTree.setEnabled(false); rescanAction.setEnabled(false); - String basePath = project.getBasePath(); - File basePathDirectory = new File(basePath); - fileTreeRoot.removeAll(); - hiddenFiles.clear(); - if (!basePathDirectory.exists() || !basePathDirectory.isDirectory()) { - /* TODO - i18n */ - JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); - } else { - scanDirectory(fileTreeRoot, basePathDirectory, "", hiddenFiles); + ProjectFile baseProjectFile = project.getBaseFile(); + if (baseProjectFile != null) { + fileTreeModel.setBaseProjectFile(baseProjectFile); } - fileTreeModel.reload(); // fileScrollPane.revalidate(); rescanAction.setEnabled(true); fileTree.setEnabled(true); } - private void scanDirectory(SortableTreeNode rootNode, File directory, String currentDirectory, List hiddenFiles) { - for (File file : directory.listFiles()) { - String fileName = file.getName(); - SortableTreeNode fileNode = new SortableTreeNode(fileName); - rootNode.add(fileNode); - if (file.isFile() && file.isHidden()) { - hiddenFiles.add((currentDirectory + File.separator + fileName).substring(1)); - } - if (file.isDirectory()) { - scanDirectory(fileNode, file, currentDirectory + File.separator + fileName, hiddenFiles); - } - } - rootNode.sort(); - } - }); } - /** - * Returns the complete path for the given node. - * - * @param treeNode - * The node - * @return The complete path for the node, or the empty string (“”) for the - * root node - */ - private String getPathForNode(TreeNode treeNode) { - TreeNode[] pathToNode = fileTreeModel.getPathToRoot(treeNode); - StringBuilder completePathBuilder = new StringBuilder(); - boolean first = true; - for (TreeNode nodePathNode : pathToNode) { - if (first) { - first = false; - continue; - } - if (!(nodePathNode instanceof SortableTreeNode)) { - logger.log(Level.WARNING, "nodePathNode is not a SortableTreeNode!"); - continue; - } - completePathBuilder.append(File.separatorChar).append(((SortableTreeNode) nodePathNode).getUserObject()); - } - String completePath = (completePathBuilder.length() == 0) ? "" : completePathBuilder.substring(1); - return completePath; - } - // // INTERFACE I18nable // @@ -385,30 +331,7 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr * {@inheritDoc} */ public void valueChanged(TreeSelectionEvent treeSelectionEvent) { - TreePath[] selectedPaths = fileTree.getSelectionPaths(); - if ((selectedPaths != null) && (selectedPaths.length == 1)) { - Object lastPathComponent = selectedPaths[0].getLastPathComponent(); - if (!(lastPathComponent instanceof SortableTreeNode)) { - logger.log(Level.WARNING, "lastPathComponent is not a SortableTreeNode!"); - return; - } - SortableTreeNode node = (SortableTreeNode) lastPathComponent; - String completePath = getPathForNode(node); - int lastSeparator = completePath.lastIndexOf(File.separatorChar); - String filePath = ""; - String fileName; - if (lastSeparator == -1) { - fileName = completePath; - } else { - filePath = completePath.substring(0, lastSeparator); - fileName = completePath.substring(lastSeparator + 1); - } - filePathTextField.setText(filePath); - fileNameTextField.setText(fileName); - return; - } - filePathTextField.setText(""); - fileNameTextField.setText(""); + /* TODO */ } // @@ -425,49 +348,264 @@ public class FileManager extends JDialog implements I18nable, ActionListener, Tr /** * 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.WARNING, "superCellRenderer is not a JLabel!"); + logger.log(Level.SEVERE, "superCellRenderer is not a JLabel!"); return superCellRenderer; } - if (!(value instanceof SortableTreeNode)) { - logger.log(Level.WARNING, "value is not a SortableTreeNode!"); + if (!(value instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "value is not a ProjectFileWrapper!"); return superCellRenderer; } - SortableTreeNode node = (SortableTreeNode) value; - TreeNode[] pathToRoot = fileTreeModel.getPathToRoot(node); - if (pathToRoot.length > 1) { - String completePath = getPathForNode(node); - if (project.getDefaultFile().equals(completePath)) { - superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD)); - } else { - if (hiddenFiles.contains(completePath)) { - /* fade hidden files’ font */ - 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.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN)); - } - } - } else { + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) value; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + String completePath = projectFile.getCompletePath(); + if (projectFile.isHidden()) { + /* TODO - check override */ + 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 if (completePath.equals(project.getDefaultFile())) { superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.BOLD)); + } else { + superCellRenderer.setFont(superCellRenderer.getFont().deriveFont(Font.PLAIN)); } return superCellRenderer; } } + /** + * TreeModel that is based on {@link Project#getBaseFile()}. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private class ProjectFileTreeModel implements TreeModel { + + /** Tree model listeners. */ + private final List treeModelListeners = Collections.synchronizedList(new ArrayList()); + + /** The base project file. */ + private ProjectFile baseProjectFile; + + /** Maps project files to wrappers. */ + private final Map projectFileWrappers = Collections.synchronizedMap(new HashMap()); + + /** + * Empty constructor. + */ + ProjectFileTreeModel() { + /* do nothing. */ + } + + // + // EVENT MANAGEMENT + // + + /** + * {@inheritDoc} + */ + public void addTreeModelListener(TreeModelListener treeModelListener) { + treeModelListeners.add(treeModelListener); + } + + /** + * {@inheritDoc} + */ + public void removeTreeModelListener(TreeModelListener treeModelListener) { + treeModelListeners.remove(treeModelListener); + } + + /** + * Notifies all listeners that the tree structure has changed + * significantly. + * + * @see TreeModelListener#treeStructureChanged(TreeModelEvent) + * @param newRootNode + */ + protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) { + for (TreeModelListener treeModelListener: treeModelListeners) { + treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode })); + } + } + + // + // ACCESSORS + // + + /** + * Sets the new base project file. This causes the model to reload. + * + * @param baseProjectFile + * The new base project file + */ + public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) { + this.baseProjectFile = baseProjectFile; + projectFileWrappers.clear(); + createWrappers(baseProjectFile); + fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile)); + } + + // + // PRIVATE METHODS + // + + /** + * 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 + */ + private void createWrappers(ProjectFile projectFile) { + projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile)); + for (ProjectFile projectFileChild: projectFile.getFiles()) { + if (projectFileChild.isDirectory()) { + createWrappers(projectFileChild); + } + projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild)); + } + } + + // + // INTERFACE TreeModel + // + + /** + * {@inheritDoc} + */ + public Object getRoot() { + return projectFileWrappers.get(baseProjectFile); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + 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)); + } + + /** + * {@inheritDoc} + */ + @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(); + } + + /** + * {@inheritDoc} + */ + @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; + } + 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()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public boolean isLeaf(Object node) { + if (!(node instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!"); + return true; + } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + return projectFile.getFiles().isEmpty(); + } + + /** + * {@inheritDoc} + */ + public void valueForPathChanged(TreePath path, Object newValue) { + /* ignore, items will not be modified in tree. */ + } + + } + + /** + * 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 ProjectFileWrapper { + + /** The wrapped project file. */ + private final ProjectFile projectFile; + + /** + * Creates a new wrapper around a project file. + * + * @param projectFile + * The project file to wrap + */ + public ProjectFileWrapper(ProjectFile projectFile) { + this.projectFile = projectFile; + } + + /** + * Returns the wrapped project file. + * + * @return The wrapped project file + */ + public ProjectFile getProjectFile() { + return projectFile; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return projectFile.getName(); + } + + } + } -- 2.7.4