0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: *
0041: * Contributor(s): Soot Phengsy
0042: */
0043:
0044: package org.netbeans.swing.dirchooser;
0045:
0046: import java.util.logging.Level;
0047: import javax.swing.*;
0048: import javax.swing.Box;
0049: import javax.swing.BoxLayout;
0050: import javax.swing.Timer;
0051: import javax.swing.event.TreeExpansionListener;
0052: import javax.swing.event.TreeSelectionEvent;
0053: import javax.swing.event.TreeSelectionListener;
0054: import javax.swing.filechooser.*;
0055: import javax.swing.plaf.basic.*;
0056: import java.awt.*;
0057: import java.awt.event.ActionEvent;
0058: import java.awt.event.ActionListener;
0059: import java.awt.event.FocusAdapter;
0060: import java.awt.event.FocusEvent;
0061: import java.awt.event.FocusListener;
0062: import java.awt.event.KeyAdapter;
0063: import java.awt.event.KeyEvent;
0064: import java.awt.event.MouseAdapter;
0065: import java.awt.event.MouseEvent;
0066: import java.beans.PropertyChangeEvent;
0067: import java.beans.PropertyChangeListener;
0068: import java.io.File;
0069: import java.io.IOException;
0070: import java.lang.ref.WeakReference;
0071: import java.lang.reflect.Constructor;
0072: import java.security.AccessControlException;
0073: import java.text.MessageFormat;
0074: import java.util.*;
0075: import java.util.logging.Logger;
0076: import java.util.regex.Matcher;
0077: import java.util.regex.Pattern;
0078: import javax.swing.border.EmptyBorder;
0079: import javax.swing.event.CellEditorListener;
0080: import javax.swing.event.ChangeEvent;
0081: import javax.swing.event.DocumentEvent;
0082: import javax.swing.event.DocumentListener;
0083: import javax.swing.event.TreeExpansionEvent;
0084: import javax.swing.plaf.ActionMapUIResource;
0085: import javax.swing.plaf.ComponentUI;
0086: import javax.swing.plaf.UIResource;
0087: import javax.swing.tree.DefaultTreeModel;
0088: import javax.swing.tree.TreeCellEditor;
0089: import javax.swing.tree.TreePath;
0090: import javax.swing.tree.TreeCellRenderer;
0091: import javax.swing.tree.TreeNode;
0092: import javax.swing.tree.TreeSelectionModel;
0093: import org.openide.awt.HtmlRenderer;
0094: import org.openide.filesystems.FileObject;
0095: import org.openide.filesystems.FileUtil;
0096: import org.openide.util.NbBundle;
0097: import org.openide.util.RequestProcessor;
0098: import org.openide.util.Utilities;
0099:
0100: /**
0101: * An implementation of a customized filechooser.
0102: *
0103: * @author Soot Phengsy, inspired by Jeff Dinkins' Swing version
0104: */
0105: public class DirectoryChooserUI extends BasicFileChooserUI {
0106:
0107: private static final Dimension horizontalStrut1 = new Dimension(25,
0108: 1);
0109: private static final Dimension verticalStrut1 = new Dimension(1, 4);
0110: private static final Dimension verticalStrut2 = new Dimension(1, 6);
0111: private static final Dimension verticalStrut3 = new Dimension(1, 8);
0112: private static Dimension PREF_SIZE = new Dimension(425, 245);
0113: private static Dimension MIN_SIZE = new Dimension(425, 245);
0114: private static Dimension TREE_PREF_SIZE = new Dimension(380, 230);
0115: private static final int ACCESSORY_WIDTH = 250;
0116:
0117: /** icon representing netbeans project folder */
0118: private static Icon projectIcon;
0119:
0120: private JPanel centerPanel;
0121:
0122: private JLabel lookInComboBoxLabel;
0123:
0124: private JComboBox directoryComboBox;
0125:
0126: private DirectoryComboBoxModel directoryComboBoxModel;
0127:
0128: private ActionListener directoryComboBoxAction = new DirectoryComboBoxAction();
0129:
0130: private FilterTypeComboBoxModel filterTypeComboBoxModel;
0131:
0132: private JTextField filenameTextField;
0133:
0134: private JComponent placesBar;
0135: private boolean placesBarFailed = false;
0136:
0137: private JButton approveButton;
0138: private JButton cancelButton;
0139:
0140: private JPanel buttonPanel;
0141: private JPanel bottomPanel;
0142:
0143: private JComboBox filterTypeComboBox;
0144:
0145: private int lookInLabelMnemonic = 0;
0146: private String lookInLabelText = null;
0147: private String saveInLabelText = null;
0148:
0149: private int fileNameLabelMnemonic = 0;
0150: private String fileNameLabelText = null;
0151:
0152: private int filesOfTypeLabelMnemonic = 0;
0153: private String filesOfTypeLabelText = null;
0154:
0155: private String upFolderToolTipText = null;
0156: private String upFolderAccessibleName = null;
0157:
0158: private String newFolderToolTipText = null;
0159:
0160: private Action newFolderAction = new NewDirectoryAction();
0161:
0162: private BasicFileView fileView = new DirectoryChooserFileView();
0163:
0164: private static JTree tree;
0165:
0166: private DirectoryTreeModel model;
0167:
0168: private DirectoryNode newFolderNode;
0169:
0170: private JComponent treeViewPanel;
0171:
0172: private InputBlocker blocker;
0173:
0174: private static JFileChooser fileChooser;
0175:
0176: private boolean changeDirectory = true;
0177:
0178: private boolean showPopupCompletion = false;
0179:
0180: private boolean addNewDirectory = false;
0181:
0182: private JPopupMenu popupMenu;
0183:
0184: private FileCompletionPopup completionPopup;
0185:
0186: private RequestProcessor.Task updateWorker;
0187:
0188: private boolean useShellFolder = false;
0189:
0190: private JButton upFolderButton;
0191: private JButton newFolderButton;
0192:
0193: private JComponent topCombo, topComboWrapper, topToolbar;
0194:
0195: public static ComponentUI createUI(JComponent c) {
0196: return new DirectoryChooserUI((JFileChooser) c);
0197: }
0198:
0199: public DirectoryChooserUI(JFileChooser filechooser) {
0200: super (filechooser);
0201: }
0202:
0203: public void installUI(JComponent c) {
0204: super .installUI(c);
0205: }
0206:
0207: public void uninstallComponents(JFileChooser fc) {
0208: fc.removeAll();
0209: super .uninstallComponents(fc);
0210: }
0211:
0212: public void installComponents(JFileChooser fc) {
0213: fileChooser = fc;
0214:
0215: fc.setFocusCycleRoot(true);
0216: fc.setBorder(new EmptyBorder(4, 10, 10, 10));
0217: fc.setLayout(new BorderLayout(8, 8));
0218:
0219: updateUseShellFolder();
0220: createCenterPanel(fc);
0221: fc.add(centerPanel, BorderLayout.CENTER);
0222:
0223: if (fc.isMultiSelectionEnabled()) {
0224: setFileName(getStringOfFileNames(fc.getSelectedFiles()));
0225: } else {
0226: setFileName(getStringOfFileName(fc.getSelectedFile()));
0227: }
0228:
0229: if (fc.getControlButtonsAreShown()) {
0230: addControlButtons();
0231: }
0232:
0233: createPopup();
0234: }
0235:
0236: private void updateUseShellFolder() {
0237: // Decide whether to use the ShellFolder class to populate shortcut
0238: // panel and combobox.
0239:
0240: Boolean prop = (Boolean) fileChooser
0241: .getClientProperty("FileChooser.useShellFolder");
0242: if (prop != null) {
0243: useShellFolder = prop.booleanValue();
0244: } else {
0245: // See if FileSystemView.getRoots() returns the desktop folder,
0246: // i.e. the normal Windows hierarchy.
0247: useShellFolder = false;
0248: File[] roots = fileChooser.getFileSystemView().getRoots();
0249: if (roots != null && roots.length == 1) {
0250: File[] cbFolders = getShellFolderRoots();
0251: if (cbFolders != null && cbFolders.length > 0
0252: && roots[0] == cbFolders[0]) {
0253: useShellFolder = true;
0254: }
0255: }
0256: }
0257:
0258: if (Utilities.isWindows()) {
0259: if (useShellFolder) {
0260: if (placesBar == null) {
0261: placesBar = getPlacesBar();
0262: }
0263: if (placesBar != null) {
0264: fileChooser.add(placesBar,
0265: BorderLayout.BEFORE_LINE_BEGINS);
0266: if (placesBar instanceof PropertyChangeListener) {
0267: fileChooser
0268: .addPropertyChangeListener((PropertyChangeListener) placesBar);
0269: }
0270: }
0271: } else {
0272: if (placesBar != null) {
0273: fileChooser.remove(placesBar);
0274: if (placesBar instanceof PropertyChangeListener) {
0275: fileChooser
0276: .removePropertyChangeListener((PropertyChangeListener) placesBar);
0277: }
0278: placesBar = null;
0279: }
0280: }
0281: }
0282: }
0283:
0284: /** Returns instance of WindowsPlacesBar class or null in case of failure
0285: */
0286: private JComponent getPlacesBar() {
0287: if (placesBarFailed) {
0288: return null;
0289: }
0290: try {
0291: Class<?> clazz = Class
0292: .forName("sun.swing.WindowsPlacesBar");
0293: Class[] params = new Class[] { JFileChooser.class,
0294: Boolean.TYPE };
0295: Constructor<?> constr = clazz.getConstructor(params);
0296: return (JComponent) constr.newInstance(fileChooser,
0297: isXPStyle().booleanValue());
0298: } catch (Exception exc) {
0299: // reflection not succesfull, just log the exception and return null
0300: Logger.getLogger(DirectoryChooserUI.class.getName()).log(
0301: Level.FINE,
0302: "WindowsPlacesBar class can't be instantiated.",
0303: exc);
0304: placesBarFailed = true;
0305: return null;
0306: }
0307: }
0308:
0309: /** Reflection alternative of
0310: * sun.awt.shell.ShellFolder.getShellFolder(file)
0311: */
0312: private File getShellFolderForFile(File file) {
0313: try {
0314: Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
0315: return (File) clazz.getMethod("getShellFolder", File.class)
0316: .invoke(null, file);
0317: } catch (Exception exc) {
0318: // reflection not succesfull, just log the exception and return null
0319: Logger.getLogger(DirectoryChooserUI.class.getName()).log(
0320: Level.FINE, "ShellFolder can't be used.", exc);
0321: return null;
0322: }
0323: }
0324:
0325: /** Reflection alternative of
0326: * sun.awt.shell.ShellFolder.getShellFolder(dir).getLinkLocation()
0327: */
0328: private File getShellFolderForFileLinkLoc(File file) {
0329: try {
0330: Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
0331: Object sf = clazz.getMethod("getShellFolder", File.class)
0332: .invoke(null, file);
0333: return (File) clazz.getMethod("getLinkLocation").invoke(sf);
0334: } catch (Exception exc) {
0335: // reflection not succesfull, just log the exception and return null
0336: Logger.getLogger(DirectoryChooserUI.class.getName()).log(
0337: Level.FINE, "ShellFolder can't be used.", exc);
0338: return null;
0339: }
0340:
0341: }
0342:
0343: /** Reflection alternative of
0344: * sun.awt.shell.ShellFolder.get("fileChooserComboBoxFolders")
0345: */
0346: private File[] getShellFolderRoots() {
0347: try {
0348: Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
0349: return (File[]) clazz.getMethod("get", String.class)
0350: .invoke(null, "fileChooserComboBoxFolders");
0351: } catch (Exception exc) {
0352: // reflection not succesfull, just log the exception and return null
0353: Logger.getLogger(DirectoryChooserUI.class.getName()).log(
0354: Level.FINE, "ShellFolder can't be used.", exc);
0355: return null;
0356: }
0357: }
0358:
0359: private void createBottomPanel(JFileChooser fc) {
0360: bottomPanel = new JPanel();
0361: bottomPanel.setLayout(new BoxLayout(bottomPanel,
0362: BoxLayout.LINE_AXIS));
0363: JPanel labelPanel = new JPanel();
0364: labelPanel.setLayout(new BoxLayout(labelPanel,
0365: BoxLayout.PAGE_AXIS));
0366: labelPanel.add(Box.createRigidArea(verticalStrut1));
0367:
0368: JLabel fnl = new JLabel(fileNameLabelText);
0369: fnl.setDisplayedMnemonic(fileNameLabelMnemonic);
0370: fnl.setAlignmentY(0);
0371: labelPanel.add(fnl);
0372:
0373: labelPanel.add(Box.createRigidArea(new Dimension(1, 12)));
0374:
0375: JLabel ftl = new JLabel(filesOfTypeLabelText);
0376: ftl.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
0377: labelPanel.add(ftl);
0378:
0379: bottomPanel.add(labelPanel);
0380: bottomPanel.add(Box.createRigidArea(new Dimension(15, 0)));
0381:
0382: JPanel fileAndFilterPanel = new JPanel();
0383: fileAndFilterPanel.add(Box.createRigidArea(verticalStrut3));
0384: fileAndFilterPanel.setLayout(new BoxLayout(fileAndFilterPanel,
0385: BoxLayout.Y_AXIS));
0386:
0387: filenameTextField = new JTextField(24) {
0388: public Dimension getMaximumSize() {
0389: return new Dimension(Short.MAX_VALUE, super
0390: .getPreferredSize().height);
0391: }
0392: };
0393:
0394: filenameTextField.getDocument().addDocumentListener(
0395: new DocumentListener() {
0396: public void insertUpdate(DocumentEvent e) {
0397: updateCompletions();
0398: }
0399:
0400: public void removeUpdate(DocumentEvent e) {
0401: updateCompletions();
0402: }
0403:
0404: public void changedUpdate(DocumentEvent e) {
0405: }
0406: });
0407:
0408: filenameTextField.addKeyListener(new TextFieldKeyListener());
0409:
0410: fnl.setLabelFor(filenameTextField);
0411: filenameTextField.addFocusListener(new FocusAdapter() {
0412: public void focusGained(FocusEvent e) {
0413: if (!getFileChooser().isMultiSelectionEnabled()) {
0414: tree.clearSelection();
0415: }
0416: }
0417: });
0418:
0419: // disable TAB focus transfer, we need it for completion
0420: Set<AWTKeyStroke> tKeys = filenameTextField
0421: .getFocusTraversalKeys(java.awt.KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
0422: Set<AWTKeyStroke> newTKeys = new HashSet<AWTKeyStroke>(tKeys);
0423: newTKeys.remove(AWTKeyStroke
0424: .getAWTKeyStroke(KeyEvent.VK_TAB, 0));
0425: // #107305: enable at least Ctrl+TAB if we have TAB for completion
0426: newTKeys.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB,
0427: KeyEvent.CTRL_DOWN_MASK));
0428: filenameTextField.setFocusTraversalKeys(
0429: KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newTKeys);
0430:
0431: fileAndFilterPanel.add(filenameTextField);
0432: fileAndFilterPanel.add(Box.createRigidArea(verticalStrut3));
0433:
0434: filterTypeComboBoxModel = createFilterComboBoxModel();
0435: fc.addPropertyChangeListener(filterTypeComboBoxModel);
0436: filterTypeComboBox = new JComboBox(filterTypeComboBoxModel);
0437: ftl.setLabelFor(filterTypeComboBox);
0438: filterTypeComboBox.setRenderer(createFilterComboBoxRenderer());
0439: fileAndFilterPanel.add(filterTypeComboBox);
0440:
0441: bottomPanel.add(fileAndFilterPanel);
0442: bottomPanel.add(Box.createRigidArea(horizontalStrut1));
0443: createButtonsPanel(fc);
0444: }
0445:
0446: private void createButtonsPanel(JFileChooser fc) {
0447: buttonPanel = new JPanel();
0448: buttonPanel.setLayout(new BoxLayout(buttonPanel,
0449: BoxLayout.Y_AXIS));
0450:
0451: approveButton = new JButton(getApproveButtonText(fc)) {
0452: public Dimension getMaximumSize() {
0453: return approveButton.getPreferredSize().width > cancelButton
0454: .getPreferredSize().width ? approveButton
0455: .getPreferredSize() : cancelButton
0456: .getPreferredSize();
0457: }
0458: };
0459: // #107791: No mnemonics desirable on Mac
0460: if (!Utilities.isMac()) {
0461: approveButton.setMnemonic(getApproveButtonMnemonic(fc));
0462: }
0463: approveButton.addActionListener(getApproveSelectionAction());
0464: approveButton.setToolTipText(getApproveButtonToolTipText(fc));
0465: buttonPanel.add(Box.createRigidArea(verticalStrut1));
0466: buttonPanel.add(approveButton);
0467: buttonPanel.add(Box.createRigidArea(verticalStrut2));
0468:
0469: cancelButton = new JButton(cancelButtonText) {
0470: public Dimension getMaximumSize() {
0471: return approveButton.getPreferredSize().width > cancelButton
0472: .getPreferredSize().width ? approveButton
0473: .getPreferredSize() : cancelButton
0474: .getPreferredSize();
0475: }
0476: };
0477: // #107791: No mnemonics desirable on Mac
0478: if (!Utilities.isMac()) {
0479: cancelButton.setMnemonic(cancelButtonMnemonic);
0480: }
0481: cancelButton.setToolTipText(cancelButtonToolTipText);
0482: cancelButton.addActionListener(getCancelSelectionAction());
0483: buttonPanel.add(cancelButton);
0484: }
0485:
0486: private void createCenterPanel(final JFileChooser fc) {
0487: centerPanel = new JPanel(new BorderLayout());
0488: treeViewPanel = createTree();
0489: treeViewPanel.setPreferredSize(TREE_PREF_SIZE);
0490: JPanel treePanel = new JPanel();
0491: treePanel.setLayout(new BorderLayout());
0492: JComponent accessory = fc.getAccessory();
0493: topToolbar = createTopToolbar();
0494: topCombo = createTopCombo(fc);
0495: topComboWrapper = new JPanel(new BorderLayout());
0496: topComboWrapper.add(topCombo, BorderLayout.CENTER);
0497: if (accessory == null) {
0498: topComboWrapper.add(topToolbar, BorderLayout.EAST);
0499: }
0500: treePanel.add(topComboWrapper, BorderLayout.NORTH);
0501: treePanel.add(treeViewPanel, BorderLayout.CENTER);
0502: centerPanel.add(treePanel, BorderLayout.CENTER);
0503: // #97049: control width of accessory panel, don't allow to jump (change width)
0504: JPanel wrapAccessory = new JPanel() {
0505: private Dimension prefSize = new Dimension(ACCESSORY_WIDTH,
0506: 0);
0507: private Dimension minSize = new Dimension(ACCESSORY_WIDTH,
0508: 0);
0509:
0510: public Dimension getMinimumSize() {
0511: if (fc.getAccessory() != null) {
0512: minSize.height = getAccessoryPanel()
0513: .getMinimumSize().height;
0514: return minSize;
0515: }
0516: return super .getMinimumSize();
0517: }
0518:
0519: public Dimension getPreferredSize() {
0520: if (fc.getAccessory() != null) {
0521: prefSize.height = getAccessoryPanel()
0522: .getPreferredSize().height;
0523: return prefSize;
0524: }
0525: return super .getPreferredSize();
0526: }
0527: };
0528: wrapAccessory.setLayout(new BorderLayout());
0529: JPanel accessoryPanel = getAccessoryPanel();
0530: if (accessory != null) {
0531: accessoryPanel.add(topToolbar, BorderLayout.NORTH);
0532: accessoryPanel.add(accessory, BorderLayout.CENTER);
0533: }
0534: wrapAccessory.add(accessoryPanel, BorderLayout.CENTER);
0535: centerPanel.add(wrapAccessory, BorderLayout.EAST);
0536: createBottomPanel(fc);
0537: centerPanel.add(bottomPanel, BorderLayout.SOUTH);
0538: }
0539:
0540: private JComponent createTopCombo(JFileChooser fc) {
0541: JPanel panel = new JPanel();
0542: if (fc.getAccessory() != null) {
0543: panel
0544: .setBorder(BorderFactory.createEmptyBorder(4, 0, 8,
0545: 0));
0546: } else {
0547: panel.setBorder(BorderFactory
0548: .createEmptyBorder(6, 0, 10, 0));
0549: }
0550: panel.setLayout(new BorderLayout());
0551:
0552: Box labelBox = Box.createHorizontalBox();
0553:
0554: lookInComboBoxLabel = new JLabel(lookInLabelText);
0555: lookInComboBoxLabel.setDisplayedMnemonic(lookInLabelMnemonic);
0556: lookInComboBoxLabel.setAlignmentX(JComponent.LEFT_ALIGNMENT);
0557: lookInComboBoxLabel.setAlignmentY(JComponent.CENTER_ALIGNMENT);
0558:
0559: labelBox.add(lookInComboBoxLabel);
0560: labelBox.add(Box.createRigidArea(new Dimension(9, 0)));
0561:
0562: // fixed #97525, made the height of the
0563: // combo box bigger.
0564: directoryComboBox = new JComboBox() {
0565: public Dimension getMinimumSize() {
0566: Dimension d = super .getMinimumSize();
0567: d.width = 60;
0568: return d;
0569: }
0570:
0571: public Dimension getPreferredSize() {
0572: Dimension d = super .getPreferredSize();
0573: // Must be small enough to not affect total width and height.
0574: d.height = 24;
0575: d.width = 150;
0576: return d;
0577: }
0578: };
0579: directoryComboBox.putClientProperty(
0580: "JComboBox.lightweightKeyboardNavigation",
0581: "Lightweight");
0582: lookInComboBoxLabel.setLabelFor(directoryComboBox);
0583: directoryComboBoxModel = createDirectoryComboBoxModel(fc);
0584: directoryComboBox.setModel(directoryComboBoxModel);
0585: directoryComboBox.addActionListener(directoryComboBoxAction);
0586: directoryComboBox
0587: .setRenderer(createDirectoryComboBoxRenderer(fc));
0588: directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
0589: directoryComboBox.setAlignmentY(JComponent.CENTER_ALIGNMENT);
0590: directoryComboBox.setMaximumRowCount(8);
0591:
0592: panel.add(labelBox, BorderLayout.WEST);
0593: panel.add(directoryComboBox, BorderLayout.CENTER);
0594:
0595: return panel;
0596: }
0597:
0598: private JComponent createTopToolbar() {
0599: JToolBar topPanel = new JToolBar();
0600: topPanel.setFloatable(false);
0601:
0602: if (Utilities.isWindows()) {
0603: topPanel.putClientProperty("JToolBar.isRollover",
0604: Boolean.TRUE);
0605: }
0606:
0607: upFolderButton = new JButton(getChangeToParentDirectoryAction());
0608: upFolderButton.setText(null);
0609: // fixed bug #97049
0610: final boolean isMac = Utilities.isMac();
0611: Icon upFolderIcon = null;
0612: if (!isMac) {
0613: upFolderIcon = UIManager
0614: .getIcon("FileChooser.upFolderIcon");
0615: }
0616: // on Mac all icons from UIManager are the same, some default, so load our own.
0617: // it's also fallback if icon from UIManager not found, may happen
0618: if (isMac || upFolderIcon == null) {
0619: upFolderIcon = new ImageIcon(
0620: Utilities
0621: .loadImage("org/netbeans/swing/dirchooser/resources/upFolderIcon.gif"));
0622: }
0623: upFolderButton.setIcon(upFolderIcon);
0624: upFolderButton.setToolTipText(upFolderToolTipText);
0625: upFolderButton.getAccessibleContext().setAccessibleName(
0626: upFolderAccessibleName);
0627: upFolderButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
0628: upFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
0629:
0630: if (useShellFolder) {
0631: upFolderButton.setFocusPainted(false);
0632: }
0633:
0634: topPanel.add(upFolderButton);
0635: topPanel.add(Box.createRigidArea(new Dimension(2, 0)));
0636:
0637: // no home on Win platform
0638: if (!Utilities.isWindows()) {
0639: JButton homeButton = new JButton(getGoHomeAction());
0640: Icon homeIcon = null;
0641: if (!isMac) {
0642: homeIcon = UIManager
0643: .getIcon("FileChooser.homeFolderIcon");
0644: }
0645: if (isMac || homeIcon == null) {
0646: homeIcon = new ImageIcon(
0647: Utilities
0648: .loadImage("org/netbeans/swing/dirchooser/resources/homeIcon.gif"));
0649: }
0650: homeButton.setIcon(homeIcon);
0651: homeButton.setText(null);
0652:
0653: topPanel.add(homeButton);
0654: }
0655:
0656: newFolderButton = new JButton(newFolderAction);
0657: newFolderButton.setText(null);
0658: // fixed bug #97049
0659: Icon newFolderIcon = null;
0660: if (!isMac) {
0661: newFolderIcon = UIManager
0662: .getIcon("FileChooser.newFolderIcon");
0663: }
0664: // on Mac all icons from UIManager are the same, some default, so load our own.
0665: // it's also fallback if icon from UIManager not found, may happen
0666: if (isMac || newFolderIcon == null) {
0667: newFolderIcon = new ImageIcon(
0668: Utilities
0669: .loadImage("org/netbeans/swing/dirchooser/resources/newFolderIcon.gif"));
0670: }
0671: newFolderButton.setIcon(newFolderIcon);
0672: newFolderButton.setToolTipText(newFolderToolTipText);
0673: newFolderButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
0674: newFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
0675:
0676: if (useShellFolder) {
0677: newFolderButton.setFocusPainted(false);
0678: }
0679:
0680: topPanel.add(newFolderButton);
0681: topPanel.add(Box.createRigidArea(new Dimension(2, 0)));
0682:
0683: JPanel panel = new JPanel();
0684: panel.setLayout(new BorderLayout());
0685: panel.setBorder(BorderFactory.createEmptyBorder(4, 9, 8, 0));
0686: panel.add(topPanel, BorderLayout.CENTER);
0687:
0688: return panel;
0689: }
0690:
0691: private JComponent createTree() {
0692: final DirectoryHandler dirHandler = createDirectoryHandler(fileChooser);
0693: // #106011: don't show "colors, food, sports" sample model after init :-)
0694: tree = new JTree(new Object[0]) {
0695:
0696: @Override
0697: protected void processMouseEvent(MouseEvent e) {
0698: dirHandler.preprocessMouseEvent(e);
0699: super .processMouseEvent(e);
0700: }
0701:
0702: @Override
0703: /* #106223: Always compute row height from cell renderer, don't allow fixed
0704: * row height */
0705: public int getRowHeight() {
0706: return 0;
0707: }
0708:
0709: };
0710: // #105642: start with right content in tree
0711: File curDir = fileChooser.getCurrentDirectory();
0712: if (curDir == null) {
0713: curDir = fileChooser.getFileSystemView().getRoots()[0];
0714: }
0715: updateTree(curDir);
0716:
0717: tree.setFocusable(true);
0718: tree.setOpaque(true);
0719: tree.setRootVisible(false);
0720: tree.setShowsRootHandles(true);
0721: tree.setToggleClickCount(0);
0722: tree.addTreeExpansionListener(new TreeExpansionHandler());
0723: TreeKeyHandler keyHandler = new TreeKeyHandler();
0724: tree.addKeyListener(keyHandler);
0725: tree.addFocusListener(keyHandler);
0726: tree.addMouseListener(dirHandler);
0727: tree.addFocusListener(dirHandler);
0728: tree.addTreeSelectionListener(dirHandler);
0729:
0730: if (fileChooser.isMultiSelectionEnabled()) {
0731: tree.getSelectionModel().setSelectionMode(
0732: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
0733: } else {
0734: tree.getSelectionModel().setSelectionMode(
0735: TreeSelectionModel.SINGLE_TREE_SELECTION);
0736: }
0737:
0738: TreeCellEditor tce = new DirectoryCellEditor(tree, fileChooser,
0739: new JTextField());
0740: tree.setCellEditor(tce);
0741: tce.addCellEditorListener(dirHandler);
0742: tree.setCellRenderer(new DirectoryTreeRenderer());
0743: JScrollPane scrollBar = new JScrollPane(tree);
0744: scrollBar.setViewportView(tree);
0745: tree.setInvokesStopCellEditing(true);
0746:
0747: return scrollBar;
0748: }
0749:
0750: /**
0751: * Handles keyboard quick search in tree and delete action.
0752: */
0753: class TreeKeyHandler extends KeyAdapter implements FocusListener {
0754:
0755: StringBuffer searchBuf = new StringBuffer();
0756:
0757: java.util.List<TreePath> paths;
0758:
0759: public void keyPressed(KeyEvent evt) {
0760: if (evt.getKeyCode() == KeyEvent.VK_DELETE) {
0761: deleteAction();
0762: }
0763:
0764: // F2 as rename shortcut
0765: if (evt.getKeyCode() == KeyEvent.VK_F2) {
0766: DirectoryNode node = (DirectoryNode) tree
0767: .getLastSelectedPathComponent();
0768: if (node != null) {
0769: applyEdit(node);
0770: }
0771: }
0772:
0773: if (isCharForSearch(evt)) {
0774: evt.consume();
0775: } else {
0776: resetBuffer();
0777: }
0778:
0779: // #105527: keyboard invocation of tree's popup menu
0780: if ((evt.getKeyCode() == KeyEvent.VK_F10)
0781: && evt.isShiftDown() && !popupMenu.isShowing()) {
0782: JTree tree = (JTree) evt.getSource();
0783: int selRow = tree.getLeadSelectionRow();
0784: if (selRow >= 0) {
0785: Rectangle bounds = tree.getRowBounds(selRow);
0786: popupMenu.show(tree, bounds.x + bounds.width / 2,
0787: bounds.y + bounds.height * 3 / 5);
0788: evt.consume();
0789: }
0790: }
0791: }
0792:
0793: @Override
0794: public void keyTyped(KeyEvent evt) {
0795: char keyChar = evt.getKeyChar();
0796: if (isCharForSearch(evt)) {
0797: if (paths == null) {
0798: paths = getVisiblePaths();
0799: }
0800: searchBuf.append(keyChar);
0801: String searchedText = searchBuf.toString()
0802: .toLowerCase();
0803: String curFileName = null;
0804: for (TreePath path : paths) {
0805: curFileName = fileChooser
0806: .getName(((DirectoryNode) path
0807: .getLastPathComponent()).getFile());
0808: if (curFileName != null
0809: && curFileName.toLowerCase().startsWith(
0810: searchedText)) {
0811: tree.makeVisible(path);
0812: tree.scrollPathToVisible(path);
0813: tree.setSelectionPath(path);
0814: break;
0815: }
0816: }
0817: } else {
0818: resetBuffer();
0819: }
0820: }
0821:
0822: public void focusGained(FocusEvent e) {
0823: resetBuffer();
0824: }
0825:
0826: public void focusLost(FocusEvent e) {
0827: resetBuffer();
0828: }
0829:
0830: private boolean isCharForSearch(KeyEvent evt) {
0831: char ch = evt.getKeyChar();
0832: // refuse backspace key
0833: if ((int) ch == 8) {
0834: return false;
0835: }
0836: // #110975: refuse modifiers
0837: if (evt.getModifiers() != 0) {
0838: return false;
0839: }
0840: return (Character.isJavaIdentifierPart(ch) && !Character
0841: .isIdentifierIgnorable(ch))
0842: || Character.isSpaceChar(ch);
0843: }
0844:
0845: private void resetBuffer() {
0846: searchBuf.delete(0, searchBuf.length());
0847: paths = null;
0848: }
0849:
0850: }
0851:
0852: private java.util.List<TreePath> getVisiblePaths() {
0853: int rowCount = tree.getRowCount();
0854: DirectoryNode node = null;
0855: java.util.List<TreePath> result = new ArrayList<TreePath>(
0856: rowCount);
0857: for (int i = 0; i < rowCount; i++) {
0858: result.add(tree.getPathForRow(i));
0859: }
0860: return result;
0861: }
0862:
0863: private void createPopup() {
0864: popupMenu = new JPopupMenu();
0865: JMenuItem item1 = new JMenuItem(getBundle().getString(
0866: "LBL_NewFolder"));
0867: item1.addActionListener(newFolderAction);
0868:
0869: JMenuItem item2 = new JMenuItem(getBundle().getString(
0870: "LBL_Rename"));
0871: item2.addActionListener(new ActionListener() {
0872: public void actionPerformed(ActionEvent e) {
0873: DirectoryNode node = (DirectoryNode) tree
0874: .getLastSelectedPathComponent();
0875: applyEdit(node);
0876: }
0877: });
0878:
0879: JMenuItem item3 = new JMenuItem(getBundle().getString(
0880: "LBL_Delete"));
0881: item3.addActionListener(new ActionListener() {
0882: public void actionPerformed(ActionEvent e) {
0883: deleteAction();
0884: }
0885: });
0886:
0887: popupMenu.add(item1);
0888: popupMenu.add(item2);
0889: popupMenu.add(item3);
0890: }
0891:
0892: // remove multiple directories
0893: private void deleteAction() {
0894: // fixed #97079 to be able to delete one or more folders
0895: final TreePath[] nodePath = tree.getSelectionPaths();
0896:
0897: if (nodePath == null) {
0898: return;
0899: }
0900: String message = "";
0901:
0902: if (nodePath.length == 1) {
0903:
0904: File file = ((DirectoryNode) nodePath[0]
0905: .getLastPathComponent()).getFile();
0906: // Don't do anything if it's a special file
0907: if (!canWrite(file)) {
0908: return;
0909: }
0910: message = MessageFormat.format(getBundle().getString(
0911: "MSG_Delete"), file.getName());
0912: } else {
0913: message = MessageFormat.format(getBundle().getString(
0914: "MSG_Delete_Multiple"), nodePath.length);
0915: }
0916:
0917: int answer = JOptionPane.showConfirmDialog(fileChooser,
0918: message, getBundle().getString("MSG_Confirm"),
0919: JOptionPane.YES_NO_OPTION);
0920:
0921: if (answer == JOptionPane.YES_OPTION) {
0922:
0923: RequestProcessor.getDefault().post(new Runnable() {
0924: DirectoryNode node;
0925: ArrayList<File> list = new ArrayList<File>();
0926: int cannotDelete;
0927:
0928: public void run() {
0929: if (!EventQueue.isDispatchThread()) {
0930: // first pass, out of EQ thread, deletes files
0931: setCursor(fileChooser, Cursor.WAIT_CURSOR);
0932: cannotDelete = 0;
0933: for (int i = 0; i < nodePath.length; i++) {
0934: DirectoryNode nodeToDelete = (DirectoryNode) nodePath[i]
0935: .getLastPathComponent();
0936: try {
0937: DefaultTreeModel model = (DefaultTreeModel) tree
0938: .getModel();
0939: FileObject fo = FileUtil
0940: .toFileObject(nodeToDelete
0941: .getFile());
0942: fo.delete();
0943: model
0944: .removeNodeFromParent(nodeToDelete);
0945: } catch (IOException ignore) {
0946: cannotDelete++;
0947:
0948: if (canWrite(nodeToDelete.getFile())) {
0949: list.add(nodeToDelete.getFile());
0950: }
0951: }
0952: }
0953: // send to second pass
0954: EventQueue.invokeLater(this );
0955: } else {
0956: // second pass, in EQ thread
0957: setCursor(fileChooser, Cursor.DEFAULT_CURSOR);
0958: if (cannotDelete > 0) {
0959: String message = "";
0960:
0961: if (cannotDelete == 1) {
0962: message = cannotDelete
0963: + " "
0964: + getBundle().getString(
0965: "MSG_Sing_Delete");
0966: } else {
0967: message = cannotDelete
0968: + " "
0969: + getBundle().getString(
0970: "MSG_Plur_Delete");
0971: }
0972:
0973: setSelected((File[]) list
0974: .toArray(new File[list.size()]));
0975:
0976: JOptionPane.showConfirmDialog(fileChooser,
0977: message, getBundle().getString(
0978: "MSG_Confirm"),
0979: JOptionPane.OK_OPTION);
0980: } else {
0981: setSelected(new File[] { null });
0982: setFileName("");
0983: }
0984: }
0985: }
0986: });
0987: }
0988: }
0989:
0990: private void updateCompletions() {
0991: String name = normalizeFile(getFileName());
0992: boolean showPopup = true;
0993: int slash = name.lastIndexOf(File.separatorChar);
0994: if (slash != -1) {
0995: String prefix = name.substring(0, slash + 1);
0996: File d = new File(prefix);
0997: if (d.isDirectory()) {
0998: File[] children = d.listFiles();
0999: if (children != null) {
1000: Vector list = buildList(name, children);
1001:
1002: if (completionPopup == null) {
1003: showPopup = true;
1004: }
1005:
1006: if ((completionPopup != null)
1007: && completionPopup.isVisible()) {
1008: showPopup = false;
1009: }
1010:
1011: if (showPopup) {
1012: completionPopup = new FileCompletionPopup(
1013: fileChooser, filenameTextField, list);
1014:
1015: if (showPopupCompletion
1016: && fileChooser.isShowing()) {
1017: java.awt.Point los = filenameTextField
1018: .getLocation();
1019: int popX = los.x;
1020: int popY = los.y
1021: + filenameTextField.getHeight() - 6;
1022: completionPopup.showPopup(
1023: filenameTextField, popX, popY);
1024: }
1025: } else {
1026: completionPopup.setDataList(list);
1027: }
1028: }
1029: }
1030: }
1031: }
1032:
1033: public Vector<File> buildList(String text, File[] children) {
1034: Vector<File> files = new Vector<File>(children.length);
1035:
1036: for (int i = children.length - 1; i >= 0; i--) {
1037: File completion = children[i];
1038:
1039: if (fileChooser.accept(completion)) {
1040: String path = completion.getAbsolutePath();
1041:
1042: if (path.regionMatches(true, 0, text, 0, text.length())) {
1043:
1044: if (fileChooser.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) {
1045: if (completion.isDirectory()) {
1046: files.add(completion);
1047: }
1048: } else if (fileChooser.getFileSelectionMode() == JFileChooser.FILES_ONLY) {
1049: if (completion.isFile()) {
1050: files.add(completion);
1051: }
1052: } else if (fileChooser.getFileSelectionMode() == JFileChooser.FILES_AND_DIRECTORIES) {
1053: files.add(completion);
1054: }
1055: }
1056: }
1057: }
1058:
1059: Collections.sort(files, DirectoryNode.FILE_NAME_COMPARATOR);
1060: return files;
1061: }
1062:
1063: private static String normalizeFile(String text) {
1064: // See #21690 for background.
1065: // XXX what are legal chars for var names? bash manual says only:
1066: // "The braces are required when PARAMETER [...] is followed by a
1067: // character that is not to be interpreted as part of its name."
1068: Pattern p = Pattern.compile("(^|[^\\\\])\\$([a-zA-Z_0-9.]+)");
1069: Matcher m;
1070: while ((m = p.matcher(text)).find()) {
1071: // Have an env var to subst...
1072: // XXX handle ${PATH} too? or don't bother
1073: String var = System.getenv(m.group(2));
1074: if (var == null) {
1075: // Try Java system props too, and fall back to "".
1076: var = System.getProperty(m.group(2), "");
1077: }
1078: // XXX full readline compat would mean vars were also completed with TAB...
1079: text = text.substring(0, m.end(1)) + var
1080: + text.substring(m.end(2));
1081: }
1082: if (text.equals("~")) {
1083: return System.getProperty("user.home");
1084: } else if (text.startsWith("~" + File.separatorChar)) {
1085: return System.getProperty("user.home") + text.substring(1);
1086: } else {
1087: int i = text.lastIndexOf("//");
1088: if (i != -1) {
1089: // Treat /home/me//usr/local as /usr/local
1090: // (so that you can use "//" to start a new path, without selecting & deleting)
1091: return text.substring(i + 1);
1092: }
1093: i = text.lastIndexOf(File.separatorChar + "~"
1094: + File.separatorChar);
1095: if (i != -1) {
1096: // Treat /usr/local/~/stuff as /home/me/stuff
1097: return System.getProperty("user.home")
1098: + text.substring(i + 2);
1099: }
1100: return text;
1101: }
1102: }
1103:
1104: private static ResourceBundle getBundle() {
1105: return NbBundle.getBundle(DirectoryChooserUI.class);
1106: }
1107:
1108: private void updateTree(final File file) {
1109: // fixed bug #97522
1110: if (updateWorker != null) {
1111: // try to cancel previous update if possible
1112: if (!updateWorker.isFinished()) {
1113: updateWorker.cancel();
1114: }
1115: // #105642 - wait for previous update, to keep time order
1116: updateWorker.waitFinished();
1117: }
1118:
1119: updateWorker = RequestProcessor.getDefault().post(
1120: new Runnable() {
1121: DirectoryNode node;
1122:
1123: public void run() {
1124: if (!EventQueue.isDispatchThread()) {
1125: // first pass, out of EQ thread
1126: setCursor(fileChooser, Cursor.WAIT_CURSOR);
1127: node = new DirectoryNode(file);
1128: node.loadChildren(fileChooser, true);
1129: // send to second pass
1130: EventQueue.invokeLater(this );
1131: } else {
1132: // second pass, in EQ thread
1133: model = new DirectoryTreeModel(node);
1134: tree.setModel(model);
1135: tree.repaint();
1136: setCursor(fileChooser,
1137: Cursor.DEFAULT_CURSOR);
1138: }
1139: }
1140: });
1141:
1142: }
1143:
1144: private Boolean isXPStyle() {
1145: Toolkit toolkit = Toolkit.getDefaultToolkit();
1146: Boolean themeActive = (Boolean) toolkit
1147: .getDesktopProperty("win.xpstyle.themeActive");
1148: if (themeActive == null)
1149: themeActive = Boolean.FALSE;
1150: if (themeActive.booleanValue()
1151: && System.getProperty("swing.noxp") == null) {
1152: themeActive = Boolean.TRUE;
1153: }
1154: return themeActive;
1155: }
1156:
1157: protected void installStrings(JFileChooser fc) {
1158: super .installStrings(fc);
1159:
1160: Locale l = fc.getLocale();
1161:
1162: lookInLabelMnemonic = UIManager
1163: .getInt("FileChooser.lookInLabelMnemonic");
1164: lookInLabelText = UIManager.getString(
1165: "FileChooser.lookInLabelText", l);
1166: saveInLabelText = UIManager.getString(
1167: "FileChooser.saveInLabelText", l);
1168:
1169: fileNameLabelMnemonic = UIManager
1170: .getInt("FileChooser.fileNameLabelMnemonic");
1171: fileNameLabelText = UIManager.getString(
1172: "FileChooser.fileNameLabelText", l);
1173:
1174: filesOfTypeLabelMnemonic = UIManager
1175: .getInt("FileChooser.filesOfTypeLabelMnemonic");
1176: filesOfTypeLabelText = UIManager.getString(
1177: "FileChooser.filesOfTypeLabelText", l);
1178:
1179: upFolderToolTipText = UIManager.getString(
1180: "FileChooser.upFolderToolTipText", l);
1181: upFolderAccessibleName = UIManager.getString(
1182: "FileChooser.upFolderAccessibleName", l);
1183:
1184: newFolderToolTipText = UIManager.getString(
1185: "FileChooser.newFolderToolTipText", l);
1186:
1187: }
1188:
1189: protected void installListeners(JFileChooser fc) {
1190: super .installListeners(fc);
1191: ActionMap actionMap = getActionMap();
1192: SwingUtilities.replaceUIActionMap(fc, actionMap);
1193: }
1194:
1195: protected ActionMap getActionMap() {
1196: return createActionMap();
1197: }
1198:
1199: protected ActionMap createActionMap() {
1200: AbstractAction escAction = new AbstractAction() {
1201: public void actionPerformed(ActionEvent e) {
1202: getFileChooser().cancelSelection();
1203: }
1204:
1205: public boolean isEnabled() {
1206: return getFileChooser().isEnabled();
1207: }
1208: };
1209: ActionMap map = new ActionMapUIResource();
1210: map.put("approveSelection", getApproveSelectionAction());
1211: map.put("cancelSelection", escAction);
1212: map.put("Go Up", getChangeToParentDirectoryAction());
1213: return map;
1214: }
1215:
1216: public Action getNewFolderAction() {
1217: return newFolderAction;
1218: }
1219:
1220: public void uninstallUI(JComponent c) {
1221: c.removePropertyChangeListener(filterTypeComboBoxModel);
1222: cancelButton.removeActionListener(getCancelSelectionAction());
1223: approveButton.removeActionListener(getApproveSelectionAction());
1224: filenameTextField
1225: .removeActionListener(getApproveSelectionAction());
1226: super .uninstallUI(c);
1227: }
1228:
1229: /**
1230: * Returns the preferred size of the specified
1231: * <code>JFileChooser</code>.
1232: * The preferred size is at least as large,
1233: * in both height and width,
1234: * as the preferred size recommended
1235: * by the file chooser's layout manager.
1236: *
1237: * @param c a <code>JFileChooser</code>
1238: * @return a <code>Dimension</code> specifying the preferred
1239: * width and height of the file chooser
1240: */
1241: public Dimension getPreferredSize(JComponent c) {
1242: int prefWidth = PREF_SIZE.width;
1243: Dimension d = c.getLayout().preferredLayoutSize(c);
1244: if (d != null) {
1245: return new Dimension(d.width < prefWidth ? prefWidth
1246: : d.width,
1247: d.height < PREF_SIZE.height ? PREF_SIZE.height
1248: : d.height);
1249: } else {
1250: return new Dimension(prefWidth, PREF_SIZE.height);
1251: }
1252: }
1253:
1254: /**
1255: * Returns the minimum size of the <code>JFileChooser</code>.
1256: *
1257: * @param c a <code>JFileChooser</code>
1258: * @return a <code>Dimension</code> specifying the minimum
1259: * width and height of the file chooser
1260: */
1261: public Dimension getMinimumSize(JComponent c) {
1262: return MIN_SIZE;
1263: }
1264:
1265: /**
1266: * Returns the maximum size of the <code>JFileChooser</code>.
1267: *
1268: * @param c a <code>JFileChooser</code>
1269: * @return a <code>Dimension</code> specifying the maximum
1270: * width and height of the file chooser
1271: */
1272: public Dimension getMaximumSize(JComponent c) {
1273: return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1274: }
1275:
1276: private String getStringOfFileName(File file) {
1277: if (file == null) {
1278: return null;
1279: } else {
1280: JFileChooser fc = getFileChooser();
1281: if (fc.isDirectorySelectionEnabled()
1282: && !fc.isFileSelectionEnabled()) {
1283: if (fc.getFileSystemView().isDrive(file)) {
1284: return file.getPath();
1285: } else {
1286: return file.getPath();
1287: }
1288: } else {
1289: return file.getName();
1290: }
1291: }
1292: }
1293:
1294: private String getStringOfFileNames(File[] files) {
1295: StringBuffer buf = new StringBuffer();
1296: for (int i = 0; files != null && i < files.length; i++) {
1297: if (i > 0) {
1298: buf.append(" ");
1299: }
1300: if (files.length > 1) {
1301: buf.append("\"");
1302: }
1303: buf.append(getStringOfFileName(files[i]));
1304: if (files.length > 1) {
1305: buf.append("\"");
1306: }
1307: }
1308: return buf.toString();
1309: }
1310:
1311: /* The following methods are used by the PropertyChange Listener */
1312:
1313: private void fireSelectedFileChanged(PropertyChangeEvent e) {
1314: File f = (File) e.getNewValue();
1315: JFileChooser fc = getFileChooser();
1316: if (f != null
1317: && ((fc.isFileSelectionEnabled() && !f.isDirectory()) || (f
1318: .isDirectory() && fc
1319: .isDirectorySelectionEnabled()))) {
1320:
1321: setFileName(getStringOfFileName(f));
1322: }
1323: }
1324:
1325: private void fireSelectedFilesChanged(PropertyChangeEvent e) {
1326: File[] files = (File[]) e.getNewValue();
1327: JFileChooser fc = getFileChooser();
1328: if (files != null
1329: && files.length > 0
1330: && (files.length > 1
1331: || fc.isDirectorySelectionEnabled() || !files[0]
1332: .isDirectory())) {
1333: setFileName(getStringOfFileNames(files));
1334: }
1335: }
1336:
1337: private void fireDirectoryChanged(PropertyChangeEvent e) {
1338: JFileChooser fc = getFileChooser();
1339: FileSystemView fsv = fc.getFileSystemView();
1340: showPopupCompletion = false;
1341: setFileName("");
1342: clearIconCache();
1343: File currentDirectory = fc.getCurrentDirectory();
1344: if (currentDirectory != null) {
1345: directoryComboBoxModel.addItem(currentDirectory);
1346: newFolderAction.setEnabled(currentDirectory.canWrite());
1347: getChangeToParentDirectoryAction().setEnabled(
1348: !fsv.isRoot(currentDirectory));
1349: updateTree(currentDirectory);
1350: if (fc.isDirectorySelectionEnabled()
1351: && !fc.isFileSelectionEnabled()) {
1352: if (fsv.isFileSystem(currentDirectory)) {
1353: setFileName(getStringOfFileName(currentDirectory));
1354: } else {
1355: setFileName(null);
1356: }
1357: }
1358: }
1359: }
1360:
1361: private void fireFilterChanged(PropertyChangeEvent e) {
1362: clearIconCache();
1363: }
1364:
1365: private void fireFileSelectionModeChanged(PropertyChangeEvent e) {
1366: clearIconCache();
1367: JFileChooser fc = getFileChooser();
1368:
1369: File currentDirectory = fc.getCurrentDirectory();
1370: if (currentDirectory != null
1371: && fc.isDirectorySelectionEnabled()
1372: && !fc.isFileSelectionEnabled()
1373: && fc.getFileSystemView()
1374: .isFileSystem(currentDirectory)) {
1375:
1376: setFileName(currentDirectory.getPath());
1377: } else {
1378: setFileName(null);
1379: }
1380: }
1381:
1382: private void fireMultiSelectionChanged(PropertyChangeEvent e) {
1383: if (getFileChooser().isMultiSelectionEnabled()) {
1384: tree.getSelectionModel().setSelectionMode(
1385: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
1386: } else {
1387: tree.getSelectionModel().setSelectionMode(
1388: TreeSelectionModel.SINGLE_TREE_SELECTION);
1389: getFileChooser().setSelectedFiles(null);
1390: }
1391: }
1392:
1393: private void fireAccessoryChanged(PropertyChangeEvent e) {
1394: if (getAccessoryPanel() != null) {
1395: JComponent oldAcc = (JComponent) e.getOldValue();
1396: JComponent newAcc = (JComponent) e.getNewValue();
1397: JComponent accessoryPanel = getAccessoryPanel();
1398: if (oldAcc != null) {
1399: accessoryPanel.remove(oldAcc);
1400: }
1401: if (oldAcc != null && newAcc == null) {
1402: accessoryPanel.remove(topToolbar);
1403: topComboWrapper.add(topToolbar, BorderLayout.EAST);
1404: topCombo.setBorder(BorderFactory.createEmptyBorder(6,
1405: 0, 10, 0));
1406: topCombo.revalidate();
1407: }
1408:
1409: if (newAcc != null) {
1410: getAccessoryPanel().add(newAcc, BorderLayout.CENTER);
1411: }
1412:
1413: if (oldAcc == null && newAcc != null) {
1414: topComboWrapper.remove(topToolbar);
1415: topCombo.setBorder(BorderFactory.createEmptyBorder(4,
1416: 0, 8, 0));
1417: accessoryPanel.add(topToolbar, BorderLayout.NORTH);
1418: }
1419:
1420: }
1421: }
1422:
1423: private void fireApproveButtonTextChanged(PropertyChangeEvent e) {
1424: JFileChooser chooser = getFileChooser();
1425: approveButton.setText(getApproveButtonText(chooser));
1426: approveButton
1427: .setToolTipText(getApproveButtonToolTipText(chooser));
1428: // #107791: No mnemonics desirable on Mac
1429: if (!Utilities.isMac()) {
1430: approveButton
1431: .setMnemonic(getApproveButtonMnemonic(chooser));
1432: }
1433: }
1434:
1435: private void fireDialogTypeChanged(PropertyChangeEvent e) {
1436: JFileChooser chooser = getFileChooser();
1437: approveButton.setText(getApproveButtonText(chooser));
1438: approveButton
1439: .setToolTipText(getApproveButtonToolTipText(chooser));
1440: // #107791: No mnemonics desirable on Mac
1441: if (!Utilities.isMac()) {
1442: approveButton
1443: .setMnemonic(getApproveButtonMnemonic(chooser));
1444: }
1445: if (chooser.getDialogType() == JFileChooser.SAVE_DIALOG) {
1446: lookInComboBoxLabel.setText(saveInLabelText);
1447: } else {
1448: lookInComboBoxLabel.setText(lookInLabelText);
1449: }
1450: }
1451:
1452: private void fireApproveButtonMnemonicChanged(PropertyChangeEvent e) {
1453: // #107791: No mnemonics desirable on Mac
1454: if (!Utilities.isMac()) {
1455: approveButton
1456: .setMnemonic(getApproveButtonMnemonic(getFileChooser()));
1457: }
1458: }
1459:
1460: private void fireControlButtonsChanged(PropertyChangeEvent e) {
1461: if (getFileChooser().getControlButtonsAreShown()) {
1462: addControlButtons();
1463: } else {
1464: removeControlButtons();
1465: }
1466: }
1467:
1468: /*
1469: * Listen for filechooser property changes, such as
1470: * the selected file changing, or the type of the dialog changing.
1471: */
1472: public PropertyChangeListener createPropertyChangeListener(
1473: JFileChooser fc) {
1474: return new PropertyChangeListener() {
1475: public void propertyChange(PropertyChangeEvent e) {
1476: String s = e.getPropertyName();
1477: if (s
1478: .equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
1479: fireSelectedFileChanged(e);
1480: } else if (s
1481: .equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
1482: fireSelectedFilesChanged(e);
1483: } else if (s
1484: .equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)
1485: && changeDirectory) {
1486: fireDirectoryChanged(e);
1487: } else if (s
1488: .equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
1489: fireFilterChanged(e);
1490: } else if (s
1491: .equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
1492: fireFileSelectionModeChanged(e);
1493: } else if (s
1494: .equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
1495: fireMultiSelectionChanged(e);
1496: } else if (s
1497: .equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY)) {
1498: fireAccessoryChanged(e);
1499: } else if (s
1500: .equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY)
1501: || s
1502: .equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY)) {
1503: fireApproveButtonTextChanged(e);
1504: } else if (s
1505: .equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)) {
1506: fireDialogTypeChanged(e);
1507: } else if (s
1508: .equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
1509: fireApproveButtonMnemonicChanged(e);
1510: } else if (s
1511: .equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
1512: fireControlButtonsChanged(e);
1513: } else if (s.equals("FileChooser.useShellFolder")) {
1514: updateUseShellFolder();
1515: fireDirectoryChanged(e);
1516: } else if (s.equals("componentOrientation")) {
1517: ComponentOrientation o = (ComponentOrientation) e
1518: .getNewValue();
1519: JFileChooser cc = (JFileChooser) e.getSource();
1520: if (o != (ComponentOrientation) e.getOldValue()) {
1521: cc.applyComponentOrientation(o);
1522: }
1523: } else if (s.equals("ancestor")) {
1524: if (e.getOldValue() == null
1525: && e.getNewValue() != null) {
1526: filenameTextField.selectAll();
1527: filenameTextField.requestFocus();
1528: }
1529: }
1530: }
1531: };
1532: }
1533:
1534: protected void removeControlButtons() {
1535: bottomPanel.remove(buttonPanel);
1536: }
1537:
1538: protected void addControlButtons() {
1539: bottomPanel.add(buttonPanel);
1540: }
1541:
1542: public String getFileName() {
1543: if (filenameTextField != null) {
1544: return filenameTextField.getText();
1545: } else {
1546: return null;
1547: }
1548: }
1549:
1550: public void setFileName(String filename) {
1551: if (filenameTextField != null) {
1552: filenameTextField.setText(filename);
1553: }
1554: }
1555:
1556: private DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(
1557: JFileChooser fc) {
1558: return new DirectoryComboBoxRenderer();
1559: }
1560:
1561: protected DirectoryComboBoxModel createDirectoryComboBoxModel(
1562: JFileChooser fc) {
1563: return new DirectoryComboBoxModel();
1564: }
1565:
1566: protected FilterComboBoxRenderer createFilterComboBoxRenderer() {
1567: return new FilterComboBoxRenderer();
1568: }
1569:
1570: protected FilterTypeComboBoxModel createFilterComboBoxModel() {
1571: return new FilterTypeComboBoxModel();
1572: }
1573:
1574: protected JButton getApproveButton(JFileChooser fc) {
1575: return approveButton;
1576: }
1577:
1578: public FileView getFileView(JFileChooser fc) {
1579:
1580: // fix bug #96957, should use DirectoryChooserFileView
1581: // only on windows
1582: if (Utilities.isWindows()) {
1583: if (useShellFolder) {
1584: fileView = new DirectoryChooserFileView();
1585: }
1586: } else {
1587: fileView = (BasicFileView) super .getFileView(fileChooser);
1588: }
1589: return fileView;
1590: }
1591:
1592: private void setSelected(File[] files) {
1593: changeDirectory = false;
1594: fileChooser.setSelectedFiles(files);
1595: changeDirectory = true;
1596: }
1597:
1598: private DirectoryHandler createDirectoryHandler(JFileChooser chooser) {
1599: return new DirectoryHandler(chooser);
1600: }
1601:
1602: private void addNewDirectory(final TreePath path) {
1603: RequestProcessor.getDefault().post(new Runnable() {
1604: public void run() {
1605: EventQueue.invokeLater(new Runnable() {
1606: public void run() {
1607: DirectoryNode selectedNode = (DirectoryNode) path
1608: .getLastPathComponent();
1609:
1610: if (selectedNode == null
1611: || !canWrite(selectedNode.getFile())) {
1612: return;
1613: }
1614:
1615: try {
1616: newFolderNode = new DirectoryNode(
1617: fileChooser.getFileSystemView()
1618: .createNewFolder(
1619: selectedNode
1620: .getFile()));
1621: model.insertNodeInto(newFolderNode,
1622: selectedNode, selectedNode
1623: .getChildCount());
1624: applyEdit(newFolderNode);
1625: } catch (IOException ex) {
1626: ex.printStackTrace();
1627: }
1628: }
1629: });
1630: }
1631: });
1632: }
1633:
1634: private void applyEdit(DirectoryNode node) {
1635: TreeNode[] nodes = model.getPathToRoot(node);
1636: TreePath editingPath = new TreePath(nodes);
1637: tree.setEditable(true);
1638: tree.makeVisible(editingPath);
1639: tree.scrollPathToVisible(editingPath);
1640: tree.setSelectionPath(editingPath);
1641: tree.startEditingAtPath(editingPath);
1642:
1643: JTextField editField = DirectoryCellEditor.getTextField();
1644: editField.setCursor(Cursor
1645: .getPredefinedCursor(Cursor.TEXT_CURSOR));
1646: editField.setRequestFocusEnabled(true);
1647: editField.requestFocus();
1648: editField.setSelectionStart(0);
1649: editField.setSelectionEnd(editField.getText().length());
1650: }
1651:
1652: private static boolean canWrite(File f) {
1653: boolean writeable = false;
1654: if (f != null) {
1655: try {
1656: writeable = f.canWrite();
1657: } catch (AccessControlException ex) {
1658: writeable = false;
1659: }
1660: }
1661: return writeable;
1662: }
1663:
1664: private void expandNode(final JFileChooser fileChooser,
1665: final TreePath path) {
1666:
1667: RequestProcessor.getDefault().post(new Runnable() {
1668: DirectoryNode node;
1669:
1670: public void run() {
1671: if (!EventQueue.isDispatchThread()) {
1672: // first pass, out of EQ thread, loads data
1673: setCursor(fileChooser, Cursor.WAIT_CURSOR);
1674: node = (DirectoryNode) path.getLastPathComponent();
1675: node.loadChildren(fileChooser, true);
1676: // send to second pass
1677: EventQueue.invokeLater(this );
1678: } else {
1679: // second pass, in EQ thread
1680: ((DefaultTreeModel) tree.getModel())
1681: .nodeStructureChanged(node);
1682: /*
1683: * This happens when the add new directory action is called
1684: * and the node is not loaded. Furthermore, it ensures that
1685: * adding a new directory execute after the UI has finished
1686: * displaying the children of the expanded node.
1687: */
1688: if (addNewDirectory) {
1689: addNewDirectory(path);
1690: addNewDirectory = false;
1691: }
1692: setCursor(fileChooser, Cursor.DEFAULT_CURSOR);
1693: }
1694: }
1695: });
1696: }
1697:
1698: private void setCursor(JComponent comp, int type) {
1699: Window window = SwingUtilities.getWindowAncestor(comp);
1700: if (window != null) {
1701: Cursor cursor = Cursor.getPredefinedCursor(type);
1702: window.setCursor(cursor);
1703: window.setFocusable(true);
1704: }
1705:
1706: JRootPane pane = fileChooser.getRootPane();
1707: blocker = new InputBlocker();
1708: if (pane != null) {
1709: pane.setGlassPane(blocker);
1710: }
1711:
1712: if (type == Cursor.WAIT_CURSOR) {
1713: blocker.block();
1714: } else if (type == Cursor.DEFAULT_CURSOR) {
1715: blocker.unBlock();
1716: }
1717: }
1718:
1719: private Icon getNbProjectIcon() {
1720: if (projectIcon == null) {
1721: projectIcon = new ImageIcon(
1722: Utilities
1723: .loadImage("org/netbeans/swing/dirchooser/resources/main_project_16.png"));
1724: }
1725: return projectIcon;
1726: }
1727:
1728: /*************** HELPER CLASSES ***************/
1729:
1730: private class IconIndenter implements Icon {
1731: final static int space = 10;
1732: Icon icon = null;
1733: int depth = 0;
1734:
1735: public void paintIcon(Component c, Graphics g, int x, int y) {
1736: if (c.getComponentOrientation().isLeftToRight()) {
1737: icon.paintIcon(c, g, x + depth * space, y);
1738: } else {
1739: icon.paintIcon(c, g, x, y);
1740: }
1741: }
1742:
1743: public int getIconWidth() {
1744: return icon.getIconWidth() + depth * space;
1745: }
1746:
1747: public int getIconHeight() {
1748: return icon.getIconHeight();
1749: }
1750:
1751: }
1752:
1753: private class DirectoryComboBoxRenderer extends JLabel implements
1754: ListCellRenderer, UIResource {
1755: IconIndenter indenter = new IconIndenter();
1756:
1757: public DirectoryComboBoxRenderer() {
1758: setOpaque(true);
1759: }
1760:
1761: public Component getListCellRendererComponent(JList list,
1762: Object value, int index, boolean isSelected,
1763: boolean cellHasFocus) {
1764: // #89393: GTK needs name to render cell renderer "natively"
1765: setName("ComboBox.listRenderer"); // NOI18N
1766:
1767: if (value == null) {
1768: setText("");
1769: return this ;
1770: }
1771: File directory = (File) value;
1772: setText(getFileChooser().getName(directory));
1773: Icon icon;
1774: if (DirectoryNode.isNetBeansProject(directory)) {
1775: icon = getNbProjectIcon();
1776: } else {
1777: icon = getFileChooser().getIcon(directory);
1778: }
1779: indenter.icon = icon;
1780: indenter.depth = directoryComboBoxModel.getDepth(index);
1781: setIcon(indenter);
1782: if (isSelected) {
1783: setBackground(list.getSelectionBackground());
1784: setForeground(list.getSelectionForeground());
1785: } else {
1786: setBackground(list.getBackground());
1787: setForeground(list.getForeground());
1788: }
1789:
1790: return this ;
1791: }
1792:
1793: // #89393: GTK needs name to render cell renderer "natively"
1794: @Override
1795: public String getName() {
1796: String name = super .getName();
1797: return name == null ? "ComboBox.renderer" : name; // NOI18N
1798: }
1799: } // end of DirectoryComboBoxRenderer
1800:
1801: /**
1802: * Data model for a type-face selection combo-box.
1803: */
1804: private class DirectoryComboBoxModel extends AbstractListModel
1805: implements ComboBoxModel {
1806: Vector<File> directories = new Vector<File>();
1807: int[] depths = null;
1808: File selectedDirectory = null;
1809: JFileChooser chooser = getFileChooser();
1810: FileSystemView fsv = chooser.getFileSystemView();
1811:
1812: public DirectoryComboBoxModel() {
1813: // Add the current directory to the model, and make it the
1814: // selectedDirectory
1815: File dir = getFileChooser().getCurrentDirectory();
1816: if (dir != null) {
1817: addItem(dir);
1818: }
1819: }
1820:
1821: /**
1822: * Adds the directory to the model and sets it to be selected,
1823: * additionally clears out the previous selected directory and
1824: * the paths leading up to it, if any.
1825: */
1826: private void addItem(File directory) {
1827:
1828: if (directory == null) {
1829: return;
1830: }
1831:
1832: directories.clear();
1833:
1834: if (useShellFolder) {
1835: directories
1836: .addAll(Arrays.asList(getShellFolderRoots()));
1837: } else {
1838: directories.addAll(Arrays.asList(fileChooser
1839: .getFileSystemView().getRoots()));
1840: }
1841:
1842: // Get the canonical (full) path. This has the side
1843: // benefit of removing extraneous chars from the path,
1844: // for example /foo/bar/ becomes /foo/bar
1845: File canonical = null;
1846: try {
1847: canonical = directory.getCanonicalFile();
1848: } catch (IOException e) {
1849: // Maybe drive is not ready. Can't abort here.
1850: canonical = directory;
1851: }
1852:
1853: // create File instances of each directory leading up to the top
1854: File sf = getShellFolderForFile(canonical);
1855: File f = sf;
1856: Vector<File> path = new Vector<File>(10);
1857:
1858: do {
1859: path.addElement(f);
1860: } while ((f = f.getParentFile()) != null);
1861:
1862: int pathCount = path.size();
1863: // Insert chain at appropriate place in vector
1864: for (int i = 0; i < pathCount; i++) {
1865: f = path.get(i);
1866: if (directories.contains(f)) {
1867: int topIndex = directories.indexOf(f);
1868: for (int j = i - 1; j >= 0; j--) {
1869: directories.insertElementAt(path.get(j),
1870: topIndex + i - j);
1871: }
1872: break;
1873: }
1874: }
1875: calculateDepths();
1876: setSelectedItem(sf);
1877: }
1878:
1879: private void calculateDepths() {
1880: depths = new int[directories.size()];
1881: for (int i = 0; i < depths.length; i++) {
1882: File dir = directories.get(i);
1883: File parent = dir.getParentFile();
1884: depths[i] = 0;
1885: if (parent != null) {
1886: for (int j = i - 1; j >= 0; j--) {
1887: if (parent.equals(directories.get(j))) {
1888: depths[i] = depths[j] + 1;
1889: break;
1890: }
1891: }
1892: }
1893: }
1894: }
1895:
1896: public int getDepth(int i) {
1897: return (depths != null && i >= 0 && i < depths.length) ? depths[i]
1898: : 0;
1899: }
1900:
1901: public void setSelectedItem(Object selectedDirectory) {
1902: this .selectedDirectory = (File) selectedDirectory;
1903: fireContentsChanged(this , -1, -1);
1904: }
1905:
1906: public Object getSelectedItem() {
1907: return selectedDirectory;
1908: }
1909:
1910: public int getSize() {
1911: return directories.size();
1912: }
1913:
1914: public Object getElementAt(int index) {
1915: return directories.elementAt(index);
1916: }
1917: }
1918:
1919: /**
1920: * Render different type sizes and styles.
1921: */
1922: private class FilterComboBoxRenderer extends JLabel implements
1923: ListCellRenderer, UIResource {
1924:
1925: public FilterComboBoxRenderer() {
1926: setOpaque(true);
1927: }
1928:
1929: public Component getListCellRendererComponent(JList list,
1930: Object value, int index, boolean isSelected,
1931: boolean cellHasFocus) {
1932:
1933: // #89393: GTK needs name to render cell renderer "natively"
1934: setName("ComboBox.listRenderer"); // NOI18N
1935:
1936: if (value != null && value instanceof FileFilter) {
1937: setText(((FileFilter) value).getDescription());
1938: }
1939:
1940: if (isSelected) {
1941: setBackground(list.getSelectionBackground());
1942: setForeground(list.getSelectionForeground());
1943: } else {
1944: setBackground(list.getBackground());
1945: setForeground(list.getForeground());
1946: }
1947:
1948: return this ;
1949: }
1950:
1951: // #89393: GTK needs name to render cell renderer "natively"
1952: public String getName() {
1953: String name = super .getName();
1954: return name == null ? "ComboBox.renderer" : name; // NOI18N
1955: }
1956:
1957: } // end of FilterComboBoxRenderer
1958:
1959: /**
1960: * Data model for a type-face selection combo-box.
1961: */
1962: protected class FilterTypeComboBoxModel extends AbstractListModel
1963: implements ComboBoxModel, PropertyChangeListener {
1964: protected FileFilter[] filters;
1965:
1966: protected FilterTypeComboBoxModel() {
1967: super ();
1968: filters = getFileChooser().getChoosableFileFilters();
1969: }
1970:
1971: public void propertyChange(PropertyChangeEvent e) {
1972: String prop = e.getPropertyName();
1973: if (prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
1974: filters = (FileFilter[]) e.getNewValue();
1975: fireContentsChanged(this , -1, -1);
1976: } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
1977: fireContentsChanged(this , -1, -1);
1978: }
1979: }
1980:
1981: public void setSelectedItem(Object filter) {
1982: if (filter != null) {
1983: getFileChooser().setFileFilter((FileFilter) filter);
1984: setFileName(null);
1985: fireContentsChanged(this , -1, -1);
1986: }
1987: }
1988:
1989: public Object getSelectedItem() {
1990: // Ensure that the current filter is in the list.
1991: // NOTE: we shouldnt' have to do this, since JFileChooser adds
1992: // the filter to the choosable filters list when the filter
1993: // is set. Lets be paranoid just in case someone overrides
1994: // setFileFilter in JFileChooser.
1995: FileFilter currentFilter = getFileChooser().getFileFilter();
1996: boolean found = false;
1997: if (currentFilter != null) {
1998: for (int i = 0; i < filters.length; i++) {
1999: if (filters[i] == currentFilter) {
2000: found = true;
2001: }
2002: }
2003: if (found == false) {
2004: getFileChooser().addChoosableFileFilter(
2005: currentFilter);
2006: }
2007: }
2008: return getFileChooser().getFileFilter();
2009: }
2010:
2011: public int getSize() {
2012: if (filters != null) {
2013: return filters.length;
2014: } else {
2015: return 0;
2016: }
2017: }
2018:
2019: public Object getElementAt(int index) {
2020: if (index > getSize() - 1) {
2021: // This shouldn't happen. Try to recover gracefully.
2022: return getFileChooser().getFileFilter();
2023: }
2024: if (filters != null) {
2025: return filters[index];
2026: } else {
2027: return null;
2028: }
2029: }
2030: }
2031:
2032: /**
2033: * Gets calls when the ComboBox has changed the selected item.
2034: */
2035: private class DirectoryComboBoxAction implements ActionListener {
2036: public void actionPerformed(ActionEvent e) {
2037: File f = (File) directoryComboBox.getSelectedItem();
2038: getFileChooser().setCurrentDirectory(f);
2039: }
2040: }
2041:
2042: private class DirectoryChooserFileView extends BasicFileView {
2043:
2044: public Icon getIcon(File f) {
2045:
2046: Icon icon = getCachedIcon(f);
2047: if (icon != null) {
2048: return icon;
2049: }
2050:
2051: if (f != null) {
2052: icon = fileChooser.getFileSystemView().getSystemIcon(f);
2053: }
2054:
2055: if (icon == null) {
2056: icon = super .getIcon(f);
2057: }
2058:
2059: cacheIcon(f, icon);
2060: return icon;
2061: }
2062: }
2063:
2064: private class TextFieldKeyListener extends KeyAdapter {
2065: public void keyPressed(KeyEvent evt) {
2066: showPopupCompletion = true;
2067: int keyCode = evt.getKeyCode();
2068: // #105801: completionPopup might not be ready when updateCompletions not called (empty text field)
2069: if (completionPopup != null && !completionPopup.isVisible()) {
2070: if (keyCode == KeyEvent.VK_ENTER) {
2071: File file = new File(filenameTextField.getText());
2072: if (file.exists() && file.isDirectory()) {
2073: setSelected(new File[] { file });
2074: fileChooser.approveSelection();
2075: }
2076: }
2077:
2078: if ((keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_DOWN)
2079: || (keyCode == KeyEvent.VK_RIGHT && (filenameTextField
2080: .getCaretPosition() >= (filenameTextField
2081: .getDocument().getLength() - 1)))) {
2082: updateCompletions();
2083: }
2084:
2085: }
2086:
2087: if (filenameTextField.isFocusOwner()
2088: && (completionPopup == null || !completionPopup
2089: .isVisible())
2090: && keyCode == KeyEvent.VK_ESCAPE) {
2091: fileChooser.cancelSelection();
2092: }
2093: }
2094: }
2095:
2096: private class DirectoryHandler extends MouseAdapter implements
2097: TreeSelectionListener, CellEditorListener, ActionListener,
2098: FocusListener, Runnable {
2099: private JFileChooser fileChooser;
2100: /** current selection holder */
2101: private WeakReference<TreePath> curSelPath;
2102: /** timer for slow click to rename feature */
2103: private Timer renameTimer;
2104: /** path to rename for slow click to rename feature */
2105: private TreePath pathToRename;
2106:
2107: public DirectoryHandler(JFileChooser fileChooser) {
2108: this .fileChooser = fileChooser;
2109: }
2110:
2111: /************ imple of TreeSelectionListener *******/
2112:
2113: public void valueChanged(TreeSelectionEvent e) {
2114: showPopupCompletion = false;
2115: FileSystemView fsv = fileChooser.getFileSystemView();
2116: JTree tree = (JTree) e.getSource();
2117: TreePath path = tree.getSelectionPath();
2118: TreePath curSel = e.getNewLeadSelectionPath();
2119: curSelPath = (curSel != null) ? new WeakReference<TreePath>(
2120: curSel)
2121: : null;
2122:
2123: if (path != null) {
2124:
2125: DirectoryNode node = (DirectoryNode) path
2126: .getLastPathComponent();
2127: File file = node.getFile();
2128:
2129: if (file != null) {
2130: setSelected(getSelectedNodes(tree
2131: .getSelectionPaths()));
2132: newFolderAction.setEnabled(canWrite(file)
2133: && file.isDirectory());
2134:
2135: if (file.isDirectory()) {
2136: setDirectorySelected(true);
2137: }
2138: }
2139: }
2140: }
2141:
2142: private File[] getSelectedNodes(TreePath[] paths) {
2143: Vector<File> files = new Vector<File>();
2144: for (int i = 0; i < paths.length; i++) {
2145: File file = ((DirectoryNode) paths[i]
2146: .getLastPathComponent()).getFile();
2147: if (file.isDirectory()
2148: && fileChooser.isTraversable(file)
2149: && !fileChooser.getFileSystemView()
2150: .isFileSystem(file)) {
2151: continue;
2152: }
2153: files.add(file);
2154: }
2155: return files.toArray(new File[files.size()]);
2156: }
2157:
2158: /********* impl of MouseListener ***********/
2159:
2160: public void mouseClicked(MouseEvent e) {
2161: final JTree tree = (JTree) e.getSource();
2162: Point p = e.getPoint();
2163: final int x = e.getX();
2164: final int y = e.getY();
2165: int row = tree.getRowForLocation(x, y);
2166: TreePath path = tree.getPathForRow(row);
2167:
2168: if (path != null) {
2169:
2170: DirectoryNode node = (DirectoryNode) path
2171: .getLastPathComponent();
2172: newFolderAction.setEnabled(canWrite(node.getFile()));
2173:
2174: if (SwingUtilities.isLeftMouseButton(e)
2175: && (e.getClickCount() == 2)) {
2176: cancelRename();
2177: if (node.isNetBeansProject()) {
2178: fileChooser.approveSelection();
2179: } else if (node.getFile().isFile()
2180: && !node.getFile().getPath().endsWith(
2181: ".lnk")) {
2182: fileChooser.approveSelection();
2183: } else {
2184: changeTreeDirectory(node.getFile());
2185: }
2186:
2187: }
2188:
2189: // handles click to rename feature
2190: if (SwingUtilities.isLeftMouseButton(e)
2191: && (e.getClickCount() == 1)) {
2192: if (pathToRename != null) {
2193: if (renameTimer != null) {
2194: renameTimer.stop();
2195: }
2196: // start slow click rename timer
2197: renameTimer = new Timer(800, this );
2198: renameTimer.setRepeats(false);
2199: renameTimer.start();
2200: }
2201: }
2202:
2203: ((DirectoryTreeModel) tree.getModel())
2204: .nodeChanged(node);
2205: if (row == 0) {
2206: tree.revalidate();
2207: tree.repaint();
2208: }
2209: }
2210: }
2211:
2212: @Override
2213: public void mousePressed(MouseEvent e) {
2214: handlePopupMenu(e);
2215: }
2216:
2217: @Override
2218: public void mouseReleased(MouseEvent e) {
2219: handlePopupMenu(e);
2220: }
2221:
2222: private void handlePopupMenu(MouseEvent e) {
2223: if (!e.isPopupTrigger()) {
2224: return;
2225: }
2226: final JTree tree = (JTree) e.getSource();
2227: Point p = e.getPoint();
2228: int x = e.getX();
2229: int y = e.getY();
2230: int row = tree.getRowForLocation(x, y);
2231: TreePath path = tree.getPathForRow(row);
2232:
2233: if (path != null) {
2234: DirectoryNode node = (DirectoryNode) path
2235: .getLastPathComponent();
2236: ((DirectoryTreeModel) tree.getModel())
2237: .nodeChanged(node);
2238: if (!fileChooser.getFileSystemView().isFileSystem(
2239: node.getFile())) {
2240: return;
2241: }
2242:
2243: tree.setSelectionPath(path);
2244: popupMenu.show(tree, x, y);
2245: }
2246: }
2247:
2248: private void changeTreeDirectory(File dir) {
2249: if (File.separatorChar == '\\'
2250: && dir.getPath().endsWith(".lnk")) {
2251: File linkLocation = getShellFolderForFileLinkLoc(dir);
2252: if (linkLocation != null
2253: && fileChooser.isTraversable(linkLocation)) {
2254: dir = linkLocation;
2255: } else {
2256: return;
2257: }
2258: }
2259: fileChooser.setCurrentDirectory(dir);
2260: }
2261:
2262: /********** implementation of CellEditorListener ****************/
2263:
2264: /** Refresh filename text field after rename */
2265: public void editingStopped(ChangeEvent e) {
2266: DirectoryNode node = (DirectoryNode) tree
2267: .getLastSelectedPathComponent();
2268: if (node != null) {
2269: setFileName(getStringOfFileName(node.getFile()));
2270: }
2271: }
2272:
2273: public void editingCanceled(ChangeEvent e) {
2274: // no operation
2275: }
2276:
2277: /********** ActionListener impl, slow-double-click rename ******/
2278:
2279: public void actionPerformed(ActionEvent e) {
2280: if (tree.isFocusOwner() && isSelectionKept(pathToRename)) {
2281: DirectoryNode node = (DirectoryNode) tree
2282: .getLastSelectedPathComponent();
2283: if (node != null) {
2284: applyEdit(node);
2285: }
2286: }
2287: // clear
2288: cancelRename();
2289: }
2290:
2291: void preprocessMouseEvent(MouseEvent e) {
2292: if ((e.getID() != MouseEvent.MOUSE_PRESSED)
2293: || (e.getButton() != MouseEvent.BUTTON1)) {
2294: return;
2295: }
2296: TreePath clickedPath = tree.getPathForLocation(e.getX(), e
2297: .getY());
2298: if (clickedPath != null && isSelectionKept(clickedPath)) {
2299: pathToRename = clickedPath;
2300: }
2301: }
2302:
2303: private boolean isSelectionKept(TreePath selPath) {
2304: if (curSelPath != null) {
2305: TreePath oldSel = curSelPath.get();
2306: if (oldSel != null && oldSel.equals(selPath)) {
2307: return true;
2308: }
2309: }
2310: return false;
2311: }
2312:
2313: private void cancelRename() {
2314: if (renameTimer != null) {
2315: renameTimer.stop();
2316: renameTimer = null;
2317: }
2318: pathToRename = null;
2319: }
2320:
2321: /******** implementation of focus listener, for slow click rename cancelling ******/
2322:
2323: public void focusGained(FocusEvent e) {
2324: // don't allow to invoke click to rename immediatelly after focus gain
2325: // what may happen is that tree gains focus by mouse
2326: // click on selected item - on some platforms selected item
2327: // is not visible without focus and click to rename will
2328: // be unwanted and surprising for users
2329:
2330: // see run method
2331: SwingUtilities.invokeLater(this );
2332: }
2333:
2334: public void run() {
2335: cancelRename();
2336: }
2337:
2338: public void focusLost(FocusEvent e) {
2339: cancelRename();
2340: }
2341:
2342: }
2343:
2344: private class TreeExpansionHandler implements TreeExpansionListener {
2345: public void treeExpanded(TreeExpansionEvent evt) {
2346: TreePath path = evt.getPath();
2347: DirectoryNode node = (DirectoryNode) path
2348: .getLastPathComponent();
2349: if (!node.isLoaded()) {
2350: expandNode(fileChooser, path);
2351: } else {
2352: // fixed #96954, to be able to add a new directory
2353: // when the node has been already loaded
2354: if (addNewDirectory) {
2355: addNewDirectory(path);
2356: addNewDirectory = false;
2357: }
2358: }
2359: }
2360:
2361: public void treeCollapsed(TreeExpansionEvent event) {
2362: }
2363: }
2364:
2365: private class NewDirectoryAction extends AbstractAction {
2366: public void actionPerformed(ActionEvent e) {
2367: final TreePath path = tree.getSelectionPath();
2368:
2369: if (path == null) {
2370: // if no nodes are selected, get the root node
2371: // fixed #96954, to be able to add a new directory
2372: // in the current directory shown in the tree
2373: addNewDirectory(new TreePath(model
2374: .getPathToRoot((DirectoryNode) tree.getModel()
2375: .getRoot())));
2376: }
2377:
2378: if (path != null) {
2379: if (tree.isExpanded(path)) {
2380: addNewDirectory(path);
2381: } else {
2382: addNewDirectory = true;
2383: tree.expandPath(path);
2384: }
2385: }
2386: }
2387: }
2388:
2389: private class DirectoryTreeRenderer implements TreeCellRenderer {
2390: HtmlRenderer.Renderer renderer = HtmlRenderer.createRenderer();
2391:
2392: public Component getTreeCellRendererComponent(JTree tree,
2393: Object value, boolean isSelected, boolean expanded,
2394: boolean leaf, int row, boolean hasFocus) {
2395:
2396: if (!tree.isFocusOwner()) {
2397: isSelected = false;
2398: }
2399:
2400: Component stringDisplayer = renderer
2401: .getTreeCellRendererComponent(tree, value,
2402: isSelected, expanded, leaf, row, hasFocus);
2403:
2404: if (value instanceof DirectoryNode) {
2405: tree.setShowsRootHandles(true);
2406: DirectoryNode node = (DirectoryNode) value;
2407: ((JLabel) stringDisplayer).setIcon(getNodeIcon(node));
2408: ((JLabel) stringDisplayer).setText(getNodeText(node
2409: .getFile()));
2410: }
2411: Font f = stringDisplayer.getFont();
2412: stringDisplayer.setPreferredSize(new Dimension(
2413: stringDisplayer.getPreferredSize().width, 30));
2414:
2415: // allow some space around icon of items
2416: ((JComponent) stringDisplayer).setBorder(BorderFactory
2417: .createEmptyBorder(1, 0, 1, 0));
2418: stringDisplayer.setSize(stringDisplayer.getPreferredSize());
2419:
2420: return stringDisplayer;
2421: }
2422:
2423: private Icon getNodeIcon(DirectoryNode node) {
2424: if (node.isNetBeansProject()) {
2425: return getNbProjectIcon();
2426: }
2427: File file = node.getFile();
2428: if (file.exists()) {
2429: return fileChooser.getIcon(file);
2430: } else {
2431: return null;
2432: }
2433: }
2434:
2435: private String getNodeText(File file) {
2436: if (file.exists()) {
2437: return "<html>" + fileChooser.getName(file) + "</html>";
2438: } else {
2439: return "<html></html>";
2440: }
2441: }
2442: }
2443:
2444: private class DirectoryTreeModel extends DefaultTreeModel {
2445:
2446: public DirectoryTreeModel(TreeNode root) {
2447: super (root);
2448: }
2449:
2450: public void valueForPathChanged(TreePath path, Object newValue) {
2451: boolean refreshTree = false;
2452: DirectoryNode node = (DirectoryNode) path
2453: .getLastPathComponent();
2454: File f = node.getFile();
2455: File newFile = new File(f.getParentFile(),
2456: (String) newValue);
2457:
2458: if (f.renameTo(newFile)) {
2459: // fix bug #97521, #96960
2460: if (tree.isExpanded(path)) {
2461: tree.collapsePath(path);
2462: refreshTree = true;
2463: }
2464:
2465: node.setFile(newFile);
2466: node.removeAllChildren();
2467:
2468: ((DefaultTreeModel) tree.getModel())
2469: .nodeStructureChanged(node);
2470: if (refreshTree) {
2471: tree.expandPath(path);
2472: }
2473: }
2474: }
2475: }
2476: }
|