X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fnet%2Fpterodactylus%2Fjsite%2Fgui%2FFileManager.java;h=b0713a32a20788a8af199679372fe6b65338711e;hb=6846df4a60a659b23346881b3c860e40002930bf;hp=78c24a722b183fa012db41a1295595b9ed97ba43;hpb=5534c6de28d64304dfd6da6ddfacfdecc85d1223;p=jSite2.git diff --git a/src/net/pterodactylus/jsite/gui/FileManager.java b/src/net/pterodactylus/jsite/gui/FileManager.java index 78c24a7..b0713a3 100644 --- a/src/net/pterodactylus/jsite/gui/FileManager.java +++ b/src/net/pterodactylus/jsite/gui/FileManager.java @@ -19,46 +19,517 @@ 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.Point; +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.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JDialog; -import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +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.TreeModel; +import javax.swing.tree.TreePath; 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.i18n.gui.I18nMenu; +import net.pterodactylus.jsite.project.FileOverride; import net.pterodactylus.jsite.project.Project; +import net.pterodactylus.jsite.project.ProjectFile; +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 { +public class FileManager extends JDialog implements I18nable, ActionListener, 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 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 “insert” action. */ + private I18nAction insertAction; + + /** The “project files” label. */ + private I18nLabel projectFilesLabel; + + /** The tree that shows the files. */ + private JTree fileTree; + + /** 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 “override” label. */ + private I18nLabel overrideLabel; + + /** The “override active” action. */ + private I18nAction overrideAction; + + /** The “override action” checkbox. */ + private JCheckBox overrideCheckBox; + + /** 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; + /** * 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())); + 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 ProjectFileTreeModel(); + project.addPropertyChangeListener(fileTreeModel); + fileCellRenderer = new FileCellRenderer(); + initActions(); initComponents(); + pack(); + SwingUtils.center(this); + } + + // + // ACTIONS + // + + /** + * @see java.awt.Component#setVisible(boolean) + */ + @Override + public void setVisible(boolean visible) { + if (visible) { + initiateFileScan(); + } + super.setVisible(visible); } // - // PRIVATE ACTIONS + // PRIVATE METHODS // /** + * Initializes all actions. + */ + private void initActions() { + closeAction = new I18nAction("fileManager.button.close") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent e) { + String defaultFile = project.getDefaultFile(); + if ((defaultFile == null) || (defaultFile.length() == 0)) { + /* TODO - i18n */ + JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); + return; + } + ProjectFile projectFile = project.getFile(defaultFile); + if (projectFile == null) { + JOptionPane.showMessageDialog(FileManager.this, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE); + return; + } + setVisible(false); + } + }; + rescanAction = new I18nAction("fileManager.button.rescan") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + 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); + } + }; + insertAction = new I18nAction("fileManager.menu.item.insert") { + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent e) { + /* TODO */ + } + }; + overrideAction = new I18nAction("fileManager.checkbox.overrideActive") { + + /** + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + public void actionPerformed(ActionEvent actionEvent) { + /* TODO */ + } + }; + applyInsertOverrideAction = new I18nAction("fileManager.menu.item.applyInsertOverride") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + applyInsertOverride(); + } + }; + applyMimeTypeOverrideAction = new I18nAction("fileManager.menu.item.applyMimeTypeOverride") { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + applyMimeTypeOverride(); + } + }; + } + + /** * Initializes all components. */ private void initComponents() { - /* TODO. */ + treeContextMenu = new JPopupMenu(); + treeContextMenu.add(setDefaultFileAction); + + overrideMenu = new I18nMenu("fileManager.menu.override"); + treeContextMenu.add(overrideMenu); + + overrideMenu.add(applyInsertOverrideAction); + overrideMenu.add(applyMimeTypeOverrideAction); + + JPanel contentPanel = new JPanel(new BorderLayout(12, 12)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); + + contentPanel.add(createFileManagerPanel(), BorderLayout.CENTER); + contentPanel.add(createButtonPanel(), BorderLayout.PAGE_END); + + setContentPane(contentPanel); + } + + /** + * 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)); + + /* file tree panel */ + JPanel fileTreePanel = new JPanel(new BorderLayout(12, 12)); + fileManagerPanel.add(fileTreePanel, BorderLayout.LINE_START); + + fileTree = new JTree(fileTreeModel); + fileTree.setShowsRootHandles(false); + fileTree.addTreeSelectionListener(this); + 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)); + fileManagerPanel.add(rightPanel, BorderLayout.CENTER); + + /* properties panel */ + JPanel propertiesPanel = new JPanel(new GridBagLayout()); + rightPanel.add(propertiesPanel, BorderLayout.CENTER); + propertiesPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(12, 12, 12, 12))); + propertiesPanel.setPreferredSize(new Dimension(400, 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, 1, 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, 1, 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, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 12, 0, 0), 0, 0)); + + /* override settings. */ + overrideLabel = new I18nLabel("fileManager.label.override"); + overrideLabel.setFont(overrideLabel.getFont().deriveFont(Font.BOLD)); + propertiesPanel.add(overrideLabel, new GridBagConstraints(0, 4, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(24, 0, 0, 0), 0, 0)); + + overrideCheckBox = new JCheckBox(overrideAction); + propertiesPanel.add(overrideCheckBox, new GridBagConstraints(0, 5, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 24, 0, 0), 0, 0)); + + /* glue panel. */ + propertiesPanel.add(new JPanel(), new GridBagConstraints(0, 6, 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() { + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12)); + + buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12)); + JButton closeButton = new JButton(closeAction); + buttonPanel.add(closeButton); + + getRootPane().setDefaultButton(closeButton); + return buttonPanel; + } + + /** + * 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 void maybeShowContextMenu(MouseEvent mouseEvent) { + if (!mouseEvent.isPopupTrigger()) { + return; + } + Point eventLocation = mouseEvent.getPoint(); + TreePath[] selectedPaths = fileTree.getSelectionPaths(); + if ((selectedPaths == null) || (selectedPaths.length == 0)) { + /* try to find item under click. */ + TreePath clickedPath = fileTree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); + if (clickedPath != null) { + fileTree.setSelectionPath(clickedPath); + selectedPaths = new TreePath[] { clickedPath }; + } else { + logger.log(Level.FINER, "nothing selected for context menu"); + return; + } + } + if (selectedPaths.length == 1) { + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPaths[0].getLastPathComponent(); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + setDefaultFileAction.setEnabled(!isHidden(projectFileWrapper) && projectFile.isFile() && !projectFile.getCompletePath().equals(project.getDefaultFile())); + } else { + setDefaultFileAction.setEnabled(false); + } + treeContextMenu.show(fileTree, eventLocation.x, eventLocation.y); + } + + /** + * 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) && (Boolean.TRUE.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.checkbox.insertFile.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()); + TreePath[] selectedPaths = fileTree.getSelectionPaths(); + for (TreePath selectedPath: selectedPaths) { + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) selectedPath.getLastPathComponent(); + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + if (!projectFile.isFile()) { + continue; + } + FileOverride fileOverride = project.getFileOverride(projectFile); + if (fileOverride == null) { + fileOverride = new FileOverride(); + project.addFileOverride(projectFile, fileOverride); + } + fileOverride.setInsert(insertCheckBox.isSelected()); + } + } + + /** + * 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() { + /* TODO */ } // @@ -70,6 +541,440 @@ public class FileManager extends JDialog implements I18nable { */ public void updateI18n() { setTitle(I18n.get("fileManager.title", project.getName())); + projectFilesLabel.updateI18n(); + filePropertiesLabel.updateI18n(); + filePathLabel.updateI18n(); + } + + // + // INTERFACE TreeSelectionListener + // + + /** + * {@inheritDoc} + */ + public void valueChanged(TreeSelectionEvent treeSelectionEvent) { + TreePath[] selectedPaths = fileTree.getSelectionPaths(); + filePathTextField.setText(""); + fileNameTextField.setText(""); + fileSizeTextField.setText(""); + if ((selectedPaths != null) && (selectedPaths.length == 1)) { + Object lastPathComponent = selectedPaths[0].getLastPathComponent(); + if (!(lastPathComponent instanceof ProjectFileWrapper)) { + logger.log(Level.SEVERE, "lastPathComponent is not a ProjectFileWrapper!"); + return; + } + ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) lastPathComponent; + ProjectFile projectFile = projectFileWrapper.getProjectFile(); + if (projectFile.isFile()) { + String completePath = projectFile.getCompletePath(); + int lastSeparator = completePath.lastIndexOf(File.separatorChar); + if (lastSeparator == -1) { + filePathTextField.setText(""); + } else { + filePathTextField.setText(completePath.substring(0, lastSeparator)); + } + fileNameTextField.setText(projectFile.getName()); + fileSizeTextField.setText(String.valueOf(projectFile.getSize())); + } + } else if ((selectedPaths != null) && (selectedPaths.length > 1)) { + /* TODO */ + } + } + + // + // INTERFACE ActionListener + // + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent actionEvent) { + /* TODO */ + } + + // + // INTERFACE MouseListener + // + + /** + * {@inheritDoc} + */ + 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(); + String completePath = projectFile.getCompletePath(); + boolean paintBold = false; + boolean paintHalfColor = false; + if (projectFile.isFile() && projectFile.isHidden()) { + /* TODO - check override */ + 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)); + } + return superCellRenderer; + } + + } + + /** + * TreeModel that is based on {@link Project#getBaseFile()}. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private class ProjectFileTreeModel implements TreeModel, PropertyChangeListener { + + /** Tree model listeners. */ + 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()); + + /** 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 a node has changed. + * + * @param changedProjectFileWrapper + * The wrapper around the changed project file + */ + 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); + } + } + + /** + * 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 + */ + @SuppressWarnings("synthetic-access") + 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)); + } + + // + // 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)); + 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); + } + } + + // + // 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; + return projectFileWrapper.getProjectFile().isFile(); + } + + /** + * {@inheritDoc} + */ + public void valueForPathChanged(TreePath path, Object newValue) { + /* ignore, items will not be modified in tree. */ + } + + // + // INTERFACE PropertyChangeListener + // + + /** + * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) + */ + @SuppressWarnings("synthetic-access") + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + if (Project.PROPERTY_DEFAULT_FILE.equals(propertyChangeEvent.getPropertyName())) { + if (propertyChangeEvent.getOldValue() != null) { + String oldCompletePath = (String) propertyChangeEvent.getOldValue(); + ProjectFile oldProjectFile = pathProjectFiles.get(oldCompletePath); + ProjectFileWrapper oldProjectFileWrapper = projectFileWrappers.get(oldProjectFile); + System.out.println("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); + } + } + + } + + /** + * 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; + + /** The override name. */ + private String nameOverride; + + /** + * 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; + } + + /** + * 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 + */ + void setNameOverride(String nameOverride) { + this.nameOverride = nameOverride; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return (nameOverride != null) ? nameOverride : projectFile.getName(); + } + } }