/** * L2FProd.com Common Components 7.3 License. * * Copyright 2005-2007 L2FProd.com * * 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 com.l2fprod.common.swing.plaf.windows; import com.l2fprod.common.swing.JDirectoryChooser; import com.l2fprod.common.swing.LookAndFeelTweaks; import com.l2fprod.common.swing.plaf.DirectoryChooserUI; import com.l2fprod.common.swing.tree.LazyMutableTreeNode; import com.l2fprod.common.util.OS; import javax.swing.*; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileView; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicFileChooserUI; import javax.swing.tree.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.List; import com.l2fprod.common.swing.plaf.windows.WindowsDirectoryChooserUI.Queue; /** * WindowsDirectoryChooserUI. <br> */ public class WindowsDirectoryChooserUI extends BasicFileChooserUI implements DirectoryChooserUI { public static ComponentUI createUI(JComponent c) { return new WindowsDirectoryChooserUI((JFileChooser) c); } private static Queue nodeQueue; private JFileChooser chooser; private JTree tree; private JScrollPane treeScroll; private JButton approveButton; private JButton cancelButton; private JPanel buttonPanel; private BasicFileView fileView = new WindowsFileView(); private Action approveSelectionAction = new ApproveSelectionAction(); private boolean useNodeQueue; private JButton newFolderButton; private Action newFolderAction = new NewFolderAction(); private String newFolderText = null; private String newFolderToolTipText = null; public WindowsDirectoryChooserUI(JFileChooser chooser) { super(chooser); } public void rescanCurrentDirectory(JFileChooser fc) { super.rescanCurrentDirectory(fc); findFile(chooser.getSelectedFile() == null ? chooser.getCurrentDirectory() : chooser.getSelectedFile(), true, true); } public void ensureFileIsVisible(JFileChooser fc, File f) { super.ensureFileIsVisible(fc, f); File selectedFile = fc.getSelectedFile(); boolean select = selectedFile != null && selectedFile.equals(f); findFile(f, select, false); } protected String getToolTipText(MouseEvent event) { TreePath path = tree.getPathForLocation(event.getX(), event.getY()); if (path != null && path.getLastPathComponent() instanceof FileTreeNode) { FileTreeNode node = (FileTreeNode) path.getLastPathComponent(); String typeDescription = getFileView(chooser).getTypeDescription(node.getFile()); if (typeDescription == null || typeDescription.length() == 0) { return node.toString(); } else { return node.toString() + " - " + typeDescription; } } else { return null; } } public void installComponents(JFileChooser chooser) { this.chooser = chooser; chooser.setLayout(LookAndFeelTweaks.createBorderLayout()); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); Component accessory = chooser.getAccessory(); if (accessory != null) { chooser.add("North", chooser.getAccessory()); } tree = new JTree() { public String getToolTipText(MouseEvent event) { String tip = WindowsDirectoryChooserUI.this.getToolTipText(event); if (tip == null) { return super.getToolTipText(event); } else { return tip; } } }; tree.addTreeExpansionListener(new TreeExpansion()); tree.setModel(new FileSystemTreeModel(chooser.getFileSystemView())); tree.setRootVisible(false); tree.setShowsRootHandles(false); tree.setCellRenderer(new FileSystemTreeRenderer()); tree.setToolTipText(""); chooser.add("Center", treeScroll = new JScrollPane(tree)); treeScroll.setPreferredSize(new Dimension(300, 300)); approveButton = new JButton(); approveButton.setAction(getApproveSelectionAction()); cancelButton = new JButton(); cancelButton.setAction(getCancelSelectionAction()); cancelButton.setDefaultCapable(true); newFolderButton = new JButton(); newFolderButton.setAction(getNewFolderAction()); buttonPanel = new JPanel(new GridBagLayout()); GridBagConstraints gridBagConstraints = new GridBagConstraints(); gridBagConstraints.insets = new Insets(0, 0, 0, 25); gridBagConstraints.anchor = GridBagConstraints.EAST; gridBagConstraints.weightx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.gridx = 0; buttonPanel.add(Box.createHorizontalStrut(0), gridBagConstraints); buttonPanel.add(newFolderButton, gridBagConstraints); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.insets = new Insets(0, 0, 0, 6); gridBagConstraints.weightx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.gridx = 1; buttonPanel.add(approveButton, gridBagConstraints); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.weightx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.gridx = 2; buttonPanel.add(cancelButton, gridBagConstraints); chooser.add("South", buttonPanel); updateView(chooser); } public Action getNewFolderAction() { return newFolderAction; } protected void installStrings(JFileChooser fc) { super.installStrings(fc); saveButtonToolTipText = UIManager.getString("DirectoryChooser.saveButtonToolTipText"); openButtonToolTipText = UIManager.getString("DirectoryChooser.openButtonToolTipText"); cancelButtonToolTipText = UIManager.getString("DirectoryChooser.cancelButtonToolTipText"); newFolderText = UIManager.getString("DirectoryChooser.newFolderButtonText"); newFolderToolTipText = UIManager.getString("DirectoryChooser.newFolderButtonToolTipText"); } protected void uninstallStrings(JFileChooser fc) { super.uninstallStrings(fc); newFolderText = null; newFolderToolTipText = null; } public void uninstallComponents(JFileChooser chooser) { chooser.remove(treeScroll); chooser.remove(buttonPanel); } public FileView getFileView(JFileChooser fc) { return fileView; } protected void installListeners(JFileChooser fc) { super.installListeners(fc); tree.addTreeSelectionListener(new SelectionListener()); fc.getActionMap().put("refreshTree", new UpdateAction()); fc.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F5"), "refreshTree"); } private class UpdateAction extends AbstractAction { public void actionPerformed(ActionEvent e) { JFileChooser fc = getFileChooser(); fc.rescanCurrentDirectory(); } } protected void uninstallListeners(JFileChooser fc) { super.uninstallListeners(fc); } public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { return new ChangeListener(); } private void updateView(JFileChooser chooser) { if (chooser.getApproveButtonText() != null) { approveButton.setText(chooser.getApproveButtonText()); approveButton.setMnemonic(chooser.getApproveButtonMnemonic()); } else { if (JFileChooser.OPEN_DIALOG == chooser.getDialogType()) { approveButton.setText(openButtonText); approveButton.setToolTipText(openButtonToolTipText); approveButton.setMnemonic(openButtonMnemonic); } else { approveButton.setText(saveButtonText); approveButton.setToolTipText(saveButtonToolTipText); approveButton.setMnemonic(saveButtonMnemonic); } } cancelButton.setText(cancelButtonText); cancelButton.setMnemonic(cancelButtonMnemonic); newFolderButton.setText(newFolderText); newFolderButton.setToolTipText(newFolderToolTipText); newFolderButton.setVisible(((JDirectoryChooser) chooser).isShowingCreateDirectory()); buttonPanel.setVisible(chooser.getControlButtonsAreShown()); // ensure approve/cancel buttons have the same width approveButton.setPreferredSize(null); cancelButton.setPreferredSize(null); Dimension preferredSize = approveButton.getMinimumSize(); preferredSize = new Dimension(Math.max(preferredSize.width, cancelButton.getPreferredSize().width), preferredSize.height); approveButton.setPreferredSize(preferredSize); cancelButton.setPreferredSize(preferredSize); } /** * Ensures the file is visible, tree expanded and optionally * selected * * @param fileToLocate * @param selectFile */ private void findFile(File fileToLocate, boolean selectFile, boolean reload) { if (fileToLocate == null || !fileToLocate.isDirectory()) { return; } // build the canonical path so we can navigate the tree model to // find the // node File file = null; try { file = fileToLocate.getCanonicalFile(); } catch (Exception e) { return; } // temporarly disable loading nodes in the background useNodeQueue = false; TreePath pathToSelect; try { // split the full path into individual files to locate them in // the tree // model List files = new ArrayList(); files.add(file); while ((file = chooser.getFileSystemView().getParentDirectory(file)) != null) { files.add(0, file); } List path = new ArrayList(); // start from the root DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot(); path.add(node); DefaultMutableTreeNode current; boolean found = true; // ...and go through the tree model to find the files. Stop as // soon as // path is completely found or if one of the files in the path // is not // found. while (files.size() > 0 && found) { found = false; for (int i = 0, c = node.getChildCount(); i < c; i++) { current = (DefaultMutableTreeNode) node.getChildAt(i); File f = ((FileTreeNode) current).getFile(); if (files.get(0).equals(f)) { path.add(current); files.remove(0); node = current; found = true; break; } } } // select the path we found, it may be the file we were looking // for or a // subpath only pathToSelect = new TreePath(path.toArray()); if (pathToSelect.getLastPathComponent() instanceof FileTreeNode && reload) { ((FileTreeNode) (pathToSelect.getLastPathComponent())).clear(); enqueueChildren((FileTreeNode) pathToSelect.getLastPathComponent()); } } finally { // re-enable background loading useNodeQueue = true; } if (selectFile) { tree.expandPath(pathToSelect); tree.setSelectionPath(pathToSelect); } tree.makeVisible(pathToSelect); //scrollPathToVisible(pathToSelect); } private class ChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { if (JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY.equals(evt.getPropertyName())) { updateView(chooser); } if (JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY.equals(evt.getPropertyName())) { if (chooser.isMultiSelectionEnabled()) { tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); } else { tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); } } if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(evt.getPropertyName())) { findFile(chooser.getCurrentDirectory(), false, false); } if (JFileChooser.ACCESSORY_CHANGED_PROPERTY.equals(evt.getPropertyName())) { Component oldValue = (Component) evt.getOldValue(); Component newValue = (Component) evt.getNewValue(); if (oldValue != null) { chooser.remove(oldValue); } if (newValue != null) { chooser.add("North", newValue); } chooser.revalidate(); chooser.repaint(); } if (JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY.equals(evt.getPropertyName())) { updateView(chooser); } if (JDirectoryChooser.SHOWING_CREATE_DIRECTORY_CHANGED_KEY.equals(evt.getPropertyName())) { updateView(chooser); } } } private class SelectionListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { getApproveSelectionAction().setEnabled(tree.getSelectionCount() > 0); setSelectedFiles(); // the current directory is the one currently selected TreePath currentDirectoryPath = tree.getSelectionPath(); if (currentDirectoryPath != null) { File currentDirectory = ((FileTreeNode) currentDirectoryPath.getLastPathComponent()).getFile(); chooser.setCurrentDirectory(currentDirectory); } } } public Action getApproveSelectionAction() { return approveSelectionAction; } private void setSelectedFiles() { TreePath[] selectedPaths = tree.getSelectionPaths(); if (selectedPaths == null || selectedPaths.length == 0) { chooser.setSelectedFile(null); return; } List files = new ArrayList(); for (int i = 0, c = selectedPaths.length; i < c; i++) { LazyMutableTreeNode node = (LazyMutableTreeNode) selectedPaths[i].getLastPathComponent(); if (node instanceof FileTreeNode) { File f = ((FileTreeNode) node).getFile(); files.add(f); } } chooser.setSelectedFiles((File[]) files.toArray(new File[0])); } private class ApproveSelectionAction extends AbstractAction { public ApproveSelectionAction() { setEnabled(false); } public void actionPerformed(ActionEvent e) { setSelectedFiles(); chooser.approveSelection(); } } /** * Listens on nodes being opened and preload their children to get * better UI experience (GUI should be more responsive and empty * nodes discovered automatically). */ private class TreeExpansion implements TreeExpansionListener { public void treeCollapsed(TreeExpansionEvent event) {} public void treeExpanded(TreeExpansionEvent event) { // ensure children gets expanded later if (event.getPath() != null) { Object lastElement = event.getPath().getLastPathComponent(); if (lastElement instanceof FileTreeNode && useNodeQueue) { if (((FileTreeNode) lastElement).isLoaded()) { enqueueChildren((FileTreeNode) lastElement); } } } } } private void enqueueChildren(FileTreeNode node) { for (Enumeration e = node.children(); e.hasMoreElements(); ) { addToQueue((FileTreeNode) e.nextElement(), tree); } } private class FileSystemTreeRenderer extends DefaultTreeCellRenderer { public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, false, // even "leaf" folders should look like other folders row, hasFocus); if (value instanceof FileTreeNode) { FileTreeNode node = (FileTreeNode) value; setText(getFileView(chooser).getName(node.getFile())); // @PMD:REVIEWED:EmptyIfStmt: by fred on 14/08/04 17:25 if (OS.isMacOSX() && UIManager.getLookAndFeel().isNativeLookAndFeel()) { // do not set icon for MacOSX when native look is used, it // seems the // Tree.icons set by the // look and feel are not that good or Apple is doing // something I // can't figure. // setIcon only if not running in MacOSX } else { setIcon(getFileView(chooser).getIcon(node.getFile())); } } return this; } } private class NewFolderAction extends AbstractAction { public void actionPerformed(ActionEvent e) { // get the currently selected folder JFileChooser fc = getFileChooser(); File currentDirectory = fc.getCurrentDirectory(); if (!currentDirectory.canWrite()) { JOptionPane.showMessageDialog(fc, UIManager.getString("DirectoryChooser.cantCreateFolderHere"), UIManager.getString("DirectoryChooser.cantCreateFolderHere.title"), JOptionPane.ERROR_MESSAGE); return; } String newFolderName = JOptionPane.showInputDialog(fc, UIManager.getString("DirectoryChooser.enterFolderName"), newFolderText, JOptionPane.QUESTION_MESSAGE); if (newFolderName != null) { File newFolder = new File(currentDirectory, newFolderName); if (newFolder.mkdir()) { if (fc.isMultiSelectionEnabled()) { fc.setSelectedFiles(new File[]{newFolder}); } else { fc.setSelectedFile(newFolder); } fc.rescanCurrentDirectory(); } else { JOptionPane.showMessageDialog(fc, UIManager.getString("DirectoryChooser.createFolderFailed"), UIManager.getString("DirectoryChooser.createFolderFailed.title"), JOptionPane.ERROR_MESSAGE); } } } } private class FileSystemTreeModel extends DefaultTreeModel { public FileSystemTreeModel(FileSystemView fsv) { super(new MyComputerTreeNode(fsv), false); } } private class MyComputerTreeNode extends LazyMutableTreeNode { public MyComputerTreeNode(FileSystemView fsv) { super(fsv); } protected void loadChildren() { FileSystemView fsv = (FileSystemView) getUserObject(); File[] roots = fsv.getRoots(); if (roots != null) { Arrays.sort(roots); for (int i = 0, c = roots.length; i < c; i++) { add(new FileTreeNode(roots[i])); } } } public String toString() { return "/"; } } private class FileTreeNode extends LazyMutableTreeNode implements Comparable { public FileTreeNode(File file) { super(file); } public boolean canEnqueue() { return !isLoaded() && !chooser.getFileSystemView().isFloppyDrive(getFile()) && !chooser.getFileSystemView().isFileSystemRoot(getFile()); } public boolean isLeaf() { if (!isLoaded()) { return false; } else { return super.isLeaf(); } } protected void loadChildren() { FileTreeNode[] nodes = getChildren(); for (int i = 0, c = nodes.length; i < c; i++) { add(nodes[i]); } } private FileTreeNode[] getChildren() { File[] files = chooser.getFileSystemView().getFiles(getFile(), chooser.isFileHidingEnabled()); ArrayList nodes = new ArrayList(); // keep only directories, no "file" in the tree. if (files != null) { for (int i = 0, c = files.length; i < c; i++) { if (files[i].isDirectory()) { nodes.add(new FileTreeNode(files[i])); } } } // sort directories, FileTreeNode implements Comparable FileTreeNode[] result = (FileTreeNode[]) nodes.toArray(new FileTreeNode[0]); Arrays.sort(result); return result; } public File getFile() { return (File) getUserObject(); } public String toString() { return chooser.getFileSystemView().getSystemDisplayName((File) getUserObject()); } public int compareTo(Object o) { if (!(o instanceof FileTreeNode)) { return 1; } return getFile().compareTo(((FileTreeNode) o).getFile()); } public void clear() { super.clear(); ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(this); } } /** * From WindowsFileChooserUI */ protected class WindowsFileView extends BasicFileView { public Icon getIcon(File f) { Icon icon = getCachedIcon(f); if (icon != null) { return icon; } if (f != null) { icon = getFileChooser().getFileSystemView().getSystemIcon(f); } if (icon == null) { icon = super.getIcon(f); } cacheIcon(f, icon); return icon; } } private static synchronized void addToQueue(FileTreeNode node, JTree tree) { if (nodeQueue == null || !nodeQueue.isAlive()) { nodeQueue = new Queue(); nodeQueue.start(); } if (node.canEnqueue()) { nodeQueue.add(node, tree); } } /** * This queue takes care of loading nodes in the background. */ static final class Queue extends Thread { private volatile Stack nodes = new Stack(); private Object lock = new Object(); private volatile boolean running = true; public Queue() { super("DirectoryChooser-BackgroundLoader"); setDaemon(true); } public void add(WindowsDirectoryChooserUI.FileTreeNode node, JTree tree) { if (!isAlive()) { throw new IllegalArgumentException("Queue is no longer alive"); } synchronized (lock) { if (running) { nodes.addElement(new QueueItem(node, tree)); lock.notifyAll(); } } } public void run() { while (running) { while (nodes.size() > 0) { final QueueItem item = (QueueItem) nodes.pop(); final WindowsDirectoryChooserUI.FileTreeNode node = item.node; final JTree tree = item.tree; // ask how many items we got node.getChildCount(); Runnable runnable = new Runnable() { public void run() { ((DefaultTreeModel) tree.getModel()).nodeChanged(node); tree.repaint(); } }; try { SwingUtilities.invokeAndWait(runnable); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } // wait for 5 seconds for someone to use the queue, else just // ends this // queue try { synchronized (lock) { lock.wait(5000); } if (nodes.size() == 0) { running = false; } } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * An entry in the queue. */ private static final class QueueItem { WindowsDirectoryChooserUI.FileTreeNode node; JTree tree; public QueueItem(WindowsDirectoryChooserUI.FileTreeNode node, JTree tree) { this.node = node; this.tree = tree; } } }