8152677: [macosx] All files filter can't be selected in JFileChooser
Reviewed-by: serb
/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.beans.*;
import java.io.File;
import java.net.URI;
import java.text.DateFormat;
import java.util.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.*;
import javax.swing.table.*;
import sun.swing.SwingUtilities2;
public class AquaFileChooserUI extends FileChooserUI {
/* FileView icons */
protected Icon directoryIcon = null;
protected Icon fileIcon = null;
protected Icon computerIcon = null;
protected Icon hardDriveIcon = null;
protected Icon floppyDriveIcon = null;
protected Icon upFolderIcon = null;
protected Icon homeFolderIcon = null;
protected Icon listViewIcon = null;
protected Icon detailsViewIcon = null;
protected int saveButtonMnemonic = 0;
protected int openButtonMnemonic = 0;
protected int cancelButtonMnemonic = 0;
protected int updateButtonMnemonic = 0;
protected int helpButtonMnemonic = 0;
protected int chooseButtonMnemonic = 0;
private String saveTitleText = null;
private String openTitleText = null;
String newFolderTitleText = null;
protected String saveButtonText = null;
protected String openButtonText = null;
protected String cancelButtonText = null;
protected String updateButtonText = null;
protected String helpButtonText = null;
protected String newFolderButtonText = null;
protected String chooseButtonText = null;
//private String newFolderErrorSeparator = null;
String newFolderErrorText = null;
String newFolderExistsErrorText = null;
protected String fileDescriptionText = null;
protected String directoryDescriptionText = null;
protected String saveButtonToolTipText = null;
protected String openButtonToolTipText = null;
protected String cancelButtonToolTipText = null;
protected String updateButtonToolTipText = null;
protected String helpButtonToolTipText = null;
protected String chooseItemButtonToolTipText = null; // Choose anything
protected String chooseFolderButtonToolTipText = null; // Choose folder
protected String directoryComboBoxToolTipText = null;
protected String filenameTextFieldToolTipText = null;
protected String filterComboBoxToolTipText = null;
protected String openDirectoryButtonToolTipText = null;
protected String cancelOpenButtonToolTipText = null;
protected String cancelSaveButtonToolTipText = null;
protected String cancelChooseButtonToolTipText = null;
protected String cancelNewFolderButtonToolTipText = null;
protected String desktopName = null;
String newFolderDialogPrompt = null;
String newFolderDefaultName = null;
private String newFileDefaultName = null;
String createButtonText = null;
JFileChooser filechooser = null;
private MouseListener doubleClickListener = null;
private PropertyChangeListener propertyChangeListener = null;
private AncestorListener ancestorListener = null;
private DropTarget dragAndDropTarget = null;
private static final AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter();
private AquaFileSystemModel model;
final AquaFileView fileView = new AquaFileView(this);
boolean selectionInProgress = false;
// The accessoryPanel is a container to place the JFileChooser accessory component
private JPanel accessoryPanel = null;
//
// ComponentUI Interface Implementation methods
//
public static ComponentUI createUI(final JComponent c) {
return new AquaFileChooserUI((JFileChooser)c);
}
public AquaFileChooserUI(final JFileChooser filechooser) {
super();
}
public void installUI(final JComponent c) {
accessoryPanel = new JPanel(new BorderLayout());
filechooser = (JFileChooser)c;
createModel();
installDefaults(filechooser);
installComponents(filechooser);
installListeners(filechooser);
AquaUtils.enforceComponentOrientation(filechooser, ComponentOrientation.getOrientation(Locale.getDefault()));
}
public void uninstallUI(final JComponent c) {
uninstallListeners(filechooser);
uninstallComponents(filechooser);
uninstallDefaults(filechooser);
if (accessoryPanel != null) {
accessoryPanel.removeAll();
}
accessoryPanel = null;
getFileChooser().removeAll();
}
protected void installListeners(final JFileChooser fc) {
doubleClickListener = createDoubleClickListener(fc, fFileList);
fFileList.addMouseListener(doubleClickListener);
propertyChangeListener = createPropertyChangeListener(fc);
if (propertyChangeListener != null) {
fc.addPropertyChangeListener(propertyChangeListener);
}
if (model != null) fc.addPropertyChangeListener(model);
ancestorListener = new AncestorListener(){
public void ancestorAdded(final AncestorEvent e) {
// Request defaultness for the appropriate button based on mode
setFocusForMode(getFileChooser());
// Request defaultness for the appropriate button based on mode
setDefaultButtonForMode(getFileChooser());
}
public void ancestorRemoved(final AncestorEvent e) {
}
public void ancestorMoved(final AncestorEvent e) {
}
};
fc.addAncestorListener(ancestorListener);
fc.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
dragAndDropTarget = new DropTarget(fc, DnDConstants.ACTION_COPY, new DnDHandler(), true);
fc.setDropTarget(dragAndDropTarget);
}
protected void uninstallListeners(final JFileChooser fc) {
if (propertyChangeListener != null) {
fc.removePropertyChangeListener(propertyChangeListener);
}
fFileList.removeMouseListener(doubleClickListener);
fc.removePropertyChangeListener(model);
fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
fc.removeAncestorListener(ancestorListener);
fc.setDropTarget(null);
ancestorListener = null;
}
protected void installDefaults(final JFileChooser fc) {
installIcons(fc);
installStrings(fc);
setPackageIsTraversable(fc.getClientProperty(PACKAGE_TRAVERSABLE_PROPERTY));
setApplicationIsTraversable(fc.getClientProperty(APPLICATION_TRAVERSABLE_PROPERTY));
}
protected void installIcons(final JFileChooser fc) {
directoryIcon = UIManager.getIcon("FileView.directoryIcon");
fileIcon = UIManager.getIcon("FileView.fileIcon");
computerIcon = UIManager.getIcon("FileView.computerIcon");
hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon");
}
String getString(final String uiKey, final String fallback) {
final String result = UIManager.getString(uiKey);
return (result == null ? fallback : result);
}
protected void installStrings(final JFileChooser fc) {
// Exist in basic.properties (though we might want to override)
fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText");
directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText");
newFolderErrorText = getString("FileChooser.newFolderErrorText", "Error occurred during folder creation");
saveButtonText = UIManager.getString("FileChooser.saveButtonText");
openButtonText = UIManager.getString("FileChooser.openButtonText");
cancelButtonText = UIManager.getString("FileChooser.cancelButtonText");
updateButtonText = UIManager.getString("FileChooser.updateButtonText");
helpButtonText = UIManager.getString("FileChooser.helpButtonText");
saveButtonMnemonic = UIManager.getInt("FileChooser.saveButtonMnemonic");
openButtonMnemonic = UIManager.getInt("FileChooser.openButtonMnemonic");
cancelButtonMnemonic = UIManager.getInt("FileChooser.cancelButtonMnemonic");
updateButtonMnemonic = UIManager.getInt("FileChooser.updateButtonMnemonic");
helpButtonMnemonic = UIManager.getInt("FileChooser.helpButtonMnemonic");
chooseButtonMnemonic = UIManager.getInt("FileChooser.chooseButtonMnemonic");
saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText");
openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText");
cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText");
updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText");
helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText");
// Mac-specific, but fallback to basic if it's missing
saveTitleText = getString("FileChooser.saveTitleText", saveButtonText);
openTitleText = getString("FileChooser.openTitleText", openButtonText);
// Mac-specific, required
newFolderExistsErrorText = getString("FileChooser.newFolderExistsErrorText", "That name is already taken");
chooseButtonText = getString("FileChooser.chooseButtonText", "Choose");
newFolderButtonText = getString("FileChooser.newFolderButtonText", "New");
newFolderTitleText = getString("FileChooser.newFolderTitleText", "New Folder");
if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
} else {
fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
}
filesOfTypeLabelText = getString("FileChooser.filesOfTypeLabelText", "Format:");
desktopName = getString("FileChooser.desktopName", "Desktop");
newFolderDialogPrompt = getString("FileChooser.newFolderPromptText", "Name of new folder:");
newFolderDefaultName = getString("FileChooser.untitledFolderName", "untitled folder");
newFileDefaultName = getString("FileChooser.untitledFileName", "untitled");
createButtonText = getString("FileChooser.createButtonText", "Create");
fColumnNames[1] = getString("FileChooser.byDateText", "Date Modified");
fColumnNames[0] = getString("FileChooser.byNameText", "Name");
// Mac-specific, optional
chooseItemButtonToolTipText = UIManager.getString("FileChooser.chooseItemButtonToolTipText");
chooseFolderButtonToolTipText = UIManager.getString("FileChooser.chooseFolderButtonToolTipText");
openDirectoryButtonToolTipText = UIManager.getString("FileChooser.openDirectoryButtonToolTipText");
directoryComboBoxToolTipText = UIManager.getString("FileChooser.directoryComboBoxToolTipText");
filenameTextFieldToolTipText = UIManager.getString("FileChooser.filenameTextFieldToolTipText");
filterComboBoxToolTipText = UIManager.getString("FileChooser.filterComboBoxToolTipText");
cancelOpenButtonToolTipText = UIManager.getString("FileChooser.cancelOpenButtonToolTipText");
cancelSaveButtonToolTipText = UIManager.getString("FileChooser.cancelSaveButtonToolTipText");
cancelChooseButtonToolTipText = UIManager.getString("FileChooser.cancelChooseButtonToolTipText");
cancelNewFolderButtonToolTipText = UIManager.getString("FileChooser.cancelNewFolderButtonToolTipText");
newFolderTitleText = UIManager.getString("FileChooser.newFolderTitleText");
newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText");
newFolderAccessibleName = getString("FileChooser.newFolderAccessibleName", newFolderTitleText);
}
protected void uninstallDefaults(final JFileChooser fc) {
uninstallIcons(fc);
uninstallStrings(fc);
}
protected void uninstallIcons(final JFileChooser fc) {
directoryIcon = null;
fileIcon = null;
computerIcon = null;
hardDriveIcon = null;
floppyDriveIcon = null;
upFolderIcon = null;
homeFolderIcon = null;
detailsViewIcon = null;
listViewIcon = null;
}
protected void uninstallStrings(final JFileChooser fc) {
saveTitleText = null;
openTitleText = null;
newFolderTitleText = null;
saveButtonText = null;
openButtonText = null;
cancelButtonText = null;
updateButtonText = null;
helpButtonText = null;
newFolderButtonText = null;
chooseButtonText = null;
cancelOpenButtonToolTipText = null;
cancelSaveButtonToolTipText = null;
cancelChooseButtonToolTipText = null;
cancelNewFolderButtonToolTipText = null;
saveButtonToolTipText = null;
openButtonToolTipText = null;
cancelButtonToolTipText = null;
updateButtonToolTipText = null;
helpButtonToolTipText = null;
chooseItemButtonToolTipText = null;
chooseFolderButtonToolTipText = null;
openDirectoryButtonToolTipText = null;
directoryComboBoxToolTipText = null;
filenameTextFieldToolTipText = null;
filterComboBoxToolTipText = null;
newFolderDefaultName = null;
newFileDefaultName = null;
desktopName = null;
}
protected void createModel() {
}
AquaFileSystemModel getModel() {
return model;
}
/*
* Listen for filechooser property changes, such as
* the selected file changing, or the type of the dialog changing.
*/
// Taken almost verbatim from Metal
protected PropertyChangeListener createPropertyChangeListener(final JFileChooser fc) {
return new PropertyChangeListener(){
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
final File f = (File)e.getNewValue();
if (f != null) {
// Select the file in the list if the selected file didn't change as
// a result of a list click.
if (!selectionInProgress && getModel().contains(f)) {
fFileList.setSelectedIndex(getModel().indexOf(f));
}
// [3643835] Need to populate the text field here. No-op on Open dialogs
// Note that this was removed for 3514735, but should not have been.
if (!f.isDirectory()) {
setFileName(getFileChooser().getName(f));
}
}
updateButtonState(getFileChooser());
} else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
JFileChooser fileChooser = getFileChooser();
if (!fileChooser.isDirectorySelectionEnabled()) {
final File[] files = (File[]) e.getNewValue();
if (files != null) {
for (int selectedRow : fFileList.getSelectedRows()) {
File file = (File) fFileList.getValueAt(selectedRow, 0);
if (fileChooser.isTraversable(file)) {
fFileList.removeSelectedIndex(selectedRow);
}
}
}
}
} else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
fFileList.clearSelection();
final File currentDirectory = getFileChooser().getCurrentDirectory();
if (currentDirectory != null) {
fDirectoryComboBoxModel.addItem(currentDirectory);
// Enable the newFolder action if the current directory
// is writable.
// PENDING(jeff) - broken - fix
getAction(kNewFolder).setEnabled(currentDirectory.canWrite());
}
updateButtonState(getFileChooser());
} else if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
fFileList.clearSelection();
setBottomPanelForMode(getFileChooser()); // Also updates approve button
} else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {
if (getAccessoryPanel() != null) {
if (e.getOldValue() != null) {
getAccessoryPanel().remove((JComponent)e.getOldValue());
}
final JComponent accessory = (JComponent)e.getNewValue();
if (accessory != null) {
getAccessoryPanel().add(accessory, BorderLayout.CENTER);
}
}
} else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) {
updateApproveButton(getFileChooser());
getFileChooser().invalidate();
} else if (prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY) {
if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) {
fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
} else {
fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
}
fTextFieldLabel.setText(fileNameLabelText);
// Mac doesn't show the text field or "new folder" button in 'Open' dialogs
setBottomPanelForMode(getFileChooser()); // Also updates approve button
} else if (prop.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
getApproveButton(getFileChooser()).setMnemonic(getApproveButtonMnemonic(getFileChooser()));
} else if (prop.equals(PACKAGE_TRAVERSABLE_PROPERTY)) {
setPackageIsTraversable(e.getNewValue());
} else if (prop.equals(APPLICATION_TRAVERSABLE_PROPERTY)) {
setApplicationIsTraversable(e.getNewValue());
} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
if (getFileChooser().isMultiSelectionEnabled()) {
fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
} else {
fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
} else if (prop.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
doControlButtonsChanged(e);
}
}
};
}
void setPackageIsTraversable(final Object o) {
int newProp = -1;
if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
if (newProp != -1) fPackageIsTraversable = newProp;
else fPackageIsTraversable = sGlobalPackageIsTraversable;
}
void setApplicationIsTraversable(final Object o) {
int newProp = -1;
if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
if (newProp != -1) fApplicationIsTraversable = newProp;
else fApplicationIsTraversable = sGlobalApplicationIsTraversable;
}
void doControlButtonsChanged(final PropertyChangeEvent e) {
if (getFileChooser().getControlButtonsAreShown()) {
fBottomPanel.add(fDirectoryPanelSpacer);
fBottomPanel.add(fDirectoryPanel);
} else {
fBottomPanel.remove(fDirectoryPanelSpacer);
fBottomPanel.remove(fDirectoryPanel);
}
}
public String getFileName() {
if (filenameTextField != null) { return filenameTextField.getText(); }
return null;
}
public String getDirectoryName() {
// PENDING(jeff) - get the name from the directory combobox
return null;
}
public void setFileName(final String filename) {
if (filenameTextField != null) {
filenameTextField.setText(filename);
}
}
public void setDirectoryName(final String dirname) {
// PENDING(jeff) - set the name in the directory combobox
}
public void rescanCurrentDirectory(final JFileChooser fc) {
getModel().invalidateFileCache();
getModel().validateFileCache();
}
public void ensureFileIsVisible(final JFileChooser fc, final File f) {
if (f == null) {
fFileList.requestFocusInWindow();
fFileList.ensureIndexIsVisible(-1);
return;
}
getModel().runWhenDone(new Runnable() {
public void run() {
fFileList.requestFocusInWindow();
fFileList.ensureIndexIsVisible(getModel().indexOf(f));
}
});
}
public JFileChooser getFileChooser() {
return filechooser;
}
public JPanel getAccessoryPanel() {
return accessoryPanel;
}
protected JButton getApproveButton(final JFileChooser fc) {
return fApproveButton;
}
public int getApproveButtonMnemonic(final JFileChooser fc) {
return fSubPanel.getApproveButtonMnemonic(fc);
}
public String getApproveButtonToolTipText(final JFileChooser fc) {
return fSubPanel.getApproveButtonToolTipText(fc);
}
public String getApproveButtonText(final JFileChooser fc) {
return fSubPanel.getApproveButtonText(fc);
}
protected String getCancelButtonToolTipText(final JFileChooser fc) {
return fSubPanel.getCancelButtonToolTipText(fc);
}
// If the item's not selectable, it'll be visible but disabled in the list
boolean isSelectableInList(final File f) {
return fSubPanel.isSelectableInList(getFileChooser(), f);
}
// Is this a file that the JFileChooser wants?
// Directories can be selected in the list regardless of mode
boolean isSelectableForMode(final JFileChooser fc, final File f) {
if (f == null) return false;
final int mode = fc.getFileSelectionMode();
if (mode == JFileChooser.FILES_AND_DIRECTORIES) return true;
boolean traversable = fc.isTraversable(f);
if (mode == JFileChooser.DIRECTORIES_ONLY) return traversable;
return !traversable;
}
// ********************************************
// ************ Create Listeners **************
// ********************************************
// From Basic
public ListSelectionListener createListSelectionListener(final JFileChooser fc) {
return new SelectionListener();
}
protected class SelectionListener implements ListSelectionListener {
public void valueChanged(final ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
File f = null;
final int selectedRow = fFileList.getSelectedRow();
final JFileChooser chooser = getFileChooser();
boolean isSave = (chooser.getDialogType() == JFileChooser.SAVE_DIALOG);
if (selectedRow >= 0) {
f = (File)fFileList.getValueAt(selectedRow, 0);
}
// Save dialog lists can't be multi select, because all we're selecting is the next folder to open
selectionInProgress = true;
if (!isSave && chooser.isMultiSelectionEnabled()) {
final int[] rows = fFileList.getSelectedRows();
int selectableCount = 0;
// Double-check that all the list selections are valid for this mode
// Directories can be selected in the list regardless of mode
if (rows.length > 0) {
for (final int element : rows) {
if (isSelectableForMode(chooser, (File)fFileList.getValueAt(element, 0))) selectableCount++;
}
}
if (selectableCount > 0) {
final File[] files = new File[selectableCount];
for (int i = 0, si = 0; i < rows.length; i++) {
f = (File)fFileList.getValueAt(rows[i], 0);
if (isSelectableForMode(chooser, f)) {
if (fileView.isAlias(f)) {
f = fileView.resolveAlias(f);
}
files[si++] = f;
}
}
chooser.setSelectedFiles(files);
} else {
chooser.setSelectedFiles(null);
}
} else {
chooser.setSelectedFiles(null);
chooser.setSelectedFile(f);
}
selectionInProgress = false;
}
}
// When the Save textfield has the focus, the button should say "Save"
// Otherwise, it depends on the list selection
protected class SaveTextFocusListener implements FocusListener {
public void focusGained(final FocusEvent e) {
updateButtonState(getFileChooser());
}
// Do nothing, we might be losing focus due to window deactivation
public void focusLost(final FocusEvent e) {
}
}
// When the Save textfield is empty and the button says "Save", it should be disabled
// Otherwise, it depends on the list selection
protected class SaveTextDocumentListener implements DocumentListener {
public void insertUpdate(final DocumentEvent e) {
textChanged();
}
public void removeUpdate(final DocumentEvent e) {
textChanged();
}
public void changedUpdate(final DocumentEvent e) {
}
void textChanged() {
updateButtonState(getFileChooser());
}
}
// Opens the File object if it's a traversable directory
protected boolean openDirectory(final File f) {
if (getFileChooser().isTraversable(f)) {
fFileList.clearSelection();
// Resolve any aliases
final File original = fileView.resolveAlias(f);
getFileChooser().setCurrentDirectory(original);
updateButtonState(getFileChooser());
return true;
}
return false;
}
// From Basic
protected class DoubleClickListener extends MouseAdapter {
JTableExtension list;
public DoubleClickListener(final JTableExtension list) {
this.list = list;
}
public void mouseClicked(final MouseEvent e) {
if (e.getClickCount() != 2) return;
final int index = list.locationToIndex(e.getPoint());
if (index < 0) return;
final File f = (File)((AquaFileSystemModel)list.getModel()).getElementAt(index);
if (openDirectory(f)) return;
if (!isSelectableInList(f)) return;
getFileChooser().approveSelection();
}
}
protected MouseListener createDoubleClickListener(final JFileChooser fc, final JTableExtension list) {
return new DoubleClickListener(list);
}
// listens for drag events onto the JFileChooser and sets the selected file or directory
class DnDHandler extends DropTargetAdapter {
public void dragEnter(final DropTargetDragEvent dtde) {
tryToAcceptDrag(dtde);
}
public void dragOver(final DropTargetDragEvent dtde) {
tryToAcceptDrag(dtde);
}
public void dropActionChanged(final DropTargetDragEvent dtde) {
tryToAcceptDrag(dtde);
}
public void drop(final DropTargetDropEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
handleFileDropEvent(dtde);
return;
}
if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
handleStringDropEvent(dtde);
return;
}
}
protected void tryToAcceptDrag(final DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
dtde.acceptDrag(DnDConstants.ACTION_COPY);
return;
}
dtde.rejectDrag();
}
protected void handleFileDropEvent(final DropTargetDropEvent dtde) {
dtde.acceptDrop(dtde.getDropAction());
final Transferable transferable = dtde.getTransferable();
try {
@SuppressWarnings("unchecked")
final java.util.List<File> fileList = (java.util.List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);
dropFiles(fileList.toArray(new File[fileList.size()]));
dtde.dropComplete(true);
} catch (final Exception e) {
dtde.dropComplete(false);
}
}
protected void handleStringDropEvent(final DropTargetDropEvent dtde) {
dtde.acceptDrop(dtde.getDropAction());
final Transferable transferable = dtde.getTransferable();
final String stringData;
try {
stringData = (String)transferable.getTransferData(DataFlavor.stringFlavor);
} catch (final Exception e) {
dtde.dropComplete(false);
return;
}
try {
final File fileAsPath = new File(stringData);
if (fileAsPath.exists()) {
dropFiles(new File[] {fileAsPath});
dtde.dropComplete(true);
return;
}
} catch (final Exception e) {
// try again
}
try {
final File fileAsURI = new File(new URI(stringData));
if (fileAsURI.exists()) {
dropFiles(new File[] {fileAsURI});
dtde.dropComplete(true);
return;
}
} catch (final Exception e) {
// nothing more to do
}
dtde.dropComplete(false);
}
protected void dropFiles(final File[] files) {
final JFileChooser jfc = getFileChooser();
if (files.length == 1) {
if (files[0].isDirectory()) {
jfc.setCurrentDirectory(files[0]);
return;
}
if (!isSelectableForMode(jfc, files[0])) {
return;
}
}
jfc.setSelectedFiles(files);
for (final File file : files) {
jfc.ensureFileIsVisible(file);
}
getModel().runWhenDone(new Runnable() {
public void run() {
final AquaFileSystemModel fileSystemModel = getModel();
for (final File element : files) {
final int index = fileSystemModel.indexOf(element);
if (index >= 0) fFileList.addRowSelectionInterval(index, index);
}
}
});
}
}
// FileChooser UI PLAF methods
/**
* Returns the default accept all file filter
*/
public FileFilter getAcceptAllFileFilter(final JFileChooser fc) {
return acceptAllFileFilter;
}
public FileView getFileView(final JFileChooser fc) {
return fileView;
}
/**
* Returns the title of this dialog
*/
public String getDialogTitle(final JFileChooser fc) {
if (fc.getDialogTitle() == null) {
if (getFileChooser().getDialogType() == JFileChooser.OPEN_DIALOG) {
return openTitleText;
} else if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) { return saveTitleText; }
}
return fc.getDialogTitle();
}
// Utility to get the first selected item regardless of whether we're single or multi select
File getFirstSelectedItem() {
// Get the selected item
File selectedFile = null;
final int index = fFileList.getSelectedRow();
if (index >= 0) {
selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
}
return selectedFile;
}
// Make a file from the filename
File makeFile(final JFileChooser fc, final String filename) {
File selectedFile = null;
// whitespace is legal on Macs, even on beginning and end of filename
if (filename != null && !filename.equals("")) {
final FileSystemView fs = fc.getFileSystemView();
selectedFile = fs.createFileObject(filename);
if (!selectedFile.isAbsolute()) {
selectedFile = fs.createFileObject(fc.getCurrentDirectory(), filename);
}
}
return selectedFile;
}
// Utility to tell if the textfield has anything in it
boolean textfieldIsValid() {
final String s = getFileName();
return (s != null && !s.equals(""));
}
// Action to attach to the file list so we can override the default action
// of the table for the return key, which is to select the next line.
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class DefaultButtonAction extends AbstractAction {
public void actionPerformed(final ActionEvent e) {
final JRootPane root = AquaFileChooserUI.this.getFileChooser().getRootPane();
final JFileChooser fc = AquaFileChooserUI.this.getFileChooser();
final JButton owner = root.getDefaultButton();
if (owner != null && SwingUtilities.getRootPane(owner) == root && owner.isEnabled()) {
owner.doClick(20);
} else if (!fc.getControlButtonsAreShown()) {
final JButton defaultButton = AquaFileChooserUI.this.fSubPanel.getDefaultButton(fc);
if (defaultButton != null) {
defaultButton.doClick(20);
}
} else {
Toolkit.getDefaultToolkit().beep();
}
}
public boolean isEnabled() {
return true;
}
}
/**
* Creates a new folder.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class NewFolderAction extends AbstractAction {
protected NewFolderAction() {
super(newFolderAccessibleName);
}
// Muchlike showInputDialog, but we give it options instead of selectionValues
private Object showNewFolderDialog(final Component parentComponent, final Object message, final String title, final int messageType, final Icon icon, final Object[] options, final Object initialSelectionValue) {
final JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.OK_CANCEL_OPTION, icon, options, null);
pane.setWantsInput(true);
pane.setInitialSelectionValue(initialSelectionValue);
final JDialog dialog = pane.createDialog(parentComponent, title);
pane.selectInitialValue();
dialog.setVisible(true);
dialog.dispose();
final Object value = pane.getValue();
if (value == null || value.equals(cancelButtonText)) {
return null;
}
return pane.getInputValue();
}
public void actionPerformed(final ActionEvent e) {
final JFileChooser fc = getFileChooser();
final File currentDirectory = fc.getCurrentDirectory();
File newFolder = null;
final String[] options = {createButtonText, cancelButtonText};
final String filename = (String)showNewFolderDialog(fc, //parentComponent
newFolderDialogPrompt, // message
newFolderTitleText, // title
JOptionPane.PLAIN_MESSAGE, // messageType
null, // icon
options, // selectionValues
newFolderDefaultName); // initialSelectionValue
if (filename != null) {
try {
newFolder = fc.getFileSystemView().createFileObject(currentDirectory, filename);
if (newFolder.exists()) {
JOptionPane.showMessageDialog(fc, newFolderExistsErrorText, "", JOptionPane.ERROR_MESSAGE);
return;
}
newFolder.mkdirs();
} catch(final Exception exc) {
JOptionPane.showMessageDialog(fc, newFolderErrorText, "", JOptionPane.ERROR_MESSAGE);
return;
}
openDirectory(newFolder);
}
}
}
/**
* Responds to an Open, Save, or Choose request
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class ApproveSelectionAction extends AbstractAction {
public void actionPerformed(final ActionEvent e) {
fSubPanel.approveSelection(getFileChooser());
}
}
/**
* Responds to an OpenDirectory request
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class OpenSelectionAction extends AbstractAction {
public void actionPerformed(final ActionEvent e) {
final int index = fFileList.getSelectedRow();
if (index >= 0) {
final File selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
if (selectedFile != null) openDirectory(selectedFile);
}
}
}
/**
* Responds to a cancel request.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class CancelSelectionAction extends AbstractAction {
public void actionPerformed(final ActionEvent e) {
getFileChooser().cancelSelection();
}
public boolean isEnabled() {
return getFileChooser().isEnabled();
}
}
/**
* Rescans the files in the current directory
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class UpdateAction extends AbstractAction {
public void actionPerformed(final ActionEvent e) {
final JFileChooser fc = getFileChooser();
fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName()));
fc.rescanCurrentDirectory();
}
}
// *****************************************
// ***** default AcceptAll file filter *****
// *****************************************
private static class AcceptAllFileFilter extends FileFilter {
public AcceptAllFileFilter() {
}
public boolean accept(final File f) {
return true;
}
public String getDescription() {
return UIManager.getString("FileChooser.acceptAllFileFilterText");
}
}
// Penultimate superclass is JLabel
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class MacFCTableCellRenderer extends DefaultTableCellRenderer {
boolean fIsSelected = false;
public MacFCTableCellRenderer(final Font f) {
super();
setFont(f);
setIconTextGap(10);
}
public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {
super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks
fIsSelected = isSelected;
return this;
}
public boolean isSelected() {
return fIsSelected && isEnabled();
}
protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon, final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {
return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());
}
protected void paintComponent(final Graphics g) {
final String text = getText();
Icon icon = getIcon();
if (icon != null && !isEnabled()) {
final Icon disabledIcon = getDisabledIcon();
if (disabledIcon != null) icon = disabledIcon;
}
if ((icon == null) && (text == null)) { return; }
// from ComponentUI update
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
// from BasicLabelUI paint
final FontMetrics fm = g.getFontMetrics();
Insets paintViewInsets = getInsets(null);
paintViewInsets.left += 10;
Rectangle paintViewR = new Rectangle(paintViewInsets.left, paintViewInsets.top, getWidth() - (paintViewInsets.left + paintViewInsets.right), getHeight() - (paintViewInsets.top + paintViewInsets.bottom));
Rectangle paintIconR = new Rectangle();
Rectangle paintTextR = new Rectangle();
final String clippedText = layoutCL(this, fm, text, icon, paintViewR, paintIconR, paintTextR);
if (icon != null) {
icon.paintIcon(this, g, paintIconR.x + 5, paintIconR.y);
}
if (text != null) {
final int textX = paintTextR.x;
final int textY = paintTextR.y + fm.getAscent() + 1;
if (isEnabled()) {
// Color background = fIsSelected ? getForeground() : getBackground();
final Color background = getBackground();
g.setColor(background);
g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);
g.setColor(getForeground());
SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
} else {
final Color background = getBackground();
g.setColor(background);
g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);
g.setColor(background.brighter());
SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
g.setColor(background.darker());
SwingUtilities2.drawString(filechooser, g, clippedText, textX + 1, textY + 1);
}
}
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class FileRenderer extends MacFCTableCellRenderer {
public FileRenderer(final Font f) {
super(f);
}
public Component getTableCellRendererComponent(final JTable list,
final Object value,
final boolean isSelected,
final boolean cellHasFocus,
final int index,
final int col) {
super.getTableCellRendererComponent(list, value, isSelected, false,
index,
col); // No focus border, thanks
final File file = (File)value;
final JFileChooser fc = getFileChooser();
setText(fc.getName(file));
setIcon(fc.getIcon(file));
setEnabled(isSelectableInList(file));
return this;
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class DateRenderer extends MacFCTableCellRenderer {
public DateRenderer(final Font f) {
super(f);
}
public Component getTableCellRendererComponent(final JTable list,
final Object value,
final boolean isSelected,
final boolean cellHasFocus,
final int index,
final int col) {
super.getTableCellRendererComponent(list, value, isSelected, false,
index, col);
final File file = (File)fFileList.getValueAt(index, 0);
setEnabled(isSelectableInList(file));
final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);
final Date date = (Date)value;
if (date != null) {
setText(formatter.format(date));
} else {
setText("");
}
return this;
}
}
@Override
public Dimension getPreferredSize(final JComponent c) {
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
@Override
public Dimension getMinimumSize(final JComponent c) {
return new Dimension(MIN_WIDTH, MIN_HEIGHT);
}
@Override
public Dimension getMaximumSize(final JComponent c) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@SuppressWarnings("serial") // anonymous class
protected ListCellRenderer<File> createDirectoryComboBoxRenderer(final JFileChooser fc) {
return new AquaComboBoxRendererInternal<File>(directoryComboBox) {
public Component getListCellRendererComponent(final JList<? extends File> list,
final File directory,
final int index,
final boolean isSelected,
final boolean cellHasFocus) {
super.getListCellRendererComponent(list, directory, index, isSelected, cellHasFocus);
if (directory == null) {
setText("");
return this;
}
final JFileChooser chooser = getFileChooser();
setText(chooser.getName(directory));
setIcon(chooser.getIcon(directory));
return this;
}
};
}
//
// DataModel for DirectoryComboxbox
//
protected DirectoryComboBoxModel createDirectoryComboBoxModel(final JFileChooser fc) {
return new DirectoryComboBoxModel();
}
/**
* Data model for a type-face selection combo-box.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class DirectoryComboBoxModel extends AbstractListModel<File> implements ComboBoxModel<File> {
Vector<File> fDirectories = new Vector<File>();
int topIndex = -1;
int fPathCount = 0;
File fSelectedDirectory = null;
public DirectoryComboBoxModel() {
super();
// Add the current directory to the model, and make it the
// selectedDirectory
addItem(getFileChooser().getCurrentDirectory());
}
/**
* Removes the selected directory, and clears out the
* path file entries leading up to that directory.
*/
private void removeSelectedDirectory() {
fDirectories.removeAllElements();
fPathCount = 0;
fSelectedDirectory = null;
// dump();
}
/**
* 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.
*/
void addItem(final File directory) {
if (directory == null) { return; }
if (fSelectedDirectory != null) {
removeSelectedDirectory();
}
// create File instances of each directory leading up to the top
File f = directory.getAbsoluteFile();
final Vector<File> path = new Vector<File>(10);
while (f.getParent() != null) {
path.addElement(f);
f = getFileChooser().getFileSystemView().createFileObject(f.getParent());
};
// Add root file (the desktop) to the model
final File[] roots = getFileChooser().getFileSystemView().getRoots();
for (final File element : roots) {
path.addElement(element);
}
fPathCount = path.size();
// insert all the path fDirectories leading up to the
// selected directory in reverse order (current directory at top)
for (int i = 0; i < path.size(); i++) {
fDirectories.addElement(path.elementAt(i));
}
setSelectedItem(fDirectories.elementAt(0));
// dump();
}
public void setSelectedItem(final Object selectedDirectory) {
this.fSelectedDirectory = (File)selectedDirectory;
fireContentsChanged(this, -1, -1);
}
public Object getSelectedItem() {
return fSelectedDirectory;
}
public int getSize() {
return fDirectories.size();
}
public File getElementAt(final int index) {
return fDirectories.elementAt(index);
}
}
//
// Renderer for Types ComboBox
//
@SuppressWarnings("serial") // anonymous class
protected ListCellRenderer<FileFilter> createFilterComboBoxRenderer() {
return new AquaComboBoxRendererInternal<FileFilter>(filterComboBox) {
public Component getListCellRendererComponent(final JList<? extends FileFilter> list,
final FileFilter filter,
final int index,
final boolean isSelected,
final boolean cellHasFocus) {
super.getListCellRendererComponent(list, filter, index, isSelected, cellHasFocus);
if (filter != null) setText(filter.getDescription());
return this;
}
};
}
//
// DataModel for Types Comboxbox
//
protected FilterComboBoxModel createFilterComboBoxModel() {
return new FilterComboBoxModel();
}
/**
* Data model for a type-face selection combo-box.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class FilterComboBoxModel extends AbstractListModel<FileFilter> implements ComboBoxModel<FileFilter>,
PropertyChangeListener {
protected FileFilter[] filters;
Object oldFileFilter = getFileChooser().getFileFilter();
protected FilterComboBoxModel() {
super();
filters = getFileChooser().getChoosableFileFilters();
}
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) {
setSelectedItem(e.getNewValue());
}
}
public void setSelectedItem(Object filter) {
if (filter != null && !isSelectedFileFilterInModel(filter)) {
oldFileFilter = filter;
getFileChooser().setFileFilter((FileFilter) filter);
fireContentsChanged(this, -1, -1);
}
}
private boolean isSelectedFileFilterInModel(Object filter) {
return Objects.equals(filter, oldFileFilter);
}
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 (FileFilter filter : filters) {
if (filter == currentFilter) {
found = true;
}
}
if(found == false) {
getFileChooser().addChoosableFileFilter(currentFilter);
}
}
return getFileChooser().getFileFilter();
}
public int getSize() {
if(filters != null) {
return filters.length;
} else {
return 0;
}
}
public FileFilter 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;
}
}
}
private boolean containsFileFilter(Object fileFilter) {
return Objects.equals(fileFilter, getFileChooser().getFileFilter());
}
/**
* Acts when FilterComboBox has changed the selected item.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class FilterComboBoxAction extends AbstractAction {
protected FilterComboBoxAction() {
super("FilterComboBoxAction");
}
public void actionPerformed(final ActionEvent e) {
Object selectedFilter = filterComboBox.getSelectedItem();
if (!containsFileFilter(selectedFilter)) {
getFileChooser().setFileFilter((FileFilter) selectedFilter);
}
}
}
/**
* Acts when DirectoryComboBox has changed the selected item.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class DirectoryComboBoxAction extends AbstractAction {
protected DirectoryComboBoxAction() {
super("DirectoryComboBoxAction");
}
public void actionPerformed(final ActionEvent e) {
getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem());
}
}
// Sorting Table operations
@SuppressWarnings("serial") // Superclass is not serializable across versions
class JSortingTableHeader extends JTableHeader {
public JSortingTableHeader(final TableColumnModel cm) {
super(cm);
setReorderingAllowed(true); // This causes mousePress to call setDraggedColumn
}
// One sort state for each column. Both are ascending by default
final boolean fSortAscending[] = {true, true};
// Instead of dragging, it selects which one to sort by
public void setDraggedColumn(final TableColumn aColumn) {
if (aColumn != null) {
final int colIndex = aColumn.getModelIndex();
if (colIndex != fSortColumn) {
filechooser.firePropertyChange(AquaFileSystemModel.SORT_BY_CHANGED, fSortColumn, colIndex);
fSortColumn = colIndex;
} else {
fSortAscending[colIndex] = !fSortAscending[colIndex];
filechooser.firePropertyChange(AquaFileSystemModel.SORT_ASCENDING_CHANGED, !fSortAscending[colIndex], fSortAscending[colIndex]);
}
// Need to repaint the highlighted column.
repaint();
}
}
// This stops mouseDrags from moving the column
public TableColumn getDraggedColumn() {
return null;
}
protected TableCellRenderer createDefaultRenderer() {
final DefaultTableCellRenderer label = new AquaTableCellRenderer();
label.setHorizontalAlignment(SwingConstants.LEFT);
return label;
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {
public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
if (localTable != null) {
final JTableHeader header = localTable.getTableHeader();
if (header != null) {
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(UIManager.getFont("TableHeader.font"));
}
}
setText((value == null) ? "" : value.toString());
// Modify the table "border" to draw smaller, and with the titles in the right position
// and sort indicators, just like an NSSave/Open panel.
final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();
cellBorder.setSelected(column == fSortColumn);
final int horizontalShift = (column == 0 ? 35 : 10);
cellBorder.setHorizontalShift(horizontalShift);
if (column == fSortColumn) {
cellBorder.setSortOrder(fSortAscending[column] ? AquaTableHeaderBorder.SORT_ASCENDING : AquaTableHeaderBorder.SORT_DECENDING);
} else {
cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);
}
setBorder(cellBorder);
return this;
}
}
}
public void installComponents(final JFileChooser fc) {
JPanel tPanel; // temp panel
// set to a Y BoxLayout. The chooser will be laid out top to bottom.
fc.setLayout(new BoxLayout(fc, BoxLayout.Y_AXIS));
fc.add(Box.createRigidArea(vstrut10));
// construct the top panel
final JPanel topPanel = new JPanel();
topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
fc.add(topPanel);
fc.add(Box.createRigidArea(vstrut10));
// Add the textfield pane
fTextfieldPanel = new JPanel();
fTextfieldPanel.setLayout(new BorderLayout());
// setBottomPanelForMode will make this visible if we need it
fTextfieldPanel.setVisible(false);
topPanel.add(fTextfieldPanel);
tPanel = new JPanel();
tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.Y_AXIS));
final JPanel labelArea = new JPanel();
labelArea.setLayout(new FlowLayout(FlowLayout.CENTER));
fTextFieldLabel = new JLabel(fileNameLabelText);
labelArea.add(fTextFieldLabel);
// text field
filenameTextField = new JTextField();
fTextFieldLabel.setLabelFor(filenameTextField);
filenameTextField.addActionListener(getAction(kOpen));
filenameTextField.addFocusListener(new SaveTextFocusListener());
final Dimension minSize = filenameTextField.getMinimumSize();
Dimension d = new Dimension(250, (int)minSize.getHeight());
filenameTextField.setPreferredSize(d);
filenameTextField.setMaximumSize(d);
labelArea.add(filenameTextField);
final File f = fc.getSelectedFile();
if (f != null) {
setFileName(fc.getName(f));
} else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
setFileName(newFileDefaultName);
}
tPanel.add(labelArea);
// separator line
@SuppressWarnings("serial") // anonymous class
final JSeparator sep = new JSeparator(){
public Dimension getPreferredSize() {
return new Dimension(((JComponent)getParent()).getWidth(), 3);
}
};
tPanel.add(Box.createRigidArea(new Dimension(1, 8)));
tPanel.add(sep);
tPanel.add(Box.createRigidArea(new Dimension(1, 7)));
fTextfieldPanel.add(tPanel, BorderLayout.CENTER);
// DirectoryComboBox, left-justified, 200x20 not including drop shadow
directoryComboBox = new JComboBox<>();
directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight");
fDirectoryComboBoxModel = createDirectoryComboBoxModel(fc);
directoryComboBox.setModel(fDirectoryComboBoxModel);
directoryComboBox.addActionListener(directoryComboBoxAction);
directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
directoryComboBox.setToolTipText(directoryComboBoxToolTipText);
d = new Dimension(250, (int)directoryComboBox.getMinimumSize().getHeight());
directoryComboBox.setPreferredSize(d);
directoryComboBox.setMaximumSize(d);
topPanel.add(directoryComboBox);
// ************************************** //
// ** Add the directory/Accessory pane ** //
// ************************************** //
final JPanel centerPanel = new JPanel(new BorderLayout());
fc.add(centerPanel);
// Accessory pane (equiv to Preview pane in NavServices)
final JComponent accessory = fc.getAccessory();
if (accessory != null) {
getAccessoryPanel().add(accessory);
}
centerPanel.add(getAccessoryPanel(), BorderLayout.LINE_START);
// Directory list(table), right-justified, resizable
final JPanel p = createList(fc);
p.setMinimumSize(LIST_MIN_SIZE);
centerPanel.add(p, BorderLayout.CENTER);
// ********************************** //
// **** Construct the bottom panel ** //
// ********************************** //
fBottomPanel = new JPanel();
fBottomPanel.setLayout(new BoxLayout(fBottomPanel, BoxLayout.Y_AXIS));
fc.add(fBottomPanel);
// Filter label and combobox.
// I know it's unMaclike, but the filter goes on Directory_only too.
tPanel = new JPanel();
tPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
tPanel.setBorder(AquaGroupBorder.getTitlelessBorder());
final JLabel formatLabel = new JLabel(filesOfTypeLabelText);
tPanel.add(formatLabel);
// Combobox
filterComboBoxModel = createFilterComboBoxModel();
fc.addPropertyChangeListener(filterComboBoxModel);
filterComboBox = new JComboBox<>(filterComboBoxModel);
formatLabel.setLabelFor(filterComboBox);
filterComboBox.setRenderer(createFilterComboBoxRenderer());
d = new Dimension(220, (int)filterComboBox.getMinimumSize().getHeight());
filterComboBox.setPreferredSize(d);
filterComboBox.setMaximumSize(d);
filterComboBox.addActionListener(filterComboBoxAction);
filterComboBox.setOpaque(false);
tPanel.add(filterComboBox);
fBottomPanel.add(tPanel);
// fDirectoryPanel: New, Open, Cancel, Approve buttons, right-justified, 82x22
// (sometimes the NewFolder and OpenFolder buttons are invisible)
fDirectoryPanel = new JPanel();
fDirectoryPanel.setLayout(new BoxLayout(fDirectoryPanel, BoxLayout.PAGE_AXIS));
JPanel directoryPanel = new JPanel(new BorderLayout());
JPanel newFolderButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
newFolderButtonPanel.add(Box.createHorizontalStrut(20));
fNewFolderButton = createNewFolderButton(); // Because we hide it depending on style
newFolderButtonPanel.add(fNewFolderButton);
directoryPanel.add(newFolderButtonPanel, BorderLayout.LINE_START);
JPanel approveCancelButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));
fOpenButton = createButton(kOpenDirectory, openButtonText);
approveCancelButtonPanel.add(fOpenButton);
approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
fCancelButton = createButton(kCancel, null);
approveCancelButtonPanel.add(fCancelButton);
approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
// The ApproveSelection button
fApproveButton = new JButton();
fApproveButton.addActionListener(fApproveSelectionAction);
approveCancelButtonPanel.add(fApproveButton);
approveCancelButtonPanel.add(Box.createHorizontalStrut(20));
directoryPanel.add(approveCancelButtonPanel, BorderLayout.LINE_END);
fDirectoryPanel.add(Box.createVerticalStrut(5));
fDirectoryPanel.add(directoryPanel);
fDirectoryPanel.add(Box.createVerticalStrut(12));
fDirectoryPanelSpacer = Box.createRigidArea(hstrut10);
if (fc.getControlButtonsAreShown()) {
fBottomPanel.add(fDirectoryPanelSpacer);
fBottomPanel.add(fDirectoryPanel);
}
setBottomPanelForMode(fc); // updates ApproveButtonText etc
// don't create til after the FCSubpanel and buttons are made
filenameTextField.getDocument().addDocumentListener(new SaveTextDocumentListener());
}
void setDefaultButtonForMode(final JFileChooser fc) {
final JButton defaultButton = fSubPanel.getDefaultButton(fc);
final JRootPane root = defaultButton.getRootPane();
if (root != null) {
root.setDefaultButton(defaultButton);
}
}
// Macs start with their focus in text areas if they have them,
// lists otherwise (the other plafs start with the focus on approveButton)
void setFocusForMode(final JFileChooser fc) {
final JComponent focusComponent = fSubPanel.getFocusComponent(fc);
if (focusComponent != null) {
focusComponent.requestFocus();
}
}
// Enable/disable buttons as needed for the current selection/focus state
void updateButtonState(final JFileChooser fc) {
fSubPanel.updateButtonState(fc, getFirstSelectedItem());
updateApproveButton(fc);
}
void updateApproveButton(final JFileChooser chooser) {
fApproveButton.setText(getApproveButtonText(chooser));
fApproveButton.setToolTipText(getApproveButtonToolTipText(chooser));
fApproveButton.setMnemonic(getApproveButtonMnemonic(chooser));
fCancelButton.setToolTipText(getCancelButtonToolTipText(chooser));
}
// Lazy-init the subpanels
synchronized FCSubpanel getSaveFilePanel() {
if (fSaveFilePanel == null) fSaveFilePanel = new SaveFilePanel();
return fSaveFilePanel;
}
synchronized FCSubpanel getOpenFilePanel() {
if (fOpenFilePanel == null) fOpenFilePanel = new OpenFilePanel();
return fOpenFilePanel;
}
synchronized FCSubpanel getOpenDirOrAnyPanel() {
if (fOpenDirOrAnyPanel == null) fOpenDirOrAnyPanel = new OpenDirOrAnyPanel();
return fOpenDirOrAnyPanel;
}
synchronized FCSubpanel getCustomFilePanel() {
if (fCustomFilePanel == null) fCustomFilePanel = new CustomFilePanel();
return fCustomFilePanel;
}
synchronized FCSubpanel getCustomDirOrAnyPanel() {
if (fCustomDirOrAnyPanel == null) fCustomDirOrAnyPanel = new CustomDirOrAnyPanel();
return fCustomDirOrAnyPanel;
}
void setBottomPanelForMode(final JFileChooser fc) {
if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) fSubPanel = getSaveFilePanel();
else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) {
if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getOpenFilePanel();
else fSubPanel = getOpenDirOrAnyPanel();
} else if (fc.getDialogType() == JFileChooser.CUSTOM_DIALOG) {
if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getCustomFilePanel();
else fSubPanel = getCustomDirOrAnyPanel();
}
fSubPanel.installPanel(fc, true);
updateApproveButton(fc);
updateButtonState(fc);
setDefaultButtonForMode(fc);
setFocusForMode(fc);
fc.invalidate();
}
// fTextfieldPanel and fDirectoryPanel both have NewFolder buttons; only one should be visible at a time
JButton createNewFolderButton() {
final JButton b = new JButton(newFolderButtonText);
b.setToolTipText(newFolderToolTipText);
b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
b.setHorizontalTextPosition(SwingConstants.LEFT);
b.setAlignmentX(Component.LEFT_ALIGNMENT);
b.setAlignmentY(Component.CENTER_ALIGNMENT);
b.addActionListener(getAction(kNewFolder));
return b;
}
JButton createButton(final int which, String label) {
if (label == null) label = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[0]);
final int mnemonic = UIManager.getInt(sDataPrefix + sButtonKinds[which] + sButtonData[1]);
final String tipText = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[2]);
final JButton b = new JButton(label);
b.setMnemonic(mnemonic);
b.setToolTipText(tipText);
b.addActionListener(getAction(which));
return b;
}
AbstractAction getAction(final int which) {
return fButtonActions[which];
}
public void uninstallComponents(final JFileChooser fc) { //$ Metal (on which this is based) doesn't uninstall its components.
}
// Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field
// with that file's display name.
protected class FileListMouseListener extends MouseAdapter {
public void mouseClicked(final MouseEvent e) {
final Point p = e.getPoint();
final int row = fFileList.rowAtPoint(p);
final int column = fFileList.columnAtPoint(p);
// The autoscroller can generate drag events outside the Table's range.
if ((column == -1) || (row == -1)) { return; }
final File clickedFile = (File)(fFileList.getValueAt(row, 0));
// rdar://problem/3734130 -- don't populate the text field if this file isn't selectable in this mode.
if (isSelectableForMode(getFileChooser(), clickedFile)) {
// [3188387] Populate the file name field with the selected file name
// [3484163] It should also use the display name, not the actual name.
setFileName(fileView.getName(clickedFile));
}
}
}
protected JPanel createList(final JFileChooser fc) {
// The first part is similar to MetalFileChooserUI.createList - same kind of listeners
final JPanel p = new JPanel(new BorderLayout());
fFileList = new JTableExtension();
fFileList.setToolTipText(null); // Workaround for 2487689
fFileList.addMouseListener(new FileListMouseListener());
model = new AquaFileSystemModel(fc, fFileList, fColumnNames);
final MacListSelectionModel listSelectionModel = new MacListSelectionModel(model);
if (getFileChooser().isMultiSelectionEnabled()) {
listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
} else {
listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
fFileList.setModel(model);
fFileList.setSelectionModel(listSelectionModel);
fFileList.getSelectionModel().addListSelectionListener(createListSelectionListener(fc));
// Now we're different, because we're a table, not a list
fc.addPropertyChangeListener(model);
fFileList.addFocusListener(new SaveTextFocusListener());
final JTableHeader th = new JSortingTableHeader(fFileList.getColumnModel());
fFileList.setTableHeader(th);
fFileList.setRowMargin(0);
fFileList.setIntercellSpacing(new Dimension(0, 1));
fFileList.setShowVerticalLines(false);
fFileList.setShowHorizontalLines(false);
final Font f = fFileList.getFont(); //ThemeFont.GetThemeFont(AppearanceConstants.kThemeViewsFont);
//fc.setFont(f);
//fFileList.setFont(f);
fFileList.setDefaultRenderer(File.class, new FileRenderer(f));
fFileList.setDefaultRenderer(Date.class, new DateRenderer(f));
final FontMetrics fm = fFileList.getFontMetrics(f);
// Row height isn't based on the renderers. It defaults to 16 so we have to set it
fFileList.setRowHeight(Math.max(fm.getHeight(), fileIcon.getIconHeight() + 2));
// Add a binding for the file list that triggers return and escape
fFileList.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
// Add a binding for the file list that triggers the default button (see DefaultButtonAction)
fFileList.registerKeyboardAction(new DefaultButtonAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
fFileList.setDropTarget(dragAndDropTarget);
final JScrollPane scrollpane = new JScrollPane(fFileList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollpane.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
scrollpane.setCorner(ScrollPaneConstants.UPPER_TRAILING_CORNER, new ScrollPaneCornerPanel());
p.add(scrollpane, BorderLayout.CENTER);
return p;
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
protected class ScrollPaneCornerPanel extends JPanel {
final Border border = UIManager.getBorder("TableHeader.cellBorder");
protected void paintComponent(final Graphics g) {
border.paintBorder(this, g, 0, 0, getWidth() + 1, getHeight());
}
}
JComboBox<File> directoryComboBox;
DirectoryComboBoxModel fDirectoryComboBoxModel;
private final Action directoryComboBoxAction = new DirectoryComboBoxAction();
JTextField filenameTextField;
JTableExtension fFileList;
private FilterComboBoxModel filterComboBoxModel;
JComboBox<FileFilter> filterComboBox;
private final Action filterComboBoxAction = new FilterComboBoxAction();
private static final Dimension hstrut10 = new Dimension(10, 1);
private static final Dimension vstrut10 = new Dimension(1, 10);
private static final int PREF_WIDTH = 550;
private static final int PREF_HEIGHT = 400;
private static final int MIN_WIDTH = 400;
private static final int MIN_HEIGHT = 250;
private static final int LIST_MIN_WIDTH = 400;
private static final int LIST_MIN_HEIGHT = 100;
private static final Dimension LIST_MIN_SIZE = new Dimension(LIST_MIN_WIDTH, LIST_MIN_HEIGHT);
static String fileNameLabelText = null;
JLabel fTextFieldLabel = null;
private static String filesOfTypeLabelText = null;
private static String newFolderToolTipText = null;
static String newFolderAccessibleName = null;
private static final String[] fColumnNames = new String[2];
JPanel fTextfieldPanel; // Filename textfield for Save or Custom
private JPanel fDirectoryPanel; // NewFolder/OpenFolder/Cancel/Approve buttons
private Component fDirectoryPanelSpacer;
private JPanel fBottomPanel; // The panel that holds fDirectoryPanel and filterComboBox
private FCSubpanel fSaveFilePanel = null;
private FCSubpanel fOpenFilePanel = null;
private FCSubpanel fOpenDirOrAnyPanel = null;
private FCSubpanel fCustomFilePanel = null;
private FCSubpanel fCustomDirOrAnyPanel = null;
FCSubpanel fSubPanel = null; // Current FCSubpanel
JButton fApproveButton; // mode-specific behavior is managed by FCSubpanel.approveSelection
JButton fOpenButton; // for Directories
JButton fNewFolderButton; // for fDirectoryPanel
// ToolTip text varies with type of dialog
private JButton fCancelButton;
private final ApproveSelectionAction fApproveSelectionAction = new ApproveSelectionAction();
protected int fSortColumn = 0;
protected int fPackageIsTraversable = -1;
protected int fApplicationIsTraversable = -1;
protected static final int sGlobalPackageIsTraversable;
protected static final int sGlobalApplicationIsTraversable;
protected static final String PACKAGE_TRAVERSABLE_PROPERTY = "JFileChooser.packageIsTraversable";
protected static final String APPLICATION_TRAVERSABLE_PROPERTY = "JFileChooser.appBundleIsTraversable";
protected static final String[] sTraversableProperties = {"always", // Bundle is always traversable
"never", // Bundle is never traversable
"conditional"}; // Bundle is traversable on command click
protected static final int kOpenAlways = 0, kOpenNever = 1, kOpenConditional = 2;
AbstractAction[] fButtonActions = {fApproveSelectionAction, fApproveSelectionAction, new CancelSelectionAction(), new OpenSelectionAction(), null, new NewFolderAction()};
static int parseTraversableProperty(final String s) {
if (s == null) return -1;
for (int i = 0; i < sTraversableProperties.length; i++) {
if (s.equals(sTraversableProperties[i])) return i;
}
return -1;
}
static {
Object o = UIManager.get(PACKAGE_TRAVERSABLE_PROPERTY);
if (o != null && o instanceof String) sGlobalPackageIsTraversable = parseTraversableProperty((String)o);
else sGlobalPackageIsTraversable = kOpenConditional;
o = UIManager.get(APPLICATION_TRAVERSABLE_PROPERTY);
if (o != null && o instanceof String) sGlobalApplicationIsTraversable = parseTraversableProperty((String)o);
else sGlobalApplicationIsTraversable = kOpenConditional;
}
static final String sDataPrefix = "FileChooser.";
static final String[] sButtonKinds = {"openButton", "saveButton", "cancelButton", "openDirectoryButton", "helpButton", "newFolderButton"};
static final String[] sButtonData = {"Text", "Mnemonic", "ToolTipText"};
static final int kOpen = 0, kSave = 1, kCancel = 2, kOpenDirectory = 3, kHelp = 4, kNewFolder = 5;
/*-------
Possible states: Save, {Open, Custom}x{Files, File and Directory, Directory}
--------- */
// This class returns the values for the Custom type, to avoid duplicating code in the two Custom subclasses
abstract class FCSubpanel {
// Install the appropriate panels for this mode
abstract void installPanel(JFileChooser fc, boolean controlButtonsAreShown);
abstract void updateButtonState(JFileChooser fc, File f);
// Can this item be selected?
// if not, it's disabled in the list
boolean isSelectableInList(final JFileChooser fc, final File f) {
if (f == null) return false;
if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) return fc.isTraversable(f);
return fc.accept(f);
}
void approveSelection(final JFileChooser fc) {
fc.approveSelection();
}
JButton getDefaultButton(final JFileChooser fc) {
return fApproveButton;
}
// Default to the textfield, panels without one should subclass
JComponent getFocusComponent(final JFileChooser fc) {
return filenameTextField;
}
String getApproveButtonText(final JFileChooser fc) {
// Fallback to "choose"
return this.getApproveButtonText(fc, chooseButtonText);
}
// Try to get the custom text. If none, use the fallback
String getApproveButtonText(final JFileChooser fc, final String fallbackText) {
final String buttonText = fc.getApproveButtonText();
if (buttonText != null) {
buttonText.trim();
if (!buttonText.equals("")) return buttonText;
}
return fallbackText;
}
int getApproveButtonMnemonic(final JFileChooser fc) {
// Don't use a default
return fc.getApproveButtonMnemonic();
}
// No fallback
String getApproveButtonToolTipText(final JFileChooser fc) {
return getApproveButtonToolTipText(fc, null);
}
String getApproveButtonToolTipText(final JFileChooser fc, final String fallbackText) {
final String tooltipText = fc.getApproveButtonToolTipText();
if (tooltipText != null) {
tooltipText.trim();
if (!tooltipText.equals("")) return tooltipText;
}
return fallbackText;
}
String getCancelButtonToolTipText(final JFileChooser fc) {
return cancelChooseButtonToolTipText;
}
}
// Custom FILES_ONLY dialog
/*
NavServices Save appearance with Open behavior
Approve button label = Open when list has focus and a directory is selected, Custom otherwise
No OpenDirectory button - Approve button is overloaded
Default button / double click = Approve
Has text field
List - everything is enabled
*/
class CustomFilePanel extends FCSubpanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
fTextfieldPanel.setVisible(true); // do we really want one in multi-select? It's confusing
fOpenButton.setVisible(false);
fNewFolderButton.setVisible(true);
}
// If the list has focus, the mode depends on the selection
// - directory = open, file = approve
// If something else has focus and we have text, it's approve
// otherwise, it depends on selection again.
boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
final boolean selectionIsDirectory = (f != null && fc.isTraversable(f));
if (fFileList.hasFocus()) return selectionIsDirectory;
else if (textfieldIsValid()) return false;
return selectionIsDirectory;
}
// The approve button is overloaded to mean OpenDirectory or Save
void approveSelection(final JFileChooser fc) {
File f = getFirstSelectedItem();
if (inOpenDirectoryMode(fc, f)) {
openDirectory(f);
} else {
f = makeFile(fc, getFileName());
if (f != null) {
selectionInProgress = true;
getFileChooser().setSelectedFile(f);
selectionInProgress = false;
}
getFileChooser().approveSelection();
}
}
// The approve button should be enabled
// - if something in the list can be opened
// - if the textfield has something in it
void updateButtonState(final JFileChooser fc, final File f) {
boolean enabled = true;
if (!inOpenDirectoryMode(fc, f)) {
enabled = (f != null) || textfieldIsValid();
}
getApproveButton(fc).setEnabled(enabled);
// The OpenDirectory button should be disabled if there's no directory selected
fOpenButton.setEnabled(f != null && fc.isTraversable(f));
// Update the default button, since we may have disabled the current default.
setDefaultButtonForMode(fc);
}
// everything's enabled, because we don't know what they're doing with them
boolean isSelectableInList(final JFileChooser fc, final File f) {
if (f == null) return false;
return fc.accept(f);
}
String getApproveButtonToolTipText(final JFileChooser fc) {
// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
return super.getApproveButtonToolTipText(fc);
}
}
// All Save dialogs
/*
NavServices Save
Approve button label = Open when list has focus and a directory is selected, Save otherwise
No OpenDirectory button - Approve button is overloaded
Default button / double click = Approve
Has text field
Has NewFolder button (by text field)
List - only traversables are enabled
List is always SINGLE_SELECT
*/
// Subclasses CustomFilePanel because they look alike and have some common behavior
class SaveFilePanel extends CustomFilePanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
fTextfieldPanel.setVisible(true);
fOpenButton.setVisible(false);
fNewFolderButton.setVisible(true);
}
// only traversables are enabled, regardless of mode
// because all you can do is select the next folder to open
boolean isSelectableInList(final JFileChooser fc, final File f) {
return fc.accept(f) && fc.isTraversable(f);
}
// The approve button means 'approve the file name in the text field.'
void approveSelection(final JFileChooser fc) {
final File f = makeFile(fc, getFileName());
if (f != null) {
selectionInProgress = true;
getFileChooser().setSelectedFile(f);
selectionInProgress = false;
getFileChooser().approveSelection();
}
}
// The approve button should be enabled if the textfield has something in it
void updateButtonState(final JFileChooser fc, final File f) {
final boolean enabled = textfieldIsValid();
getApproveButton(fc).setEnabled(enabled);
}
String getApproveButtonText(final JFileChooser fc) {
// Get the custom text, or fallback to "Save"
return this.getApproveButtonText(fc, saveButtonText);
}
int getApproveButtonMnemonic(final JFileChooser fc) {
return saveButtonMnemonic;
}
String getApproveButtonToolTipText(final JFileChooser fc) {
// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
return this.getApproveButtonToolTipText(fc, saveButtonToolTipText);
}
String getCancelButtonToolTipText(final JFileChooser fc) {
return cancelSaveButtonToolTipText;
}
}
// Open FILES_ONLY
/*
NSOpenPanel-style
Approve button label = Open
Default button / double click = Approve
No text field
No NewFolder button
List - all items are enabled
*/
class OpenFilePanel extends FCSubpanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
fTextfieldPanel.setVisible(false);
fOpenButton.setVisible(false);
fNewFolderButton.setVisible(false);
setDefaultButtonForMode(fc);
}
boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
return (f != null && fc.isTraversable(f));
}
// Default to the list
JComponent getFocusComponent(final JFileChooser fc) {
return fFileList;
}
void updateButtonState(final JFileChooser fc, final File f) {
// Button is disabled if there's nothing selected
final boolean enabled = (f != null) && !fc.isTraversable(f);
getApproveButton(fc).setEnabled(enabled);
}
// all items are enabled
boolean isSelectableInList(final JFileChooser fc, final File f) {
return f != null && fc.accept(f);
}
String getApproveButtonText(final JFileChooser fc) {
// Get the custom text, or fallback to "Open"
return this.getApproveButtonText(fc, openButtonText);
}
int getApproveButtonMnemonic(final JFileChooser fc) {
return openButtonMnemonic;
}
String getApproveButtonToolTipText(final JFileChooser fc) {
return this.getApproveButtonToolTipText(fc, openButtonToolTipText);
}
String getCancelButtonToolTipText(final JFileChooser fc) {
return cancelOpenButtonToolTipText;
}
}
// used by open and custom panels for Directory only or files and directories
abstract class DirOrAnyPanel extends FCSubpanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
fOpenButton.setVisible(false);
}
JButton getDefaultButton(final JFileChooser fc) {
return getApproveButton(fc);
}
void updateButtonState(final JFileChooser fc, final File f) {
// Button is disabled if there's nothing selected
// Approve button is handled by the subclasses
// getApproveButton(fc).setEnabled(f != null);
// The OpenDirectory button should be disabled if there's no directory selected
// - we only check the first item
fOpenButton.setEnabled(false);
setDefaultButtonForMode(fc);
}
}
// Open FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
/*
NavServices Choose
Approve button label = Choose/Custom
Has OpenDirectory button
Default button / double click = OpenDirectory
No text field
List - files are disabled in DIRECTORIES_ONLY
*/
class OpenDirOrAnyPanel extends DirOrAnyPanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
super.installPanel(fc, controlButtonsAreShown);
fTextfieldPanel.setVisible(false);
fNewFolderButton.setVisible(false);
}
// Default to the list
JComponent getFocusComponent(final JFileChooser fc) {
return fFileList;
}
int getApproveButtonMnemonic(final JFileChooser fc) {
return chooseButtonMnemonic;
}
String getApproveButtonToolTipText(final JFileChooser fc) {
String fallbackText;
if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) fallbackText = chooseFolderButtonToolTipText;
else fallbackText = chooseItemButtonToolTipText;
return this.getApproveButtonToolTipText(fc, fallbackText);
}
void updateButtonState(final JFileChooser fc, final File f) {
// Button is disabled if there's nothing selected
getApproveButton(fc).setEnabled(f != null);
super.updateButtonState(fc, f);
}
}
// Custom FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
/*
No NavServices equivalent
Approve button label = user defined or Choose
Has OpenDirectory button
Default button / double click = OpenDirectory
Has text field
Has NewFolder button (by text field)
List - files are disabled in DIRECTORIES_ONLY
*/
class CustomDirOrAnyPanel extends DirOrAnyPanel {
void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
super.installPanel(fc, controlButtonsAreShown);
fTextfieldPanel.setVisible(true);
fNewFolderButton.setVisible(true);
}
// If there's text, make a file and select it
void approveSelection(final JFileChooser fc) {
final File f = makeFile(fc, getFileName());
if (f != null) {
selectionInProgress = true;
getFileChooser().setSelectedFile(f);
selectionInProgress = false;
}
getFileChooser().approveSelection();
}
void updateButtonState(final JFileChooser fc, final File f) {
// Button is disabled if there's nothing selected
getApproveButton(fc).setEnabled(f != null || textfieldIsValid());
super.updateButtonState(fc, f);
}
}
// See FileRenderer - documents in Save dialogs draw disabled, so they shouldn't be selected
@SuppressWarnings("serial") // Superclass is not serializable across versions
class MacListSelectionModel extends DefaultListSelectionModel {
AquaFileSystemModel fModel;
MacListSelectionModel(final AquaFileSystemModel model) {
fModel = model;
}
// Can the file be selected in this mode?
// (files are visible even if they can't be selected)
boolean isSelectableInListIndex(final int index) {
final File file = (File)fModel.getValueAt(index, 0);
return (file != null && isSelectableInList(file));
}
// Make sure everything in the selection interval is valid
void verifySelectionInterval(int index0, int index1, boolean isSetSelection) {
if (index0 > index1) {
final int tmp = index1;
index1 = index0;
index0 = tmp;
}
int start = index0;
int end;
do {
// Find the first selectable file in the range
for (; start <= index1; start++) {
if (isSelectableInListIndex(start)) break;
}
end = -1;
// Find the last selectable file in the range
for (int i = start; i <= index1; i++) {
if (!isSelectableInListIndex(i)) {
break;
}
end = i;
}
// Select the range
if (end >= 0) {
// If setting the selection, do "set" the first time to clear the old one
// after that do "add" to extend it
if (isSetSelection) {
super.setSelectionInterval(start, end);
isSetSelection = false;
} else {
super.addSelectionInterval(start, end);
}
start = end + 1;
} else {
break;
}
} while (start <= index1);
}
public void setAnchorSelectionIndex(final int anchorIndex) {
if (isSelectableInListIndex(anchorIndex)) super.setAnchorSelectionIndex(anchorIndex);
}
public void setLeadSelectionIndex(final int leadIndex) {
if (isSelectableInListIndex(leadIndex)) super.setLeadSelectionIndex(leadIndex);
}
public void setSelectionInterval(final int index0, final int index1) {
if (index0 == -1 || index1 == -1) { return; }
if ((getSelectionMode() == SINGLE_SELECTION) || (index0 == index1)) {
if (isSelectableInListIndex(index1)) super.setSelectionInterval(index1, index1);
} else {
verifySelectionInterval(index0, index1, true);
}
}
public void addSelectionInterval(final int index0, final int index1) {
if (index0 == -1 || index1 == -1) { return; }
if (index0 == index1) {
if (isSelectableInListIndex(index1)) super.addSelectionInterval(index1, index1);
return;
}
if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
setSelectionInterval(index0, index1);
return;
}
verifySelectionInterval(index0, index1, false);
}
}
// Convenience, to translate from the JList directory view to the Mac-style JTable
// & minimize diffs between this and BasicFileChooserUI
@SuppressWarnings("serial") // Superclass is not serializable across versions
class JTableExtension extends JTable {
public void setSelectedIndex(final int index) {
getSelectionModel().setSelectionInterval(index, index);
}
public void removeSelectedIndex(final int index) {
getSelectionModel().removeSelectionInterval(index, index);
}
public void ensureIndexIsVisible(final int index) {
final Rectangle cellBounds = getCellRect(index, 0, false);
if (cellBounds != null) {
scrollRectToVisible(cellBounds);
}
}
public int locationToIndex(final Point location) {
return rowAtPoint(location);
}
}
}