All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.io.swing.JFileTree Maven / Gradle / Ivy

Go to download

TrueZIP is a Java based Virtual File System (VFS) to enable transparent, multi-threaded read/write access to archive files (ZIP, TAR etc.) as if they were directories. Archive files may be arbitrarily nested and the nesting level is only limited by heap and file system size.

The newest version!
/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.io.swing;

import de.schlichtherle.io.swing.tree.*;

import java.awt.*;
import java.io.*;
import java.util.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

/**
 * A custom {@link JTree} to browse files and directories.
 * There are a couple of file creation/modification/removal methods added
 * which notify the tree of any changes in the file system and update the
 * current path expansions and selection.
 *
 * @author Christian Schlichtherle
 * @version $Id: JFileTree.java,v 1.4 2010/08/20 13:09:46 christian_schlichtherle Exp $
 * @since TrueZIP 5.1
 */
public class JFileTree extends JTree {

    /** The name of the property {@code displayingSuffixes}. */
    private static final String PROPERTY_DISPLAYING_SUFFIXES = "displayingSuffixes"; // NOI18N

    /** The name of the property {@code editingSuffixes}. */
    private static final String PROPERTY_EDITING_SUFFIXES = "editingSuffixes"; // NOI18N

    /** The name of the property {@code defaultSuffix}. */
    private static final String PROPERTY_DEFAULT_SUFFIX = "defaultSuffix"; // NOI18N

    private final Controller controller = new Controller();

    /** Holds value of property displayingSuffixes. */
    private boolean displayingSuffixes = true;

    /** Holds value of property editingSuffixes. */
    private boolean editingSuffixes = true;

    /** Holds value of property defaultSuffix. */
    private String defaultSuffix;

    /** Holds value of property editedNode. */
    private java.io.File editedNode;

    /**
     * Creates an empty {@code JFileTree} with no root.
     * You shouldn't use this constructor.
     * It's only provided to implement the JavaBean pattern.
     */
    public JFileTree() {
        this(new FileTreeModel());
    }

    /**
     * Creates a new {@code JFileTree} which traverses the given
     * root {@code File}.
     * The ZipDetector of the given file is used to detect and configure any
     * ZIP compatible files in this directory tree.
     * 
     * @see de.schlichtherle.io.File#getDefaultArchiveDetector()
     * @see de.schlichtherle.io.File#setDefaultArchiveDetector(ArchiveDetector)
     */
    public JFileTree(java.io.File root) {
        this(new FileTreeModel(root));
    }

    /**
     * Creates a new {@code JFileTree} which traverses the given
     * {@link FileTreeModel}.
     */
    public JFileTree(FileTreeModel model) {
        super(model);
        super.setCellRenderer(createTreeCellRenderer());
        super.addTreeExpansionListener(controller);
    }

