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);
}
}
}