package org.rr.commons.swing.dialogs; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Frame; import java.awt.HeadlessException; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.EventObject; import java.util.List; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JRootPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileSystemView; import javax.swing.tree.DefaultTreeCellEditor; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.rr.commons.swing.dialogs.JDirectoryChooser.JFolderTree.FolderTreeModel.IFolderNode; import org.rr.commons.utils.StringUtil; public class JDirectoryChooser extends Component { private static final long serialVersionUID = 5358634581899478319L; private final List<File> selectedDirectory = new ArrayList<>(); private Action abortAction = null; private Action okAction = null; private Action newFolderAction = null; private DocumentListener pathTextFieldDocumentListener = null; private TreeSelectionListener folderTreeSelectionListener; private String title = null; private String headline = null; private Frame parent = null; private JDialog dialog = null; private KeyAdapter dialogCloseListener; private boolean multiselect; private Point dialogLocation; private Dimension dialogSize; /** Creates new form JDirectoryChooser */ public JDirectoryChooser() { this(false); } public JDirectoryChooser(boolean multiselect) { super(); this.multiselect = multiselect; } /** * Initializes the gui actions which are needed that the * dialog do something. */ private void setupComponents() { this.jButtonCancel.setAction(this.getAbortAction()); this.jButtonOK.setAction(this.getOKAction()); this.jButtonNewFolder.setAction(this.getNewFolderAction()); FolderTreeCellRenderer renderer = new FolderTreeCellRenderer(); this.jTreeFolders.setCellRenderer(renderer); this.jTreeFolders.setEditable(true); this.jTreeFolders.setCellEditor(new FolderTreeCellEditor(jTreeFolders, renderer)); if(this.getSelectedDirectory()!=null) { this.jTreeFolders.setSelectionFilePath(this.getSelectedDirectory(), false); this.jTreeFolders.scrollPathToVisible(this.getSelectedDirectory()); } this.jTreeFolders.addTreeSelectionListener(this.getFolderTreeSelectionListener()); this.jTextFieldPathInput.getDocument().addDocumentListener(this.getPathTextFieldDocumentListener(jTextFieldPathInput)); this.jTextFieldPathInput.requestFocus(); } /** * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ private void initComponents(Container rootPane) { java.awt.GridBagConstraints gridBagConstraints; jPanel1 = new javax.swing.JPanel(); jLabelTitle = new javax.swing.JLabel(); jTextFieldPathInput = new javax.swing.JTextField(); jLabel1 = new javax.swing.JLabel(); jScrollPaneDirectoryTree = new javax.swing.JScrollPane(); jTreeFolders = new JFolderTree(); jButtonOK = new javax.swing.JButton(); jButtonCancel = new javax.swing.JButton(); jButtonNewFolder = new javax.swing.JButton(); jPanel1.setLayout(new java.awt.GridBagLayout()); if(this.multiselect) { jTreeFolders.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); } else { jTreeFolders.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); } jPanel1.setBorder(new javax.swing.border.EmptyBorder(new java.awt.Insets(10, 10, 5, 10))); jPanel1.setPreferredSize(new java.awt.Dimension(310, 330)); jLabelTitle.setText(this.getHeadline()); jLabelTitle.setName(""); jLabelTitle.setPreferredSize(new java.awt.Dimension(99, 14)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridy = 0; gridBagConstraints.gridwidth = 4; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 0, 15, 0); jPanel1.add(jLabelTitle, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 5; gridBagConstraints.gridwidth = 3; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 0, 15, 0); jPanel1.add(jTextFieldPathInput, gridBagConstraints); jLabel1.setText(Bundle.getString("SDirectoryChooser.Action.PathLabel")); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 5; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.insets = new java.awt.Insets(0, 0, 15, 15); jPanel1.add(jLabel1, gridBagConstraints); jScrollPaneDirectoryTree.setViewportView(jTreeFolders); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 4; gridBagConstraints.gridwidth = 4; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 0, 10, 0); jPanel1.add(jScrollPaneDirectoryTree, gridBagConstraints); jButtonOK.setMinimumSize(new java.awt.Dimension(80, 23)); jButtonOK.setPreferredSize(new java.awt.Dimension(80, 23)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 10; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 5); jPanel1.add(jButtonOK, gridBagConstraints); jButtonCancel.setMinimumSize(new java.awt.Dimension(80, 23)); jButtonCancel.setPreferredSize(new java.awt.Dimension(80, 23)); gridBagConstraints = new java.awt.GridBagConstraints(); jButtonCancel.setMargin(new java.awt.Insets(0, 5, 0, 5)); gridBagConstraints.gridx = 3; gridBagConstraints.gridy = 10; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; jPanel1.add(jButtonCancel, gridBagConstraints); jButtonNewFolder.setMargin(new java.awt.Insets(2, 0, 2, 0)); jButtonNewFolder.setMaximumSize(new java.awt.Dimension(99, 23)); jButtonNewFolder.setMinimumSize(new java.awt.Dimension(99, 23)); jButtonNewFolder.setPreferredSize(new java.awt.Dimension(99, 23)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 10; gridBagConstraints.gridwidth = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 5); jPanel1.add(jButtonNewFolder, gridBagConstraints); rootPane.add(jPanel1, java.awt.BorderLayout.CENTER); } /** * Returns the selected diretcory. This can be set either by the programmer via <code>setFile</code> or by a user action, such as either typing the * filename into the UI or selecting the file from a list in the UI. * * @see #setSelectedDirectory(File) * @return the selected directory */ public File getSelectedDirectory() { if(!this.selectedDirectory.isEmpty()) { return this.selectedDirectory.get(0); } return null; } /** * Returns the selected diretcory. This can be set either by the programmer via <code>setFile</code> or by a user action, such as either typing the * filename into the UI or selecting the file from a list in the UI. * * @see #setSelectedDirectory(File) * @return the selected directory */ public List<File> getSelectedDirectories() { return this.selectedDirectory; } /** * Sets the selected directory. If the file's parent directory is not the current directory, changes the current directory to be the file's parent * directory. * * @beaninfo preferred: true bound: true * * @see #getSelectedDirectory() * * @param selectedDirectory * the selected directory */ public void setSelectedDirectory(File selectedDirectory) { this.selectedDirectory.clear(); this.selectedDirectory.add(selectedDirectory); } /** * Pops a custom diretory chooser dialog. * For example, the following code * <pre> * directorychooser.showDialog(parentComponent); * </pre> * * Alternatively, the following code does the same thing: * <pre> * JFileChooser chooser = new JFileChooser(null); * chooser.setApproveButtonText("Run Application"); * chooser.showDialog(parentFrame, null); * </pre> * * <!--PENDING(jeff) - the following method should be added to the api: * showDialog(Component parent);--> * <!--PENDING(kwalrath) - should specify modality and what * "depends" means.--> * * <p> * * The <code>parent</code> argument determines two things: * the frame on which the open dialog depends and * the component whose position the look and feel * should consider when placing the dialog. If the parent * is a <code>Frame</code> object (such as a <code>JFrame</code>) * then the dialog depends on the frame and * the look and feel positions the dialog * relative to the frame (for example, centered over the frame). * If the parent is a component, then the dialog * depends on the frame containing the component, * and is positioned relative to the component * (for example, centered over the component). * If the parent is <code>null</code>, then the dialog depends on * no visible window, and it's placed in a * look-and-feel-dependent position * such as the center of the screen. * * @param parent the parent component of the dialog; * can be <code>null</code> * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ public void showDialog(Component parent) { this.parent = JOptionPane.getFrameForComponent(parent); this.dialog = this.createDialog(parent); this.setupComponents(); //take over the selected path which was possibly set by the developer //before invoking showDialog(). if(this.getSelectedDirectory()!= null) { this.jTextFieldPathInput.setText(this.getSelectedDirectory().getPath()); } //should be the last to be done. this.dialog.setVisible(true); } /** * Gets the location of the dialog when it was opened or the setted value if it wasn't opened. * @return The dialog location. */ public Point getDialogLocation() { return this.dialogLocation; } /** * Sets the location where the dialog should be opened. * @param p The location of the dialog. */ public void setDialogLocation(Point p) { this.dialogLocation = p; } /** * Sets the size of the dialog if it gets opened. * @param d The size for the dialog. */ public void setDialogSize(Dimension d) { this.dialogSize = d; } /** * Gets the dialog size when it was opened or the setted value if it wasn't already opened. * @return The dialog size. */ public Dimension getDialogSize() { return this.dialogSize; } /** * Creates and returns a new <code>JDialog</code> wrapping <code>this</code> centered on the <code>parent</code> in the <code>parent</code>'s * frame. This method can be overriden to further manipulate the dialog, to disable resizing, set the location, etc. Example: * * <pre> * class MyFileChooser extends JFileChooser { * protected JDialog createDialog(Component parent) throws HeadlessException { * JDialog dialog = super.createDialog(parent); * dialog.setLocation(300, 200); * dialog.setResizable(false); * return dialog; * } * } * </pre> * * @param parent * the parent component of the dialog; can be <code>null</code> * @return a new <code>JDialog</code> containing this instance * @exception HeadlessException * if GraphicsEnvironment.isHeadless() returns true. * @see java.awt.GraphicsEnvironment#isHeadless * @since 1.4 */ protected JDialog createDialog(Component parent) throws HeadlessException { String title = this.getTitle(); // getAccessibleContext().setAccessibleDescription(title); if (this.parent instanceof Frame) { dialog = new JDialog((Frame) this.parent, title, true); } else { dialog = new JDialog(); dialog.setTitle(this.getTitle()); dialog.setModal(false); } dialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { getAbortAction().actionPerformed(null); } }); dialog.setComponentOrientation(this.getComponentOrientation()); Container contentPane = dialog.getContentPane(); this.initComponents(contentPane); //hang in the ESC-close listiner into all componentens Component[] all = getAllComponentsRecursive(contentPane, null, new ArrayList<Component>()).toArray(new Component[0]); for (int i = 0; i < all.length; i++) { all[i].addKeyListener(getDialogCloseKeyListener()); } dialog.getRootPane().setDefaultButton(jButtonOK); if (JDialog.isDefaultLookAndFeelDecorated()) { boolean supportsWindowDecorations = UIManager.getLookAndFeel().getSupportsWindowDecorations(); if (supportsWindowDecorations) { dialog.getRootPane().setWindowDecorationStyle(JRootPane.FILE_CHOOSER_DIALOG); } } dialog.pack(); if(this.dialogLocation != null) { dialog.setLocation(this.dialogLocation); } else { dialog.setLocationRelativeTo(parent); } if(this.dialogSize != null) { dialog.setSize(this.dialogSize); } return dialog; } /** * Fills up the given ArrayList with all Components, matching to the given * class file, found downwards the given Container. The Container should be * a JRootPane for example. * * @param container * Container that should be searched for The Component * @param recursive * An empty ArrayList which will be filled up or null if a new * ArrayList should be returned. * @return An array of the type specified with the className will be * returned. If className is <code>null</code> an array over * {@link Component} will be returned. */ @SuppressWarnings("unchecked") private static <T extends Component>ArrayList<Component> getAllComponentsRecursive(Container container, Class<T> classNames[], ArrayList<Component> recursive) { //is there no class name defined, just setup the default value. if (classNames == null || classNames.length == 0) { classNames = new Class[] {Component.class}; } // No ArrayList, just create a new one. if (recursive == null) { // recursive = new Object[0]; recursive = new ArrayList<>(100); } // No Container, nothing to do. if (container == null) { return recursive; } // Search for the component which is an instance of the Class specified // with the className parameter for (int i = 0; i < container.getComponentCount(); i++) { try { Component comp = container.getComponent(i); if (comp instanceof Container) { getAllComponentsRecursive((Container) comp, classNames, recursive); } for (int j = 0; j < classNames.length; j++) { if (classNames[j]==null || classNames[j].isInstance(comp)) { recursive.add(comp); break; } } } catch (Exception e) { // container.getComponent(i); can fail if it was removed. } } return recursive; } /** * Gets the Action which is performed while hitting the Cancel button or closing the window using the X in the window title. * * @return The desired Action. */ private Action getAbortAction() { if (this.abortAction == null) { this.abortAction = new AbstractAction(Bundle.getString("SDirectoryChooser.Action.Cancel")) { private static final long serialVersionUID = -684918749910419234L; public void actionPerformed(ActionEvent e) { selectedDirectory.clear(); dispose(); } }; } return this.abortAction; } /** * Gets the Action which is performed while hitting the OK button. * * @return The desired Action. */ private Action getOKAction() { if (this.okAction == null) { this.okAction = new AbstractAction(Bundle.getString("SDirectoryChooser.Action.OK")) { private static final long serialVersionUID = -684918749910419234L; public void actionPerformed(ActionEvent e) { selectedDirectory.clear(); selectedDirectory.addAll(jTreeFolders.getSelectionFolderPath()); dispose(); } }; } return this.okAction; } /** * Gets the Action which is performed while hitting the OK button. * * @return The desired Action. */ private Action getNewFolderAction() { if (this.newFolderAction == null) { this.newFolderAction = new AbstractAction(Bundle.getString("SDirectoryChooser.Action.NewFolder")) { private static final long serialVersionUID = -522395172298834915L; public void actionPerformed(ActionEvent e) { List<File> currentSelection = jTreeFolders.getSelectionFolderPath(); try { if(currentSelection!=null && currentSelection.size() == 1) { File newFolder = jTreeFolders.addFolder(currentSelection.get(0)); jTreeFolders.renameFolder(newFolder); } } catch (IOException e1) { Logger.getAnonymousLogger().logp(Level.WARNING, this.getClass().getName(), "actionPerformed", "Creating a new folder has been failed", e1); } } }; } return this.newFolderAction; } /** * Gets the DocumentListener to be used for the JTextField where * the path is displayed to. * * @param fieldInstance The JTextField instance * @return */ private DocumentListener getPathTextFieldDocumentListener(final JTextField fieldInstance) { if(this.pathTextFieldDocumentListener == null) { this.pathTextFieldDocumentListener = new DocumentListener() { public void removeUpdate(DocumentEvent e) { this.selectTreePath(e); } public void insertUpdate(DocumentEvent e) { this.selectTreePath(e); } public void changedUpdate(DocumentEvent e) { this.selectTreePath(e); } /** * If There is a valid entry, the entry is * to be selected within the JTree. * * @param e The DocumentEvent which has been triggered this listener instance. */ private void selectTreePath(DocumentEvent e) { String text = fieldInstance.getText(); File f = new File(text); jTreeFolders.removeTreeSelectionListener(getFolderTreeSelectionListener()); jTreeFolders.setSelectionFilePath(f, true); jTreeFolders.expandFilePath(f); jTreeFolders.addTreeSelectionListener(getFolderTreeSelectionListener()); } }; } return this.pathTextFieldDocumentListener; } private TreeSelectionListener getFolderTreeSelectionListener() { if(this.folderTreeSelectionListener==null) { this.folderTreeSelectionListener = new TreeSelectionListener() { public void valueChanged(final TreeSelectionEvent e) { JFolderTree tree = (JFolderTree) e.getSource(); if(tree.getSelectionFolderPath()!=null && !tree.getSelectionFolderPath().isEmpty()) { File path = tree.getSelectionFolderPath().get(0); if(path!=null && path.exists()) { SwingUtilities.invokeLater(new Runnable() { public void run() { jTextFieldPathInput.getDocument().removeDocumentListener(getPathTextFieldDocumentListener(jTextFieldPathInput)); List<File> selectionFolderPath = ((JFolderTree) e.getSource()).getSelectionFolderPath(); for(File f : selectionFolderPath) { jTextFieldPathInput.setText( f.getPath() ); } jTextFieldPathInput.getDocument().addDocumentListener(getPathTextFieldDocumentListener(jTextFieldPathInput)); } }); } } } }; } return this.folderTreeSelectionListener; } private KeyListener getDialogCloseKeyListener() { if(this.dialogCloseListener==null) { this.dialogCloseListener=new KeyAdapter() { public void keyReleased(KeyEvent e) { if (jTreeFolders.isEditing()) { return; } if(e.getKeyCode() == KeyEvent.VK_ESCAPE) { //TODO Muss noch ausprogrammiert werden, dass der bei //Escape nicht nachm Editieren im Tree zu geht, weil sich //das Escape ja dann auf den Editor bezieht. // getAbortAction().actionPerformed(null); } } }; } return this.dialogCloseListener; } /** * Sets the title for the dialog which is displayed * in the windows title. * * @param string The title to be displayed in the title of the dialog. * @see #getTitle(); */ public void setTitle(String string) { this.title = string; } /** * Gets the title to be displayed within the title of the dialog. * @return The dialog window title. * * @see #setTitle(String) */ public String getTitle() { if(this.title == null) { this.title = Bundle.getString("SDirectoryChooser.Action.DefaultTitle"); } return this.title; } /** * Gets the headline string which is displayed at the top of the dialog * above the folder tree where the suer can click arount for selecting * a folder. * * @return The dialog headline. * @see #setHeadline(String) */ public String getHeadline() { if(this.headline==null) { this.headline = getDefaultHeadline(); } return this.headline; } /** * Gets the defualt localized headline string which is displayed at the top of the dialog * above the folder tree where the suer can click arount for selecting * a folder. * * @return The default dialog headline. */ public static String getDefaultHeadline() { return Bundle.getString("SDirectoryChooser.Action.DefaultHeadline"); } /** * Sets the headline string which is displayed at the top of the dialog * above the folder tree where the suer can click arount for selecting * a folder. * * @param headline The headline to be set. * * @see #getHeadline(String) */ public void setHeadline(String headline) { this.headline = headline; } /** * removes all no loonger needed references. Will always be invoked * after the dialog gets invisible. A new dialog instance * will be created for each <code>{@link #showDialog(Component)}</code> invokement. */ public void dispose() { this.dialogSize = this.dialog.getSize(); this.dialogLocation = this.dialog.getLocation(); this.dialog.setVisible(false); // this.dialog.dispose(); this.dialog = null; //remove also the component references this.jButtonCancel = null; this.jButtonNewFolder = null; this.jButtonOK = null; this.jLabel1 = null; this.jLabelTitle= null; this.jPanel1 = null; this.jScrollPaneDirectoryTree = null; this.jTextFieldPathInput = null; this.jTreeFolders = null; } /** * @param args * the command line arguments */ public static void main(String args[]) { JDirectoryChooser sc = new JDirectoryChooser(true); sc.setSelectedDirectory(new File("/home/guru")); sc.showDialog(null); List<File> selectedDirectories = sc.getSelectedDirectories(); for (int i = 0; i < selectedDirectories.size(); i++) { System.out.println(selectedDirectories.get(i)); } System.exit(0); } /** * Opens a {@link JFileChooser} and returns the selected folder or * <code>null</code> if no folder was selected. * @return The selected folder or <code>null</code>. */ public File getDirectorySelection(String folder, Component invoker) { List<File> directorySelections = getDirectorySelections(folder, invoker); if(directorySelections != null && !directorySelections.isEmpty()) { return directorySelections.get(0); } return null; } /** * Opens a {@link JFileChooser} and returns the selected folder or * <code>null</code> if no folder was selected. * @return The selected folder or <code>null</code>. */ public List<File> getDirectorySelections(String folder, Component invoker) { if(folder!=null && folder.length() > 0) { setSelectedDirectory(new File(folder)); } else { setSelectedDirectory(new File(System.getProperties().getProperty("user.home"))); } showDialog(invoker); List<File> selectedDirectory = getSelectedDirectories(); return selectedDirectory; } // Variables declaration - do not modify private javax.swing.JButton jButtonCancel; private javax.swing.JButton jButtonNewFolder; private javax.swing.JButton jButtonOK; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabelTitle; private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPaneDirectoryTree; private javax.swing.JTextField jTextFieldPathInput; private JFolderTree jTreeFolders; // End of variables declaration static class JFolderTree extends JTree { private static final long serialVersionUID = -3688036899738477139L; public JFolderTree() { super(); } /** * Determines the {@link TreePath} for the given file <code>path</code>. * @param path The file to be converted into a TreePath. * @return The {@link TreePath} matching to the file. Returns <code>null</code> if no matching path could be found. */ public TreePath getPathForFolder(final File path) { final FolderTreeModel folderTreeModel = (FolderTreeModel) this.getModel(); final Object[] pathA = folderTreeModel.getPath(path); if(pathA == null) { return null; } final TreePath result = new TreePath(pathA); return result; } /** * Expands the JTree view to the given File path. * * @param path The file to be expanded in the tree view. */ public void expandFilePath(final File path) { final TreePath treePath = this.getPathForFolder(path); if(treePath!=null) { this.expandPath(treePath); } } /** * Selects the node identified by the specified path. If any * component of the path is hidden (under a collapsed node), and * <code>getExpandsSelectedPaths</code> is true it is * exposed (made viewable). * <BR><BR> * Will also scrolls to visible! This happens for perfromance reasons * because the node for the file must not be searched again. * * @param path the <code>TreePath</code> specifying the node to select * @param cloudy searches for the entry and find also a match if there only * the first letters of an entry. */ public void setSelectionFilePath(final File path, boolean cloudy) { final TreePath treePath = this.getPathForFolder(path); if(treePath!=null) { //save the old selection TreePath oldSelectionPath = this.getSelectionPath(); //set a new selection this.setSelectionPath(treePath); //get the new selection TreePath newSelectionPath = this.getSelectionPath(); //test if the selection has changed. if((oldSelectionPath==null && newSelectionPath!=null) || !oldSelectionPath.equals(newSelectionPath)) { //scroll to the new entry and return this.scrollPathToVisible(treePath); return; } } else if (cloudy) { // the first match was not good, now search cloudy. File parentFile = path.getParentFile(); if(parentFile!=null) { TreePath pathForFolder = getPathForFolder(parentFile); if(pathForFolder != null) { IFolderNode lastFolderNode = (IFolderNode) pathForFolder.getLastPathComponent(); for (int i = 0; i < lastFolderNode.getChildCount(); i++) { IFolderNode child = (IFolderNode) lastFolderNode.getChildAt(i); if(toOSCompareString(child.toString()).startsWith(toOSCompareString(path.getName()))) { //possibly matching node found TreePath found = getPathForFolder(child.getFile()); this.setSelectionPath(found); this.scrollPathToVisible(found); return; } } } } } } /** * Operating system dependant returns a toLowercased string or * not. * * @param s The string to be changed. * @return The changed string */ private static String toOSCompareString(String s) { if(s==null) { return null; } if(System.getProperty("os.name").toLowerCase().indexOf("windows")!=-1) { return s.toLowerCase(); } else { return s; } } /** * Makes sure all the path components in path are expanded (except * for the last path component) and scrolls so that the * node identified by the path is displayed. Only works when this * <code>JTree</code> is contained in a <code>JScrollPane</code>. * * @param path the <code>TreePath</code> identifying the node to * bring into view */ public void scrollPathToVisible(final File path) { final TreePath treePath = this.getPathForFolder(path); if(treePath!=null) { this.scrollPathToVisible(treePath); } } /** * Adds a folder with the default name to the given path. * * @param path The path where the folder should be added to. * @throws IOException */ public File addFolder(File path) throws IOException { TreePath treePath = getPathForFolder(path); File newFolder = ((FolderTreeModel)this.getModel()).addFolder(treePath); this.setSelectionFilePath(newFolder, false); return newFolder; } public void renameFolder(File path) throws IOException { TreePath treePath = getPathForFolder(path); this.startEditingAtPath(treePath); } /** * Removes the node but did not perform a delete! * * @param node The node which children should be reread. */ void refreshChildren(IFolderNode node) { node.refreshChildren(); ((FolderTreeModel)this.getModel()).fireTreeStructureChanged(node.getParent(), ((FolderTreeModel)this.getModel()).getPathToRoot(node), null, null); } static class FolderTreeModel extends DefaultTreeModel { private static final long serialVersionUID = -456216843620742653L; public FolderTreeModel() { /**create an instance with a TreeNode which handles the root.*/ super(new FileSystemRootNode()); } public IFolderNode[] getPath(File path) { StringTokenizer fileTokenezier = new StringTokenizer(path.getPath(), String.valueOf(File.separatorChar+":")); //searching the node one after the other is fundamental because the children will be also read for the path //and the search only works with children which knows it's sub childs. IFolderNode lastNode = (IFolderNode) this.getRoot(); while(fileTokenezier.hasMoreTokens()) { String name = fileTokenezier.nextToken(); lastNode = this.searchNode(name, lastNode); if(lastNode == null) { return null; //nothing found. } if(fileTokenezier.hasMoreTokens()) { lastNode.getChildCount(); //invokes the read of all children for the node. } } if(lastNode!=null) { ArrayList<IFolderNode> result = new ArrayList<>(); IFolderNode parent = lastNode; while (parent!=null) { result.add(parent); parent = (IFolderNode) parent.getParent(); } Collections.reverse(result); return result.toArray(new IFolderNode[result.size()]); //TODO CHANGE // return (IFolderNode[]) VBValueListFunctions.reverse(result.toArray(new IFolderNode[result.size()])) ; } return null; } /** * Searches the visible node area for a node with the given name. * * @param name * @param searchNode * @return */ private IFolderNode searchNode(String name, IFolderNode searchNode) { if(searchNode.isChildrenLoaded()) { for (int i = 0; i < searchNode.getChildCount(); i++) { IFolderNode childNode = (IFolderNode) searchNode.getChildAt(i); if(childNode.matchName(name)) { return childNode; } else if (childNode.isChildrenLoaded()) { IFolderNode result = searchNode(name, childNode); if(result!=null) { return result; } } } } return null; } File addFolder(TreePath treePath) throws IOException { IFolderNode lastNode = (IFolderNode) treePath.getLastPathComponent(); File result = lastNode.addFolder(); this.fireTreeStructureChanged(lastNode, treePath.getPath(), null, null); return result; } protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { super.fireTreeStructureChanged(source, path, childIndices, children); } /** * This sets the user object of the TreeNode identified by path * and posts a node changed. If you use custom user objects in * the TreeModel you're going to need to subclass this and * set the user object of the changed node to something meaningful. */ public void valueForPathChanged(TreePath path, Object newValue) { final IFolderNode aNode = (IFolderNode)path.getLastPathComponent(); String oldPathName = aNode.getFile().getPath(); String newPathName = oldPathName.substring(0, oldPathName.length() - aNode.getFile().getName().length()) + newValue; final File newPath = new File(newPathName); aNode.renameTo(newPath); nodeChanged(aNode); } public interface IFolderNode extends TreeNode { /** * Icon for a file, directory, or folder as it would be displayed in * a system file browser. Example from Windows: the "M:\" directory * displays a CD-ROM icon. * * The default implementation gets information from the ShellFolder class. * * @return an icon as it would be displayed by a native file chooser * @see JFileChooser#getIcon */ public Icon getSystemIcon(); /** * Determines if the sub folders are already * loaded. This should help for searching because * it's no good idea to search all nodes. This will be * like a complete filesystem scan. * * @return <code>true</code> if the node has already loaded * it's sub folders. */ public boolean isChildrenLoaded(); /** * Test if the given name is matching to the node name. * The match should also include some impreciseness, so c: * matches also to the node "c:\" or "c:\ Datentr�ger" * * @param name * @return */ public boolean matchName(String name); /** * Returns the file for this node or <code>null</code> if this is * a node without a file path. * * @return The file path for the node instance. */ public File getFile(); /** * Adds a new folder as child to the node instance where this method is invoked. * the folder gets the default name by the OS. * * @throws IOException */ public File addFolder() throws IOException; /** * Renames the node instance to the given file. The file system rename * should also be performed. * @param newPathName The name of the new path */ public void renameTo(File newPathName); /** * tells the node taht it's children should be reread. * @param node The node which childre should be refreshed. */ void refreshChildren(); } private static class FileSystemRootNode extends FolderNode implements IFolderNode { FileSystemRootNode() { super(null, null); File[] roots = FileSystemView.getFileSystemView().getRoots(); //File.listRoots(); if(roots.length == 1) { folder = roots[0]; } } public boolean isChildrenLoaded() { this.getChildCount(); //init the children //and also init the children for the first sub entry ((IFolderNode)this.getChildAt(0)).getChildCount(); return true; } } /** * Should be created for each folder which gets * visible. */ private static class FolderNode implements IFolderNode { protected File folder; protected TreeNode parent; private File[] subFolders; private IFolderNode[] childFolderNodes; private FileSystemView fileSystemViewInstance = FileSystemView.getFileSystemView(); /** * @param folder The folder which is represented by * the <code>FolderNode</code>. */ FolderNode(File folder, TreeNode parent) { this.folder = folder; this.parent = parent; } /** * Invoking this method will cause that the * subfolders are read. * * @return An array with null values. The nodes are created on demand. */ private IFolderNode[] getChildren() { if(this.subFolders==null) { File[] subFiles = fileSystemViewInstance.getFiles(this.folder, true); ArrayList<File> processedSubFolders = new ArrayList<>(); for (int i = 0; i < subFiles.length; i++) { if(subFiles[i].isDirectory()) { processedSubFolders.add(subFiles[i]); } } this.subFolders= (File[]) processedSubFolders.toArray(new File[processedSubFolders.size()]); Arrays.sort(this.subFolders); } if(this.subFolders==null) { this.subFolders = new File[0]; } if(this.childFolderNodes==null) { this.childFolderNodes = new FolderNode[subFolders.length]; //only create the array but not the instances. } return this.childFolderNodes; } /** * @return The folder which is represented by this TreeNode. */ public File getFile() { return this.folder; } /** * Gets the name of the folder. This is the last part * of the path. * * @return The folder name. */ public String getFolderName() { String result = this.folder.getPath(); //use system display folder names if possible. if(!fileSystemViewInstance.isFloppyDrive(this.folder)) { String symbolicName = fileSystemViewInstance.getSystemDisplayName(this.folder); if(!toOSCompareString(result).equals(toOSCompareString(symbolicName)) && symbolicName.length()>0) { return symbolicName; } } if(result.indexOf('/')==-1 && result.indexOf('\\')==-1) { return result; } else { result = result.replace('\\', '/'); } result = result.substring(result.lastIndexOf('/')+1); if(result.length()>1) { return result; } else { return this.folder.getPath(); } } /** * creates a new node if not already done. Nodes will * be created on demand. */ public Enumeration<File> children() { throw new RuntimeException("not supported"); } /** * I not really know, but children's are allows here. Change it * if it gets to disturb something. */ public boolean getAllowsChildren() { return true; } /** * Gets the child at the desired index. The node will be * created on demand. */ public TreeNode getChildAt(int childIndex) { try { IFolderNode[] children = this.getChildren(); if(children[childIndex]==null) { children[childIndex] = new FolderNode(subFolders[childIndex], FolderNode.this); } return (TreeNode) children[childIndex]; } catch (Exception e) { return null; } } /** * @return the number of children available with this node instance. */ public int getChildCount() { return this.getChildren().length; } public int getIndex(TreeNode node) { IFolderNode[] children = this.getChildren(); for (int i = 0; i < children.length; i++) { if(children[i]!=null && children[i] == node) { return i; } else if (children[i]!=null && children[i] instanceof FolderNode && ((FolderNode)children[i]).getFile().getPath().equals(((FolderNode)children[i]).getFile().getPath())) { return i; } } return -1; } /** * The parent TreeNode for this folder node instance. */ public TreeNode getParent() { return this.parent; } /** * @return <code>true</code> if no sub folders are present. */ public boolean isLeaf() { if(fileSystemViewInstance.isDrive(this.getFile()) || fileSystemViewInstance.isRoot(this.getFile())) { return false; } else if(this.subFolders==null) { return false; } return this.getChildCount()==0; } public String toString() { return this.getFolderName(); } /** * Gets an Icon matching to the file represented by this node. */ public Icon getSystemIcon() { if(this.folder.exists()) { return fileSystemViewInstance.getSystemIcon(this.folder); } return null; } public boolean isChildrenLoaded() { return !(this.subFolders==null); } /** * Tests if the given name matches to the path segement for this node. */ public boolean matchName(String name) { if(this.fileSystemViewInstance.isDrive(this.folder)) { String match = StringUtil.replace(this.folder.getPath(), String.valueOf(File.separatorChar), ""); match = StringUtil.replace(match, ":", ""); if(toOSCompareString(match).equals(toOSCompareString(name))) { return true; } } else if (this.getFolderName().equalsIgnoreCase(name)) { return true; } return false; } /** * Adds a child folder to this {@link FolderNode} instance. * @throws IOException */ public File addFolder() throws IOException { File newFolder = fileSystemViewInstance.createNewFolder(this.getFile()); //take shure taht tzhe children are loaded this.getChildCount(); ArrayList<File> newSubFolders = new ArrayList<>(this.subFolders.length + 1); newSubFolders.addAll(Arrays.asList(this.subFolders)); newSubFolders.add(newFolder); this.subFolders = newSubFolders.toArray(new File[newSubFolders.size()]); // this.subFolders = (File[]) VBValueListFunctions.append(this.subFolders, newFolder); ArrayList<IFolderNode> result = new ArrayList<>(this.childFolderNodes.length + 1); result.addAll(Arrays.asList(this.childFolderNodes)); result.add(new FolderNode(newFolder, this)); this.childFolderNodes = result.toArray(new IFolderNode[result.size()]); // this.childFolderNodes = (IFolderNode[]) // VBValuelunctions.append(this.childFolderNodes, new FolderNode(newFolder, this)); return newFolder; } /** * renames the folder which is represented by this node instance to * the given <code>newPathName</code>. */ public void renameTo(File newPathName) { this.folder.renameTo(newPathName); this.folder = newPathName; } public void refreshChildren() { this.subFolders = null; this.childFolderNodes = null; } } } /** * Gets the selected folder path or <code>null</code> if nothing is selected. * @return The user selection. */ public List<File> getSelectionFolderPath() { final TreePath[] treePath = this.getSelectionPaths(); final ArrayList<File> result = new ArrayList<>(); if(treePath==null) { return null; } for(TreePath tp : treePath) { IFolderNode lastNode = (IFolderNode) tp.getLastPathComponent(); if(lastNode==null) { return null; } result.add(lastNode.getFile()); } return result; } public TreeModel getModel() { if(this.treeModel==null) { this.treeModel=new FolderTreeModel(); super.setModel(this.treeModel); } return this.treeModel; } public void setModel(TreeModel newModel) { //do nothing } } /** * Does the same as the DefaultTreeCellRenderer but implements the * Icons which are coming with the {@link org.rr.commons.swing.dialogs.JDirectoryChooser.eva3.rt.se.form.SDirectoryChooser.FolderTreeModel.FolderNode}s. */ static class FolderTreeCellRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = -7057675192468615801L; public Component getTreeCellRendererComponent(final JTree tree, final Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); if(value instanceof IFolderNode) { Icon icon = ((IFolderNode)value).getSystemIcon(); setIcon(icon); } return this; } } static class FolderTreeCellEditor extends DefaultTreeCellEditor { public FolderTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) { super(tree, renderer); } public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { Component result = super.getTreeCellEditorComponent(tree, value, isSelected, expanded, leaf, row); editingIcon = ((IFolderNode)value).getSystemIcon(); //setup the icon to be used while editing return result; } public boolean isCellEditable(EventObject event) { return super.isCellEditable(event); } } }