Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaFileChooserUI.java
38831 views
/*1* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.awt.datatransfer.*;29import java.awt.dnd.*;30import java.awt.event.*;31import java.beans.*;32import java.io.File;33import java.net.URI;34import java.text.DateFormat;35import java.util.*;3637import javax.swing.*;38import javax.swing.border.Border;39import javax.swing.event.*;40import javax.swing.filechooser.*;41import javax.swing.plaf.*;42import javax.swing.table.*;4344import sun.swing.SwingUtilities2;4546public class AquaFileChooserUI extends FileChooserUI {47/* FileView icons */48protected Icon directoryIcon = null;49protected Icon fileIcon = null;50protected Icon computerIcon = null;51protected Icon hardDriveIcon = null;52protected Icon floppyDriveIcon = null;5354protected Icon upFolderIcon = null;55protected Icon homeFolderIcon = null;56protected Icon listViewIcon = null;57protected Icon detailsViewIcon = null;5859protected int saveButtonMnemonic = 0;60protected int openButtonMnemonic = 0;61protected int cancelButtonMnemonic = 0;62protected int updateButtonMnemonic = 0;63protected int helpButtonMnemonic = 0;64protected int chooseButtonMnemonic = 0;6566private String saveTitleText = null;67private String openTitleText = null;68String newFolderTitleText = null;6970protected String saveButtonText = null;71protected String openButtonText = null;72protected String cancelButtonText = null;73protected String updateButtonText = null;74protected String helpButtonText = null;75protected String newFolderButtonText = null;76protected String chooseButtonText = null;7778//private String newFolderErrorSeparator = null;79String newFolderErrorText = null;80String newFolderExistsErrorText = null;81protected String fileDescriptionText = null;82protected String directoryDescriptionText = null;8384protected String saveButtonToolTipText = null;85protected String openButtonToolTipText = null;86protected String cancelButtonToolTipText = null;87protected String updateButtonToolTipText = null;88protected String helpButtonToolTipText = null;89protected String chooseItemButtonToolTipText = null; // Choose anything90protected String chooseFolderButtonToolTipText = null; // Choose folder91protected String directoryComboBoxToolTipText = null;92protected String filenameTextFieldToolTipText = null;93protected String filterComboBoxToolTipText = null;94protected String openDirectoryButtonToolTipText = null;9596protected String cancelOpenButtonToolTipText = null;97protected String cancelSaveButtonToolTipText = null;98protected String cancelChooseButtonToolTipText = null;99protected String cancelNewFolderButtonToolTipText = null;100101protected String desktopName = null;102String newFolderDialogPrompt = null;103String newFolderDefaultName = null;104private String newFileDefaultName = null;105String createButtonText = null;106107JFileChooser filechooser = null;108109private MouseListener doubleClickListener = null;110private PropertyChangeListener propertyChangeListener = null;111private AncestorListener ancestorListener = null;112private DropTarget dragAndDropTarget = null;113114private final AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter();115116private AquaFileSystemModel model;117118final AquaFileView fileView = new AquaFileView(this);119120boolean selectionInProgress = false;121122// The accessoryPanel is a container to place the JFileChooser accessory component123private JPanel accessoryPanel = null;124125//126// ComponentUI Interface Implementation methods127//128public static ComponentUI createUI(final JComponent c) {129return new AquaFileChooserUI((JFileChooser)c);130}131132public AquaFileChooserUI(final JFileChooser filechooser) {133super();134}135136public void installUI(final JComponent c) {137accessoryPanel = new JPanel(new BorderLayout());138filechooser = (JFileChooser)c;139140createModel();141142installDefaults(filechooser);143installComponents(filechooser);144installListeners(filechooser);145146AquaUtils.enforceComponentOrientation(filechooser, ComponentOrientation.getOrientation(Locale.getDefault()));147}148149public void uninstallUI(final JComponent c) {150uninstallListeners(filechooser);151uninstallComponents(filechooser);152uninstallDefaults(filechooser);153154if (accessoryPanel != null) {155accessoryPanel.removeAll();156}157158accessoryPanel = null;159getFileChooser().removeAll();160}161162protected void installListeners(final JFileChooser fc) {163doubleClickListener = createDoubleClickListener(fc, fFileList);164fFileList.addMouseListener(doubleClickListener);165166propertyChangeListener = createPropertyChangeListener(fc);167if (propertyChangeListener != null) {168fc.addPropertyChangeListener(propertyChangeListener);169}170if (model != null) fc.addPropertyChangeListener(model);171172ancestorListener = new AncestorListener(){173public void ancestorAdded(final AncestorEvent e) {174// Request defaultness for the appropriate button based on mode175setFocusForMode(getFileChooser());176// Request defaultness for the appropriate button based on mode177setDefaultButtonForMode(getFileChooser());178}179180public void ancestorRemoved(final AncestorEvent e) {181}182183public void ancestorMoved(final AncestorEvent e) {184}185};186fc.addAncestorListener(ancestorListener);187188fc.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);189dragAndDropTarget = new DropTarget(fc, DnDConstants.ACTION_COPY, new DnDHandler(), true);190fc.setDropTarget(dragAndDropTarget);191}192193protected void uninstallListeners(final JFileChooser fc) {194if (propertyChangeListener != null) {195fc.removePropertyChangeListener(propertyChangeListener);196}197fFileList.removeMouseListener(doubleClickListener);198fc.removePropertyChangeListener(model);199fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));200fc.removeAncestorListener(ancestorListener);201fc.setDropTarget(null);202ancestorListener = null;203}204205protected void installDefaults(final JFileChooser fc) {206installIcons(fc);207installStrings(fc);208setPackageIsTraversable(fc.getClientProperty(PACKAGE_TRAVERSABLE_PROPERTY));209setApplicationIsTraversable(fc.getClientProperty(APPLICATION_TRAVERSABLE_PROPERTY));210}211212protected void installIcons(final JFileChooser fc) {213directoryIcon = UIManager.getIcon("FileView.directoryIcon");214fileIcon = UIManager.getIcon("FileView.fileIcon");215computerIcon = UIManager.getIcon("FileView.computerIcon");216hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon");217}218219String getString(final String uiKey, final String fallback) {220final String result = UIManager.getString(uiKey);221return (result == null ? fallback : result);222}223224protected void installStrings(final JFileChooser fc) {225// Exist in basic.properties (though we might want to override)226fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText");227directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText");228newFolderErrorText = getString("FileChooser.newFolderErrorText", "Error occurred during folder creation");229230saveButtonText = UIManager.getString("FileChooser.saveButtonText");231openButtonText = UIManager.getString("FileChooser.openButtonText");232cancelButtonText = UIManager.getString("FileChooser.cancelButtonText");233updateButtonText = UIManager.getString("FileChooser.updateButtonText");234helpButtonText = UIManager.getString("FileChooser.helpButtonText");235236saveButtonMnemonic = UIManager.getInt("FileChooser.saveButtonMnemonic");237openButtonMnemonic = UIManager.getInt("FileChooser.openButtonMnemonic");238cancelButtonMnemonic = UIManager.getInt("FileChooser.cancelButtonMnemonic");239updateButtonMnemonic = UIManager.getInt("FileChooser.updateButtonMnemonic");240helpButtonMnemonic = UIManager.getInt("FileChooser.helpButtonMnemonic");241chooseButtonMnemonic = UIManager.getInt("FileChooser.chooseButtonMnemonic");242243saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText");244openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText");245cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText");246updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText");247helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText");248249// Mac-specific, but fallback to basic if it's missing250saveTitleText = getString("FileChooser.saveTitleText", saveButtonText);251openTitleText = getString("FileChooser.openTitleText", openButtonText);252253// Mac-specific, required254newFolderExistsErrorText = getString("FileChooser.newFolderExistsErrorText", "That name is already taken");255chooseButtonText = getString("FileChooser.chooseButtonText", "Choose");256newFolderButtonText = getString("FileChooser.newFolderButtonText", "New");257newFolderTitleText = getString("FileChooser.newFolderTitleText", "New Folder");258259if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {260fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");261} else {262fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");263}264265filesOfTypeLabelText = getString("FileChooser.filesOfTypeLabelText", "Format:");266267desktopName = getString("FileChooser.desktopName", "Desktop");268newFolderDialogPrompt = getString("FileChooser.newFolderPromptText", "Name of new folder:");269newFolderDefaultName = getString("FileChooser.untitledFolderName", "untitled folder");270newFileDefaultName = getString("FileChooser.untitledFileName", "untitled");271createButtonText = getString("FileChooser.createButtonText", "Create");272273fColumnNames[1] = getString("FileChooser.byDateText", "Date Modified");274fColumnNames[0] = getString("FileChooser.byNameText", "Name");275276// Mac-specific, optional277chooseItemButtonToolTipText = UIManager.getString("FileChooser.chooseItemButtonToolTipText");278chooseFolderButtonToolTipText = UIManager.getString("FileChooser.chooseFolderButtonToolTipText");279openDirectoryButtonToolTipText = UIManager.getString("FileChooser.openDirectoryButtonToolTipText");280281directoryComboBoxToolTipText = UIManager.getString("FileChooser.directoryComboBoxToolTipText");282filenameTextFieldToolTipText = UIManager.getString("FileChooser.filenameTextFieldToolTipText");283filterComboBoxToolTipText = UIManager.getString("FileChooser.filterComboBoxToolTipText");284285cancelOpenButtonToolTipText = UIManager.getString("FileChooser.cancelOpenButtonToolTipText");286cancelSaveButtonToolTipText = UIManager.getString("FileChooser.cancelSaveButtonToolTipText");287cancelChooseButtonToolTipText = UIManager.getString("FileChooser.cancelChooseButtonToolTipText");288cancelNewFolderButtonToolTipText = UIManager.getString("FileChooser.cancelNewFolderButtonToolTipText");289290newFolderTitleText = UIManager.getString("FileChooser.newFolderTitleText");291newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText");292newFolderAccessibleName = getString("FileChooser.newFolderAccessibleName", newFolderTitleText);293}294295protected void uninstallDefaults(final JFileChooser fc) {296uninstallIcons(fc);297uninstallStrings(fc);298}299300protected void uninstallIcons(final JFileChooser fc) {301directoryIcon = null;302fileIcon = null;303computerIcon = null;304hardDriveIcon = null;305floppyDriveIcon = null;306307upFolderIcon = null;308homeFolderIcon = null;309detailsViewIcon = null;310listViewIcon = null;311}312313protected void uninstallStrings(final JFileChooser fc) {314saveTitleText = null;315openTitleText = null;316newFolderTitleText = null;317318saveButtonText = null;319openButtonText = null;320cancelButtonText = null;321updateButtonText = null;322helpButtonText = null;323newFolderButtonText = null;324chooseButtonText = null;325326cancelOpenButtonToolTipText = null;327cancelSaveButtonToolTipText = null;328cancelChooseButtonToolTipText = null;329cancelNewFolderButtonToolTipText = null;330331saveButtonToolTipText = null;332openButtonToolTipText = null;333cancelButtonToolTipText = null;334updateButtonToolTipText = null;335helpButtonToolTipText = null;336chooseItemButtonToolTipText = null;337chooseFolderButtonToolTipText = null;338openDirectoryButtonToolTipText = null;339directoryComboBoxToolTipText = null;340filenameTextFieldToolTipText = null;341filterComboBoxToolTipText = null;342343newFolderDefaultName = null;344newFileDefaultName = null;345346desktopName = null;347}348349protected void createModel() {350}351352AquaFileSystemModel getModel() {353return model;354}355356/*357* Listen for filechooser property changes, such as358* the selected file changing, or the type of the dialog changing.359*/360// Taken almost verbatim from Metal361protected PropertyChangeListener createPropertyChangeListener(final JFileChooser fc) {362return new PropertyChangeListener(){363public void propertyChange(final PropertyChangeEvent e) {364final String prop = e.getPropertyName();365if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {366final File f = (File)e.getNewValue();367if (f != null) {368// Select the file in the list if the selected file didn't change as369// a result of a list click.370if (!selectionInProgress && getModel().contains(f)) {371fFileList.setSelectedIndex(getModel().indexOf(f));372}373374// [3643835] Need to populate the text field here. No-op on Open dialogs375// Note that this was removed for 3514735, but should not have been.376if (!f.isDirectory()) {377setFileName(getFileChooser().getName(f));378}379}380updateButtonState(getFileChooser());381} else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {382JFileChooser fileChooser = getFileChooser();383if (!fileChooser.isDirectorySelectionEnabled()) {384final File[] files = (File[]) e.getNewValue();385if (files != null) {386for (int selectedRow : fFileList.getSelectedRows()) {387File file = (File) fFileList.getValueAt(selectedRow, 0);388if (fileChooser.isTraversable(file)) {389fFileList.removeSelectedIndex(selectedRow);390}391}392}393}394} else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {395fFileList.clearSelection();396final File currentDirectory = getFileChooser().getCurrentDirectory();397if (currentDirectory != null) {398fDirectoryComboBoxModel.addItem(currentDirectory);399// Enable the newFolder action if the current directory400// is writable.401// PENDING(jeff) - broken - fix402getAction(kNewFolder).setEnabled(currentDirectory.canWrite());403}404updateButtonState(getFileChooser());405} else if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {406fFileList.clearSelection();407setBottomPanelForMode(getFileChooser()); // Also updates approve button408} else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {409if (getAccessoryPanel() != null) {410if (e.getOldValue() != null) {411getAccessoryPanel().remove((JComponent)e.getOldValue());412}413final JComponent accessory = (JComponent)e.getNewValue();414if (accessory != null) {415getAccessoryPanel().add(accessory, BorderLayout.CENTER);416}417}418} else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) {419updateApproveButton(getFileChooser());420getFileChooser().invalidate();421} else if (prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY) {422if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) {423fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");424} else {425fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");426}427fTextFieldLabel.setText(fileNameLabelText);428429// Mac doesn't show the text field or "new folder" button in 'Open' dialogs430setBottomPanelForMode(getFileChooser()); // Also updates approve button431} else if (prop.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {432getApproveButton(getFileChooser()).setMnemonic(getApproveButtonMnemonic(getFileChooser()));433} else if (prop.equals(PACKAGE_TRAVERSABLE_PROPERTY)) {434setPackageIsTraversable(e.getNewValue());435} else if (prop.equals(APPLICATION_TRAVERSABLE_PROPERTY)) {436setApplicationIsTraversable(e.getNewValue());437} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {438if (getFileChooser().isMultiSelectionEnabled()) {439fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);440} else {441fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);442}443} else if (prop.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {444doControlButtonsChanged(e);445}446}447};448}449450void setPackageIsTraversable(final Object o) {451int newProp = -1;452if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);453if (newProp != -1) fPackageIsTraversable = newProp;454else fPackageIsTraversable = sGlobalPackageIsTraversable;455}456457void setApplicationIsTraversable(final Object o) {458int newProp = -1;459if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);460if (newProp != -1) fApplicationIsTraversable = newProp;461else fApplicationIsTraversable = sGlobalApplicationIsTraversable;462}463464void doControlButtonsChanged(final PropertyChangeEvent e) {465if (getFileChooser().getControlButtonsAreShown()) {466fBottomPanel.add(fDirectoryPanelSpacer);467fBottomPanel.add(fDirectoryPanel);468} else {469fBottomPanel.remove(fDirectoryPanelSpacer);470fBottomPanel.remove(fDirectoryPanel);471}472}473474public String getFileName() {475if (filenameTextField != null) { return filenameTextField.getText(); }476return null;477}478479public String getDirectoryName() {480// PENDING(jeff) - get the name from the directory combobox481return null;482}483484public void setFileName(final String filename) {485if (filenameTextField != null) {486filenameTextField.setText(filename);487}488}489490public void setDirectoryName(final String dirname) {491// PENDING(jeff) - set the name in the directory combobox492}493494public void rescanCurrentDirectory(final JFileChooser fc) {495getModel().invalidateFileCache();496getModel().validateFileCache();497}498499public void ensureFileIsVisible(final JFileChooser fc, final File f) {500if (f == null) {501fFileList.requestFocusInWindow();502fFileList.ensureIndexIsVisible(-1);503return;504}505506getModel().runWhenDone(new Runnable() {507public void run() {508fFileList.requestFocusInWindow();509fFileList.ensureIndexIsVisible(getModel().indexOf(f));510}511});512}513514public JFileChooser getFileChooser() {515return filechooser;516}517518public JPanel getAccessoryPanel() {519return accessoryPanel;520}521522protected JButton getApproveButton(final JFileChooser fc) {523return fApproveButton;524}525526public int getApproveButtonMnemonic(final JFileChooser fc) {527return fSubPanel.getApproveButtonMnemonic(fc);528}529530public String getApproveButtonToolTipText(final JFileChooser fc) {531return fSubPanel.getApproveButtonToolTipText(fc);532}533534public String getApproveButtonText(final JFileChooser fc) {535return fSubPanel.getApproveButtonText(fc);536}537538protected String getCancelButtonToolTipText(final JFileChooser fc) {539return fSubPanel.getCancelButtonToolTipText(fc);540}541542// If the item's not selectable, it'll be visible but disabled in the list543boolean isSelectableInList(final File f) {544return fSubPanel.isSelectableInList(getFileChooser(), f);545}546547// Is this a file that the JFileChooser wants?548// Directories can be selected in the list regardless of mode549boolean isSelectableForMode(final JFileChooser fc, final File f) {550if (f == null) return false;551final int mode = fc.getFileSelectionMode();552if (mode == JFileChooser.FILES_AND_DIRECTORIES) return true;553boolean traversable = fc.isTraversable(f);554if (mode == JFileChooser.DIRECTORIES_ONLY) return traversable;555return !traversable;556}557558// ********************************************559// ************ Create Listeners **************560// ********************************************561562// From Basic563public ListSelectionListener createListSelectionListener(final JFileChooser fc) {564return new SelectionListener();565}566567protected class SelectionListener implements ListSelectionListener {568public void valueChanged(final ListSelectionEvent e) {569if (e.getValueIsAdjusting()) return;570571File f = null;572final int selectedRow = fFileList.getSelectedRow();573final JFileChooser chooser = getFileChooser();574boolean isSave = (chooser.getDialogType() == JFileChooser.SAVE_DIALOG);575if (selectedRow >= 0) {576f = (File)fFileList.getValueAt(selectedRow, 0);577}578579// Save dialog lists can't be multi select, because all we're selecting is the next folder to open580selectionInProgress = true;581if (!isSave && chooser.isMultiSelectionEnabled()) {582final int[] rows = fFileList.getSelectedRows();583int selectableCount = 0;584// Double-check that all the list selections are valid for this mode585// Directories can be selected in the list regardless of mode586if (rows.length > 0) {587for (final int element : rows) {588if (isSelectableForMode(chooser, (File)fFileList.getValueAt(element, 0))) selectableCount++;589}590}591if (selectableCount > 0) {592final File[] files = new File[selectableCount];593for (int i = 0, si = 0; i < rows.length; i++) {594f = (File)fFileList.getValueAt(rows[i], 0);595if (isSelectableForMode(chooser, f)) {596if (fileView.isAlias(f)) {597f = fileView.resolveAlias(f);598}599files[si++] = f;600}601}602chooser.setSelectedFiles(files);603} else {604chooser.setSelectedFiles(null);605}606} else {607chooser.setSelectedFiles(null);608chooser.setSelectedFile(f);609}610selectionInProgress = false;611}612}613614// When the Save textfield has the focus, the button should say "Save"615// Otherwise, it depends on the list selection616protected class SaveTextFocusListener implements FocusListener {617public void focusGained(final FocusEvent e) {618updateButtonState(getFileChooser());619}620621// Do nothing, we might be losing focus due to window deactivation622public void focusLost(final FocusEvent e) {623624}625}626627// When the Save textfield is empty and the button says "Save", it should be disabled628// Otherwise, it depends on the list selection629protected class SaveTextDocumentListener implements DocumentListener {630public void insertUpdate(final DocumentEvent e) {631textChanged();632}633634public void removeUpdate(final DocumentEvent e) {635textChanged();636}637638public void changedUpdate(final DocumentEvent e) {639640}641642void textChanged() {643updateButtonState(getFileChooser());644}645}646647// Opens the File object if it's a traversable directory648protected boolean openDirectory(final File f) {649if (getFileChooser().isTraversable(f)) {650fFileList.clearSelection();651// Resolve any aliases652final File original = fileView.resolveAlias(f);653getFileChooser().setCurrentDirectory(original);654updateButtonState(getFileChooser());655return true;656}657return false;658}659660// From Basic661protected class DoubleClickListener extends MouseAdapter {662JTableExtension list;663664public DoubleClickListener(final JTableExtension list) {665this.list = list;666}667668public void mouseClicked(final MouseEvent e) {669if (e.getClickCount() != 2) return;670671final int index = list.locationToIndex(e.getPoint());672if (index < 0) return;673674final File f = (File)((AquaFileSystemModel)list.getModel()).getElementAt(index);675if (openDirectory(f)) return;676677if (!isSelectableInList(f)) return;678getFileChooser().approveSelection();679}680}681682protected MouseListener createDoubleClickListener(final JFileChooser fc, final JTableExtension list) {683return new DoubleClickListener(list);684}685686// listens for drag events onto the JFileChooser and sets the selected file or directory687class DnDHandler extends DropTargetAdapter {688public void dragEnter(final DropTargetDragEvent dtde) {689tryToAcceptDrag(dtde);690}691692public void dragOver(final DropTargetDragEvent dtde) {693tryToAcceptDrag(dtde);694}695696public void dropActionChanged(final DropTargetDragEvent dtde) {697tryToAcceptDrag(dtde);698}699700public void drop(final DropTargetDropEvent dtde) {701if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {702handleFileDropEvent(dtde);703return;704}705706if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {707handleStringDropEvent(dtde);708return;709}710}711712protected void tryToAcceptDrag(final DropTargetDragEvent dtde) {713if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {714dtde.acceptDrag(DnDConstants.ACTION_COPY);715return;716}717718dtde.rejectDrag();719}720721protected void handleFileDropEvent(final DropTargetDropEvent dtde) {722dtde.acceptDrop(dtde.getDropAction());723final Transferable transferable = dtde.getTransferable();724725try {726final java.util.List<File> fileList = (java.util.List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);727dropFiles(fileList.toArray(new File[fileList.size()]));728dtde.dropComplete(true);729} catch (final Exception e) {730dtde.dropComplete(false);731}732}733734protected void handleStringDropEvent(final DropTargetDropEvent dtde) {735dtde.acceptDrop(dtde.getDropAction());736final Transferable transferable = dtde.getTransferable();737738final String stringData;739try {740stringData = (String)transferable.getTransferData(DataFlavor.stringFlavor);741} catch (final Exception e) {742dtde.dropComplete(false);743return;744}745746try {747final File fileAsPath = new File(stringData);748if (fileAsPath.exists()) {749dropFiles(new File[] {fileAsPath});750dtde.dropComplete(true);751return;752}753} catch (final Exception e) {754// try again755}756757try {758final File fileAsURI = new File(new URI(stringData));759if (fileAsURI.exists()) {760dropFiles(new File[] {fileAsURI});761dtde.dropComplete(true);762return;763}764} catch (final Exception e) {765// nothing more to do766}767768dtde.dropComplete(false);769}770771protected void dropFiles(final File[] files) {772final JFileChooser jfc = getFileChooser();773774if (files.length == 1) {775if (files[0].isDirectory()) {776jfc.setCurrentDirectory(files[0]);777return;778}779780if (!isSelectableForMode(jfc, files[0])) {781return;782}783}784785jfc.setSelectedFiles(files);786for (final File file : files) {787jfc.ensureFileIsVisible(file);788}789getModel().runWhenDone(new Runnable() {790public void run() {791final AquaFileSystemModel fileSystemModel = getModel();792for (final File element : files) {793final int index = fileSystemModel.indexOf(element);794if (index >= 0) fFileList.addRowSelectionInterval(index, index);795}796}797});798}799}800801// FileChooser UI PLAF methods802803/**804* Returns the default accept all file filter805*/806public FileFilter getAcceptAllFileFilter(final JFileChooser fc) {807return acceptAllFileFilter;808}809810public FileView getFileView(final JFileChooser fc) {811return fileView;812}813814/**815* Returns the title of this dialog816*/817public String getDialogTitle(final JFileChooser fc) {818if (fc.getDialogTitle() == null) {819if (getFileChooser().getDialogType() == JFileChooser.OPEN_DIALOG) {820return openTitleText;821} else if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) { return saveTitleText; }822}823return fc.getDialogTitle();824}825826// Utility to get the first selected item regardless of whether we're single or multi select827File getFirstSelectedItem() {828// Get the selected item829File selectedFile = null;830final int index = fFileList.getSelectedRow();831if (index >= 0) {832selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);833}834return selectedFile;835}836837// Make a file from the filename838File makeFile(final JFileChooser fc, final String filename) {839File selectedFile = null;840// whitespace is legal on Macs, even on beginning and end of filename841if (filename != null && !filename.equals("")) {842final FileSystemView fs = fc.getFileSystemView();843selectedFile = fs.createFileObject(filename);844if (!selectedFile.isAbsolute()) {845selectedFile = fs.createFileObject(fc.getCurrentDirectory(), filename);846}847}848return selectedFile;849}850851// Utility to tell if the textfield has anything in it852boolean textfieldIsValid() {853final String s = getFileName();854return (s != null && !s.equals(""));855}856857// Action to attach to the file list so we can override the default action858// of the table for the return key, which is to select the next line.859protected class DefaultButtonAction extends AbstractAction {860public void actionPerformed(final ActionEvent e) {861final JRootPane root = AquaFileChooserUI.this.getFileChooser().getRootPane();862final JFileChooser fc = AquaFileChooserUI.this.getFileChooser();863final JButton owner = root.getDefaultButton();864if (owner != null && SwingUtilities.getRootPane(owner) == root && owner.isEnabled()) {865owner.doClick(20);866} else if (!fc.getControlButtonsAreShown()) {867final JButton defaultButton = AquaFileChooserUI.this.fSubPanel.getDefaultButton(fc);868869if (defaultButton != null) {870defaultButton.doClick(20);871}872} else {873Toolkit.getDefaultToolkit().beep();874}875}876877public boolean isEnabled() {878return true;879}880}881882/**883* Creates a new folder.884*/885protected class NewFolderAction extends AbstractAction {886protected NewFolderAction() {887super(newFolderAccessibleName);888}889890// Muchlike showInputDialog, but we give it options instead of selectionValues891private Object showNewFolderDialog(final Component parentComponent, final Object message, final String title, final int messageType, final Icon icon, final Object[] options, final Object initialSelectionValue) {892final JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.OK_CANCEL_OPTION, icon, options, null);893894pane.setWantsInput(true);895pane.setInitialSelectionValue(initialSelectionValue);896897final JDialog dialog = pane.createDialog(parentComponent, title);898899pane.selectInitialValue();900dialog.setVisible(true);901dialog.dispose();902903final Object value = pane.getValue();904905if (value == null || value.equals(cancelButtonText)) {906return null;907}908return pane.getInputValue();909}910911public void actionPerformed(final ActionEvent e) {912final JFileChooser fc = getFileChooser();913final File currentDirectory = fc.getCurrentDirectory();914File newFolder = null;915final String[] options = {createButtonText, cancelButtonText};916final String filename = (String)showNewFolderDialog(fc, //parentComponent917newFolderDialogPrompt, // message918newFolderTitleText, // title919JOptionPane.PLAIN_MESSAGE, // messageType920null, // icon921options, // selectionValues922newFolderDefaultName); // initialSelectionValue923924if (filename != null) {925try {926newFolder = fc.getFileSystemView().createFileObject(currentDirectory, filename);927if (newFolder.exists()) {928JOptionPane.showMessageDialog(fc, newFolderExistsErrorText, "", JOptionPane.ERROR_MESSAGE);929return;930}931932newFolder.mkdirs();933} catch(final Exception exc) {934JOptionPane.showMessageDialog(fc, newFolderErrorText, "", JOptionPane.ERROR_MESSAGE);935return;936}937938openDirectory(newFolder);939}940}941}942943/**944* Responds to an Open, Save, or Choose request945*/946protected class ApproveSelectionAction extends AbstractAction {947public void actionPerformed(final ActionEvent e) {948fSubPanel.approveSelection(getFileChooser());949}950}951952/**953* Responds to an OpenDirectory request954*/955protected class OpenSelectionAction extends AbstractAction {956public void actionPerformed(final ActionEvent e) {957final int index = fFileList.getSelectedRow();958if (index >= 0) {959final File selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);960if (selectedFile != null) openDirectory(selectedFile);961}962}963}964965/**966* Responds to a cancel request.967*/968protected class CancelSelectionAction extends AbstractAction {969public void actionPerformed(final ActionEvent e) {970getFileChooser().cancelSelection();971}972973public boolean isEnabled() {974return getFileChooser().isEnabled();975}976}977978/**979* Rescans the files in the current directory980*/981protected class UpdateAction extends AbstractAction {982public void actionPerformed(final ActionEvent e) {983final JFileChooser fc = getFileChooser();984fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName()));985fc.rescanCurrentDirectory();986}987}988989// *****************************************990// ***** default AcceptAll file filter *****991// *****************************************992protected class AcceptAllFileFilter extends FileFilter {993public AcceptAllFileFilter() {994}995996public boolean accept(final File f) {997return true;998}9991000public String getDescription() {1001return UIManager.getString("FileChooser.acceptAllFileFilterText");1002}1003}10041005// Penultimate superclass is JLabel1006protected class MacFCTableCellRenderer extends DefaultTableCellRenderer {1007boolean fIsSelected = false;10081009public MacFCTableCellRenderer(final Font f) {1010super();1011setFont(f);1012setIconTextGap(10);1013}10141015public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {1016super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks1017fIsSelected = isSelected;1018return this;1019}10201021public boolean isSelected() {1022return fIsSelected && isEnabled();1023}10241025protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon, final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {1026return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());1027}10281029protected void paintComponent(final Graphics g) {1030final String text = getText();1031Icon icon = getIcon();1032if (icon != null && !isEnabled()) {1033final Icon disabledIcon = getDisabledIcon();1034if (disabledIcon != null) icon = disabledIcon;1035}10361037if ((icon == null) && (text == null)) { return; }10381039// from ComponentUI update1040g.setColor(getBackground());1041g.fillRect(0, 0, getWidth(), getHeight());10421043// from BasicLabelUI paint1044final FontMetrics fm = g.getFontMetrics();1045Insets paintViewInsets = getInsets(null);1046paintViewInsets.left += 10;10471048Rectangle paintViewR = new Rectangle(paintViewInsets.left, paintViewInsets.top, getWidth() - (paintViewInsets.left + paintViewInsets.right), getHeight() - (paintViewInsets.top + paintViewInsets.bottom));10491050Rectangle paintIconR = new Rectangle();1051Rectangle paintTextR = new Rectangle();10521053final String clippedText = layoutCL(this, fm, text, icon, paintViewR, paintIconR, paintTextR);10541055if (icon != null) {1056icon.paintIcon(this, g, paintIconR.x + 5, paintIconR.y);1057}10581059if (text != null) {1060final int textX = paintTextR.x;1061final int textY = paintTextR.y + fm.getAscent() + 1;1062if (isEnabled()) {1063// Color background = fIsSelected ? getForeground() : getBackground();1064final Color background = getBackground();10651066g.setColor(background);1067g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);10681069g.setColor(getForeground());1070SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);1071} else {1072final Color background = getBackground();1073g.setColor(background);1074g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);10751076g.setColor(background.brighter());1077SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);1078g.setColor(background.darker());1079SwingUtilities2.drawString(filechooser, g, clippedText, textX + 1, textY + 1);1080}1081}1082}10831084}10851086protected class FileRenderer extends MacFCTableCellRenderer {1087public FileRenderer(final Font f) {1088super(f);1089}10901091public Component getTableCellRendererComponent(final JTable list,1092final Object value,1093final boolean isSelected,1094final boolean cellHasFocus,1095final int index,1096final int col) {1097super.getTableCellRendererComponent(list, value, isSelected, false,1098index,1099col); // No focus border, thanks1100final File file = (File)value;1101final JFileChooser fc = getFileChooser();1102setText(fc.getName(file));1103setIcon(fc.getIcon(file));1104setEnabled(isSelectableInList(file));1105return this;1106}1107}11081109protected class DateRenderer extends MacFCTableCellRenderer {1110public DateRenderer(final Font f) {1111super(f);1112}11131114public Component getTableCellRendererComponent(final JTable list,1115final Object value,1116final boolean isSelected,1117final boolean cellHasFocus,1118final int index,1119final int col) {1120super.getTableCellRendererComponent(list, value, isSelected, false,1121index, col);1122final File file = (File)fFileList.getValueAt(index, 0);1123setEnabled(isSelectableInList(file));1124final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);1125final Date date = (Date)value;11261127if (date != null) {1128setText(formatter.format(date));1129} else {1130setText("");1131}11321133return this;1134}1135}11361137@Override1138public Dimension getPreferredSize(final JComponent c) {1139return new Dimension(PREF_WIDTH, PREF_HEIGHT);1140}11411142@Override1143public Dimension getMinimumSize(final JComponent c) {1144return new Dimension(MIN_WIDTH, MIN_HEIGHT);1145}11461147@Override1148public Dimension getMaximumSize(final JComponent c) {1149return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);1150}11511152protected ListCellRenderer createDirectoryComboBoxRenderer(final JFileChooser fc) {1153return new AquaComboBoxRendererInternal(directoryComboBox) {1154public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {1155super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);1156final File directory = (File)value;1157if (directory == null) {1158setText("");1159return this;1160}11611162final JFileChooser chooser = getFileChooser();1163setText(chooser.getName(directory));1164setIcon(chooser.getIcon(directory));1165return this;1166}1167};1168}11691170//1171// DataModel for DirectoryComboxbox1172//1173protected DirectoryComboBoxModel createDirectoryComboBoxModel(final JFileChooser fc) {1174return new DirectoryComboBoxModel();1175}11761177/**1178* Data model for a type-face selection combo-box.1179*/1180protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel {1181Vector<File> fDirectories = new Vector<File>();1182int topIndex = -1;1183int fPathCount = 0;11841185File fSelectedDirectory = null;11861187public DirectoryComboBoxModel() {1188super();1189// Add the current directory to the model, and make it the1190// selectedDirectory1191addItem(getFileChooser().getCurrentDirectory());1192}11931194/**1195* Removes the selected directory, and clears out the1196* path file entries leading up to that directory.1197*/1198private void removeSelectedDirectory() {1199fDirectories.removeAllElements();1200fPathCount = 0;1201fSelectedDirectory = null;1202// dump();1203}12041205/**1206* Adds the directory to the model and sets it to be selected,1207* additionally clears out the previous selected directory and1208* the paths leading up to it, if any.1209*/1210void addItem(final File directory) {1211if (directory == null) { return; }1212if (fSelectedDirectory != null) {1213removeSelectedDirectory();1214}12151216// create File instances of each directory leading up to the top1217File f = directory.getAbsoluteFile();1218final Vector<File> path = new Vector<File>(10);1219while (f.getParent() != null) {1220path.addElement(f);1221f = getFileChooser().getFileSystemView().createFileObject(f.getParent());1222};12231224// Add root file (the desktop) to the model1225final File[] roots = getFileChooser().getFileSystemView().getRoots();1226for (final File element : roots) {1227path.addElement(element);1228}1229fPathCount = path.size();12301231// insert all the path fDirectories leading up to the1232// selected directory in reverse order (current directory at top)1233for (int i = 0; i < path.size(); i++) {1234fDirectories.addElement(path.elementAt(i));1235}12361237setSelectedItem(fDirectories.elementAt(0));12381239// dump();1240}12411242public void setSelectedItem(final Object selectedDirectory) {1243this.fSelectedDirectory = (File)selectedDirectory;1244fireContentsChanged(this, -1, -1);1245}12461247public Object getSelectedItem() {1248return fSelectedDirectory;1249}12501251public int getSize() {1252return fDirectories.size();1253}12541255public Object getElementAt(final int index) {1256return fDirectories.elementAt(index);1257}1258}12591260//1261// Renderer for Types ComboBox1262//1263protected ListCellRenderer createFilterComboBoxRenderer() {1264return new AquaComboBoxRendererInternal(filterComboBox) {1265public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {1266super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);1267final FileFilter filter = (FileFilter)value;1268if (filter != null) setText(filter.getDescription());1269return this;1270}1271};1272}12731274//1275// DataModel for Types Comboxbox1276//1277protected FilterComboBoxModel createFilterComboBoxModel() {1278return new FilterComboBoxModel();1279}12801281/**1282* Data model for a type-face selection combo-box.1283*/1284protected class FilterComboBoxModel extends AbstractListModel<FileFilter> implements ComboBoxModel<FileFilter>,1285PropertyChangeListener {1286protected FileFilter[] filters;1287protected FilterComboBoxModel() {1288super();1289filters = getFileChooser().getChoosableFileFilters();1290}12911292public void propertyChange(PropertyChangeEvent e) {1293String prop = e.getPropertyName();1294if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {1295filters = (FileFilter[]) e.getNewValue();1296fireContentsChanged(this, -1, -1);1297} else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {1298setSelectedItem(e.getNewValue());1299}1300}13011302public void setSelectedItem(Object filter) {1303if (filter != null && !containsFileFilter(filter)) {1304getFileChooser().setFileFilter((FileFilter) filter);1305fireContentsChanged(this, -1, -1);1306}1307}13081309public Object getSelectedItem() {1310// Ensure that the current filter is in the list.1311// NOTE: we shouldnt' have to do this, since JFileChooser adds1312// the filter to the choosable filters list when the filter1313// is set. Lets be paranoid just in case someone overrides1314// setFileFilter in JFileChooser.1315FileFilter currentFilter = getFileChooser().getFileFilter();1316boolean found = false;1317if(currentFilter != null) {1318for (FileFilter filter : filters) {1319if (filter == currentFilter) {1320found = true;1321}1322}1323if(found == false) {1324getFileChooser().addChoosableFileFilter(currentFilter);1325}1326}1327return getFileChooser().getFileFilter();1328}13291330public int getSize() {1331if(filters != null) {1332return filters.length;1333} else {1334return 0;1335}1336}13371338public FileFilter getElementAt(int index) {1339if(index > getSize() - 1) {1340// This shouldn't happen. Try to recover gracefully.1341return getFileChooser().getFileFilter();1342}1343if(filters != null) {1344return filters[index];1345} else {1346return null;1347}1348}1349}13501351private boolean containsFileFilter(Object fileFilter) {1352return Objects.equals(fileFilter, getFileChooser().getFileFilter());1353}13541355/**1356* Acts when FilterComboBox has changed the selected item.1357*/1358protected class FilterComboBoxAction extends AbstractAction {1359protected FilterComboBoxAction() {1360super("FilterComboBoxAction");1361}13621363public void actionPerformed(final ActionEvent e) {1364Object selectedFilter = filterComboBox.getSelectedItem();1365if (!containsFileFilter(selectedFilter)) {1366getFileChooser().setFileFilter((FileFilter) selectedFilter);1367}1368}1369}13701371/**1372* Acts when DirectoryComboBox has changed the selected item.1373*/1374protected class DirectoryComboBoxAction extends AbstractAction {1375protected DirectoryComboBoxAction() {1376super("DirectoryComboBoxAction");1377}13781379public void actionPerformed(final ActionEvent e) {1380getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem());1381}1382}13831384// Sorting Table operations1385class JSortingTableHeader extends JTableHeader {1386public JSortingTableHeader(final TableColumnModel cm) {1387super(cm);1388setReorderingAllowed(true); // This causes mousePress to call setDraggedColumn1389}13901391// One sort state for each column. Both are ascending by default1392final boolean fSortAscending[] = {true, true};13931394// Instead of dragging, it selects which one to sort by1395public void setDraggedColumn(final TableColumn aColumn) {1396if (aColumn != null) {1397final int colIndex = aColumn.getModelIndex();1398if (colIndex != fSortColumn) {1399filechooser.firePropertyChange(AquaFileSystemModel.SORT_BY_CHANGED, fSortColumn, colIndex);1400fSortColumn = colIndex;1401} else {1402fSortAscending[colIndex] = !fSortAscending[colIndex];1403filechooser.firePropertyChange(AquaFileSystemModel.SORT_ASCENDING_CHANGED, !fSortAscending[colIndex], fSortAscending[colIndex]);1404}1405// Need to repaint the highlighted column.1406repaint();1407}1408}14091410// This stops mouseDrags from moving the column1411public TableColumn getDraggedColumn() {1412return null;1413}14141415protected TableCellRenderer createDefaultRenderer() {1416final DefaultTableCellRenderer label = new AquaTableCellRenderer();1417label.setHorizontalAlignment(SwingConstants.LEFT);1418return label;1419}14201421class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {1422public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {1423if (localTable != null) {1424final JTableHeader header = localTable.getTableHeader();1425if (header != null) {1426setForeground(header.getForeground());1427setBackground(header.getBackground());1428setFont(UIManager.getFont("TableHeader.font"));1429}1430}14311432setText((value == null) ? "" : value.toString());14331434// Modify the table "border" to draw smaller, and with the titles in the right position1435// and sort indicators, just like an NSSave/Open panel.1436final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();1437cellBorder.setSelected(column == fSortColumn);1438final int horizontalShift = (column == 0 ? 35 : 10);1439cellBorder.setHorizontalShift(horizontalShift);14401441if (column == fSortColumn) {1442cellBorder.setSortOrder(fSortAscending[column] ? AquaTableHeaderBorder.SORT_ASCENDING : AquaTableHeaderBorder.SORT_DECENDING);1443} else {1444cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);1445}1446setBorder(cellBorder);1447return this;1448}1449}1450}14511452public void installComponents(final JFileChooser fc) {1453JPanel tPanel; // temp panel1454// set to a Y BoxLayout. The chooser will be laid out top to bottom.1455fc.setLayout(new BoxLayout(fc, BoxLayout.Y_AXIS));1456fc.add(Box.createRigidArea(vstrut10));14571458// construct the top panel14591460final JPanel topPanel = new JPanel();1461topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));1462fc.add(topPanel);1463fc.add(Box.createRigidArea(vstrut10));14641465// Add the textfield pane14661467fTextfieldPanel = new JPanel();1468fTextfieldPanel.setLayout(new BorderLayout());1469// setBottomPanelForMode will make this visible if we need it1470fTextfieldPanel.setVisible(false);1471topPanel.add(fTextfieldPanel);14721473tPanel = new JPanel();1474tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.Y_AXIS));1475final JPanel labelArea = new JPanel();1476labelArea.setLayout(new FlowLayout(FlowLayout.CENTER));1477fTextFieldLabel = new JLabel(fileNameLabelText);1478labelArea.add(fTextFieldLabel);14791480// text field1481filenameTextField = new JTextField();1482fTextFieldLabel.setLabelFor(filenameTextField);1483filenameTextField.addActionListener(getAction(kOpen));1484filenameTextField.addFocusListener(new SaveTextFocusListener());1485final Dimension minSize = filenameTextField.getMinimumSize();1486Dimension d = new Dimension(250, (int)minSize.getHeight());1487filenameTextField.setPreferredSize(d);1488filenameTextField.setMaximumSize(d);1489labelArea.add(filenameTextField);1490final File f = fc.getSelectedFile();1491if (f != null) {1492setFileName(fc.getName(f));1493} else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {1494setFileName(newFileDefaultName);1495}14961497tPanel.add(labelArea);1498// separator line1499final JSeparator sep = new JSeparator(){1500public Dimension getPreferredSize() {1501return new Dimension(((JComponent)getParent()).getWidth(), 3);1502}1503};1504tPanel.add(Box.createRigidArea(new Dimension(1, 8)));1505tPanel.add(sep);1506tPanel.add(Box.createRigidArea(new Dimension(1, 7)));1507fTextfieldPanel.add(tPanel, BorderLayout.CENTER);15081509// DirectoryComboBox, left-justified, 200x20 not including drop shadow1510directoryComboBox = new JComboBox();1511directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight");1512fDirectoryComboBoxModel = createDirectoryComboBoxModel(fc);1513directoryComboBox.setModel(fDirectoryComboBoxModel);1514directoryComboBox.addActionListener(directoryComboBoxAction);1515directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));1516directoryComboBox.setToolTipText(directoryComboBoxToolTipText);1517d = new Dimension(250, (int)directoryComboBox.getMinimumSize().getHeight());1518directoryComboBox.setPreferredSize(d);1519directoryComboBox.setMaximumSize(d);1520topPanel.add(directoryComboBox);15211522// ************************************** //1523// ** Add the directory/Accessory pane ** //1524// ************************************** //1525final JPanel centerPanel = new JPanel(new BorderLayout());1526fc.add(centerPanel);15271528// Accessory pane (equiv to Preview pane in NavServices)1529final JComponent accessory = fc.getAccessory();1530if (accessory != null) {1531getAccessoryPanel().add(accessory);1532}1533centerPanel.add(getAccessoryPanel(), BorderLayout.LINE_START);15341535// Directory list(table), right-justified, resizable1536final JPanel p = createList(fc);1537p.setMinimumSize(LIST_MIN_SIZE);1538centerPanel.add(p, BorderLayout.CENTER);15391540// ********************************** //1541// **** Construct the bottom panel ** //1542// ********************************** //1543fBottomPanel = new JPanel();1544fBottomPanel.setLayout(new BoxLayout(fBottomPanel, BoxLayout.Y_AXIS));1545fc.add(fBottomPanel);15461547// Filter label and combobox.1548// I know it's unMaclike, but the filter goes on Directory_only too.1549tPanel = new JPanel();1550tPanel.setLayout(new FlowLayout(FlowLayout.CENTER));1551tPanel.setBorder(AquaGroupBorder.getTitlelessBorder());1552final JLabel formatLabel = new JLabel(filesOfTypeLabelText);1553tPanel.add(formatLabel);15541555// Combobox1556filterComboBoxModel = createFilterComboBoxModel();1557fc.addPropertyChangeListener(filterComboBoxModel);1558filterComboBox = new JComboBox(filterComboBoxModel);1559formatLabel.setLabelFor(filterComboBox);1560filterComboBox.setRenderer(createFilterComboBoxRenderer());1561d = new Dimension(220, (int)filterComboBox.getMinimumSize().getHeight());1562filterComboBox.setPreferredSize(d);1563filterComboBox.setMaximumSize(d);1564filterComboBox.addActionListener(filterComboBoxAction);1565filterComboBox.setOpaque(false);1566tPanel.add(filterComboBox);15671568fBottomPanel.add(tPanel);15691570// fDirectoryPanel: New, Open, Cancel, Approve buttons, right-justified, 82x221571// (sometimes the NewFolder and OpenFolder buttons are invisible)1572fDirectoryPanel = new JPanel();1573fDirectoryPanel.setLayout(new BoxLayout(fDirectoryPanel, BoxLayout.PAGE_AXIS));1574JPanel directoryPanel = new JPanel(new BorderLayout());1575JPanel newFolderButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));1576newFolderButtonPanel.add(Box.createHorizontalStrut(20));1577fNewFolderButton = createNewFolderButton(); // Because we hide it depending on style1578newFolderButtonPanel.add(fNewFolderButton);1579directoryPanel.add(newFolderButtonPanel, BorderLayout.LINE_START);1580JPanel approveCancelButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));1581fOpenButton = createButton(kOpenDirectory, openButtonText);1582approveCancelButtonPanel.add(fOpenButton);1583approveCancelButtonPanel.add(Box.createHorizontalStrut(8));1584fCancelButton = createButton(kCancel, null);1585approveCancelButtonPanel.add(fCancelButton);1586approveCancelButtonPanel.add(Box.createHorizontalStrut(8));1587// The ApproveSelection button1588fApproveButton = new JButton();1589fApproveButton.addActionListener(fApproveSelectionAction);1590approveCancelButtonPanel.add(fApproveButton);1591approveCancelButtonPanel.add(Box.createHorizontalStrut(20));1592directoryPanel.add(approveCancelButtonPanel, BorderLayout.LINE_END);1593fDirectoryPanel.add(Box.createVerticalStrut(5));1594fDirectoryPanel.add(directoryPanel);1595fDirectoryPanel.add(Box.createVerticalStrut(12));1596fDirectoryPanelSpacer = Box.createRigidArea(hstrut10);15971598if (fc.getControlButtonsAreShown()) {1599fBottomPanel.add(fDirectoryPanelSpacer);1600fBottomPanel.add(fDirectoryPanel);1601}16021603setBottomPanelForMode(fc); // updates ApproveButtonText etc16041605// don't create til after the FCSubpanel and buttons are made1606filenameTextField.getDocument().addDocumentListener(new SaveTextDocumentListener());1607}16081609void setDefaultButtonForMode(final JFileChooser fc) {1610final JButton defaultButton = fSubPanel.getDefaultButton(fc);1611final JRootPane root = defaultButton.getRootPane();1612if (root != null) {1613root.setDefaultButton(defaultButton);1614}1615}16161617// Macs start with their focus in text areas if they have them,1618// lists otherwise (the other plafs start with the focus on approveButton)1619void setFocusForMode(final JFileChooser fc) {1620final JComponent focusComponent = fSubPanel.getFocusComponent(fc);1621if (focusComponent != null) {1622focusComponent.requestFocus();1623}1624}16251626// Enable/disable buttons as needed for the current selection/focus state1627void updateButtonState(final JFileChooser fc) {1628fSubPanel.updateButtonState(fc, getFirstSelectedItem());1629updateApproveButton(fc);1630}16311632void updateApproveButton(final JFileChooser chooser) {1633fApproveButton.setText(getApproveButtonText(chooser));1634fApproveButton.setToolTipText(getApproveButtonToolTipText(chooser));1635fApproveButton.setMnemonic(getApproveButtonMnemonic(chooser));1636fCancelButton.setToolTipText(getCancelButtonToolTipText(chooser));1637}16381639// Lazy-init the subpanels1640synchronized FCSubpanel getSaveFilePanel() {1641if (fSaveFilePanel == null) fSaveFilePanel = new SaveFilePanel();1642return fSaveFilePanel;1643}16441645synchronized FCSubpanel getOpenFilePanel() {1646if (fOpenFilePanel == null) fOpenFilePanel = new OpenFilePanel();1647return fOpenFilePanel;1648}16491650synchronized FCSubpanel getOpenDirOrAnyPanel() {1651if (fOpenDirOrAnyPanel == null) fOpenDirOrAnyPanel = new OpenDirOrAnyPanel();1652return fOpenDirOrAnyPanel;1653}16541655synchronized FCSubpanel getCustomFilePanel() {1656if (fCustomFilePanel == null) fCustomFilePanel = new CustomFilePanel();1657return fCustomFilePanel;1658}16591660synchronized FCSubpanel getCustomDirOrAnyPanel() {1661if (fCustomDirOrAnyPanel == null) fCustomDirOrAnyPanel = new CustomDirOrAnyPanel();1662return fCustomDirOrAnyPanel;1663}16641665void setBottomPanelForMode(final JFileChooser fc) {1666if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) fSubPanel = getSaveFilePanel();1667else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) {1668if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getOpenFilePanel();1669else fSubPanel = getOpenDirOrAnyPanel();1670} else if (fc.getDialogType() == JFileChooser.CUSTOM_DIALOG) {1671if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getCustomFilePanel();1672else fSubPanel = getCustomDirOrAnyPanel();1673}16741675fSubPanel.installPanel(fc, true);1676updateApproveButton(fc);1677updateButtonState(fc);1678setDefaultButtonForMode(fc);1679setFocusForMode(fc);1680fc.invalidate();1681}16821683// fTextfieldPanel and fDirectoryPanel both have NewFolder buttons; only one should be visible at a time1684JButton createNewFolderButton() {1685final JButton b = new JButton(newFolderButtonText);1686b.setToolTipText(newFolderToolTipText);1687b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);1688b.setHorizontalTextPosition(SwingConstants.LEFT);1689b.setAlignmentX(Component.LEFT_ALIGNMENT);1690b.setAlignmentY(Component.CENTER_ALIGNMENT);1691b.addActionListener(getAction(kNewFolder));1692return b;1693}16941695JButton createButton(final int which, String label) {1696if (label == null) label = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[0]);1697final int mnemonic = UIManager.getInt(sDataPrefix + sButtonKinds[which] + sButtonData[1]);1698final String tipText = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[2]);1699final JButton b = new JButton(label);1700b.setMnemonic(mnemonic);1701b.setToolTipText(tipText);1702b.addActionListener(getAction(which));1703return b;1704}17051706AbstractAction getAction(final int which) {1707return fButtonActions[which];1708}17091710public void uninstallComponents(final JFileChooser fc) { //$ Metal (on which this is based) doesn't uninstall its components.1711}17121713// Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field1714// with that file's display name.1715protected class FileListMouseListener extends MouseAdapter {1716public void mouseClicked(final MouseEvent e) {1717final Point p = e.getPoint();1718final int row = fFileList.rowAtPoint(p);1719final int column = fFileList.columnAtPoint(p);17201721// The autoscroller can generate drag events outside the Table's range.1722if ((column == -1) || (row == -1)) { return; }17231724final File clickedFile = (File)(fFileList.getValueAt(row, 0));17251726// rdar://problem/3734130 -- don't populate the text field if this file isn't selectable in this mode.1727if (isSelectableForMode(getFileChooser(), clickedFile)) {1728// [3188387] Populate the file name field with the selected file name1729// [3484163] It should also use the display name, not the actual name.1730setFileName(fileView.getName(clickedFile));1731}1732}1733}17341735protected JPanel createList(final JFileChooser fc) {1736// The first part is similar to MetalFileChooserUI.createList - same kind of listeners1737final JPanel p = new JPanel(new BorderLayout());1738fFileList = new JTableExtension();1739fFileList.setToolTipText(null); // Workaround for 24876891740fFileList.addMouseListener(new FileListMouseListener());1741model = new AquaFileSystemModel(fc, fFileList, fColumnNames);1742final MacListSelectionModel listSelectionModel = new MacListSelectionModel(model);17431744if (getFileChooser().isMultiSelectionEnabled()) {1745listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);1746} else {1747listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);1748}17491750fFileList.setModel(model);1751fFileList.setSelectionModel(listSelectionModel);1752fFileList.getSelectionModel().addListSelectionListener(createListSelectionListener(fc));17531754// Now we're different, because we're a table, not a list1755fc.addPropertyChangeListener(model);1756fFileList.addFocusListener(new SaveTextFocusListener());1757final JTableHeader th = new JSortingTableHeader(fFileList.getColumnModel());1758fFileList.setTableHeader(th);1759fFileList.setRowMargin(0);1760fFileList.setIntercellSpacing(new Dimension(0, 1));1761fFileList.setShowVerticalLines(false);1762fFileList.setShowHorizontalLines(false);1763final Font f = fFileList.getFont(); //ThemeFont.GetThemeFont(AppearanceConstants.kThemeViewsFont);1764//fc.setFont(f);1765//fFileList.setFont(f);1766fFileList.setDefaultRenderer(File.class, new FileRenderer(f));1767fFileList.setDefaultRenderer(Date.class, new DateRenderer(f));1768final FontMetrics fm = fFileList.getFontMetrics(f);17691770// Row height isn't based on the renderers. It defaults to 16 so we have to set it1771fFileList.setRowHeight(Math.max(fm.getHeight(), fileIcon.getIconHeight() + 2));17721773// Add a binding for the file list that triggers return and escape1774fFileList.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);1775// Add a binding for the file list that triggers the default button (see DefaultButtonAction)1776fFileList.registerKeyboardAction(new DefaultButtonAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);1777fFileList.setDropTarget(dragAndDropTarget);17781779final JScrollPane scrollpane = new JScrollPane(fFileList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);1780scrollpane.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));1781scrollpane.setCorner(ScrollPaneConstants.UPPER_TRAILING_CORNER, new ScrollPaneCornerPanel());1782p.add(scrollpane, BorderLayout.CENTER);1783return p;1784}17851786protected class ScrollPaneCornerPanel extends JPanel {1787final Border border = UIManager.getBorder("TableHeader.cellBorder");17881789protected void paintComponent(final Graphics g) {1790border.paintBorder(this, g, 0, 0, getWidth() + 1, getHeight());1791}1792}17931794JComboBox directoryComboBox;1795DirectoryComboBoxModel fDirectoryComboBoxModel;1796private final Action directoryComboBoxAction = new DirectoryComboBoxAction();17971798JTextField filenameTextField;17991800JTableExtension fFileList;18011802private FilterComboBoxModel filterComboBoxModel;1803JComboBox filterComboBox;1804private final Action filterComboBoxAction = new FilterComboBoxAction();18051806private static final Dimension hstrut10 = new Dimension(10, 1);1807private static final Dimension vstrut10 = new Dimension(1, 10);18081809private static final int PREF_WIDTH = 550;1810private static final int PREF_HEIGHT = 400;1811private static final int MIN_WIDTH = 400;1812private static final int MIN_HEIGHT = 250;1813private static final int LIST_MIN_WIDTH = 400;1814private static final int LIST_MIN_HEIGHT = 100;1815private static final Dimension LIST_MIN_SIZE = new Dimension(LIST_MIN_WIDTH, LIST_MIN_HEIGHT);18161817static String fileNameLabelText = null;1818JLabel fTextFieldLabel = null;18191820private static String filesOfTypeLabelText = null;18211822private static String newFolderToolTipText = null;1823static String newFolderAccessibleName = null;18241825private static final String[] fColumnNames = new String[2];18261827JPanel fTextfieldPanel; // Filename textfield for Save or Custom1828private JPanel fDirectoryPanel; // NewFolder/OpenFolder/Cancel/Approve buttons1829private Component fDirectoryPanelSpacer;1830private JPanel fBottomPanel; // The panel that holds fDirectoryPanel and filterComboBox18311832private FCSubpanel fSaveFilePanel = null;1833private FCSubpanel fOpenFilePanel = null;1834private FCSubpanel fOpenDirOrAnyPanel = null;1835private FCSubpanel fCustomFilePanel = null;1836private FCSubpanel fCustomDirOrAnyPanel = null;18371838FCSubpanel fSubPanel = null; // Current FCSubpanel18391840JButton fApproveButton; // mode-specific behavior is managed by FCSubpanel.approveSelection1841JButton fOpenButton; // for Directories1842JButton fNewFolderButton; // for fDirectoryPanel18431844// ToolTip text varies with type of dialog1845private JButton fCancelButton;18461847private final ApproveSelectionAction fApproveSelectionAction = new ApproveSelectionAction();1848protected int fSortColumn = 0;1849protected int fPackageIsTraversable = -1;1850protected int fApplicationIsTraversable = -1;18511852protected static final int sGlobalPackageIsTraversable;1853protected static final int sGlobalApplicationIsTraversable;18541855protected static final String PACKAGE_TRAVERSABLE_PROPERTY = "JFileChooser.packageIsTraversable";1856protected static final String APPLICATION_TRAVERSABLE_PROPERTY = "JFileChooser.appBundleIsTraversable";1857protected static final String[] sTraversableProperties = {"always", // Bundle is always traversable1858"never", // Bundle is never traversable1859"conditional"}; // Bundle is traversable on command click1860protected static final int kOpenAlways = 0, kOpenNever = 1, kOpenConditional = 2;18611862AbstractAction[] fButtonActions = {fApproveSelectionAction, fApproveSelectionAction, new CancelSelectionAction(), new OpenSelectionAction(), null, new NewFolderAction()};18631864static int parseTraversableProperty(final String s) {1865if (s == null) return -1;1866for (int i = 0; i < sTraversableProperties.length; i++) {1867if (s.equals(sTraversableProperties[i])) return i;1868}1869return -1;1870}18711872static {1873Object o = UIManager.get(PACKAGE_TRAVERSABLE_PROPERTY);1874if (o != null && o instanceof String) sGlobalPackageIsTraversable = parseTraversableProperty((String)o);1875else sGlobalPackageIsTraversable = kOpenConditional;18761877o = UIManager.get(APPLICATION_TRAVERSABLE_PROPERTY);1878if (o != null && o instanceof String) sGlobalApplicationIsTraversable = parseTraversableProperty((String)o);1879else sGlobalApplicationIsTraversable = kOpenConditional;1880}1881static final String sDataPrefix = "FileChooser.";1882static final String[] sButtonKinds = {"openButton", "saveButton", "cancelButton", "openDirectoryButton", "helpButton", "newFolderButton"};1883static final String[] sButtonData = {"Text", "Mnemonic", "ToolTipText"};1884static final int kOpen = 0, kSave = 1, kCancel = 2, kOpenDirectory = 3, kHelp = 4, kNewFolder = 5;18851886/*-------18871888Possible states: Save, {Open, Custom}x{Files, File and Directory, Directory}1889--------- */18901891// This class returns the values for the Custom type, to avoid duplicating code in the two Custom subclasses1892abstract class FCSubpanel {1893// Install the appropriate panels for this mode1894abstract void installPanel(JFileChooser fc, boolean controlButtonsAreShown);18951896abstract void updateButtonState(JFileChooser fc, File f);18971898// Can this item be selected?1899// if not, it's disabled in the list1900boolean isSelectableInList(final JFileChooser fc, final File f) {1901if (f == null) return false;1902if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) return fc.isTraversable(f);1903return fc.accept(f);1904}19051906void approveSelection(final JFileChooser fc) {1907fc.approveSelection();1908}19091910JButton getDefaultButton(final JFileChooser fc) {1911return fApproveButton;1912}19131914// Default to the textfield, panels without one should subclass1915JComponent getFocusComponent(final JFileChooser fc) {1916return filenameTextField;1917}19181919String getApproveButtonText(final JFileChooser fc) {1920// Fallback to "choose"1921return this.getApproveButtonText(fc, chooseButtonText);1922}19231924// Try to get the custom text. If none, use the fallback1925String getApproveButtonText(final JFileChooser fc, final String fallbackText) {1926final String buttonText = fc.getApproveButtonText();1927if (buttonText != null) {1928buttonText.trim();1929if (!buttonText.equals("")) return buttonText;1930}1931return fallbackText;1932}19331934int getApproveButtonMnemonic(final JFileChooser fc) {1935// Don't use a default1936return fc.getApproveButtonMnemonic();1937}19381939// No fallback1940String getApproveButtonToolTipText(final JFileChooser fc) {1941return getApproveButtonToolTipText(fc, null);1942}19431944String getApproveButtonToolTipText(final JFileChooser fc, final String fallbackText) {1945final String tooltipText = fc.getApproveButtonToolTipText();1946if (tooltipText != null) {1947tooltipText.trim();1948if (!tooltipText.equals("")) return tooltipText;1949}1950return fallbackText;1951}19521953String getCancelButtonToolTipText(final JFileChooser fc) {1954return cancelChooseButtonToolTipText;1955}1956}19571958// Custom FILES_ONLY dialog1959/*1960NavServices Save appearance with Open behavior1961Approve button label = Open when list has focus and a directory is selected, Custom otherwise1962No OpenDirectory button - Approve button is overloaded1963Default button / double click = Approve1964Has text field1965List - everything is enabled1966*/1967class CustomFilePanel extends FCSubpanel {1968void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {1969fTextfieldPanel.setVisible(true); // do we really want one in multi-select? It's confusing1970fOpenButton.setVisible(false);1971fNewFolderButton.setVisible(true);1972}19731974// If the list has focus, the mode depends on the selection1975// - directory = open, file = approve1976// If something else has focus and we have text, it's approve1977// otherwise, it depends on selection again.1978boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {1979final boolean selectionIsDirectory = (f != null && fc.isTraversable(f));1980if (fFileList.hasFocus()) return selectionIsDirectory;1981else if (textfieldIsValid()) return false;1982return selectionIsDirectory;1983}19841985// The approve button is overloaded to mean OpenDirectory or Save1986void approveSelection(final JFileChooser fc) {1987File f = getFirstSelectedItem();1988if (inOpenDirectoryMode(fc, f)) {1989openDirectory(f);1990} else {1991f = makeFile(fc, getFileName());1992if (f != null) {1993selectionInProgress = true;1994getFileChooser().setSelectedFile(f);1995selectionInProgress = false;1996}1997getFileChooser().approveSelection();1998}1999}20002001// The approve button should be enabled2002// - if something in the list can be opened2003// - if the textfield has something in it2004void updateButtonState(final JFileChooser fc, final File f) {2005boolean enabled = true;2006if (!inOpenDirectoryMode(fc, f)) {2007enabled = (f != null) || textfieldIsValid();2008}2009getApproveButton(fc).setEnabled(enabled);20102011// The OpenDirectory button should be disabled if there's no directory selected2012fOpenButton.setEnabled(f != null && fc.isTraversable(f));20132014// Update the default button, since we may have disabled the current default.2015setDefaultButtonForMode(fc);2016}20172018// everything's enabled, because we don't know what they're doing with them2019boolean isSelectableInList(final JFileChooser fc, final File f) {2020if (f == null) return false;2021return fc.accept(f);2022}20232024String getApproveButtonToolTipText(final JFileChooser fc) {2025// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...2026if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;2027return super.getApproveButtonToolTipText(fc);2028}2029}20302031// All Save dialogs2032/*2033NavServices Save2034Approve button label = Open when list has focus and a directory is selected, Save otherwise2035No OpenDirectory button - Approve button is overloaded2036Default button / double click = Approve2037Has text field2038Has NewFolder button (by text field)2039List - only traversables are enabled2040List is always SINGLE_SELECT2041*/2042// Subclasses CustomFilePanel because they look alike and have some common behavior2043class SaveFilePanel extends CustomFilePanel {2044void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2045fTextfieldPanel.setVisible(true);2046fOpenButton.setVisible(false);2047fNewFolderButton.setVisible(true);2048}20492050// only traversables are enabled, regardless of mode2051// because all you can do is select the next folder to open2052boolean isSelectableInList(final JFileChooser fc, final File f) {2053return fc.accept(f) && fc.isTraversable(f);2054}20552056// The approve button means 'approve the file name in the text field.'2057void approveSelection(final JFileChooser fc) {2058final File f = makeFile(fc, getFileName());2059if (f != null) {2060selectionInProgress = true;2061getFileChooser().setSelectedFile(f);2062selectionInProgress = false;2063getFileChooser().approveSelection();2064}2065}20662067// The approve button should be enabled if the textfield has something in it2068void updateButtonState(final JFileChooser fc, final File f) {2069final boolean enabled = textfieldIsValid();2070getApproveButton(fc).setEnabled(enabled);2071}20722073String getApproveButtonText(final JFileChooser fc) {2074// Get the custom text, or fallback to "Save"2075return this.getApproveButtonText(fc, saveButtonText);2076}20772078int getApproveButtonMnemonic(final JFileChooser fc) {2079return saveButtonMnemonic;2080}20812082String getApproveButtonToolTipText(final JFileChooser fc) {2083// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...2084if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;2085return this.getApproveButtonToolTipText(fc, saveButtonToolTipText);2086}20872088String getCancelButtonToolTipText(final JFileChooser fc) {2089return cancelSaveButtonToolTipText;2090}2091}20922093// Open FILES_ONLY2094/*2095NSOpenPanel-style2096Approve button label = Open2097Default button / double click = Approve2098No text field2099No NewFolder button2100List - all items are enabled2101*/2102class OpenFilePanel extends FCSubpanel {2103void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2104fTextfieldPanel.setVisible(false);2105fOpenButton.setVisible(false);2106fNewFolderButton.setVisible(false);2107setDefaultButtonForMode(fc);2108}21092110boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {2111return (f != null && fc.isTraversable(f));2112}21132114// Default to the list2115JComponent getFocusComponent(final JFileChooser fc) {2116return fFileList;2117}21182119void updateButtonState(final JFileChooser fc, final File f) {2120// Button is disabled if there's nothing selected2121final boolean enabled = (f != null) && !fc.isTraversable(f);2122getApproveButton(fc).setEnabled(enabled);2123}21242125// all items are enabled2126boolean isSelectableInList(final JFileChooser fc, final File f) {2127return f != null && fc.accept(f);2128}21292130String getApproveButtonText(final JFileChooser fc) {2131// Get the custom text, or fallback to "Open"2132return this.getApproveButtonText(fc, openButtonText);2133}21342135int getApproveButtonMnemonic(final JFileChooser fc) {2136return openButtonMnemonic;2137}21382139String getApproveButtonToolTipText(final JFileChooser fc) {2140return this.getApproveButtonToolTipText(fc, openButtonToolTipText);2141}21422143String getCancelButtonToolTipText(final JFileChooser fc) {2144return cancelOpenButtonToolTipText;2145}2146}21472148// used by open and custom panels for Directory only or files and directories2149abstract class DirOrAnyPanel extends FCSubpanel {2150void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2151fOpenButton.setVisible(false);2152}21532154JButton getDefaultButton(final JFileChooser fc) {2155return getApproveButton(fc);2156}21572158void updateButtonState(final JFileChooser fc, final File f) {2159// Button is disabled if there's nothing selected2160// Approve button is handled by the subclasses2161// getApproveButton(fc).setEnabled(f != null);21622163// The OpenDirectory button should be disabled if there's no directory selected2164// - we only check the first item21652166fOpenButton.setEnabled(false);2167setDefaultButtonForMode(fc);2168}2169}21702171// Open FILES_AND_DIRECTORIES or DIRECTORIES_ONLY2172/*2173NavServices Choose2174Approve button label = Choose/Custom2175Has OpenDirectory button2176Default button / double click = OpenDirectory2177No text field2178List - files are disabled in DIRECTORIES_ONLY2179*/2180class OpenDirOrAnyPanel extends DirOrAnyPanel {2181void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2182super.installPanel(fc, controlButtonsAreShown);2183fTextfieldPanel.setVisible(false);2184fNewFolderButton.setVisible(false);2185}21862187// Default to the list2188JComponent getFocusComponent(final JFileChooser fc) {2189return fFileList;2190}21912192int getApproveButtonMnemonic(final JFileChooser fc) {2193return chooseButtonMnemonic;2194}21952196String getApproveButtonToolTipText(final JFileChooser fc) {2197String fallbackText;2198if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) fallbackText = chooseFolderButtonToolTipText;2199else fallbackText = chooseItemButtonToolTipText;2200return this.getApproveButtonToolTipText(fc, fallbackText);2201}22022203void updateButtonState(final JFileChooser fc, final File f) {2204// Button is disabled if there's nothing selected2205getApproveButton(fc).setEnabled(f != null);2206super.updateButtonState(fc, f);2207}2208}22092210// Custom FILES_AND_DIRECTORIES or DIRECTORIES_ONLY2211/*2212No NavServices equivalent2213Approve button label = user defined or Choose2214Has OpenDirectory button2215Default button / double click = OpenDirectory2216Has text field2217Has NewFolder button (by text field)2218List - files are disabled in DIRECTORIES_ONLY2219*/2220class CustomDirOrAnyPanel extends DirOrAnyPanel {2221void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2222super.installPanel(fc, controlButtonsAreShown);2223fTextfieldPanel.setVisible(true);2224fNewFolderButton.setVisible(true);2225}22262227// If there's text, make a file and select it2228void approveSelection(final JFileChooser fc) {2229final File f = makeFile(fc, getFileName());2230if (f != null) {2231selectionInProgress = true;2232getFileChooser().setSelectedFile(f);2233selectionInProgress = false;2234}2235getFileChooser().approveSelection();2236}22372238void updateButtonState(final JFileChooser fc, final File f) {2239// Button is disabled if there's nothing selected2240getApproveButton(fc).setEnabled(f != null || textfieldIsValid());2241super.updateButtonState(fc, f);2242}2243}22442245// See FileRenderer - documents in Save dialogs draw disabled, so they shouldn't be selected2246class MacListSelectionModel extends DefaultListSelectionModel {2247AquaFileSystemModel fModel;22482249MacListSelectionModel(final AquaFileSystemModel model) {2250fModel = model;2251}22522253// Can the file be selected in this mode?2254// (files are visible even if they can't be selected)2255boolean isSelectableInListIndex(final int index) {2256final File file = (File)fModel.getValueAt(index, 0);2257return (file != null && isSelectableInList(file));2258}22592260// Make sure everything in the selection interval is valid2261void verifySelectionInterval(int index0, int index1, boolean isSetSelection) {2262if (index0 > index1) {2263final int tmp = index1;2264index1 = index0;2265index0 = tmp;2266}2267int start = index0;2268int end;2269do {2270// Find the first selectable file in the range2271for (; start <= index1; start++) {2272if (isSelectableInListIndex(start)) break;2273}2274end = -1;2275// Find the last selectable file in the range2276for (int i = start; i <= index1; i++) {2277if (!isSelectableInListIndex(i)) {2278break;2279}2280end = i;2281}2282// Select the range2283if (end >= 0) {2284// If setting the selection, do "set" the first time to clear the old one2285// after that do "add" to extend it2286if (isSetSelection) {2287super.setSelectionInterval(start, end);2288isSetSelection = false;2289} else {2290super.addSelectionInterval(start, end);2291}2292start = end + 1;2293} else {2294break;2295}2296} while (start <= index1);2297}22982299public void setAnchorSelectionIndex(final int anchorIndex) {2300if (isSelectableInListIndex(anchorIndex)) super.setAnchorSelectionIndex(anchorIndex);2301}23022303public void setLeadSelectionIndex(final int leadIndex) {2304if (isSelectableInListIndex(leadIndex)) super.setLeadSelectionIndex(leadIndex);2305}23062307public void setSelectionInterval(final int index0, final int index1) {2308if (index0 == -1 || index1 == -1) { return; }23092310if ((getSelectionMode() == SINGLE_SELECTION) || (index0 == index1)) {2311if (isSelectableInListIndex(index1)) super.setSelectionInterval(index1, index1);2312} else {2313verifySelectionInterval(index0, index1, true);2314}2315}23162317public void addSelectionInterval(final int index0, final int index1) {2318if (index0 == -1 || index1 == -1) { return; }23192320if (index0 == index1) {2321if (isSelectableInListIndex(index1)) super.addSelectionInterval(index1, index1);2322return;2323}23242325if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {2326setSelectionInterval(index0, index1);2327return;2328}23292330verifySelectionInterval(index0, index1, false);2331}2332}23332334// Convenience, to translate from the JList directory view to the Mac-style JTable2335// & minimize diffs between this and BasicFileChooserUI2336class JTableExtension extends JTable {2337public void setSelectedIndex(final int index) {2338getSelectionModel().setSelectionInterval(index, index);2339}23402341public void removeSelectedIndex(final int index) {2342getSelectionModel().removeSelectionInterval(index, index);2343}23442345public void ensureIndexIsVisible(final int index) {2346final Rectangle cellBounds = getCellRect(index, 0, false);2347if (cellBounds != null) {2348scrollRectToVisible(cellBounds);2349}2350}23512352public int locationToIndex(final Point location) {2353return rowAtPoint(location);2354}2355}2356}235723582359