blob: a6de9dc675d3ed175844c096e3de1f10dfd7eedd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* Contributor(s): Soot Phengsy
*/
package org.netbeans.swing.dirchooser;
import java.util.logging.Level;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.*;
import javax.swing.plaf.basic.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.security.AccessControlException;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.border.EmptyBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreeSelectionModel;
import org.openide.awt.HtmlRenderer;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
/**
* An implementation of a customized filechooser.
*
* @author Soot Phengsy, inspired by Jeff Dinkins' Swing version
*/
public class DirectoryChooserUI extends BasicFileChooserUI {
private static final String DIALOG_IS_CLOSING = "JFileChooserDialogIsClosingProperty";
private static final Dimension horizontalStrut1 = new Dimension(25, 1);
private static final Dimension verticalStrut1 = new Dimension(1, 4);
private static final Dimension verticalStrut2 = new Dimension(1, 6);
private static final Dimension verticalStrut3 = new Dimension(1, 8);
private static Dimension PREF_SIZE = new Dimension(425, 245);
private static Dimension MIN_SIZE = new Dimension(425, 245);
private static Dimension TREE_PREF_SIZE = new Dimension(380, 230);
private static final int ACCESSORY_WIDTH = 250;
private static final Logger LOG = Logger.getLogger(DirectoryChooserUI.class.getName());
private static final RequestProcessor RP = new RequestProcessor("DirChooser Update Worker"); // NOI18N
private static final String TIMEOUT_KEY="nb.fileChooser.timeout"; // NOI18N
private JPanel centerPanel;
private JLabel lookInComboBoxLabel;
private JComboBox directoryComboBox;
private DirectoryComboBoxModel directoryComboBoxModel;
private ActionListener directoryComboBoxAction = new DirectoryComboBoxAction();
private FilterTypeComboBoxModel filterTypeComboBoxModel;
private JTextField filenameTextField;
private JComponent placesBar;
private boolean placesBarFailed = false;
private JButton approveButton;
private JButton cancelButton;
private JPanel buttonPanel;
private JPanel bottomPanel;
private JComboBox filterTypeComboBox;
private int lookInLabelMnemonic = 0;
private String lookInLabelText = null;
private String saveInLabelText = null;
private int fileNameLabelMnemonic = 0;
private String fileNameLabelText = null;
private int filesOfTypeLabelMnemonic = 0;
private String filesOfTypeLabelText = null;
private String upFolderToolTipText = null;
private String upFolderAccessibleName = null;
private String newFolderToolTipText = null;
private String newFolderAccessibleName = null;
private String homeFolderTooltipText = null;
private String homeFolderAccessibleName = null;
private final NewDirectoryAction newFolderAction = new NewDirectoryAction();
private BasicFileView fileView = new DirectoryChooserFileView();
private JTree tree;
private DirectoryTreeModel model;
private DirectoryNode newFolderNode;
private JComponent treeViewPanel;
private InputBlocker blocker;
private JFileChooser fileChooser;
private boolean changeDirectory = true;
private boolean showPopupCompletion = false;
private boolean addNewDirectory = false;
private JPopupMenu popupMenu;
private FileCompletionPopup completionPopup;
private final UpdateWorker updateWorker = new UpdateWorker();
private boolean useShellFolder = false;
private JButton upFolderButton;
private JButton newFolderButton;
private JComponent topCombo, topComboWrapper, topToolbar;
private JPanel slownessPanel;
private final ListFilesWorker listFilesWorker = new ListFilesWorker();
private final RequestProcessor.Task listFilesTask = RP.create(listFilesWorker);
public static ComponentUI createUI(JComponent c) {
return new DirectoryChooserUI((JFileChooser) c);
}
public DirectoryChooserUI(JFileChooser filechooser) {
super(filechooser);
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
}
@Override
public void uninstallComponents(JFileChooser fc) {
fc.removeAll();
super.uninstallComponents(fc);
}
@Override
public void installComponents(JFileChooser fc) {
fileChooser = fc;
fc.setFocusCycleRoot(true);
fc.setBorder(new EmptyBorder(4, 10, 10, 10));
fc.setLayout(new BorderLayout(8, 8));
updateUseShellFolder();
createCenterPanel(fc);
fc.add(centerPanel, BorderLayout.CENTER);
if (fc.isMultiSelectionEnabled()) {
setFileName(getStringOfFileNames(fc.getSelectedFiles()));
} else {
setFileName(getStringOfFileName(fc.getSelectedFile()));
}
if(fc.getControlButtonsAreShown()) {
addControlButtons();
}
createPopup();
initUpdateWorker();
}
@Override
public String getDialogTitle(JFileChooser fc) {
String title = super.getDialogTitle(fc);
fc.getAccessibleContext().setAccessibleDescription(title);
return title;
}
private void updateUseShellFolder() {
// Decide whether to use the ShellFolder class to populate shortcut
// panel and combobox.
Boolean prop =
(Boolean)fileChooser.getClientProperty(DelegatingChooserUI.USE_SHELL_FOLDER);
if (prop != null) {
useShellFolder = prop.booleanValue();
} else {
// See if FileSystemView.getRoots() returns the desktop folder,
// i.e. the normal Windows hierarchy.
useShellFolder = false;
File[] roots = fileChooser.getFileSystemView().getRoots();
if (roots != null && roots.length == 1) {
File[] cbFolders = getShellFolderRoots();
if (cbFolders != null && cbFolders.length > 0 && Arrays.asList(cbFolders).contains(roots[0])) {
useShellFolder = true;
}
}
}
if (Utilities.isWindows()) {
if (useShellFolder) {
if (placesBar == null) {
placesBar = getPlacesBar();
}
if (placesBar != null) {
fileChooser.add(placesBar, BorderLayout.BEFORE_LINE_BEGINS);
if (placesBar instanceof PropertyChangeListener) {
fileChooser.addPropertyChangeListener((PropertyChangeListener)placesBar);
}
}
} else {
if (placesBar != null) {
fileChooser.remove(placesBar);
if (placesBar instanceof PropertyChangeListener) {
fileChooser.removePropertyChangeListener((PropertyChangeListener)placesBar);
}
placesBar = null;
}
}
}
}
/** Returns instance of WindowsPlacesBar class or null in case of failure
*/
private JComponent getPlacesBar () {
if (placesBarFailed) {
return null;
}
try {
Class<?> clazz = Class.forName("sun.swing.WindowsPlacesBar");
Class[] params = new Class[] { JFileChooser.class, Boolean.TYPE };
Constructor<?> constr = clazz.getConstructor(params);
return (JComponent)constr.newInstance(fileChooser, isXPStyle().booleanValue());
} catch (Exception exc) {
// reflection not succesfull, just log the exception and return null
Logger.getLogger(DirectoryChooserUI.class.getName()).log(
Level.FINE, "WindowsPlacesBar class can't be instantiated.", exc);
placesBarFailed = true;
return null;
}
}
/** Reflection alternative of
* sun.awt.shell.ShellFolder.getShellFolder(file)
*/
private File getShellFolderForFile (File file) {
try {
Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
return (File) clazz.getMethod("getShellFolder", File.class).invoke(null, file);
} catch (Exception exc) {
// reflection not succesfull, just log the exception and return null
Logger.getLogger(DirectoryChooserUI.class.getName()).log(
Level.FINE, "ShellFolder can't be used.", exc);
return null;
}
}
/** Reflection alternative of
* sun.awt.shell.ShellFolder.getShellFolder(dir).getLinkLocation()
*/
private File getShellFolderForFileLinkLoc (File file) {
try {
Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
Object sf = clazz.getMethod("getShellFolder", File.class).invoke(null, file);
return (File) clazz.getMethod("getLinkLocation").invoke(sf);
} catch (Exception exc) {
// reflection not succesfull, just log the exception and return null
Logger.getLogger(DirectoryChooserUI.class.getName()).log(
Level.FINE, "ShellFolder can't be used.", exc);
return null;
}
}
/** Reflection alternative of
* sun.awt.shell.ShellFolder.get("fileChooserComboBoxFolders")
*/
private File[] getShellFolderRoots () {
try {
Class<?> clazz = Class.forName("sun.awt.shell.ShellFolder");
return (File[]) clazz.getMethod("get", String.class).invoke(null, "fileChooserComboBoxFolders");
} catch (Exception exc) {
// reflection not succesfull, just log the exception and return null
Logger.getLogger(DirectoryChooserUI.class.getName()).log(
Level.FINE, "ShellFolder can't be used.", exc);
return null;
}
}
private void createBottomPanel(JFileChooser fc) {
bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(Box.createRigidArea(verticalStrut1));
JLabel fnl = new JLabel(fileNameLabelText);
fnl.setDisplayedMnemonic(fileNameLabelMnemonic);
fnl.setAlignmentY(0);
labelPanel.add(fnl);
labelPanel.add(Box.createRigidArea(new Dimension(1,12)));
JLabel ftl = new JLabel(filesOfTypeLabelText);
ftl.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
labelPanel.add(ftl);
bottomPanel.add(labelPanel);
bottomPanel.add(Box.createRigidArea(new Dimension(15, 0)));
JPanel fileAndFilterPanel = new JPanel();
fileAndFilterPanel.add(Box.createRigidArea(verticalStrut3));
fileAndFilterPanel.setLayout(new BoxLayout(fileAndFilterPanel, BoxLayout.Y_AXIS));
filenameTextField = new JTextField(24) {
@Override
public Dimension getMaximumSize() {
return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height);
}
};
filenameTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
updateCompletions();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateCompletions();
}
@Override
public void changedUpdate(DocumentEvent e) {}
});
filenameTextField.addKeyListener(new TextFieldKeyListener());
filenameTextField.addKeyListener(new AltUpHandler(filenameTextField));
fnl.setLabelFor(filenameTextField);
filenameTextField.addFocusListener(
new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (!getFileChooser().isMultiSelectionEnabled()) {
tree.clearSelection();
}
}
});
// disable TAB focus transfer, we need it for completion
Set<AWTKeyStroke> tKeys = filenameTextField.getFocusTraversalKeys(java.awt.KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
Set<AWTKeyStroke> newTKeys = new HashSet<AWTKeyStroke>(tKeys);
newTKeys.remove(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, 0));
// #107305: enable at least Ctrl+TAB if we have TAB for completion
newTKeys.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK));
filenameTextField.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newTKeys);
fileAndFilterPanel.add(filenameTextField);
fileAndFilterPanel.add(Box.createRigidArea(verticalStrut3));
filterTypeComboBoxModel = createFilterComboBoxModel();
fc.addPropertyChangeListener(filterTypeComboBoxModel);
filterTypeComboBox = new JComboBox(filterTypeComboBoxModel);
ftl.setLabelFor(filterTypeComboBox);
filterTypeComboBox.setRenderer(createFilterComboBoxRenderer());
fileAndFilterPanel.add(filterTypeComboBox);
bottomPanel.add(fileAndFilterPanel);
bottomPanel.add(Box.createRigidArea(horizontalStrut1));
createButtonsPanel(fc);
}
private void createButtonsPanel(JFileChooser fc) {
buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
approveButton = new JButton(getApproveButtonText(fc)) {
@Override
public Dimension getMaximumSize() {
return approveButton.getPreferredSize().width > cancelButton.getPreferredSize().width ?
approveButton.getPreferredSize() : cancelButton.getPreferredSize();
}
};
// #107791: No mnemonics desirable on Mac
if (!Utilities.isMac()) {
approveButton.setMnemonic(getApproveButtonMnemonic(fc));
}
approveButton.addActionListener(getApproveSelectionAction());
approveButton.setToolTipText(getApproveButtonToolTipText(fc));
buttonPanel.add(Box.createRigidArea(verticalStrut1));
buttonPanel.add(approveButton);
buttonPanel.add(Box.createRigidArea(verticalStrut2));
cancelButton = new JButton(cancelButtonText) {
@Override
public Dimension getMaximumSize() {
return approveButton.getPreferredSize().width > cancelButton.getPreferredSize().width ?
approveButton.getPreferredSize() : cancelButton.getPreferredSize();
}
};
// #107791: No mnemonics desirable on Mac
if (!Utilities.isMac()) {
cancelButton.setMnemonic(cancelButtonMnemonic);
}
cancelButton.setToolTipText(cancelButtonToolTipText);
cancelButton.addActionListener(getCancelSelectionAction());
buttonPanel.add(cancelButton);
}
private void createCenterPanel(final JFileChooser fc) {
centerPanel = new JPanel(new BorderLayout());
treeViewPanel = createTree();
treeViewPanel.setPreferredSize(TREE_PREF_SIZE);
JPanel treePanel = new JPanel();
treePanel.setLayout(new BorderLayout());
JComponent accessory = fc.getAccessory();
topToolbar = createTopToolbar();
topCombo = createTopCombo(fc);
topComboWrapper = new JPanel(new BorderLayout());
topComboWrapper.add(topCombo, BorderLayout.CENTER);
if (accessory == null) {
topComboWrapper.add(topToolbar, BorderLayout.EAST);
}
treePanel.add(topComboWrapper, BorderLayout.NORTH);
treePanel.add(treeViewPanel, BorderLayout.CENTER);
centerPanel.add(treePanel, BorderLayout.CENTER);
// control width of accessory panel, don't allow to jump (change width)
JPanel wrapAccessory = new JPanel() {
private Dimension prefSize = new Dimension(ACCESSORY_WIDTH, 0);
private Dimension minSize = new Dimension(ACCESSORY_WIDTH, 0);
@Override
public Dimension getMinimumSize () {
if (fc.getAccessory() != null) {
minSize.height = getAccessoryPanel().getMinimumSize().height;
return minSize;
}
return super.getMinimumSize();
}
@Override
public Dimension getPreferredSize () {
if (fc.getAccessory() != null) {
Dimension origPref = getAccessoryPanel().getPreferredSize();
LOG.fine("AccessoryWrapper.getPreferredSize: orig pref size: " + origPref);
prefSize.height = origPref.height;
prefSize.width = Math.max(prefSize.width, origPref.width);
int centerW = centerPanel.getWidth();
if (centerW != 0 && prefSize.width > centerW / 2) {
prefSize.width = centerW / 2;
}
LOG.fine("AccessoryWrapper.getPreferredSize: resulting pref size: " + prefSize);
return prefSize;
}
return super.getPreferredSize();
}
};
wrapAccessory.setLayout(new BorderLayout());
JPanel accessoryPanel = getAccessoryPanel();
if (accessory != null) {
accessoryPanel.add(topToolbar, BorderLayout.NORTH);
accessoryPanel.add(accessory, BorderLayout.CENTER);
}
wrapAccessory.add(accessoryPanel, BorderLayout.CENTER);
centerPanel.add(wrapAccessory, BorderLayout.EAST);
createBottomPanel(fc);
centerPanel.add(bottomPanel, BorderLayout.SOUTH);
}
private JComponent createTopCombo(JFileChooser fc) {
JPanel panel = new JPanel();
if (fc.getAccessory() != null) {
panel.setBorder(BorderFactory.createEmptyBorder(4, 0, 8, 0));
} else {
panel.setBorder(BorderFactory.createEmptyBorder(6, 0, 10, 0));
}
panel.setLayout(new BorderLayout());
Box labelBox = Box.createHorizontalBox();
lookInComboBoxLabel = new JLabel(lookInLabelText);
lookInComboBoxLabel.setDisplayedMnemonic(lookInLabelMnemonic);
lookInComboBoxLabel.setAlignmentX(JComponent.LEFT_ALIGNMENT);
lookInComboBoxLabel.setAlignmentY(JComponent.CENTER_ALIGNMENT);
labelBox.add(lookInComboBoxLabel);
labelBox.add(Box.createRigidArea(new Dimension(9,0)));
// fixed #97525, made the height of the
// combo box bigger.
directoryComboBox = new JComboBox() {
@Override
public Dimension getMinimumSize() {
Dimension d = super.getMinimumSize();
d.width = 60;
return d;
}
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
// Must be small enough to not affect total width and height.
d.height = 24;
d.width = 150;
return d;
}
};
directoryComboBox.putClientProperty( "JComboBox.lightweightKeyboardNavigation", "Lightweight" );
lookInComboBoxLabel.setLabelFor(directoryComboBox);
directoryComboBoxModel = createDirectoryComboBoxModel(fc);
directoryComboBox.setModel(directoryComboBoxModel);
directoryComboBox.addActionListener(directoryComboBoxAction);
directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
directoryComboBox.setAlignmentY(JComponent.CENTER_ALIGNMENT);
directoryComboBox.setMaximumRowCount(8);
panel.add(labelBox, BorderLayout.WEST);
panel.add(directoryComboBox, BorderLayout.CENTER);
return panel;
}
private JComponent createTopToolbar() {
JToolBar topPanel = new JToolBar();
topPanel.setFloatable(false);
if (Utilities.isWindows()) {
topPanel.putClientProperty("JToolBar.isRollover", Boolean.TRUE);
}
upFolderButton = new JButton(getChangeToParentDirectoryAction());
upFolderButton.setText(null);
// fixed bug #97049
final boolean isMac = Utilities.isMac();
Icon upOneLevelIcon = null;
if (!isMac) {
upOneLevelIcon = UIManager.getIcon("FileChooser.upFolderIcon");
}
// on Mac all icons from UIManager are the same, some default, so load our own.
// it's also fallback if icon from UIManager not found, may happen
if (isMac || upOneLevelIcon == null || jdkBug6840086Workaround() ) {
if (isMac) {
upOneLevelIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/upFolderIcon_mac.png", false);
} else {
upOneLevelIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/upFolderIcon.gif", false);
}
}
upFolderButton.setIcon(upOneLevelIcon);
upFolderButton.setToolTipText(upFolderToolTipText);
upFolderButton.getAccessibleContext().setAccessibleName(upFolderAccessibleName);
upFolderButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
upFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
if(useShellFolder) {
upFolderButton.setFocusPainted(false);
}
topPanel.add(upFolderButton);
topPanel.add(Box.createRigidArea(new Dimension(2, 0)));
// no home on Win platform
if (!Utilities.isWindows()) {
JButton homeButton = new JButton(getGoHomeAction());
Icon homeIcon = null;
if (!isMac) {
homeIcon = UIManager.getIcon("FileChooser.homeFolderIcon");
}
if (isMac || homeIcon == null) {
if (isMac) {
homeIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/homeIcon_mac.png", false);
} else {
homeIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/homeIcon.gif", false);
}
}
homeButton.setIcon(homeIcon);
homeButton.setText(null);
String tooltip = homeButton.getToolTipText();
if (tooltip == null) {
tooltip = homeFolderTooltipText;
if (tooltip == null) {
tooltip = NbBundle.getMessage(DirectoryChooserUI.class,
"TLTP_HomeFolder");
}
homeButton.setToolTipText( tooltip );
}
if( null != homeFolderAccessibleName )
homeButton.getAccessibleContext().setAccessibleName(homeFolderAccessibleName);
topPanel.add(homeButton);
}
newFolderButton = new JButton(newFolderAction);
newFolderButton.setText(null);
// fixed bug #97049
Icon newFoldIcon = null;
if (!isMac) {
newFoldIcon = UIManager.getIcon("FileChooser.newFolderIcon");
}
// on Mac all icons from UIManager are the same, some default, so load our own.
// it's also fallback if icon from UIManager not found, may happen
if (isMac || newFoldIcon == null || jdkBug6840086Workaround()) {
if (isMac) {
newFoldIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/newFolderIcon_mac.png", false);
} else {
newFoldIcon = ImageUtilities.loadImageIcon("org/netbeans/swing/dirchooser/resources/newFolderIcon.gif", false);
}
}
newFolderButton.setIcon(newFoldIcon);
newFolderButton.setToolTipText(newFolderToolTipText);
newFolderButton.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
newFolderButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
newFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
if(useShellFolder) {
newFolderButton.setFocusPainted(false);
}
topPanel.add(newFolderButton);
topPanel.add(Box.createRigidArea(new Dimension(2, 0)));
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(4, 9, 8, 0));
panel.add(topPanel, BorderLayout.CENTER);
return panel;
}
private JComponent createTree() {
final DirectoryHandler dirHandler = createDirectoryHandler(fileChooser);
// #106011: don't show "colors, food, sports" sample model after init :-)
tree = new JTree(new Object[0]) {
@Override
protected void processMouseEvent(MouseEvent e) {
dirHandler.preprocessMouseEvent(e);
super.processMouseEvent(e);
}
// For speed (#127170):
@Override
public boolean isLargeModel() {
return true;
}
// #169303: on GTK or Nimbus L&F the row height can be initially set
// to 0 which disables the "large model" optimizations (#127170)
@Override
public void setRowHeight(int rowHeight) {
if (rowHeight > 0) {
super.setRowHeight(rowHeight);
}
}
// To work with different font sizes (#106223); see: http://www.javalobby.org/java/forums/t19562.html
private boolean firstPaint = true;
@Override
public void setFont(Font f) {
firstPaint = true;
super.setFont(f);
}
@Override
public void paint(Graphics g) {
if (firstPaint) {
g.setFont(getFont());
setRowHeight(Math.max(/* icon height plus insets? */17, g.getFontMetrics().getHeight()));
firstPaint = false;
// Setting the fixed height will generate another paint request, no need to complete this one
return;
}
super.paint(g);
}
};
tree.setFocusable(true);
tree.setOpaque(true);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.setToggleClickCount(0);
tree.addTreeExpansionListener(new TreeExpansionHandler());
TreeKeyHandler keyHandler = new TreeKeyHandler();
tree.addKeyListener(keyHandler);
tree.addKeyListener(new AltUpHandler(tree));
tree.addFocusListener(keyHandler);
tree.addMouseListener(dirHandler);
tree.addFocusListener(dirHandler);
tree.addTreeSelectionListener(dirHandler);
if(fileChooser.isMultiSelectionEnabled()) {
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
} else {
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
TreeCellEditor tce = new DirectoryCellEditor(tree, fileChooser, new JTextField());
tree.setCellEditor(tce);
tce.addCellEditorListener(dirHandler);
tree.setCellRenderer(new DirectoryTreeRenderer());
JScrollPane scrollBar = new JScrollPane(tree);
scrollBar.setViewportView(tree);
tree.setInvokesStopCellEditing(true);
return scrollBar;
}
private boolean jdkBug6840086Workaround() {
//see issue #167080
return Utilities.isWindows()
&& "Windows 7".equals(System.getProperty("os.name"))
&& "1.6.0_16".compareTo(System.getProperty("java.version")) >=0;
}
/**
* Handles alt-up key
*/
private class AltUpHandler extends KeyAdapter {
private final JComponent component;
public AltUpHandler(JComponent component) {
this.component = component;
}
@Override
public void keyPressed(KeyEvent evt) {
if(evt.getKeyCode() == KeyEvent.VK_UP && (evt.getModifiers() & KeyEvent.ALT_MASK) == KeyEvent.ALT_MASK) {
Action action = getChangeToParentDirectoryAction();
action.actionPerformed(new ActionEvent(evt.getSource(), 0, ""));
component.requestFocus();
}
}
}
/**
* Handles keyboard quick search in tree and delete action.
*/
class TreeKeyHandler extends KeyAdapter implements FocusListener {
StringBuffer searchBuf = new StringBuffer();
java.util.List<TreePath> paths;
private final Timer resetBufferTimer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed (ActionEvent e) {
resetBuffer();
}
});
@Override
public void keyPressed(KeyEvent evt) {
if(evt.getKeyCode() == KeyEvent.VK_DELETE) {
deleteAction();
}
// F2 as rename shortcut
if (evt.getKeyCode() == KeyEvent.VK_F2) {
DirectoryNode node = (DirectoryNode)tree.getLastSelectedPathComponent();
if (node != null) {
applyEdit(node);
}
}
if (isCharForSearch(evt)) {
evt.consume();
} else {
resetBuffer();
}
// #105527: keyboard invocation of tree's popup menu
if ((evt.getKeyCode() == KeyEvent.VK_F10) && evt.isShiftDown() && !popupMenu.isShowing()) {
JTree tree = (JTree) evt.getSource();
int selRow = tree.getLeadSelectionRow();
if (selRow >= 0) {
Rectangle bounds = tree.getRowBounds(selRow);
popupMenu.show(tree, bounds.x + bounds.width / 2, bounds.y + bounds.height * 3 / 5);
evt.consume();
}
}
}
@Override
public void keyTyped(KeyEvent evt) {
char keyChar = evt.getKeyChar();
if (isCharForSearch(evt)) {
if (paths == null) {
paths = getVisiblePaths();
}
searchBuf.append(keyChar);
resetBufferTimer.restart();
TreePath activePath = tree.getSelectionPath();
for (int i = 0; i < 2; ++i) {
String searchedText = searchBuf.toString().toLowerCase();
String curFileName = null;
if (i == 0 && activePath != null && (curFileName = fileChooser.getName(((DirectoryNode) activePath.getLastPathComponent()).getFile())) != null
&& curFileName.toLowerCase().startsWith(searchedText)) {
// keep selection
return;
}
for (TreePath path : paths) {
curFileName = fileChooser.getName(((DirectoryNode) path.getLastPathComponent()).getFile());
if (curFileName != null && curFileName.toLowerCase().startsWith(searchedText)) {
tree.makeVisible(path);
tree.scrollPathToVisible(path);
tree.setSelectionPath(path);
return;
}
}
searchBuf.delete(0, searchBuf.length() - 1);
}
} else {
resetBuffer();
}
}
@Override
public void focusGained(FocusEvent e) {
resetBuffer();
}
@Override
public void focusLost(FocusEvent e) {
resetBuffer();
}
private boolean isCharForSearch (KeyEvent evt) {
char ch = evt.getKeyChar();
// refuse backspace key
if ((int)ch == 8) {
return false;
}
// #110975: refuse modifiers
if (evt.getModifiers() != 0) {
return false;
}
return (Character.isJavaIdentifierPart(ch) && !Character.isIdentifierIgnorable(ch))
|| Character.isSpaceChar(ch);
}
private void resetBuffer () {
searchBuf.delete(0, searchBuf.length());
paths = null;
}
}
private java.util.List<TreePath> getVisiblePaths () {
int rowCount = tree.getRowCount();
DirectoryNode node = null;
java.util.List<TreePath> result = new ArrayList<TreePath>(rowCount);
for (int i = 0; i < rowCount; i++) {
result.add(tree.getPathForRow(i));
}
return result;
}
private void createPopup() {
popupMenu = new JPopupMenu();
JMenuItem item1 = new JMenuItem(getBundle().getString("LBL_NewFolder"));
item1.addActionListener(newFolderAction);
JMenuItem item2 = new JMenuItem(getBundle().getString("LBL_Rename"));
item2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DirectoryNode node = (DirectoryNode)tree.getLastSelectedPathComponent();
applyEdit(node);
}
});
JMenuItem item3 = new JMenuItem(getBundle().getString("LBL_Delete"));
item3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
deleteAction();
}
});
popupMenu.add(item1);
popupMenu.add(item2);
popupMenu.add(item3);
}
// remove multiple directories
private void deleteAction() {
// fixed #97079 to be able to delete one or more folders
final TreePath[] nodePath = tree.getSelectionPaths();
if(nodePath == null) {
return;
}
String message = "";
if(nodePath.length == 1) {
File file = ((DirectoryNode)nodePath[0].getLastPathComponent()).getFile();
// Don't do anything if it's a special file
if(!canWrite(file)) {
return;
}
message = MessageFormat.format(getBundle().getString("MSG_Delete"), file.getName());
} else {
message = MessageFormat.format(getBundle().getString("MSG_Delete_Multiple"), nodePath.length);
}
int answer = JOptionPane.showConfirmDialog(fileChooser, message , getBundle().getString("MSG_Confirm"), JOptionPane.YES_NO_OPTION);
if (answer == JOptionPane.YES_OPTION) {
RequestProcessor.getDefault().post(new Runnable() {
DirectoryNode node;
ArrayList<File> list = new ArrayList<File>();
int cannotDelete;
ArrayList<DirectoryNode> nodes2Remove = new ArrayList<DirectoryNode>(nodePath.length);
@Override
public void run() {
if (!EventQueue.isDispatchThread()) {
// first pass, out of EQ thread, deletes files
setCursor(fileChooser, Cursor.WAIT_CURSOR);
cannotDelete = 0;
for(int i = 0; i < nodePath.length; i++) {
DirectoryNode nodeToDelete = (DirectoryNode)nodePath[i].getLastPathComponent();
try {
delete(nodeToDelete.getFile());
nodes2Remove.add(nodeToDelete);
} catch (IOException ignore) {
cannotDelete++;
if(canWrite(nodeToDelete.getFile())) {
list.add(nodeToDelete.getFile());
}
}
}
// send to second pass
EventQueue.invokeLater(this);
} else {
// second pass, in EQ thread
for (DirectoryNode curNode : nodes2Remove) {
model.removeNodeFromParent(curNode);
}
setCursor(fileChooser, Cursor.DEFAULT_CURSOR);
if(cannotDelete > 0) {
String message = "";
if(cannotDelete == 1) {
message = cannotDelete + " " + getBundle().getString("MSG_Sing_Delete");
} else {
message = cannotDelete + " " + getBundle().getString("MSG_Plur_Delete");
}
setSelected((File[])list.toArray(new File[list.size()]));
JOptionPane.showConfirmDialog(fileChooser, message , getBundle().getString("MSG_Confirm"), JOptionPane.OK_OPTION);
} else {
setSelected(new File[] {null});
setFileName("");
}
}
}
});
}
}
private void delete(File file) throws IOException {
FileObject fo = FileUtil.toFileObject(file);
// Fixing #207116 - NPE when deleting a directory in remote browser.
// This might be a pseudo-file based on another FileSystem' object (for example, remote file);
// FileUtil.toFileObject will return correspondent *local* file if local file with such path exists.
// That's why apart from null check, we check file.equals(FileUtil.toFile(fo),
// which means that file object really corresponds to the given file.
if (fo != null && file.equals(FileUtil.toFile(fo))) {
fo.delete();
} else {
if (!file.delete()) {
throw new IOException();
}
}
}
private File lastDir;
private File[] lastChildren;
private void updateCompletions() {
if (showPopupCompletion) {
final String name = normalizeFile(getFileName());
int slash = name.lastIndexOf(File.separatorChar);
if (slash != -1) {
String prefix = name.substring(0, slash + 1);
File dir = getFileChooser().getFileSystemView().createFileObject(prefix);
File[] children;
synchronized (listFilesWorker) {
if (!dir.equals(lastDir)) {
if (completionPopup != null) {
completionPopup.setDataList(new Vector<File>(0));
completionPopup.detach();
completionPopup = null;
}
listFilesWorker.d = dir;
listFilesTask.schedule(0);
return;
} else {
children = lastChildren;
}
}
if (children != null) {
Vector<File> list = buildList(name, children, 20);
if(completionPopup == null) {
completionPopup = new FileCompletionPopup(fileChooser, filenameTextField, list);
} else if (completionPopup.isShowing() ||
(showPopupCompletion && fileChooser.isShowing())) {
completionPopup.setDataList(list);
}
if (fileChooser.isShowing() && !completionPopup.isShowing()) {
java.awt.Point los = filenameTextField.getLocation();
int popX = los.x;
int popY = los.y + filenameTextField.getHeight() - 6;
completionPopup.showPopup(filenameTextField, popX, popY);
}
}
}
}
}
private class ListFilesWorker implements Runnable {
private File d;
@Override
public void run() {
File dir;
synchronized (this) {
dir = d;
}
List<File> files = new LinkedList<File>();
File[] children = dir.listFiles();
if (children != null) {
for (File f : children) {
if(fileChooser.accept(f)) {
if(fileChooser.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) {
if(f.isDirectory()) {
files.add(f);
}
} else if(fileChooser.getFileSelectionMode() == JFileChooser.FILES_ONLY) {
if(f.isFile()) {
files.add(f);
}
} else if(fileChooser.getFileSelectionMode() == JFileChooser.FILES_AND_DIRECTORIES) {
files.add(f);
}
}
}
}
synchronized (this) {
lastDir = dir;
lastChildren = files.toArray(new File[files.size()]);
}
if (lastChildren.length > 0) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
updateCompletions();
}
});
}
}
}
public Vector<File> buildList(String text, File[] children, int max) {
Vector<File> files = new Vector<File>(children.length);
Arrays.sort(children, DirectoryNode.FILE_NAME_COMPARATOR);
for (File completion : children) {
String path = completion.getAbsolutePath();
if (path.regionMatches(true, 0, text, 0, text.length())) {
files.add(completion);
}
if (files.size() >= max) {
break;
}
}
return files;
}
private static String normalizeFile(String text) {
// See #21690 for background.
// XXX what are legal chars for var names? bash manual says only:
// "The braces are required when PARAMETER [...] is followed by a
// character that is not to be interpreted as part of its name."
Pattern p = Pattern.compile("(^|[^\\\\])\\$([a-zA-Z_0-9.]+)");
Matcher m;
while ((m = p.matcher(text)).find()) {
// Have an env var to subst...
// XXX handle ${PATH} too? or don't bother
String var = System.getenv(m.group(2));
if (var == null) {
// Try Java system props too, and fall back to "".
var = System.getProperty(m.group(2), "");
}
// XXX full readline compat would mean vars were also completed with TAB...
text = text.substring(0, m.end(1)) + var + text.substring(m.end(2));
}
if (text.equals("~")) {
return System.getProperty("user.home");
} else if (text.startsWith("~" + File.separatorChar)) {
return System.getProperty("user.home") + text.substring(1);
} else {
int i = text.lastIndexOf("//");
if (i != -1) {
// Treat /home/me//usr/local as /usr/local
// (so that you can use "//" to start a new path, without selecting & deleting)
return text.substring(i + 1);
}
i = text.lastIndexOf(File.separatorChar + "~" + File.separatorChar);
if (i != -1) {
// Treat /usr/local/~/stuff as /home/me/stuff
return System.getProperty("user.home") + text.substring(i + 2);
}
return text;
}
}
private static ResourceBundle getBundle() {
return NbBundle.getBundle(DirectoryChooserUI.class);
}
@Override
public void rescanCurrentDirectory(JFileChooser fc) {
super.rescanCurrentDirectory(fc);
File curDir = fc.getCurrentDirectory();
if (curDir == null) {
curDir = fc.getFileSystemView().getRoots()[0];
}
updateWorker.updateTree(curDir);
}
private void initUpdateWorker () {
updateWorker.attachFileChooser(this);
}
private void markStartTime () {
fileChooser.putClientProperty(DelegatingChooserUI.START_TIME, Long.valueOf(System.currentTimeMillis()));
}
private void checkUpdate() {
if (Utilities.isWindows() && useShellFolder) {
Long startTime = (Long) fileChooser.getClientProperty(DelegatingChooserUI.START_TIME);
if (startTime == null) {
return;
}
// clean for future marking
fileChooser.putClientProperty(DelegatingChooserUI.START_TIME, null);
long elapsed = System.currentTimeMillis() - startTime.longValue();
long timeOut = NbPreferences.forModule(DirectoryChooserUI.class).
getLong(TIMEOUT_KEY, 10000);
if (timeOut > 0 && elapsed > timeOut && slownessPanel == null) {
JLabel slownessNote = new JLabel(
NbBundle.getMessage(DirectoryChooserUI.class, "MSG_SlownessNote"));
slownessNote.setForeground(Color.RED);
slownessPanel = new JPanel();
JButton notShow = new JButton(
NbBundle.getMessage(DirectoryChooserUI.class, "BTN_NotShow"));
notShow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NbPreferences.forModule(DirectoryChooserUI.class).putLong(TIMEOUT_KEY, 0);
centerPanel.remove(slownessPanel);
centerPanel.revalidate();
}
});
JPanel notShowP = new JPanel();
notShowP.add(notShow);
slownessPanel.setLayout(new BorderLayout());
slownessPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
slownessPanel.add(BorderLayout.CENTER, slownessNote);
slownessPanel.add(BorderLayout.SOUTH, notShowP);
centerPanel.add(BorderLayout.NORTH, slownessPanel);
centerPanel.revalidate();
}
}
}
private Boolean isXPStyle() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Boolean themeActive = (Boolean)toolkit.getDesktopProperty("win.xpstyle.themeActive");
if(themeActive == null)
themeActive = Boolean.FALSE;
if (themeActive.booleanValue() && System.getProperty("swing.noxp") == null) {
themeActive = Boolean.TRUE;
}
return themeActive;
}
@Override
protected void installStrings(JFileChooser fc) {
super.installStrings(fc);
Locale l = fc.getLocale();
lookInLabelMnemonic = UIManager.getInt("FileChooser.lookInLabelMnemonic");
lookInLabelText = UIManager.getString("FileChooser.lookInLabelText",l);
saveInLabelText = UIManager.getString("FileChooser.saveInLabelText",l);
fileNameLabelMnemonic = UIManager.getInt("FileChooser.fileNameLabelMnemonic");
fileNameLabelText = UIManager.getString("FileChooser.fileNameLabelText",l);
filesOfTypeLabelMnemonic = UIManager.getInt("FileChooser.filesOfTypeLabelMnemonic");
filesOfTypeLabelText = UIManager.getString("FileChooser.filesOfTypeLabelText",l);
upFolderToolTipText = UIManager.getString("FileChooser.upFolderToolTipText",l);
if( null == upFolderToolTipText )
upFolderToolTipText = NbBundle.getMessage(DirectoryChooserUI.class, "TLTP_UpFolder"); //NOI18N
upFolderAccessibleName = UIManager.getString("FileChooser.upFolderAccessibleName",l);
if( null == upFolderAccessibleName )
upFolderAccessibleName = NbBundle.getMessage(DirectoryChooserUI.class, "ACN_UpFolder"); //NOI18N
newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText",l);
if( null == newFolderToolTipText )
newFolderToolTipText = NbBundle.getMessage(DirectoryChooserUI.class, "TLTP_NewFolder"); //NOI18N
newFolderAccessibleName = NbBundle.getMessage(DirectoryChooserUI.class, "ACN_NewFolder"); //NOI18N
homeFolderTooltipText = UIManager.getString("FileChooser.homeFolderToolTipText",l);
if( null == homeFolderTooltipText )
homeFolderTooltipText = NbBundle.getMessage(DirectoryChooserUI.class, "TLTP_HomeFolder"); //NOI18N
homeFolderAccessibleName = NbBundle.getMessage(DirectoryChooserUI.class, "ACN_HomeFolder"); //NOI18N
}
@Override
protected void installListeners(JFileChooser fc) {
super.installListeners(fc);
ActionMap actionMap = getActionMap();
SwingUtilities.replaceUIActionMap(fc, actionMap);
}
protected ActionMap getActionMap() {
return createActionMap();
}
protected ActionMap createActionMap() {
AbstractAction escAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
getFileChooser().cancelSelection();
}
@Override
public boolean isEnabled(){
return getFileChooser().isEnabled();
}
};
ActionMap map = new ActionMapUIResource();
map.put("approveSelection", getApproveSelectionAction());
map.put("cancelSelection", escAction);
map.put("Go Up", getChangeToParentDirectoryAction());
return map;
}
@Override
public Action getNewFolderAction() {
return newFolderAction;
}
@Override
public void uninstallUI(JComponent c) {
c.removePropertyChangeListener(filterTypeComboBoxModel);
cancelButton.removeActionListener(getCancelSelectionAction());
approveButton.removeActionListener(getApproveSelectionAction());
filenameTextField.removeActionListener(getApproveSelectionAction());
super.uninstallUI(c);
}
/**
* Returns the preferred size of the specified
* <code>JFileChooser</code>.
* The preferred size is at least as large,
* in both height and width,
* as the preferred size recommended
* by the file chooser's layout manager.
*
* @param c a <code>JFileChooser</code>
* @return a <code>Dimension</code> specifying the preferred
* width and height of the file chooser
*/
@Override
public Dimension getPreferredSize(JComponent c) {
int prefWidth = PREF_SIZE.width;
Dimension d = c.getLayout().preferredLayoutSize(c);
if (d != null) {
return new Dimension(d.width < prefWidth ? prefWidth : d.width,
d.height < PREF_SIZE.height ? PREF_SIZE.height : d.height);
} else {
return new Dimension(prefWidth, PREF_SIZE.height);
}
}
/**
* Returns the minimum size of the <code>JFileChooser</code>.
*
* @param c a <code>JFileChooser</code>
* @return a <code>Dimension</code> specifying the minimum
* width and height of the file chooser
*/
@Override
public Dimension getMinimumSize(JComponent c) {
return MIN_SIZE;
}
/**
* Returns the maximum size of the <code>JFileChooser</code>.
*
* @param c a <code>JFileChooser</code>
* @return a <code>Dimension</code> specifying the maximum
* width and height of the file chooser
*/
@Override
public Dimension getMaximumSize(JComponent c) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
private String getStringOfFileName(File file) {
if (file == null) {
return null;
} else {
JFileChooser fc = getFileChooser();
if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
if(fc.getFileSystemView().isDrive(file)) {
return file.getPath();
} else {
return file.getPath();
}
} else {
return file.getName();
}
}
}
private String getStringOfFileNames(File[] files) {
StringBuilder buf = new StringBuilder();
for (int i = 0; files != null && i < files.length; i++) {
if (i > 0) {
buf.append(" ");
}
if (files.length > 1) {
buf.append("\"");
}
buf.append(getStringOfFileName(files[i]));
if (files.length > 1) {
buf.append("\"");
}
}
return buf.toString();
}
/* The following methods are used by the PropertyChange Listener */
private void fireSelectedFileChanged(PropertyChangeEvent e) {
File f = (File) e.getNewValue();
JFileChooser fc = getFileChooser();
if (f != null
&& ((fc.isFileSelectionEnabled() && !f.isDirectory())
|| (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
setFileName(getStringOfFileName(f));
}
}
private void fireSelectedFilesChanged(PropertyChangeEvent e) {
File[] files = (File[]) e.getNewValue();
JFileChooser fc = getFileChooser();
if (files != null
&& files.length > 0
&& (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
setFileName(getStringOfFileNames(files));
}
}
private void fireDirectoryChanged(PropertyChangeEvent e) {
JFileChooser fc = getFileChooser();
FileSystemView fsv = fc.getFileSystemView();
showPopupCompletion = false;
setFileName("");
clearIconCache();
File currentDirectory = fc.getCurrentDirectory();
if(currentDirectory != null) {
directoryComboBoxModel.addItem(currentDirectory);
newFolderAction.enable(currentDirectory);
getChangeToParentDirectoryAction().setEnabled(!fsv.isRoot(currentDirectory));
updateWorker.updateTree(currentDirectory);
if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
if (fsv.isFileSystem(currentDirectory)) {
setFileName(getStringOfFileName(currentDirectory));
} else {
setFileName(null);
}
}
}
}
private void fireFilterChanged(PropertyChangeEvent e) {
clearIconCache();
}
private void fireFileSelectionModeChanged(PropertyChangeEvent e) {
clearIconCache();
JFileChooser fc = getFileChooser();
File currentDirectory = fc.getCurrentDirectory();
if (currentDirectory != null
&& fc.isDirectorySelectionEnabled()
&& !fc.isFileSelectionEnabled()
&& fc.getFileSystemView().isFileSystem(currentDirectory)) {
setFileName(currentDirectory.getPath());
} else {
setFileName(null);
}
}
private void fireMultiSelectionChanged(PropertyChangeEvent e) {
if (getFileChooser().isMultiSelectionEnabled()) {
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
} else {
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
getFileChooser().setSelectedFiles(null);
}
}
private void fireAccessoryChanged(PropertyChangeEvent e) {
if(getAccessoryPanel() != null) {
JComponent oldAcc = (JComponent) e.getOldValue();
JComponent newAcc = (JComponent) e.getNewValue();
JComponent accessoryPanel = getAccessoryPanel();
if(oldAcc != null) {
accessoryPanel.remove(oldAcc);
}
if (oldAcc != null && newAcc == null) {
accessoryPanel.remove(topToolbar);
topComboWrapper.add(topToolbar, BorderLayout.EAST);
topCombo.setBorder(BorderFactory.createEmptyBorder(6, 0, 10, 0));
topCombo.revalidate();
}
if(newAcc != null) {
getAccessoryPanel().add(newAcc, BorderLayout.CENTER);
}
if (oldAcc == null && newAcc != null) {
topComboWrapper.remove(topToolbar);
topCombo.setBorder(BorderFactory.createEmptyBorder(4, 0, 8, 0));
accessoryPanel.add(topToolbar, BorderLayout.NORTH);
}
}
}
private void fireApproveButtonTextChanged(PropertyChangeEvent e) {
JFileChooser chooser = getFileChooser();
approveButton.setText(getApproveButtonText(chooser));
approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
// #107791: No mnemonics desirable on Mac
if (!Utilities.isMac()) {
approveButton.setMnemonic(getApproveButtonMnemonic(chooser));
}
}
private void fireDialogTypeChanged(PropertyChangeEvent e) {
JFileChooser chooser = getFileChooser();
approveButton.setText(getApproveButtonText(chooser));
approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
// #107791: No mnemonics desirable on Mac
if (!Utilities.isMac()) {
approveButton.setMnemonic(getApproveButtonMnemonic(chooser));
}
if (chooser.getDialogType() == JFileChooser.SAVE_DIALOG) {
lookInComboBoxLabel.setText(saveInLabelText);
} else {
lookInComboBoxLabel.setText(lookInLabelText);
}
}
private void fireApproveButtonMnemonicChanged(PropertyChangeEvent e) {
// #107791: No mnemonics desirable on Mac
if (!Utilities.isMac()) {
approveButton.setMnemonic(getApproveButtonMnemonic(getFileChooser()));
}
}
private void fireControlButtonsChanged(PropertyChangeEvent e) {
if(getFileChooser().getControlButtonsAreShown()) {
addControlButtons();
} else {
removeControlButtons();
}
}
/*
* Listen for filechooser property changes, such as
* the selected file changing, or the type of the dialog changing.
*/
@Override
public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) {
return new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
String s = e.getPropertyName();
if(s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
fireSelectedFileChanged(e);
} else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
fireSelectedFilesChanged(e);
} else if(s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY) && changeDirectory) {
fireDirectoryChanged(e);
} else if(s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
fireFilterChanged(e);
} else if(s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
fireFileSelectionModeChanged(e);
} else if(s.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
fireMultiSelectionChanged(e);
} else if(s.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY)) {
fireAccessoryChanged(e);
} else if (s.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) ||
s.equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY)) {
fireApproveButtonTextChanged(e);
} else if(s.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)) {
fireDialogTypeChanged(e);
} else if(s.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
fireApproveButtonMnemonicChanged(e);
} else if(s.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
fireControlButtonsChanged(e);
} else if (s.equals(DelegatingChooserUI.USE_SHELL_FOLDER)) {
updateUseShellFolder();
fireDirectoryChanged(e);
} else if (s.equals("componentOrientation")) {
ComponentOrientation o = (ComponentOrientation)e.getNewValue();
JFileChooser cc = (JFileChooser)e.getSource();
if (o != (ComponentOrientation)e.getOldValue()) {
cc.applyComponentOrientation(o);
}
} else if (s.equals("ancestor")) {
if (e.getOldValue() == null && e.getNewValue() != null) {
filenameTextField.selectAll();
filenameTextField.requestFocus();
}
}
}
};
}
protected void removeControlButtons() {
bottomPanel.remove(buttonPanel);
}
protected void addControlButtons() {
bottomPanel.add(buttonPanel);
}
@Override
public String getFileName() {
if(filenameTextField != null) {
return filenameTextField.getText();
} else {
return null;
}
}
@Override
public void setFileName(String filename) {
if(filenameTextField != null) {
filenameTextField.setText(filename);
}
}
private DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(JFileChooser fc) {
return new DirectoryComboBoxRenderer();
}
private DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) {
return new DirectoryComboBoxModel();
}
private FilterComboBoxRenderer createFilterComboBoxRenderer() {
return new FilterComboBoxRenderer();
}
protected FilterTypeComboBoxModel createFilterComboBoxModel() {
return new FilterTypeComboBoxModel();
}
@Override
protected JButton getApproveButton(JFileChooser fc) {
return approveButton;
}
@Override
public FileView getFileView(JFileChooser fc) {
// fix bug #96957, should use DirectoryChooserFileView
// only on windows
if (Utilities.isWindows()) {
if (useShellFolder) {
fileView = new DirectoryChooserFileView();
}
} else {
fileView = (BasicFileView) super.getFileView(fileChooser);
}
return fileView;
}
private void setSelected(File[] files) {
changeDirectory = false;
fileChooser.setSelectedFiles(files);
changeDirectory = true;
}
private DirectoryHandler createDirectoryHandler(JFileChooser chooser) {
return new DirectoryHandler(chooser);
}
private void addNewDirectory(final TreePath path) {
RequestProcessor.getDefault().post(new Runnable() {
@Override
public void run() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
DirectoryNode selectedNode = (DirectoryNode)path.getLastPathComponent();
if(selectedNode == null || !canWrite(selectedNode.getFile())) {
return;
}
try {
newFolderNode = new DirectoryNode(fileChooser.getFileSystemView().createNewFolder(selectedNode.getFile()));
model.insertNodeInto(newFolderNode, selectedNode, selectedNode.getChildCount());
EventQueue.invokeLater(new Runnable() {
@Override
public void run () {
applyEdit(newFolderNode);
}
});
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
});
}
});
}
private void applyEdit(DirectoryNode node) {
TreeNode[] nodes = model.getPathToRoot(node);
TreePath editingPath = new TreePath(nodes);
tree.setEditable(true);
tree.makeVisible(editingPath);
tree.scrollPathToVisible(editingPath);
tree.setSelectionPath(editingPath);
tree.startEditingAtPath(editingPath);
JTextField editField = DirectoryCellEditor.getTextField();
editField.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
editField.setRequestFocusEnabled(true);
editField.requestFocus();
editField.setSelectionStart(0);
editField.setSelectionEnd(editField.getText().length());
}
private static boolean canWrite(File f) {
boolean writeable = false;
if (f != null) {
try {
writeable = f.canWrite();
} catch (AccessControlException ex) {
writeable = false;
}
}
return writeable;
}
private void expandNode(final JFileChooser fileChooser, final TreePath path) {
RequestProcessor.getDefault().post(new Runnable() {
DirectoryNode node;
@Override
public void run() {
if (!EventQueue.isDispatchThread()) {
// first pass, out of EQ thread, loads data
markStartTime();
setCursor(fileChooser, Cursor.WAIT_CURSOR);
node = (DirectoryNode) path.getLastPathComponent();
node.loadChildren(fileChooser, true);
// send to second pass
EventQueue.invokeLater(this);
} else {
// second pass, in EQ thread
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
/*
* This happens when the add new directory action is called
* and the node is not loaded. Furthermore, it ensures that
* adding a new directory execute after the UI has finished
* displaying the children of the expanded node.
*/
if(addNewDirectory) {
addNewDirectory(path);
addNewDirectory = false;
}
setCursor(fileChooser, Cursor.DEFAULT_CURSOR);
checkUpdate();
}
}
});
}
private void setCursor (final JComponent comp, final int type) {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(new Runnable () {
@Override
public void run () {
setCursor(comp, type);
}
});
} else {
Window window = SwingUtilities.getWindowAncestor(comp);
if (window != null) {
Cursor cursor = Cursor.getPredefinedCursor(type);
window.setCursor(cursor);
window.setFocusable(true);
}
JRootPane pane = fileChooser.getRootPane();
if( null == blocker )
blocker = new InputBlocker();
if(type == Cursor.WAIT_CURSOR) {
blocker.block(pane);
} else if (type == Cursor.DEFAULT_CURSOR){
blocker.unBlock(pane);
}
}
}
/*************** HELPER CLASSES ***************/
private class IconIndenter implements Icon {
static final int space = 10;
Icon icon = null;
int depth = 0;
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (icon == null) {
return;
}
if (c.getComponentOrientation().isLeftToRight()) {
icon.paintIcon(c, g, x+depth*space, y);
} else {
icon.paintIcon(c, g, x, y);
}
}
@Override
public int getIconWidth() {
return icon != null ? icon.getIconWidth() + depth*space : 0;
}
@Override
public int getIconHeight() {
return icon != null ? icon.getIconHeight() : 0;
}
}
private class DirectoryComboBoxRenderer extends JLabel implements ListCellRenderer, UIResource {
IconIndenter indenter = new IconIndenter();
public DirectoryComboBoxRenderer() {
setOpaque(true);
}
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected,
boolean cellHasFocus) {
// #89393: GTK needs name to render cell renderer "natively"
setName("ComboBox.listRenderer"); // NOI18N
if (value == null) {
setText("");
return this;
}
File directory = (File)value;
setText(getFileChooser().getName(directory));
Icon icon = getFileChooser().getIcon(directory);
indenter.icon = icon;
indenter.depth = directoryComboBoxModel.getDepth(index);
setIcon(indenter);
if ( isSelected ) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
// #89393: GTK needs name to render cell renderer "natively"
@Override
public String getName() {
String name = super.getName();
return name == null ? "ComboBox.renderer" : name; // NOI18N
}
} // end of DirectoryComboBoxRenderer
/**
* Data model for a type-face selection combo-box.
*/
private class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel {
Vector<File> directories = new Vector<File>();
int[] depths = null;
File selectedDirectory = null;
JFileChooser chooser = getFileChooser();
FileSystemView fsv = chooser.getFileSystemView();
public DirectoryComboBoxModel() {
// Add the current directory to the model, and make it the
// selectedDirectory
File dir = getFileChooser().getCurrentDirectory();
if(dir != null) {
addItem(dir);
}
}
/**
* Adds the directory to the model and sets it to be selected,
* additionally clears out the previous selected directory and
* the paths leading up to it, if any.
*/
private void addItem(File directory) {
if(directory == null) {
return;
}
directories.clear();
if(useShellFolder) {
directories.addAll(Arrays.asList(getShellFolderRoots()));
} else {
directories.addAll(Arrays.asList(fileChooser.getFileSystemView().getRoots()));
}
// Get the canonical (full) path. This has the side
// benefit of removing extraneous chars from the path,
// for example /foo/bar/ becomes /foo/bar
File canonical = null;
try {
canonical = directory.getCanonicalFile();
} catch (IOException e) {
// Maybe drive is not ready. Can't abort here.
canonical = directory;
}
// create File instances of each directory leading up to the top
File sf = useShellFolder? getShellFolderForFile(canonical) : canonical;
File f = sf;
Vector<File> path = new Vector<File>(10);
/*
* Fix for IZ#122534 :
* NullPointerException at
* org.netbeans.swing.dirchooser.DirectoryChooserUI$DirectoryComboBoxModel.addItem
*
*/
while( f!= null) {
path.addElement(f);
f = f.getParentFile();
}
int pathCount = path.size();
// Insert chain at appropriate place in vector
for (int i = 0; i < pathCount; i++) {
f = path.get(i);
if (directories.contains(f)) {
int topIndex = directories.indexOf(f);
for (int j = i-1; j >= 0; j--) {
directories.insertElementAt(path.get(j), topIndex+i-j);
}
break;
}
}
calculateDepths();
setSelectedItem(sf);
}
private void calculateDepths() {
depths = new int[directories.size()];
for (int i = 0; i < depths.length; i++) {
File dir = directories.get(i);
File parent = dir.getParentFile();
depths[i] = 0;
if (parent != null) {
for (int j = i-1; j >= 0; j--) {
if (parent.equals(directories.get(j))) {
depths[i] = depths[j] + 1;
break;
}
}
}
}
}
public int getDepth(int i) {
return (depths != null && i >= 0 && i < depths.length) ? depths[i] : 0;
}
@Override
public void setSelectedItem(Object selectedDirectory) {
this.selectedDirectory = (File)selectedDirectory;
fireContentsChanged(this, -1, -1);
}
@Override
public Object getSelectedItem() {
return selectedDirectory;
}
@Override
public int getSize() {
return directories.size();
}
@Override
public Object getElementAt(int index) {
return directories.elementAt(index);
}
}
/**
* Render different type sizes and styles.
*/
private class FilterComboBoxRenderer extends JLabel implements ListCellRenderer, UIResource {
public FilterComboBoxRenderer() {
setOpaque(true);
}
@Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
// #89393: GTK needs name to render cell renderer "natively"
setName("ComboBox.listRenderer"); // NOI18N
if (value != null && value instanceof FileFilter) {
setText(((FileFilter)value).getDescription());
}
if ( isSelected ) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
// #89393: GTK needs name to render cell renderer "natively"
@Override
public String getName() {
String name = super.getName();
return name == null ? "ComboBox.renderer" : name; // NOI18N
}
} // end of FilterComboBoxRenderer
/**
* Data model for a type-face selection combo-box.
*/
protected class FilterTypeComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener {
protected FileFilter[] filters;
protected FilterTypeComboBoxModel() {
super();
filters = getFileChooser().getChoosableFileFilters();
}
@Override
public void propertyChange(PropertyChangeEvent e) {
String prop = e.getPropertyName();
if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
filters = (FileFilter[]) e.getNewValue();
fireContentsChanged(this, -1, -1);
} else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
fireContentsChanged(this, -1, -1);
}
}
@Override
public void setSelectedItem(Object filter) {
if(filter != null) {
getFileChooser().setFileFilter((FileFilter) filter);
setFileName(null);
fireContentsChanged(this, -1, -1);
}
}
@Override
public Object getSelectedItem() {
// Ensure that the current filter is in the list.
// NOTE: we shouldnt' have to do this, since JFileChooser adds
// the filter to the choosable filters list when the filter
// is set. Lets be paranoid just in case someone overrides
// setFileFilter in JFileChooser.
FileFilter currentFilter = getFileChooser().getFileFilter();
boolean found = false;
if(currentFilter != null) {
for(int i=0; i < filters.length; i++) {
if(filters[i] == currentFilter) {
found = true;
}
}
if(found == false) {
getFileChooser().addChoosableFileFilter(currentFilter);
}
}
return getFileChooser().getFileFilter();
}
@Override
public int getSize() {
if(filters != null) {
return filters.length;
} else {
return 0;
}
}
@Override
public Object getElementAt(int index) {
if(index > getSize() - 1) {
// This shouldn't happen. Try to recover gracefully.
return getFileChooser().getFileFilter();
}
if(filters != null) {
return filters[index];
} else {
return null;
}
}
}
/**
* Gets calls when the ComboBox has changed the selected item.
*/
private class DirectoryComboBoxAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
File f = (File)directoryComboBox.getSelectedItem();
getFileChooser().setCurrentDirectory(f);
}
}
private class DirectoryChooserFileView extends BasicFileView {
@Override
public Icon getIcon(File f) {
Icon icon = getCachedIcon(f);
if (icon != null) {
return icon;
}
if (f != null) {
try {
icon = fileChooser.getFileSystemView().getSystemIcon(f);
} catch (NullPointerException exc) {
// workaround for JDK bug 6357445, in IZ: 145832, please remove when fixed
LOG.log(Level.FINE, "JDK bug 6357445 encountered, NPE caught", exc); // NOI18N
}
}
if (icon == null) {
icon = super.getIcon(f);
}
cacheIcon(f, icon);
return icon;
}
}
private class TextFieldKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent evt) {
showPopupCompletion = true;
int keyCode = evt.getKeyCode();
// #105801: completionPopup might not be ready when updateCompletions not called (empty text field)
if (completionPopup != null && !completionPopup.isVisible()) {
if (keyCode == KeyEvent.VK_ENTER) {
File file = getFileChooser().getFileSystemView().createFileObject(filenameTextField.getText());
if(file.exists() && file.isDirectory()) {
setSelected(new File[] {file});
fileChooser.approveSelection();
if (file.getParentFile() == null) {
// this will hopefully prevent popup to take inappropriate action
evt.consume();
}
}
}
if ((keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_DOWN) ||
(keyCode == KeyEvent.VK_RIGHT &&
(filenameTextField.getCaretPosition() >= (filenameTextField.getDocument().getLength() - 1)))) {
updateCompletions();
}
}
if(filenameTextField.isFocusOwner() &&
(completionPopup == null || !completionPopup.isVisible()) &&
keyCode == KeyEvent.VK_ESCAPE) {
fileChooser.cancelSelection();
}
}
}
private class DirectoryHandler extends MouseAdapter
implements TreeSelectionListener, CellEditorListener, ActionListener,
FocusListener, Runnable {
private JFileChooser fileChooser;
/** current selection holder */
private WeakReference<TreePath> curSelPath;
/** timer for slow click to rename feature */
private Timer renameTimer;
/** timer for slow dbl-click */
private Timer dblClickTimer;
/** path to rename for slow click to rename feature */
private TreePath pathToRename;
public DirectoryHandler(JFileChooser fileChooser) {
this.fileChooser = fileChooser;
}
/************ imple of TreeSelectionListener *******/
@Override
public void valueChanged(TreeSelectionEvent e) {
showPopupCompletion = false;
FileSystemView fsv = fileChooser.getFileSystemView();
JTree tree = (JTree) e.getSource();
TreePath path = tree.getSelectionPath();
TreePath curSel = e.getNewLeadSelectionPath();
curSelPath = (curSel != null) ? new WeakReference<TreePath>(curSel) : null;
if(path != null) {
DirectoryNode node = (DirectoryNode)path.getLastPathComponent();
File file = node.getFile();
if(file != null) {
setSelected(getSelectedNodes(tree.getSelectionPaths()));
newFolderAction.setEnabled(false);
if(!node.isLeaf()) {
newFolderAction.enable(file);
setDirectorySelected(true);
}
}
}
}
private File[] getSelectedNodes(TreePath[] paths) {
List<File> files = new LinkedList<File>();
for(int i = 0; i < paths.length; i++) {
File file = ((DirectoryNode)paths[i].getLastPathComponent()).getFile();
if(file.isDirectory()
&& fileChooser.isTraversable(file)
&& !fileChooser.getFileSystemView().isFileSystem(file)) {
continue;
}
files.add(file);
}
return files.toArray(new File[files.size()]);
}
/********* impl of MouseListener ***********/
@Override
public void mouseClicked(MouseEvent e) {
final JTree tree = (JTree) e.getSource();
Point p = e.getPoint();
final int x = e.getX();
final int y = e.getY();
int row = tree.getRowForLocation(x, y);
TreePath path = tree.getPathForRow(row);
if (path != null) {
DirectoryNode node = (DirectoryNode) path.getLastPathComponent();
newFolderAction.enable(node.getFile());
if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) {
handleDblClick(node);
return;
}
// handles click to rename feature
if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 1)) {
if (pathToRename != null) {
if (dblClickTimer != null) {
handleDblClick(node);
return;
}
// start slow click rename timer
renameTimer = new Timer(800, this);
renameTimer.setRepeats(false);
renameTimer.start();
startDblClickTimer();
}
}
startDblClickTimer();
((DirectoryTreeModel) tree.getModel()).nodeChanged(node);
if (row == 0) {
tree.revalidate();
tree.repaint();
}
}
}
private void handleDblClick (DirectoryNode node) {
cancelRename();
cancelDblClick();
if(node.isNetBeansProject()) {
fileChooser.approveSelection();
} else if (node.getFile().isFile() && !node.getFile().getPath().endsWith(".lnk")){
fileChooser.approveSelection();
} else {
changeTreeDirectory(node.getFile());
}
}
private void startDblClickTimer () {
cancelDblClick();
dblClickTimer = new Timer(500, this);
dblClickTimer.setRepeats(false);
dblClickTimer.start();
}
@Override
public void mousePressed(MouseEvent e) {
handlePopupMenu(e);
}
@Override
public void mouseReleased(MouseEvent e) {
handlePopupMenu(e);
}
private void handlePopupMenu (MouseEvent e) {
if (!e.isPopupTrigger()) {
return;
}
final JTree tree = (JTree) e.getSource();
Point p = e.getPoint();
int x = e.getX();
int y = e.getY();
int row = tree.getRowForLocation(x, y);
TreePath path = tree.getPathForRow(row);
if (path != null) {
DirectoryNode node = (DirectoryNode) path.getLastPathComponent();
((DirectoryTreeModel) tree.getModel()).nodeChanged(node);
if(!fileChooser.getFileSystemView().isFileSystem(node.getFile())) {
return;
}
tree.setSelectionPath(path);
popupMenu.show(tree, x, y);
}
}
private void changeTreeDirectory(File dir) {
if (File.separatorChar == '\\' && dir.getPath().endsWith(".lnk")) {
File linkLocation = getShellFolderForFileLinkLoc(dir);
if (linkLocation != null && fileChooser.isTraversable(linkLocation)) {
dir = linkLocation;
} else {
return;
}
}
fileChooser.setCurrentDirectory(dir);
}
/********** implementation of CellEditorListener ****************/
/** Refresh filename text field after rename */
@Override
public void editingStopped(ChangeEvent e) {
DirectoryNode node = (DirectoryNode) tree.getLastSelectedPathComponent();
if (node != null) {
setFileName(getStringOfFileName(node.getFile()));
}
}
@Override
public void editingCanceled(ChangeEvent e) {
// no operation
}
/********** ActionListener impl, slow-double-click rename ******/
@Override
public void actionPerformed(ActionEvent e) {
if (tree.isFocusOwner() && isSelectionKept(pathToRename)) {
if (e.getSource() == renameTimer) {
DirectoryNode node = (DirectoryNode)tree.getLastSelectedPathComponent();
if (node != null) {
applyEdit(node);
}
}
}
// clear
if (e.getSource() != dblClickTimer) {
cancelRename();
}
cancelDblClick();
}
void preprocessMouseEvent (MouseEvent e) {
if ((e.getID() != MouseEvent.MOUSE_PRESSED) || (e.getButton() != MouseEvent.BUTTON1)) {
return;
}
TreePath clickedPath = tree.getPathForLocation(e.getX(), e.getY());
if (clickedPath != null && isSelectionKept(clickedPath)) {
pathToRename = clickedPath;
}
}
private boolean isSelectionKept (TreePath selPath) {
if (curSelPath != null) {
TreePath oldSel = curSelPath.get();
if (oldSel != null && oldSel.equals(selPath)) {
return true;
}
}
return false;
}
private void cancelRename () {
if (renameTimer != null) {
renameTimer.stop();
renameTimer = null;
}
pathToRename = null;
}
private void cancelDblClick () {
Timer t = dblClickTimer;
if (t != null) {
t.stop();
dblClickTimer = null;
}
}
/******** implementation of focus listener, for slow click rename cancelling ******/
@Override
public void focusGained(FocusEvent e) {
// don't allow to invoke click to rename immediatelly after focus gain
// what may happen is that tree gains focus by mouse
// click on selected item - on some platforms selected item
// is not visible without focus and click to rename will
// be unwanted and surprising for users
// see run method
SwingUtilities.invokeLater(this);
}
@Override
public void run() {
cancelRename();
}
@Override
public void focusLost(FocusEvent e) {
cancelRename();
}
}
private class TreeExpansionHandler implements TreeExpansionListener {
@Override
public void treeExpanded(TreeExpansionEvent evt) {
TreePath path = evt.getPath();
DirectoryNode node = (DirectoryNode) path
.getLastPathComponent();
if(!node.isLoaded()) {
expandNode(fileChooser, path);
} else {
// fixed #96954, to be able to add a new directory
// when the node has been already loaded
if(addNewDirectory) {
addNewDirectory(path);
addNewDirectory = false;
}
// Fix for IZ#123815 : Cannot refresh the tree content
refreshNode( path , node );
}
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
}
}
// Fix for IZ#123815 : Cannot refresh the tree content
private void refreshNode( final TreePath path, final DirectoryNode node ){
final File folder = node.getFile();
// Additional fixes for IZ#116859 [60cat] Node update bug in the "open project" panel while deleting directories
if ( !folder.exists() ){
TreePath parentPath = path.getParentPath();
boolean refreshTree = false;
if(tree.isExpanded(path)) {
tree.collapsePath(path);
refreshTree = true;
}
model.removeNodeFromParent( node );
if ( refreshTree ){
tree.expandPath( parentPath );
}
return;
}
RequestProcessor.getDefault().post(new Runnable() {
private Set<String> realDirs;
@Override
public void run() {
if (!EventQueue.isDispatchThread()) {
// first phase
realDirs = new HashSet<String>();
File[] files = folder.listFiles();
files = files == null ? new File[0] : files;
for (File file : files) {
if ( !file.isDirectory() ){
continue;
}
String name = file.getName();
realDirs.add( name );
}
SwingUtilities.invokeLater(this);
} else {
// second phase, in EQ thread, invoked from first phase
int count = node.getChildCount();
Map<String,DirectoryNode> currentFiles =
new HashMap<String,DirectoryNode>( );
for( int i=0; i< count ; i++ ){
TreeNode child = node.getChildAt(i);
if ( child instanceof DirectoryNode ){
File file = ((DirectoryNode)child).getFile();
currentFiles.put( file.getName() , (DirectoryNode)child);
}
}
Set<String> realCloned = new HashSet<String>( realDirs );
if ( realCloned.removeAll( currentFiles.keySet()) ){
// Handle added folders
for ( String name : realCloned ){
DirectoryNode added = new DirectoryNode( getFileChooser().getFileSystemView().createFileObject( folder, name ) );
model.insertNodeInto( added, node, node.getChildCount());
}
}
Set<String> currentNames = new HashSet<String>( currentFiles.keySet());
if ( currentNames.removeAll( realDirs )){
// Handle deleted folders
for ( String name : currentNames ){
DirectoryNode removed = currentFiles.get( name );
model.removeNodeFromParent( removed );
}
}
}
}
});
}
private class NewDirectoryAction extends AbstractAction {
private final RequestProcessor.Task enableTask = RP.create(new ActionEnabler());
private File file;
@Override
public void actionPerformed(ActionEvent e) {
final TreePath path = tree.getSelectionPath();
if(path == null) {
// if no nodes are selected, get the root node
// fixed #96954, to be able to add a new directory
// in the current directory shown in the tree
addNewDirectory(new TreePath(model.getPathToRoot((DirectoryNode)tree.getModel().getRoot())));
}
if(path != null) {
if(tree.isExpanded(path)) {
addNewDirectory(path);
} else {
addNewDirectory = true;
tree.expandPath(path);
}
}
}
private void enable (File file) {
setEnabled(false);
this.file = file;
enableTask.schedule(0);
}
private class ActionEnabler implements Runnable {
@Override
public void run () {
final File f = file;
if (canWrite(f)) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run () {
if (f == file) {
setEnabled(true);
}
}
});
}
};
}
}
private class DirectoryTreeRenderer implements TreeCellRenderer {
HtmlRenderer.Renderer renderer = HtmlRenderer.createRenderer();
@Override
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean isSelected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
Component stringDisplayer = renderer.getTreeCellRendererComponent(tree,
value,
isSelected,
expanded,
leaf,
row,
hasFocus);
if(value instanceof DirectoryNode) {
tree.setShowsRootHandles(true);
DirectoryNode node = (DirectoryNode)value;
((JLabel)stringDisplayer).setIcon(getNodeIcon(node));
((JLabel)stringDisplayer).setText(getNodeText(node));
}
Font f = stringDisplayer.getFont();
stringDisplayer.setPreferredSize(new Dimension(stringDisplayer.getPreferredSize().width, 30));
// allow some space around icon of items
((JComponent)stringDisplayer).setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0));
stringDisplayer.setSize(stringDisplayer.getPreferredSize());
return stringDisplayer;
}
private Icon getNodeIcon(DirectoryNode node) {
return node.getIcon(fileChooser);
}
private String getNodeText (DirectoryNode node) {
return node.getText(fileChooser);
}
}
private class DirectoryTreeModel extends DefaultTreeModel {
public DirectoryTreeModel(TreeNode root) {
super(root);
}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
boolean refreshTree = false;
if (path == null) {
// no idea
return;
}
DirectoryNode node = (DirectoryNode)path.getLastPathComponent();
File f = node.getFile();
File newFile = getFileChooser().getFileSystemView().createFileObject(f.getParentFile(), (String)newValue);
if(f.renameTo(newFile)) {
// fix bug #97521, #96960
if(tree.isExpanded(path)) {
tree.collapsePath(path);
refreshTree = true;
}
node.setFile(newFile);
node.removeAllChildren();
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
if(refreshTree) {
tree.expandPath(path);
}
}
}
}
private class UpdateWorker implements Runnable {
private volatile String lastUpdatePathName = null;
private DirectoryChooserUI ui;
private volatile File work;
public void updateTree(final File file) {
work = file;
RP.post(this);
}
public void attachFileChooser(final DirectoryChooserUI ui) {
this.ui = ui;
this.lastUpdatePathName = null;
}
@Override
public void run() {
assert !EventQueue.isDispatchThread();
File file = work;
if (file == null) {
return;
}
if (lastUpdatePathName != null && lastUpdatePathName.equals(file.getPath())) {
restoreCursor();
return;
}
lastUpdatePathName = file.getPath();
ui.markStartTime();
ui.setCursor(fileChooser, Cursor.WAIT_CURSOR);
final DirectoryNode node = new DirectoryNode(file);
node.loadChildren(fileChooser, true);
// update UI in EQ thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ui.model = new DirectoryTreeModel(node);
tree.setModel(ui.model);
tree.repaint();
ui.checkUpdate();
restoreCursor();
}
});
}
private void restoreCursor () {
ui.setCursor(fileChooser, Cursor.DEFAULT_CURSOR);
}
} // end of UpdateWorker
}