    /**
     * Returns a new tree cell renderer.
     * This method may be overridden by subclasses.
     * 

* This implementation simply returns a new {@link FileTreeCellRenderer}. */ protected TreeCellRenderer createTreeCellRenderer() { return new FileTreeCellRenderer(this); } // // Properties. // /** * @throws ClassCastException If {@code model} is not an instance * of {@link FileTreeModel}. */ public void setModel(TreeModel model) { super.setModel((FileTreeModel) model); } public void setEditable(final boolean editable) { if (editable) { super.setEditable(true); getCellEditor().addCellEditorListener(controller); } else { final CellEditor ce = getCellEditor(); if (ce != null) ce.removeCellEditorListener(controller); super.setEditable(false); } } /** * Getter for bound property displayingSuffixes. * * @return Value of property displayingSuffixes. */ public boolean isDisplayingSuffixes() { return this.displayingSuffixes; } /** * Setter for bound property displayingSuffixes. * If this is {@code false}, the suffix of files will not be displayed * in this tree. * Defaults to {@code true}. * * @param displayingSuffixes New value of property displayingSuffixes. */ public void setDisplayingSuffixes(boolean displayingSuffixes) { boolean oldDisplayingSuffixes = this.displayingSuffixes; this.displayingSuffixes = displayingSuffixes; firePropertyChange(PROPERTY_DISPLAYING_SUFFIXES, oldDisplayingSuffixes, displayingSuffixes); } /** * Getter for bound property editingSuffixes. * * @return Value of property editingSuffixes. */ public boolean isEditingSuffixes() { return this.editingSuffixes; } /** * Setter for bound property editingSuffixes. * If this is {@code false}, the suffix of a file will be truncated * before editing its name starts. * Defaults to {@code true}. * * @param editingSuffixes New value of property editingSuffixes. */ public void setEditingSuffixes(boolean editingSuffixes) { boolean oldEditingSuffixes = this.editingSuffixes; this.editingSuffixes = editingSuffixes; firePropertyChange(PROPERTY_EDITING_SUFFIXES, oldEditingSuffixes, editingSuffixes); } /** * Getter for bound property defaultSuffix. * * @return Value of property defaultSuffix. */ public String getDefaultSuffix() { return this.defaultSuffix; } /** * Setter for bound property defaultSuffixes. * Sets the default suffix to use when suffixes are shown and allowed to * be edited, but the user did not provide a suffix when editing a file * name. * This property defaults to {@code null} and is ignored for * directories. * * @param defaultSuffix The new default suffix. * If not {@code null}, this parameter is fixed to always * start with a {@code '.'}. */ public void setDefaultSuffix(String defaultSuffix) { final String oldDefaultSuffix = this.defaultSuffix; if (defaultSuffix != null) { defaultSuffix = defaultSuffix.trim(); if (defaultSuffix.length() <= 0) defaultSuffix = null; else if (defaultSuffix.charAt(0) != '.') defaultSuffix = "." + defaultSuffix; } this.defaultSuffix = defaultSuffix; firePropertyChange(PROPERTY_DEFAULT_SUFFIX, oldDefaultSuffix, defaultSuffix); } // // Editing. // /** * Returns the node that is currently edited, if any. * This method is not intended for public use - do not use it! */ public java.io.File getEditedNode() { return editedNode; } public boolean isEditing() { return editedNode != null; } public void startEditingAtPath(TreePath path) { editedNode = (java.io.File) path.getLastPathComponent(); super.startEditingAtPath(path); } public void cancelEditing() { editedNode = null; super.cancelEditing(); } public boolean stopEditing() { final boolean stop = super.stopEditing(); if (stop) editedNode = null; return stop; } /** * Called when the editing of a cell has been stopped. * The implementation in this class will rename the edited file, * obeying the rules for suffix handling and updating the expanded and * selected paths accordingly. * * @param evt The change event passed to * {@link CellEditorListener#editingStopped(ChangeEvent)}. */ protected void onEditingStopped(final ChangeEvent evt) { final TreeCellEditor tce = (TreeCellEditor) evt.getSource(); String base = tce.getCellEditorValue().toString().trim(); final java.io.File oldNode = (java.io.File) getLeadSelectionPath().getLastPathComponent(); final java.io.File parent = oldNode.getParentFile(); assert parent != null; if (!oldNode.isDirectory()) { if (isDisplayingSuffixes() && isEditingSuffixes()) { final String suffix = getSuffix(base); if (suffix == null) { final String defaultSuffix = getDefaultSuffix(); if (defaultSuffix != null) base += defaultSuffix; } } else { final String suffix = getSuffix(oldNode.getName()); if (suffix != null) base += suffix; } } final java.io.File node = new de.schlichtherle.io.File(parent, base); if (!renameTo(oldNode, node)) Toolkit.getDefaultToolkit().beep(); } private String getSuffix(final String base) { final int i = base.lastIndexOf('.'); return i != -1 ? base.substring(i) : null; } // // Rendering: // public String convertValueToText( final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { final java.io.File node = (java.io.File) value; final java.io.File editedNode = getEditedNode(); if (node != editedNode && !node.exists()) { // You will see this occur for files which have been deleted // concurrently or which are returned by File.listFiles(), but do // not actually File.exists(), such as "C:\hiberfile.sys" on the // Windows platform. return "?"; // NOI18N } final String base = node.getName(); if (base.length() <= 0) return node.getPath(); // This is a file system root. if (node.isDirectory() || isDisplayingSuffixes() && (!node.equals(editedNode) || isEditingSuffixes())) return base; final int i = base.lastIndexOf('.'); return i != -1 ? base.substring(0, i) : base; } // // Refreshing: // /** * Refreshes the entire tree, * restores the expanded and selected paths and scrolls to the lead * selection path if necessary. */ public void refresh() { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath((java.io.File) ftm.getRoot()); if (path != null) refresh(new TreePath[] { path }); } /** * Refreshes the subtree for the given node, * restores the expanded and selected paths and scrolls to the lead * selection path if necessary. * * @param node The file or directory to refresh. * This may not be {@code null}. * */ public void refresh(final java.io.File node) { if (node == null) throw new NullPointerException(); final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path != null) refresh(new TreePath[] { path }); } /** * Refreshes the subtree for the given paths, * restores the expanded and selected paths and scrolls to the lead * selection path if necessary. * * @param paths The array of {@code TreePath}s to refresh. * This may be {@code null}. */ public void refresh(final TreePath paths[]) { if (paths == null || paths.length <= 0) return; final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath lead = getLeadSelectionPath(); final TreePath anchor = getAnchorSelectionPath(); final TreePath[] selections = getSelectionPaths(); for (int i = 0, l = paths.length; i < l; i++) { final TreePath path = paths[i]; final Enumeration expansions = getExpandedDescendants(path); ftm.refresh((java.io.File) path.getLastPathComponent()); setExpandedDescendants(expansions); } setSelectionPaths(selections); setAnchorSelectionPath(anchor); setLeadSelectionPath(lead); scrollPathToVisible(lead); } private void setExpandedDescendants(final Enumeration expansions) { if (expansions == null) return; while (expansions.hasMoreElements()) setExpandedState((TreePath) expansions.nextElement(), true); } // // File operations: // /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the newly created file * is selected and visible. * If you would like to create a new file with initial content, please * check {@link #copyFrom(de.schlichtherle.io.File, InputStream)}. */ public boolean createNewFile(final java.io.File node) throws IOException { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.createNewFile(node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the newly created directory * is selected and visible. */ public boolean mkdir(final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.mkdir(node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the newly created directory * is selected and visible. */ public boolean mkdirs(final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.mkdirs(node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the copied node * is selected and visible. */ public boolean copyFrom(final de.schlichtherle.io.File node, final InputStream in) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.copyFrom(node, in)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the copied node * is selected and visible. */ public boolean copyTo(final de.schlichtherle.io.File oldNode, final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.copyTo(oldNode, node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the recursively copied node * is selected and visible. */ public boolean copyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.copyAllTo(oldNode, node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the copied node * is selected and visible. */ public boolean archiveCopyTo(final de.schlichtherle.io.File oldNode, final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.archiveCopyTo(oldNode, node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the recursively copied node * is selected and visible. */ public boolean archiveCopyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; if (!ftm.archiveCopyAllTo(oldNode, node)) return false; setSelectionPath(path); scrollPathToVisible(path); return true; } /** * Forwards the call to the {@link FileTreeModel}, * restores the expanded paths, selects {@code node} and scrolls to * it if necessary. */ public boolean renameTo(final java.io.File oldNode, final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; final Enumeration expansions; final TreePath oldPath = ftm.createTreePath(oldNode); if (oldPath != null) expansions = getExpandedDescendants(oldPath); else expansions = null; if (!ftm.renameTo(oldNode, node)) return false; if (expansions != null) while (expansions.hasMoreElements()) setExpandedState( substPath((TreePath) expansions.nextElement(), oldPath, path), true); setSelectionPath(path); scrollPathToVisible(path); return true; } private TreePath substPath( final TreePath tp, final TreePath oldPath, final TreePath path) { final java.io.File file = (java.io.File) tp.getLastPathComponent(); if (file.equals(oldPath.getLastPathComponent())) { return path; } else { final TreePath parent = substPath(tp.getParentPath(), oldPath, path); return parent.pathByAddingChild( new de.schlichtherle.io.File((java.io.File) parent.getLastPathComponent(), file.getName())); } } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the successor to the deleted node * is selected and visible. */ public boolean delete(final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; scrollPathToVisible(path); final int row = getRowForPath(path); if (!ftm.delete(node)) return false; setSelectionRow(row); return true; } /** * Forwards the call to the {@link FileTreeModel} * and scrolls the tree so that the successor to the deleted node * is selected and visible. */ public boolean deleteAll(final de.schlichtherle.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path == null) return false; scrollPathToVisible(path); final int row = getRowForPath(path); if (!ftm.deleteAll(node)) return false; setSelectionRow(row); return true; } public void setSelectionNode(final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path != null) { setSelectionPath(path); } } public void setSelectionNodes(final java.io.File[] nodes) { final FileTreeModel ftm = (FileTreeModel) getModel(); final java.util.List list = new LinkedList(); TreePath lastPath = null; for (int i = 0, l = nodes.length; i < l; i++) { lastPath = ftm.createTreePath(nodes[i]); if (lastPath != null) list.add(lastPath); } final int size = list.size(); if (size > 0) { final TreePath[] paths = new TreePath[size]; list.toArray(paths); setSelectionPaths(paths); } } public void scrollNodeToVisible(final java.io.File node) { final FileTreeModel ftm = (FileTreeModel) getModel(); final TreePath path = ftm.createTreePath(node); if (path != null) scrollPathToVisible(path); } // // Member classes: // private class Controller implements TreeExpansionListener, CellEditorListener, Serializable { public void treeCollapsed(TreeExpansionEvent evt) { ((FileTreeModel) getModel()).forget( (java.io.File) evt.getPath().getLastPathComponent()); } public void treeExpanded(TreeExpansionEvent evt) { } public void editingCanceled(ChangeEvent evt) { } public void editingStopped(ChangeEvent evt) { onEditingStopped(evt); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy