Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaFileChooserUI.java
41154 views
/*1* Copyright (c) 2011, 2021, 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.BorderLayout;28import java.awt.Color;29import java.awt.Component;30import java.awt.ComponentOrientation;31import java.awt.Dimension;32import java.awt.FlowLayout;33import java.awt.Font;34import java.awt.FontMetrics;35import java.awt.Graphics;36import java.awt.Insets;37import java.awt.Point;38import java.awt.Rectangle;39import java.awt.Toolkit;40import java.awt.datatransfer.DataFlavor;41import java.awt.datatransfer.Transferable;42import java.awt.dnd.DnDConstants;43import java.awt.dnd.DropTarget;44import java.awt.dnd.DropTargetAdapter;45import java.awt.dnd.DropTargetDragEvent;46import java.awt.dnd.DropTargetDropEvent;47import java.awt.event.ActionEvent;48import java.awt.event.FocusEvent;49import java.awt.event.FocusListener;50import java.awt.event.KeyEvent;51import java.awt.event.MouseAdapter;52import java.awt.event.MouseEvent;53import java.awt.event.MouseListener;54import java.beans.PropertyChangeEvent;55import java.beans.PropertyChangeListener;56import java.io.File;57import java.net.URI;58import java.text.DateFormat;59import java.util.Date;60import java.util.Locale;61import java.util.Objects;62import java.util.Vector;6364import javax.swing.AbstractAction;65import javax.swing.AbstractListModel;66import javax.swing.Action;67import javax.swing.Box;68import javax.swing.BoxLayout;69import javax.swing.ComboBoxModel;70import javax.swing.DefaultListSelectionModel;71import javax.swing.Icon;72import javax.swing.JButton;73import javax.swing.JComboBox;74import javax.swing.JComponent;75import javax.swing.JDialog;76import javax.swing.JFileChooser;77import javax.swing.JLabel;78import javax.swing.JList;79import javax.swing.JOptionPane;80import javax.swing.JPanel;81import javax.swing.JRootPane;82import javax.swing.JScrollPane;83import javax.swing.JSeparator;84import javax.swing.JTable;85import javax.swing.JTextField;86import javax.swing.KeyStroke;87import javax.swing.ListCellRenderer;88import javax.swing.ListSelectionModel;89import javax.swing.ScrollPaneConstants;90import javax.swing.SwingConstants;91import javax.swing.SwingUtilities;92import javax.swing.UIManager;93import javax.swing.border.Border;94import javax.swing.event.AncestorEvent;95import javax.swing.event.AncestorListener;96import javax.swing.event.DocumentEvent;97import javax.swing.event.DocumentListener;98import javax.swing.event.ListSelectionEvent;99import javax.swing.event.ListSelectionListener;100import javax.swing.filechooser.FileFilter;101import javax.swing.filechooser.FileSystemView;102import javax.swing.filechooser.FileView;103import javax.swing.plaf.ComponentUI;104import javax.swing.plaf.FileChooserUI;105import javax.swing.plaf.UIResource;106import javax.swing.table.DefaultTableCellRenderer;107import javax.swing.table.JTableHeader;108import javax.swing.table.TableCellRenderer;109import javax.swing.table.TableColumn;110import javax.swing.table.TableColumnModel;111112import sun.swing.SwingUtilities2;113114public class AquaFileChooserUI extends FileChooserUI {115/* FileView icons */116protected Icon directoryIcon = null;117protected Icon fileIcon = null;118protected Icon computerIcon = null;119protected Icon hardDriveIcon = null;120protected Icon floppyDriveIcon = null;121122protected Icon upFolderIcon = null;123protected Icon homeFolderIcon = null;124protected Icon listViewIcon = null;125protected Icon detailsViewIcon = null;126127protected int saveButtonMnemonic = 0;128protected int openButtonMnemonic = 0;129protected int cancelButtonMnemonic = 0;130protected int updateButtonMnemonic = 0;131protected int helpButtonMnemonic = 0;132protected int chooseButtonMnemonic = 0;133134private String saveTitleText = null;135private String openTitleText = null;136String newFolderTitleText = null;137138protected String saveButtonText = null;139protected String openButtonText = null;140protected String cancelButtonText = null;141protected String updateButtonText = null;142protected String helpButtonText = null;143protected String newFolderButtonText = null;144protected String chooseButtonText = null;145146//private String newFolderErrorSeparator = null;147String newFolderErrorText = null;148String newFolderExistsErrorText = null;149protected String fileDescriptionText = null;150protected String directoryDescriptionText = null;151152protected String saveButtonToolTipText = null;153protected String openButtonToolTipText = null;154protected String cancelButtonToolTipText = null;155protected String updateButtonToolTipText = null;156protected String helpButtonToolTipText = null;157protected String chooseItemButtonToolTipText = null; // Choose anything158protected String chooseFolderButtonToolTipText = null; // Choose folder159protected String directoryComboBoxToolTipText = null;160protected String filenameTextFieldToolTipText = null;161protected String filterComboBoxToolTipText = null;162protected String openDirectoryButtonToolTipText = null;163164protected String cancelOpenButtonToolTipText = null;165protected String cancelSaveButtonToolTipText = null;166protected String cancelChooseButtonToolTipText = null;167protected String cancelNewFolderButtonToolTipText = null;168169protected String desktopName = null;170String newFolderDialogPrompt = null;171String newFolderDefaultName = null;172private String newFileDefaultName = null;173String createButtonText = null;174175JFileChooser filechooser = null;176177private MouseListener doubleClickListener = null;178private PropertyChangeListener propertyChangeListener = null;179private AncestorListener ancestorListener = null;180private DropTarget dragAndDropTarget = null;181182private static final AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter();183184private AquaFileSystemModel model;185186final AquaFileView fileView = new AquaFileView(this);187188boolean selectionInProgress = false;189190// The accessoryPanel is a container to place the JFileChooser accessory component191private JPanel accessoryPanel = null;192193//194// ComponentUI Interface Implementation methods195//196public static ComponentUI createUI(final JComponent c) {197return new AquaFileChooserUI((JFileChooser)c);198}199200public AquaFileChooserUI(final JFileChooser filechooser) {201super();202}203204public void installUI(final JComponent c) {205accessoryPanel = new JPanel(new BorderLayout());206filechooser = (JFileChooser)c;207208createModel();209210installDefaults(filechooser);211installComponents(filechooser);212installListeners(filechooser);213214AquaUtils.enforceComponentOrientation(filechooser, ComponentOrientation.getOrientation(Locale.getDefault()));215}216217public void uninstallUI(final JComponent c) {218uninstallListeners(filechooser);219uninstallComponents(filechooser);220uninstallDefaults(filechooser);221222if (accessoryPanel != null) {223accessoryPanel.removeAll();224}225226accessoryPanel = null;227getFileChooser().removeAll();228}229230protected void installListeners(final JFileChooser fc) {231doubleClickListener = createDoubleClickListener(fc, fFileList);232fFileList.addMouseListener(doubleClickListener);233234propertyChangeListener = createPropertyChangeListener(fc);235if (propertyChangeListener != null) {236fc.addPropertyChangeListener(propertyChangeListener);237}238239ancestorListener = new AncestorListener(){240public void ancestorAdded(final AncestorEvent e) {241// Request defaultness for the appropriate button based on mode242setFocusForMode(getFileChooser());243// Request defaultness for the appropriate button based on mode244setDefaultButtonForMode(getFileChooser());245}246247public void ancestorRemoved(final AncestorEvent e) {248}249250public void ancestorMoved(final AncestorEvent e) {251}252};253fc.addAncestorListener(ancestorListener);254255fc.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);256dragAndDropTarget = new DropTarget(fc, DnDConstants.ACTION_COPY, new DnDHandler(), true);257fc.setDropTarget(dragAndDropTarget);258}259260protected void uninstallListeners(final JFileChooser fc) {261if (propertyChangeListener != null) {262fc.removePropertyChangeListener(propertyChangeListener);263}264fFileList.removeMouseListener(doubleClickListener);265fc.removePropertyChangeListener(filterComboBoxModel);266fc.removePropertyChangeListener(model);267fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));268fc.removeAncestorListener(ancestorListener);269fc.setDropTarget(null);270ancestorListener = null;271}272273protected void installDefaults(final JFileChooser fc) {274installIcons(fc);275installStrings(fc);276setPackageIsTraversable(fc.getClientProperty(PACKAGE_TRAVERSABLE_PROPERTY));277setApplicationIsTraversable(fc.getClientProperty(APPLICATION_TRAVERSABLE_PROPERTY));278}279280protected void installIcons(final JFileChooser fc) {281directoryIcon = UIManager.getIcon("FileView.directoryIcon");282fileIcon = UIManager.getIcon("FileView.fileIcon");283computerIcon = UIManager.getIcon("FileView.computerIcon");284hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon");285}286287String getString(final String uiKey, final String fallback) {288final String result = UIManager.getString(uiKey);289return (result == null ? fallback : result);290}291292protected void installStrings(final JFileChooser fc) {293// Exist in basic.properties (though we might want to override)294fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText");295directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText");296newFolderErrorText = getString("FileChooser.newFolderErrorText", "Error occurred during folder creation");297298saveButtonText = UIManager.getString("FileChooser.saveButtonText");299openButtonText = UIManager.getString("FileChooser.openButtonText");300cancelButtonText = UIManager.getString("FileChooser.cancelButtonText");301updateButtonText = UIManager.getString("FileChooser.updateButtonText");302helpButtonText = UIManager.getString("FileChooser.helpButtonText");303304saveButtonMnemonic = UIManager.getInt("FileChooser.saveButtonMnemonic");305openButtonMnemonic = UIManager.getInt("FileChooser.openButtonMnemonic");306cancelButtonMnemonic = UIManager.getInt("FileChooser.cancelButtonMnemonic");307updateButtonMnemonic = UIManager.getInt("FileChooser.updateButtonMnemonic");308helpButtonMnemonic = UIManager.getInt("FileChooser.helpButtonMnemonic");309chooseButtonMnemonic = UIManager.getInt("FileChooser.chooseButtonMnemonic");310311saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText");312openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText");313cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText");314updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText");315helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText");316317// Mac-specific, but fallback to basic if it's missing318saveTitleText = getString("FileChooser.saveTitleText", saveButtonText);319openTitleText = getString("FileChooser.openTitleText", openButtonText);320321// Mac-specific, required322newFolderExistsErrorText = getString("FileChooser.newFolderExistsErrorText", "That name is already taken");323chooseButtonText = getString("FileChooser.chooseButtonText", "Choose");324newFolderButtonText = getString("FileChooser.newFolderButtonText", "New");325newFolderTitleText = getString("FileChooser.newFolderTitleText", "New Folder");326327if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {328fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");329} else {330fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");331}332333filesOfTypeLabelText = getString("FileChooser.filesOfTypeLabelText", "Format:");334335desktopName = getString("FileChooser.desktopName", "Desktop");336newFolderDialogPrompt = getString("FileChooser.newFolderPromptText", "Name of new folder:");337newFolderDefaultName = getString("FileChooser.untitledFolderName", "untitled folder");338newFileDefaultName = getString("FileChooser.untitledFileName", "untitled");339createButtonText = getString("FileChooser.createButtonText", "Create");340341fColumnNames[1] = getString("FileChooser.byDateText", "Date Modified");342fColumnNames[0] = getString("FileChooser.byNameText", "Name");343344// Mac-specific, optional345chooseItemButtonToolTipText = UIManager.getString("FileChooser.chooseItemButtonToolTipText");346chooseFolderButtonToolTipText = UIManager.getString("FileChooser.chooseFolderButtonToolTipText");347openDirectoryButtonToolTipText = UIManager.getString("FileChooser.openDirectoryButtonToolTipText");348349directoryComboBoxToolTipText = UIManager.getString("FileChooser.directoryComboBoxToolTipText");350filenameTextFieldToolTipText = UIManager.getString("FileChooser.filenameTextFieldToolTipText");351filterComboBoxToolTipText = UIManager.getString("FileChooser.filterComboBoxToolTipText");352353cancelOpenButtonToolTipText = UIManager.getString("FileChooser.cancelOpenButtonToolTipText");354cancelSaveButtonToolTipText = UIManager.getString("FileChooser.cancelSaveButtonToolTipText");355cancelChooseButtonToolTipText = UIManager.getString("FileChooser.cancelChooseButtonToolTipText");356cancelNewFolderButtonToolTipText = UIManager.getString("FileChooser.cancelNewFolderButtonToolTipText");357358newFolderTitleText = UIManager.getString("FileChooser.newFolderTitleText");359newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText");360newFolderAccessibleName = getString("FileChooser.newFolderAccessibleName", newFolderTitleText);361}362363protected void uninstallDefaults(final JFileChooser fc) {364uninstallIcons(fc);365uninstallStrings(fc);366}367368protected void uninstallIcons(final JFileChooser fc) {369directoryIcon = null;370fileIcon = null;371computerIcon = null;372hardDriveIcon = null;373floppyDriveIcon = null;374375upFolderIcon = null;376homeFolderIcon = null;377detailsViewIcon = null;378listViewIcon = null;379}380381protected void uninstallStrings(final JFileChooser fc) {382saveTitleText = null;383openTitleText = null;384newFolderTitleText = null;385386saveButtonText = null;387openButtonText = null;388cancelButtonText = null;389updateButtonText = null;390helpButtonText = null;391newFolderButtonText = null;392chooseButtonText = null;393394cancelOpenButtonToolTipText = null;395cancelSaveButtonToolTipText = null;396cancelChooseButtonToolTipText = null;397cancelNewFolderButtonToolTipText = null;398399saveButtonToolTipText = null;400openButtonToolTipText = null;401cancelButtonToolTipText = null;402updateButtonToolTipText = null;403helpButtonToolTipText = null;404chooseItemButtonToolTipText = null;405chooseFolderButtonToolTipText = null;406openDirectoryButtonToolTipText = null;407directoryComboBoxToolTipText = null;408filenameTextFieldToolTipText = null;409filterComboBoxToolTipText = null;410411newFolderDefaultName = null;412newFileDefaultName = null;413414desktopName = null;415}416417protected void createModel() {418}419420AquaFileSystemModel getModel() {421return model;422}423424/*425* Listen for filechooser property changes, such as426* the selected file changing, or the type of the dialog changing.427*/428// Taken almost verbatim from Metal429protected PropertyChangeListener createPropertyChangeListener(final JFileChooser fc) {430return new PropertyChangeListener(){431public void propertyChange(final PropertyChangeEvent e) {432final String prop = e.getPropertyName();433if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {434final File f = (File)e.getNewValue();435if (f != null) {436// Select the file in the list if the selected file didn't change as437// a result of a list click.438if (!selectionInProgress && getModel().contains(f)) {439fFileList.setSelectedIndex(getModel().indexOf(f));440}441442// [3643835] Need to populate the text field here. No-op on Open dialogs443// Note that this was removed for 3514735, but should not have been.444if (!f.isDirectory()) {445setFileName(getFileChooser().getName(f));446}447}448updateButtonState(getFileChooser());449} else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {450JFileChooser fileChooser = getFileChooser();451if (!fileChooser.isDirectorySelectionEnabled()) {452final File[] files = (File[]) e.getNewValue();453if (files != null) {454for (int selectedRow : fFileList.getSelectedRows()) {455File file = (File) fFileList.getValueAt(selectedRow, 0);456if (fileChooser.isTraversable(file)) {457fFileList.removeSelectedIndex(selectedRow);458}459}460}461}462} else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {463fFileList.clearSelection();464final File currentDirectory = getFileChooser().getCurrentDirectory();465if (currentDirectory != null) {466fDirectoryComboBoxModel.addItem(currentDirectory);467// Enable the newFolder action if the current directory468// is writable.469// PENDING(jeff) - broken - fix470getAction(kNewFolder).setEnabled(currentDirectory.canWrite());471}472updateButtonState(getFileChooser());473} else if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {474fFileList.clearSelection();475setBottomPanelForMode(getFileChooser()); // Also updates approve button476} else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {477if (getAccessoryPanel() != null) {478if (e.getOldValue() != null) {479getAccessoryPanel().remove((JComponent)e.getOldValue());480}481final JComponent accessory = (JComponent)e.getNewValue();482if (accessory != null) {483getAccessoryPanel().add(accessory, BorderLayout.CENTER);484}485}486} else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) {487updateApproveButton(getFileChooser());488getFileChooser().invalidate();489} else if (prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY) {490if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) {491fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");492} else {493fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");494}495fTextFieldLabel.setText(fileNameLabelText);496497// Mac doesn't show the text field or "new folder" button in 'Open' dialogs498setBottomPanelForMode(getFileChooser()); // Also updates approve button499} else if (prop.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {500getApproveButton(getFileChooser()).setMnemonic(getApproveButtonMnemonic(getFileChooser()));501} else if (prop.equals(PACKAGE_TRAVERSABLE_PROPERTY)) {502setPackageIsTraversable(e.getNewValue());503} else if (prop.equals(APPLICATION_TRAVERSABLE_PROPERTY)) {504setApplicationIsTraversable(e.getNewValue());505} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {506if (getFileChooser().isMultiSelectionEnabled()) {507fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);508} else {509fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);510}511} else if (prop.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {512doControlButtonsChanged(e);513}514}515};516}517518void setPackageIsTraversable(final Object o) {519int newProp = -1;520if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);521if (newProp != -1) fPackageIsTraversable = newProp;522else fPackageIsTraversable = sGlobalPackageIsTraversable;523}524525void setApplicationIsTraversable(final Object o) {526int newProp = -1;527if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);528if (newProp != -1) fApplicationIsTraversable = newProp;529else fApplicationIsTraversable = sGlobalApplicationIsTraversable;530}531532void doControlButtonsChanged(final PropertyChangeEvent e) {533if (getFileChooser().getControlButtonsAreShown()) {534fBottomPanel.add(fDirectoryPanelSpacer);535fBottomPanel.add(fDirectoryPanel);536} else {537fBottomPanel.remove(fDirectoryPanelSpacer);538fBottomPanel.remove(fDirectoryPanel);539}540}541542public String getFileName() {543if (filenameTextField != null) { return filenameTextField.getText(); }544return null;545}546547public String getDirectoryName() {548// PENDING(jeff) - get the name from the directory combobox549return null;550}551552public void setFileName(final String filename) {553if (filenameTextField != null) {554filenameTextField.setText(filename);555}556}557558public void setDirectoryName(final String dirname) {559// PENDING(jeff) - set the name in the directory combobox560}561562public void rescanCurrentDirectory(final JFileChooser fc) {563getModel().invalidateFileCache();564getModel().validateFileCache();565}566567public void ensureFileIsVisible(final JFileChooser fc, final File f) {568if (f == null) {569fFileList.requestFocusInWindow();570fFileList.ensureIndexIsVisible(-1);571return;572}573574getModel().runWhenDone(new Runnable() {575public void run() {576fFileList.requestFocusInWindow();577fFileList.ensureIndexIsVisible(getModel().indexOf(f));578}579});580}581582public JFileChooser getFileChooser() {583return filechooser;584}585586public JPanel getAccessoryPanel() {587return accessoryPanel;588}589590protected JButton getApproveButton(final JFileChooser fc) {591return fApproveButton;592}593594public int getApproveButtonMnemonic(final JFileChooser fc) {595return fSubPanel.getApproveButtonMnemonic(fc);596}597598public String getApproveButtonToolTipText(final JFileChooser fc) {599return fSubPanel.getApproveButtonToolTipText(fc);600}601602public String getApproveButtonText(final JFileChooser fc) {603return fSubPanel.getApproveButtonText(fc);604}605606protected String getCancelButtonToolTipText(final JFileChooser fc) {607return fSubPanel.getCancelButtonToolTipText(fc);608}609610// If the item's not selectable, it'll be visible but disabled in the list611boolean isSelectableInList(final File f) {612return fSubPanel.isSelectableInList(getFileChooser(), f);613}614615// Is this a file that the JFileChooser wants?616// Directories can be selected in the list regardless of mode617boolean isSelectableForMode(final JFileChooser fc, final File f) {618if (f == null) return false;619final int mode = fc.getFileSelectionMode();620if (mode == JFileChooser.FILES_AND_DIRECTORIES) return true;621boolean traversable = fc.isTraversable(f);622if (mode == JFileChooser.DIRECTORIES_ONLY) return traversable;623return !traversable;624}625626// ********************************************627// ************ Create Listeners **************628// ********************************************629630// From Basic631public ListSelectionListener createListSelectionListener(final JFileChooser fc) {632return new SelectionListener();633}634635protected class SelectionListener implements ListSelectionListener {636public void valueChanged(final ListSelectionEvent e) {637if (e.getValueIsAdjusting()) return;638639File f = null;640final int selectedRow = fFileList.getSelectedRow();641final JFileChooser chooser = getFileChooser();642boolean isSave = (chooser.getDialogType() == JFileChooser.SAVE_DIALOG);643if (selectedRow >= 0) {644f = (File)fFileList.getValueAt(selectedRow, 0);645}646647// Save dialog lists can't be multi select, because all we're selecting is the next folder to open648selectionInProgress = true;649if (!isSave && chooser.isMultiSelectionEnabled()) {650final int[] rows = fFileList.getSelectedRows();651int selectableCount = 0;652// Double-check that all the list selections are valid for this mode653// Directories can be selected in the list regardless of mode654if (rows.length > 0) {655for (final int element : rows) {656if (isSelectableForMode(chooser, (File)fFileList.getValueAt(element, 0))) selectableCount++;657}658}659if (selectableCount > 0) {660final File[] files = new File[selectableCount];661for (int i = 0, si = 0; i < rows.length; i++) {662f = (File)fFileList.getValueAt(rows[i], 0);663if (isSelectableForMode(chooser, f)) {664if (fileView.isAlias(f)) {665f = fileView.resolveAlias(f);666}667files[si++] = f;668}669}670chooser.setSelectedFiles(files);671} else {672chooser.setSelectedFiles(null);673}674} else {675chooser.setSelectedFiles(null);676chooser.setSelectedFile(f);677}678selectionInProgress = false;679}680}681682// When the Save textfield has the focus, the button should say "Save"683// Otherwise, it depends on the list selection684protected class SaveTextFocusListener implements FocusListener {685public void focusGained(final FocusEvent e) {686updateButtonState(getFileChooser());687}688689// Do nothing, we might be losing focus due to window deactivation690public void focusLost(final FocusEvent e) {691692}693}694695// When the Save textfield is empty and the button says "Save", it should be disabled696// Otherwise, it depends on the list selection697protected class SaveTextDocumentListener implements DocumentListener {698public void insertUpdate(final DocumentEvent e) {699textChanged();700}701702public void removeUpdate(final DocumentEvent e) {703textChanged();704}705706public void changedUpdate(final DocumentEvent e) {707708}709710void textChanged() {711updateButtonState(getFileChooser());712}713}714715// Opens the File object if it's a traversable directory716protected boolean openDirectory(final File f) {717if (getFileChooser().isTraversable(f)) {718fFileList.clearSelection();719// Resolve any aliases720final File original = fileView.resolveAlias(f);721getFileChooser().setCurrentDirectory(original);722updateButtonState(getFileChooser());723return true;724}725return false;726}727728// From Basic729protected class DoubleClickListener extends MouseAdapter {730JTableExtension list;731732public DoubleClickListener(final JTableExtension list) {733this.list = list;734}735736public void mouseClicked(final MouseEvent e) {737if (e.getClickCount() != 2) return;738739final int index = list.locationToIndex(e.getPoint());740if (index < 0) return;741742final File f = (File)((AquaFileSystemModel)list.getModel()).getElementAt(index);743if (openDirectory(f)) return;744745if (!isSelectableInList(f)) return;746getFileChooser().approveSelection();747}748}749750protected MouseListener createDoubleClickListener(final JFileChooser fc, final JTableExtension list) {751return new DoubleClickListener(list);752}753754// listens for drag events onto the JFileChooser and sets the selected file or directory755class DnDHandler extends DropTargetAdapter {756public void dragEnter(final DropTargetDragEvent dtde) {757tryToAcceptDrag(dtde);758}759760public void dragOver(final DropTargetDragEvent dtde) {761tryToAcceptDrag(dtde);762}763764public void dropActionChanged(final DropTargetDragEvent dtde) {765tryToAcceptDrag(dtde);766}767768public void drop(final DropTargetDropEvent dtde) {769if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {770handleFileDropEvent(dtde);771return;772}773774if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {775handleStringDropEvent(dtde);776return;777}778}779780protected void tryToAcceptDrag(final DropTargetDragEvent dtde) {781if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {782dtde.acceptDrag(DnDConstants.ACTION_COPY);783return;784}785786dtde.rejectDrag();787}788789protected void handleFileDropEvent(final DropTargetDropEvent dtde) {790dtde.acceptDrop(dtde.getDropAction());791final Transferable transferable = dtde.getTransferable();792793try {794@SuppressWarnings("unchecked")795final java.util.List<File> fileList = (java.util.List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);796dropFiles(fileList.toArray(new File[fileList.size()]));797dtde.dropComplete(true);798} catch (final Exception e) {799dtde.dropComplete(false);800}801}802803protected void handleStringDropEvent(final DropTargetDropEvent dtde) {804dtde.acceptDrop(dtde.getDropAction());805final Transferable transferable = dtde.getTransferable();806807final String stringData;808try {809stringData = (String)transferable.getTransferData(DataFlavor.stringFlavor);810} catch (final Exception e) {811dtde.dropComplete(false);812return;813}814815try {816final File fileAsPath = new File(stringData);817if (fileAsPath.exists()) {818dropFiles(new File[] {fileAsPath});819dtde.dropComplete(true);820return;821}822} catch (final Exception e) {823// try again824}825826try {827final File fileAsURI = new File(new URI(stringData));828if (fileAsURI.exists()) {829dropFiles(new File[] {fileAsURI});830dtde.dropComplete(true);831return;832}833} catch (final Exception e) {834// nothing more to do835}836837dtde.dropComplete(false);838}839840protected void dropFiles(final File[] files) {841final JFileChooser jfc = getFileChooser();842843if (files.length == 1) {844if (files[0].isDirectory()) {845jfc.setCurrentDirectory(files[0]);846return;847}848849if (!isSelectableForMode(jfc, files[0])) {850return;851}852}853854jfc.setSelectedFiles(files);855for (final File file : files) {856jfc.ensureFileIsVisible(file);857}858getModel().runWhenDone(new Runnable() {859public void run() {860final AquaFileSystemModel fileSystemModel = getModel();861for (final File element : files) {862final int index = fileSystemModel.indexOf(element);863if (index >= 0) fFileList.addRowSelectionInterval(index, index);864}865}866});867}868}869870// FileChooser UI PLAF methods871872/**873* Returns the default accept all file filter874*/875public FileFilter getAcceptAllFileFilter(final JFileChooser fc) {876return acceptAllFileFilter;877}878879public FileView getFileView(final JFileChooser fc) {880return fileView;881}882883/**884* Returns the title of this dialog885*/886public String getDialogTitle(final JFileChooser fc) {887if (fc.getDialogTitle() == null) {888if (getFileChooser().getDialogType() == JFileChooser.OPEN_DIALOG) {889return openTitleText;890} else if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) { return saveTitleText; }891}892return fc.getDialogTitle();893}894895// Utility to get the first selected item regardless of whether we're single or multi select896File getFirstSelectedItem() {897// Get the selected item898File selectedFile = null;899final int index = fFileList.getSelectedRow();900if (index >= 0) {901selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);902}903return selectedFile;904}905906// Make a file from the filename907File makeFile(final JFileChooser fc, final String filename) {908File selectedFile = null;909// whitespace is legal on Macs, even on beginning and end of filename910if (filename != null && !filename.isEmpty()) {911final FileSystemView fs = fc.getFileSystemView();912selectedFile = fs.createFileObject(filename);913if (!selectedFile.isAbsolute()) {914selectedFile = fs.createFileObject(fc.getCurrentDirectory(), filename);915}916}917return selectedFile;918}919920// Utility to tell if the textfield has anything in it921boolean textfieldIsValid() {922final String s = getFileName();923return (s != null && !s.isEmpty());924}925926// Action to attach to the file list so we can override the default action927// of the table for the return key, which is to select the next line.928@SuppressWarnings("serial") // Superclass is not serializable across versions929protected class DefaultButtonAction extends AbstractAction {930public void actionPerformed(final ActionEvent e) {931final JRootPane root = AquaFileChooserUI.this.getFileChooser().getRootPane();932final JFileChooser fc = AquaFileChooserUI.this.getFileChooser();933final JButton owner = root.getDefaultButton();934if (owner != null && SwingUtilities.getRootPane(owner) == root && owner.isEnabled()) {935owner.doClick(20);936} else if (!fc.getControlButtonsAreShown()) {937final JButton defaultButton = AquaFileChooserUI.this.fSubPanel.getDefaultButton(fc);938939if (defaultButton != null) {940defaultButton.doClick(20);941}942} else {943Toolkit.getDefaultToolkit().beep();944}945}946947public boolean isEnabled() {948return true;949}950}951952/**953* Creates a new folder.954*/955@SuppressWarnings("serial") // Superclass is not serializable across versions956protected class NewFolderAction extends AbstractAction {957protected NewFolderAction() {958super(newFolderAccessibleName);959}960961// Muchlike showInputDialog, but we give it options instead of selectionValues962private Object showNewFolderDialog(final Component parentComponent, final Object message, final String title, final int messageType, final Icon icon, final Object[] options, final Object initialSelectionValue) {963final JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.OK_CANCEL_OPTION, icon, options, null);964965pane.setWantsInput(true);966pane.setInitialSelectionValue(initialSelectionValue);967968final JDialog dialog = pane.createDialog(parentComponent, title);969970pane.selectInitialValue();971dialog.setVisible(true);972dialog.dispose();973974final Object value = pane.getValue();975976if (value == null || value.equals(cancelButtonText)977|| value.equals(JOptionPane.CLOSED_OPTION)) {978return null;979}980return pane.getInputValue();981}982983public void actionPerformed(final ActionEvent e) {984final JFileChooser fc = getFileChooser();985final File currentDirectory = fc.getCurrentDirectory();986File newFolder = null;987final String[] options = {createButtonText, cancelButtonText};988final String filename = (String)showNewFolderDialog(fc, //parentComponent989newFolderDialogPrompt, // message990newFolderTitleText, // title991JOptionPane.PLAIN_MESSAGE, // messageType992null, // icon993options, // selectionValues994newFolderDefaultName); // initialSelectionValue995996if (filename != null) {997try {998newFolder = fc.getFileSystemView().createFileObject(currentDirectory, filename);999if (newFolder.exists()) {1000JOptionPane.showMessageDialog(fc, newFolderExistsErrorText, "", JOptionPane.ERROR_MESSAGE);1001return;1002}10031004newFolder.mkdirs();1005} catch(final Exception exc) {1006JOptionPane.showMessageDialog(fc, newFolderErrorText, "", JOptionPane.ERROR_MESSAGE);1007return;1008}10091010openDirectory(newFolder);1011}1012}1013}10141015/**1016* Responds to an Open, Save, or Choose request1017*/1018@SuppressWarnings("serial") // Superclass is not serializable across versions1019protected class ApproveSelectionAction extends AbstractAction {1020public void actionPerformed(final ActionEvent e) {1021fSubPanel.approveSelection(getFileChooser());1022}1023}10241025/**1026* Responds to an OpenDirectory request1027*/1028@SuppressWarnings("serial") // Superclass is not serializable across versions1029protected class OpenSelectionAction extends AbstractAction {1030public void actionPerformed(final ActionEvent e) {1031final int index = fFileList.getSelectedRow();1032if (index >= 0) {1033final File selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);1034if (selectedFile != null) openDirectory(selectedFile);1035}1036}1037}10381039/**1040* Responds to a cancel request.1041*/1042@SuppressWarnings("serial") // Superclass is not serializable across versions1043protected class CancelSelectionAction extends AbstractAction {1044public void actionPerformed(final ActionEvent e) {1045getFileChooser().cancelSelection();1046}10471048public boolean isEnabled() {1049return getFileChooser().isEnabled();1050}1051}10521053/**1054* Rescans the files in the current directory1055*/1056@SuppressWarnings("serial") // Superclass is not serializable across versions1057protected class UpdateAction extends AbstractAction {1058public void actionPerformed(final ActionEvent e) {1059final JFileChooser fc = getFileChooser();1060fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName()));1061fc.rescanCurrentDirectory();1062}1063}10641065// *****************************************1066// ***** default AcceptAll file filter *****1067// *****************************************1068private static class AcceptAllFileFilter extends FileFilter {1069public AcceptAllFileFilter() {1070}10711072public boolean accept(final File f) {1073return true;1074}10751076public String getDescription() {1077return UIManager.getString("FileChooser.acceptAllFileFilterText");1078}1079}10801081// Penultimate superclass is JLabel1082@SuppressWarnings("serial") // Superclass is not serializable across versions1083protected class MacFCTableCellRenderer extends DefaultTableCellRenderer {1084boolean fIsSelected = false;10851086public MacFCTableCellRenderer(final Font f) {1087super();1088setFont(f);1089setIconTextGap(10);1090}10911092public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {1093super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks1094fIsSelected = isSelected;1095return this;1096}10971098public boolean isSelected() {1099return fIsSelected && isEnabled();1100}11011102protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon, final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {1103return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());1104}11051106protected void paintComponent(final Graphics g) {1107final String text = getText();1108Icon icon = getIcon();1109if (icon != null && !isEnabled()) {1110final Icon disabledIcon = getDisabledIcon();1111if (disabledIcon != null) icon = disabledIcon;1112}11131114if ((icon == null) && (text == null)) { return; }11151116// from ComponentUI update1117g.setColor(getBackground());1118g.fillRect(0, 0, getWidth(), getHeight());11191120// from BasicLabelUI paint1121final FontMetrics fm = g.getFontMetrics();1122Insets paintViewInsets = getInsets(null);1123paintViewInsets.left += 10;11241125Rectangle paintViewR = new Rectangle(paintViewInsets.left, paintViewInsets.top, getWidth() - (paintViewInsets.left + paintViewInsets.right), getHeight() - (paintViewInsets.top + paintViewInsets.bottom));11261127Rectangle paintIconR = new Rectangle();1128Rectangle paintTextR = new Rectangle();11291130final String clippedText = layoutCL(this, fm, text, icon, paintViewR, paintIconR, paintTextR);11311132if (icon != null) {1133icon.paintIcon(this, g, paintIconR.x + 5, paintIconR.y);1134}11351136if (text != null) {1137final int textX = paintTextR.x;1138final int textY = paintTextR.y + fm.getAscent() + 1;1139if (isEnabled()) {1140// Color background = fIsSelected ? getForeground() : getBackground();1141final Color background = getBackground();11421143g.setColor(background);1144g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);11451146g.setColor(getForeground());1147SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);1148} else {1149final Color background = getBackground();1150g.setColor(background);1151g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);11521153g.setColor(background.brighter());1154SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);1155g.setColor(background.darker());1156SwingUtilities2.drawString(filechooser, g, clippedText, textX + 1, textY + 1);1157}1158}1159}11601161}11621163@SuppressWarnings("serial") // Superclass is not serializable across versions1164protected class FileRenderer extends MacFCTableCellRenderer {1165public FileRenderer(final Font f) {1166super(f);1167}11681169public Component getTableCellRendererComponent(final JTable list,1170final Object value,1171final boolean isSelected,1172final boolean cellHasFocus,1173final int index,1174final int col) {1175super.getTableCellRendererComponent(list, value, isSelected, false,1176index,1177col); // No focus border, thanks1178final File file = (File)value;1179final JFileChooser fc = getFileChooser();1180setText(fc.getName(file));1181setIcon(fc.getIcon(file));1182setEnabled(isSelectableInList(file));1183return this;1184}1185}11861187@SuppressWarnings("serial") // Superclass is not serializable across versions1188protected class DateRenderer extends MacFCTableCellRenderer {1189public DateRenderer(final Font f) {1190super(f);1191}11921193public Component getTableCellRendererComponent(final JTable list,1194final Object value,1195final boolean isSelected,1196final boolean cellHasFocus,1197final int index,1198final int col) {1199super.getTableCellRendererComponent(list, value, isSelected, false,1200index, col);1201final File file = (File)fFileList.getValueAt(index, 0);1202setEnabled(isSelectableInList(file));1203final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);1204final Date date = (Date)value;12051206if (date != null) {1207setText(formatter.format(date));1208} else {1209setText("");1210}12111212return this;1213}1214}12151216@Override1217public Dimension getPreferredSize(final JComponent c) {1218return new Dimension(PREF_WIDTH, PREF_HEIGHT);1219}12201221@Override1222public Dimension getMinimumSize(final JComponent c) {1223return new Dimension(MIN_WIDTH, MIN_HEIGHT);1224}12251226@Override1227public Dimension getMaximumSize(final JComponent c) {1228return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);1229}12301231@SuppressWarnings("serial") // anonymous class1232protected ListCellRenderer<File> createDirectoryComboBoxRenderer(final JFileChooser fc) {1233return new AquaComboBoxRendererInternal<File>(directoryComboBox) {1234public Component getListCellRendererComponent(final JList<? extends File> list,1235final File directory,1236final int index,1237final boolean isSelected,1238final boolean cellHasFocus) {1239super.getListCellRendererComponent(list, directory, index, isSelected, cellHasFocus);1240if (directory == null) {1241setText("");1242return this;1243}12441245final JFileChooser chooser = getFileChooser();1246setText(chooser.getName(directory));1247setIcon(chooser.getIcon(directory));1248return this;1249}1250};1251}12521253//1254// DataModel for DirectoryComboxbox1255//1256protected DirectoryComboBoxModel createDirectoryComboBoxModel(final JFileChooser fc) {1257return new DirectoryComboBoxModel();1258}12591260/**1261* Data model for a type-face selection combo-box.1262*/1263@SuppressWarnings("serial") // Superclass is not serializable across versions1264protected class DirectoryComboBoxModel extends AbstractListModel<File> implements ComboBoxModel<File> {1265Vector<File> fDirectories = new Vector<File>();1266int topIndex = -1;1267int fPathCount = 0;12681269File fSelectedDirectory = null;12701271public DirectoryComboBoxModel() {1272super();1273// Add the current directory to the model, and make it the1274// selectedDirectory1275addItem(getFileChooser().getCurrentDirectory());1276}12771278/**1279* Removes the selected directory, and clears out the1280* path file entries leading up to that directory.1281*/1282private void removeSelectedDirectory() {1283fDirectories.removeAllElements();1284fPathCount = 0;1285fSelectedDirectory = null;1286// dump();1287}12881289/**1290* Adds the directory to the model and sets it to be selected,1291* additionally clears out the previous selected directory and1292* the paths leading up to it, if any.1293*/1294void addItem(final File directory) {1295if (directory == null) { return; }1296if (fSelectedDirectory != null) {1297removeSelectedDirectory();1298}12991300// create File instances of each directory leading up to the top1301File f = directory.getAbsoluteFile();1302final Vector<File> path = new Vector<File>(10);1303while (f.getParent() != null) {1304path.addElement(f);1305f = getFileChooser().getFileSystemView().createFileObject(f.getParent());1306};13071308// Add root file (the desktop) to the model1309final File[] roots = getFileChooser().getFileSystemView().getRoots();1310for (final File element : roots) {1311path.addElement(element);1312}1313fPathCount = path.size();13141315// insert all the path fDirectories leading up to the1316// selected directory in reverse order (current directory at top)1317for (int i = 0; i < path.size(); i++) {1318fDirectories.addElement(path.elementAt(i));1319}13201321setSelectedItem(fDirectories.elementAt(0));13221323// dump();1324}13251326public void setSelectedItem(final Object selectedDirectory) {1327this.fSelectedDirectory = (File)selectedDirectory;1328fireContentsChanged(this, -1, -1);1329}13301331public Object getSelectedItem() {1332return fSelectedDirectory;1333}13341335public int getSize() {1336return fDirectories.size();1337}13381339public File getElementAt(final int index) {1340return fDirectories.elementAt(index);1341}1342}13431344//1345// Renderer for Types ComboBox1346//1347@SuppressWarnings("serial") // anonymous class1348protected ListCellRenderer<FileFilter> createFilterComboBoxRenderer() {1349return new AquaComboBoxRendererInternal<FileFilter>(filterComboBox) {1350public Component getListCellRendererComponent(final JList<? extends FileFilter> list,1351final FileFilter filter,1352final int index,1353final boolean isSelected,1354final boolean cellHasFocus) {1355super.getListCellRendererComponent(list, filter, index, isSelected, cellHasFocus);1356if (filter != null) setText(filter.getDescription());1357return this;1358}1359};1360}13611362//1363// DataModel for Types Comboxbox1364//1365protected FilterComboBoxModel createFilterComboBoxModel() {1366return new FilterComboBoxModel();1367}13681369/**1370* Data model for a type-face selection combo-box.1371*/1372@SuppressWarnings("serial") // Superclass is not serializable across versions1373protected class FilterComboBoxModel extends AbstractListModel<FileFilter> implements ComboBoxModel<FileFilter>,1374PropertyChangeListener {1375protected FileFilter[] filters;1376Object oldFileFilter = getFileChooser().getFileFilter();13771378protected FilterComboBoxModel() {1379super();1380filters = getFileChooser().getChoosableFileFilters();1381}13821383public void propertyChange(PropertyChangeEvent e) {1384String prop = e.getPropertyName();1385if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {1386filters = (FileFilter[]) e.getNewValue();1387fireContentsChanged(this, -1, -1);1388} else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {1389setSelectedItem(e.getNewValue());1390}1391}13921393public void setSelectedItem(Object filter) {1394if (filter != null && !isSelectedFileFilterInModel(filter)) {1395oldFileFilter = filter;1396getFileChooser().setFileFilter((FileFilter) filter);1397fireContentsChanged(this, -1, -1);1398}1399}14001401private boolean isSelectedFileFilterInModel(Object filter) {1402return Objects.equals(filter, oldFileFilter);1403}14041405public Object getSelectedItem() {1406// Ensure that the current filter is in the list.1407// NOTE: we shouldnt' have to do this, since JFileChooser adds1408// the filter to the choosable filters list when the filter1409// is set. Lets be paranoid just in case someone overrides1410// setFileFilter in JFileChooser.1411FileFilter currentFilter = getFileChooser().getFileFilter();1412boolean found = false;1413if(currentFilter != null) {1414for (FileFilter filter : filters) {1415if (filter == currentFilter) {1416found = true;1417}1418}1419if(found == false) {1420getFileChooser().addChoosableFileFilter(currentFilter);1421}1422}1423return getFileChooser().getFileFilter();1424}14251426public int getSize() {1427if(filters != null) {1428return filters.length;1429} else {1430return 0;1431}1432}14331434public FileFilter getElementAt(int index) {1435if(index > getSize() - 1) {1436// This shouldn't happen. Try to recover gracefully.1437return getFileChooser().getFileFilter();1438}1439if(filters != null) {1440return filters[index];1441} else {1442return null;1443}1444}1445}14461447private boolean containsFileFilter(Object fileFilter) {1448return Objects.equals(fileFilter, getFileChooser().getFileFilter());1449}14501451/**1452* Acts when FilterComboBox has changed the selected item.1453*/1454@SuppressWarnings("serial") // Superclass is not serializable across versions1455protected class FilterComboBoxAction extends AbstractAction {1456protected FilterComboBoxAction() {1457super("FilterComboBoxAction");1458}14591460public void actionPerformed(final ActionEvent e) {1461Object selectedFilter = filterComboBox.getSelectedItem();1462if (!containsFileFilter(selectedFilter)) {1463getFileChooser().setFileFilter((FileFilter) selectedFilter);1464}1465}1466}14671468/**1469* Acts when DirectoryComboBox has changed the selected item.1470*/1471@SuppressWarnings("serial") // Superclass is not serializable across versions1472protected class DirectoryComboBoxAction extends AbstractAction {1473protected DirectoryComboBoxAction() {1474super("DirectoryComboBoxAction");1475}14761477public void actionPerformed(final ActionEvent e) {1478getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem());1479}1480}14811482// Sorting Table operations1483@SuppressWarnings("serial") // Superclass is not serializable across versions1484class JSortingTableHeader extends JTableHeader {1485public JSortingTableHeader(final TableColumnModel cm) {1486super(cm);1487setReorderingAllowed(true); // This causes mousePress to call setDraggedColumn1488}14891490// One sort state for each column. Both are ascending by default1491final boolean[] fSortAscending = {true, true};14921493// Instead of dragging, it selects which one to sort by1494public void setDraggedColumn(final TableColumn aColumn) {1495if (aColumn != null) {1496final int colIndex = aColumn.getModelIndex();1497if (colIndex != fSortColumn) {1498filechooser.firePropertyChange(AquaFileSystemModel.SORT_BY_CHANGED, fSortColumn, colIndex);1499fSortColumn = colIndex;1500} else {1501fSortAscending[colIndex] = !fSortAscending[colIndex];1502filechooser.firePropertyChange(AquaFileSystemModel.SORT_ASCENDING_CHANGED, !fSortAscending[colIndex], fSortAscending[colIndex]);1503}1504// Need to repaint the highlighted column.1505repaint();1506}1507}15081509// This stops mouseDrags from moving the column1510public TableColumn getDraggedColumn() {1511return null;1512}15131514protected TableCellRenderer createDefaultRenderer() {1515final DefaultTableCellRenderer label = new AquaTableCellRenderer();1516label.setHorizontalAlignment(SwingConstants.LEFT);1517return label;1518}15191520@SuppressWarnings("serial") // Superclass is not serializable across versions1521class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {1522public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {1523if (localTable != null) {1524final JTableHeader header = localTable.getTableHeader();1525if (header != null) {1526setForeground(header.getForeground());1527setBackground(header.getBackground());1528setFont(UIManager.getFont("TableHeader.font"));1529}1530}15311532setText((value == null) ? "" : value.toString());15331534// Modify the table "border" to draw smaller, and with the titles in the right position1535// and sort indicators, just like an NSSave/Open panel.1536final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();1537cellBorder.setSelected(column == fSortColumn);1538final int horizontalShift = (column == 0 ? 35 : 10);1539cellBorder.setHorizontalShift(horizontalShift);15401541if (column == fSortColumn) {1542cellBorder.setSortOrder(fSortAscending[column] ? AquaTableHeaderBorder.SORT_ASCENDING : AquaTableHeaderBorder.SORT_DECENDING);1543} else {1544cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);1545}1546setBorder(cellBorder);1547return this;1548}1549}1550}15511552public void installComponents(final JFileChooser fc) {1553JPanel tPanel; // temp panel1554// set to a Y BoxLayout. The chooser will be laid out top to bottom.1555fc.setLayout(new BoxLayout(fc, BoxLayout.Y_AXIS));1556fc.add(Box.createRigidArea(vstrut10));15571558// construct the top panel15591560final JPanel topPanel = new JPanel();1561topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));1562fc.add(topPanel);1563fc.add(Box.createRigidArea(vstrut10));15641565// Add the textfield pane15661567fTextfieldPanel = new JPanel();1568fTextfieldPanel.setLayout(new BorderLayout());1569// setBottomPanelForMode will make this visible if we need it1570fTextfieldPanel.setVisible(false);1571topPanel.add(fTextfieldPanel);15721573tPanel = new JPanel();1574tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.Y_AXIS));1575final JPanel labelArea = new JPanel();1576labelArea.setLayout(new FlowLayout(FlowLayout.CENTER));1577fTextFieldLabel = new JLabel(fileNameLabelText);1578labelArea.add(fTextFieldLabel);15791580// text field1581filenameTextField = new JTextField();1582fTextFieldLabel.setLabelFor(filenameTextField);1583filenameTextField.addActionListener(getAction(kOpen));1584filenameTextField.addFocusListener(new SaveTextFocusListener());1585final Dimension minSize = filenameTextField.getMinimumSize();1586Dimension d = new Dimension(250, (int)minSize.getHeight());1587filenameTextField.setPreferredSize(d);1588filenameTextField.setMaximumSize(d);1589labelArea.add(filenameTextField);1590final File f = fc.getSelectedFile();1591if (f != null) {1592setFileName(fc.getName(f));1593} else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {1594setFileName(newFileDefaultName);1595}15961597tPanel.add(labelArea);1598// separator line1599@SuppressWarnings("serial") // anonymous class1600final JSeparator sep = new JSeparator(){1601public Dimension getPreferredSize() {1602return new Dimension(((JComponent)getParent()).getWidth(), 3);1603}1604};1605tPanel.add(Box.createRigidArea(new Dimension(1, 8)));1606tPanel.add(sep);1607tPanel.add(Box.createRigidArea(new Dimension(1, 7)));1608fTextfieldPanel.add(tPanel, BorderLayout.CENTER);16091610// DirectoryComboBox, left-justified, 200x20 not including drop shadow1611directoryComboBox = new JComboBox<>();1612directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight");1613fDirectoryComboBoxModel = createDirectoryComboBoxModel(fc);1614directoryComboBox.setModel(fDirectoryComboBoxModel);1615directoryComboBox.addActionListener(directoryComboBoxAction);1616directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));1617directoryComboBox.setToolTipText(directoryComboBoxToolTipText);1618d = new Dimension(250, (int)directoryComboBox.getMinimumSize().getHeight());1619directoryComboBox.setPreferredSize(d);1620directoryComboBox.setMaximumSize(d);1621topPanel.add(directoryComboBox);16221623// ************************************** //1624// ** Add the directory/Accessory pane ** //1625// ************************************** //1626final JPanel centerPanel = new JPanel(new BorderLayout());1627fc.add(centerPanel);16281629// Accessory pane (equiv to Preview pane in NavServices)1630final JComponent accessory = fc.getAccessory();1631if (accessory != null) {1632getAccessoryPanel().add(accessory);1633}1634centerPanel.add(getAccessoryPanel(), BorderLayout.LINE_START);16351636// Directory list(table), right-justified, resizable1637final JPanel p = createList(fc);1638p.setMinimumSize(LIST_MIN_SIZE);1639centerPanel.add(p, BorderLayout.CENTER);16401641// ********************************** //1642// **** Construct the bottom panel ** //1643// ********************************** //1644fBottomPanel = new JPanel();1645fBottomPanel.setLayout(new BoxLayout(fBottomPanel, BoxLayout.Y_AXIS));1646fc.add(fBottomPanel);16471648// Filter label and combobox.1649// I know it's unMaclike, but the filter goes on Directory_only too.1650tPanel = new JPanel();1651tPanel.setLayout(new FlowLayout(FlowLayout.CENTER));1652tPanel.setBorder(AquaGroupBorder.getTitlelessBorder());1653final JLabel formatLabel = new JLabel(filesOfTypeLabelText);1654tPanel.add(formatLabel);16551656// Combobox1657filterComboBoxModel = createFilterComboBoxModel();1658fc.addPropertyChangeListener(filterComboBoxModel);1659filterComboBox = new JComboBox<>(filterComboBoxModel);1660formatLabel.setLabelFor(filterComboBox);1661filterComboBox.setRenderer(createFilterComboBoxRenderer());1662d = new Dimension(220, (int)filterComboBox.getMinimumSize().getHeight());1663filterComboBox.setPreferredSize(d);1664filterComboBox.setMaximumSize(d);1665filterComboBox.addActionListener(filterComboBoxAction);1666filterComboBox.setOpaque(false);1667tPanel.add(filterComboBox);16681669fBottomPanel.add(tPanel);16701671// fDirectoryPanel: New, Open, Cancel, Approve buttons, right-justified, 82x221672// (sometimes the NewFolder and OpenFolder buttons are invisible)1673fDirectoryPanel = new JPanel();1674fDirectoryPanel.setLayout(new BoxLayout(fDirectoryPanel, BoxLayout.PAGE_AXIS));1675JPanel directoryPanel = new JPanel(new BorderLayout());1676JPanel newFolderButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));1677newFolderButtonPanel.add(Box.createHorizontalStrut(20));1678fNewFolderButton = createNewFolderButton(); // Because we hide it depending on style1679newFolderButtonPanel.add(fNewFolderButton);1680directoryPanel.add(newFolderButtonPanel, BorderLayout.LINE_START);1681JPanel approveCancelButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));1682fOpenButton = createButton(kOpenDirectory, openButtonText);1683approveCancelButtonPanel.add(fOpenButton);1684approveCancelButtonPanel.add(Box.createHorizontalStrut(8));1685fCancelButton = createButton(kCancel, null);1686approveCancelButtonPanel.add(fCancelButton);1687approveCancelButtonPanel.add(Box.createHorizontalStrut(8));1688// The ApproveSelection button1689fApproveButton = new JButton();1690fApproveButton.addActionListener(fApproveSelectionAction);1691approveCancelButtonPanel.add(fApproveButton);1692approveCancelButtonPanel.add(Box.createHorizontalStrut(20));1693directoryPanel.add(approveCancelButtonPanel, BorderLayout.LINE_END);1694fDirectoryPanel.add(Box.createVerticalStrut(5));1695fDirectoryPanel.add(directoryPanel);1696fDirectoryPanel.add(Box.createVerticalStrut(12));1697fDirectoryPanelSpacer = Box.createRigidArea(hstrut10);16981699if (fc.getControlButtonsAreShown()) {1700fBottomPanel.add(fDirectoryPanelSpacer);1701fBottomPanel.add(fDirectoryPanel);1702}17031704setBottomPanelForMode(fc); // updates ApproveButtonText etc17051706// don't create til after the FCSubpanel and buttons are made1707filenameTextField.getDocument().addDocumentListener(new SaveTextDocumentListener());1708}17091710void setDefaultButtonForMode(final JFileChooser fc) {1711final JButton defaultButton = fSubPanel.getDefaultButton(fc);1712final JRootPane root = defaultButton.getRootPane();1713if (root != null) {1714root.setDefaultButton(defaultButton);1715}1716}17171718// Macs start with their focus in text areas if they have them,1719// lists otherwise (the other plafs start with the focus on approveButton)1720void setFocusForMode(final JFileChooser fc) {1721final JComponent focusComponent = fSubPanel.getFocusComponent(fc);1722if (focusComponent != null) {1723focusComponent.requestFocus();1724}1725}17261727// Enable/disable buttons as needed for the current selection/focus state1728void updateButtonState(final JFileChooser fc) {1729fSubPanel.updateButtonState(fc, getFirstSelectedItem());1730updateApproveButton(fc);1731}17321733void updateApproveButton(final JFileChooser chooser) {1734fApproveButton.setText(getApproveButtonText(chooser));1735fApproveButton.setToolTipText(getApproveButtonToolTipText(chooser));1736fApproveButton.setMnemonic(getApproveButtonMnemonic(chooser));1737fCancelButton.setToolTipText(getCancelButtonToolTipText(chooser));1738}17391740// Lazy-init the subpanels1741synchronized FCSubpanel getSaveFilePanel() {1742if (fSaveFilePanel == null) fSaveFilePanel = new SaveFilePanel();1743return fSaveFilePanel;1744}17451746synchronized FCSubpanel getOpenFilePanel() {1747if (fOpenFilePanel == null) fOpenFilePanel = new OpenFilePanel();1748return fOpenFilePanel;1749}17501751synchronized FCSubpanel getOpenDirOrAnyPanel() {1752if (fOpenDirOrAnyPanel == null) fOpenDirOrAnyPanel = new OpenDirOrAnyPanel();1753return fOpenDirOrAnyPanel;1754}17551756synchronized FCSubpanel getCustomFilePanel() {1757if (fCustomFilePanel == null) fCustomFilePanel = new CustomFilePanel();1758return fCustomFilePanel;1759}17601761synchronized FCSubpanel getCustomDirOrAnyPanel() {1762if (fCustomDirOrAnyPanel == null) fCustomDirOrAnyPanel = new CustomDirOrAnyPanel();1763return fCustomDirOrAnyPanel;1764}17651766void setBottomPanelForMode(final JFileChooser fc) {1767if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) fSubPanel = getSaveFilePanel();1768else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) {1769if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getOpenFilePanel();1770else fSubPanel = getOpenDirOrAnyPanel();1771} else if (fc.getDialogType() == JFileChooser.CUSTOM_DIALOG) {1772if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getCustomFilePanel();1773else fSubPanel = getCustomDirOrAnyPanel();1774}17751776fSubPanel.installPanel(fc, true);1777updateApproveButton(fc);1778updateButtonState(fc);1779setDefaultButtonForMode(fc);1780setFocusForMode(fc);1781fc.invalidate();1782}17831784// fTextfieldPanel and fDirectoryPanel both have NewFolder buttons; only one should be visible at a time1785JButton createNewFolderButton() {1786final JButton b = new JButton(newFolderButtonText);1787b.setToolTipText(newFolderToolTipText);1788b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);1789b.setHorizontalTextPosition(SwingConstants.LEFT);1790b.setAlignmentX(Component.LEFT_ALIGNMENT);1791b.setAlignmentY(Component.CENTER_ALIGNMENT);1792b.addActionListener(getAction(kNewFolder));1793return b;1794}17951796JButton createButton(final int which, String label) {1797if (label == null) label = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[0]);1798final int mnemonic = UIManager.getInt(sDataPrefix + sButtonKinds[which] + sButtonData[1]);1799final String tipText = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[2]);1800final JButton b = new JButton(label);1801b.setMnemonic(mnemonic);1802b.setToolTipText(tipText);1803b.addActionListener(getAction(which));1804return b;1805}18061807AbstractAction getAction(final int which) {1808return fButtonActions[which];1809}18101811public void uninstallComponents(final JFileChooser fc) {1812// AquaButtonUI install some listeners to all parents, which means that1813// we need to uninstall UI here to remove those listeners, because after1814// we remove them from FileChooser we lost the latest reference to them,1815// and our standard uninstallUI machinery will not call them.1816fApproveButton.getUI().uninstallUI(fApproveButton);1817fOpenButton.getUI().uninstallUI(fOpenButton);1818fNewFolderButton.getUI().uninstallUI(fNewFolderButton);1819fCancelButton.getUI().uninstallUI(fCancelButton);1820directoryComboBox.getUI().uninstallUI(directoryComboBox);1821filterComboBox.getUI().uninstallUI(filterComboBox);1822}18231824// Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field1825// with that file's display name.1826protected class FileListMouseListener extends MouseAdapter {1827public void mouseClicked(final MouseEvent e) {1828final Point p = e.getPoint();1829final int row = fFileList.rowAtPoint(p);1830final int column = fFileList.columnAtPoint(p);18311832// The autoscroller can generate drag events outside the Table's range.1833if ((column == -1) || (row == -1)) { return; }18341835final File clickedFile = (File)(fFileList.getValueAt(row, 0));18361837// rdar://problem/3734130 -- don't populate the text field if this file isn't selectable in this mode.1838if (isSelectableForMode(getFileChooser(), clickedFile)) {1839// [3188387] Populate the file name field with the selected file name1840// [3484163] It should also use the display name, not the actual name.1841setFileName(fileView.getName(clickedFile));1842}1843}1844}18451846protected JPanel createList(final JFileChooser fc) {1847// The first part is similar to MetalFileChooserUI.createList - same kind of listeners1848final JPanel p = new JPanel(new BorderLayout());1849fFileList = new JTableExtension();1850fFileList.setToolTipText(null); // Workaround for 24876891851fFileList.addMouseListener(new FileListMouseListener());1852model = new AquaFileSystemModel(fc, fFileList, fColumnNames);1853final MacListSelectionModel listSelectionModel = new MacListSelectionModel(model);18541855if (getFileChooser().isMultiSelectionEnabled()) {1856listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);1857} else {1858listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);1859}18601861fFileList.setModel(model);1862fFileList.setSelectionModel(listSelectionModel);1863fFileList.getSelectionModel().addListSelectionListener(createListSelectionListener(fc));18641865// Now we're different, because we're a table, not a list1866fc.addPropertyChangeListener(model);1867fFileList.addFocusListener(new SaveTextFocusListener());1868final JTableHeader th = new JSortingTableHeader(fFileList.getColumnModel());1869fFileList.setTableHeader(th);1870fFileList.setRowMargin(0);1871fFileList.setIntercellSpacing(new Dimension(0, 1));1872fFileList.setShowVerticalLines(false);1873fFileList.setShowHorizontalLines(false);1874final Font f = fFileList.getFont(); //ThemeFont.GetThemeFont(AppearanceConstants.kThemeViewsFont);1875//fc.setFont(f);1876//fFileList.setFont(f);1877fFileList.setDefaultRenderer(File.class, new FileRenderer(f));1878fFileList.setDefaultRenderer(Date.class, new DateRenderer(f));1879final FontMetrics fm = fFileList.getFontMetrics(f);18801881// Row height isn't based on the renderers. It defaults to 16 so we have to set it1882fFileList.setRowHeight(Math.max(fm.getHeight(), fileIcon.getIconHeight() + 2));18831884// Add a binding for the file list that triggers return and escape1885fFileList.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);1886// Add a binding for the file list that triggers the default button (see DefaultButtonAction)1887fFileList.registerKeyboardAction(new DefaultButtonAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);1888fFileList.setDropTarget(dragAndDropTarget);18891890final JScrollPane scrollpane = new JScrollPane(fFileList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);1891scrollpane.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));1892scrollpane.setCorner(ScrollPaneConstants.UPPER_TRAILING_CORNER, new ScrollPaneCornerPanel());1893p.add(scrollpane, BorderLayout.CENTER);1894return p;1895}18961897@SuppressWarnings("serial") // Superclass is not serializable across versions1898protected class ScrollPaneCornerPanel extends JPanel {1899final Border border = UIManager.getBorder("TableHeader.cellBorder");19001901protected void paintComponent(final Graphics g) {1902border.paintBorder(this, g, 0, 0, getWidth() + 1, getHeight());1903}1904}19051906JComboBox<File> directoryComboBox;1907DirectoryComboBoxModel fDirectoryComboBoxModel;1908private final Action directoryComboBoxAction = new DirectoryComboBoxAction();19091910JTextField filenameTextField;19111912JTableExtension fFileList;19131914private FilterComboBoxModel filterComboBoxModel;1915JComboBox<FileFilter> filterComboBox;1916private final Action filterComboBoxAction = new FilterComboBoxAction();19171918private static final Dimension hstrut10 = new Dimension(10, 1);1919private static final Dimension vstrut10 = new Dimension(1, 10);19201921private static final int PREF_WIDTH = 550;1922private static final int PREF_HEIGHT = 400;1923private static final int MIN_WIDTH = 400;1924private static final int MIN_HEIGHT = 250;1925private static final int LIST_MIN_WIDTH = 400;1926private static final int LIST_MIN_HEIGHT = 100;1927private static final Dimension LIST_MIN_SIZE = new Dimension(LIST_MIN_WIDTH, LIST_MIN_HEIGHT);19281929static String fileNameLabelText = null;1930JLabel fTextFieldLabel = null;19311932private static String filesOfTypeLabelText = null;19331934private static String newFolderToolTipText = null;1935static String newFolderAccessibleName = null;19361937private static final String[] fColumnNames = new String[2];19381939JPanel fTextfieldPanel; // Filename textfield for Save or Custom1940private JPanel fDirectoryPanel; // NewFolder/OpenFolder/Cancel/Approve buttons1941private Component fDirectoryPanelSpacer;1942private JPanel fBottomPanel; // The panel that holds fDirectoryPanel and filterComboBox19431944private FCSubpanel fSaveFilePanel = null;1945private FCSubpanel fOpenFilePanel = null;1946private FCSubpanel fOpenDirOrAnyPanel = null;1947private FCSubpanel fCustomFilePanel = null;1948private FCSubpanel fCustomDirOrAnyPanel = null;19491950FCSubpanel fSubPanel = null; // Current FCSubpanel19511952JButton fApproveButton; // mode-specific behavior is managed by FCSubpanel.approveSelection1953JButton fOpenButton; // for Directories1954JButton fNewFolderButton; // for fDirectoryPanel19551956// ToolTip text varies with type of dialog1957private JButton fCancelButton;19581959private final ApproveSelectionAction fApproveSelectionAction = new ApproveSelectionAction();1960protected int fSortColumn = 0;1961protected int fPackageIsTraversable = -1;1962protected int fApplicationIsTraversable = -1;19631964protected static final int sGlobalPackageIsTraversable;1965protected static final int sGlobalApplicationIsTraversable;19661967protected static final String PACKAGE_TRAVERSABLE_PROPERTY = "JFileChooser.packageIsTraversable";1968protected static final String APPLICATION_TRAVERSABLE_PROPERTY = "JFileChooser.appBundleIsTraversable";1969protected static final String[] sTraversableProperties = {"always", // Bundle is always traversable1970"never", // Bundle is never traversable1971"conditional"}; // Bundle is traversable on command click1972protected static final int kOpenAlways = 0, kOpenNever = 1, kOpenConditional = 2;19731974AbstractAction[] fButtonActions = {fApproveSelectionAction, fApproveSelectionAction, new CancelSelectionAction(), new OpenSelectionAction(), null, new NewFolderAction()};19751976static int parseTraversableProperty(final String s) {1977if (s == null) return -1;1978for (int i = 0; i < sTraversableProperties.length; i++) {1979if (s.equals(sTraversableProperties[i])) return i;1980}1981return -1;1982}19831984static {1985Object o = UIManager.get(PACKAGE_TRAVERSABLE_PROPERTY);1986if (o != null && o instanceof String) sGlobalPackageIsTraversable = parseTraversableProperty((String)o);1987else sGlobalPackageIsTraversable = kOpenConditional;19881989o = UIManager.get(APPLICATION_TRAVERSABLE_PROPERTY);1990if (o != null && o instanceof String) sGlobalApplicationIsTraversable = parseTraversableProperty((String)o);1991else sGlobalApplicationIsTraversable = kOpenConditional;1992}1993static final String sDataPrefix = "FileChooser.";1994static final String[] sButtonKinds = {"openButton", "saveButton", "cancelButton", "openDirectoryButton", "helpButton", "newFolderButton"};1995static final String[] sButtonData = {"Text", "Mnemonic", "ToolTipText"};1996static final int kOpen = 0, kSave = 1, kCancel = 2, kOpenDirectory = 3, kHelp = 4, kNewFolder = 5;19971998/*-------19992000Possible states: Save, {Open, Custom}x{Files, File and Directory, Directory}2001--------- */20022003// This class returns the values for the Custom type, to avoid duplicating code in the two Custom subclasses2004abstract class FCSubpanel {2005// Install the appropriate panels for this mode2006abstract void installPanel(JFileChooser fc, boolean controlButtonsAreShown);20072008abstract void updateButtonState(JFileChooser fc, File f);20092010// Can this item be selected?2011// if not, it's disabled in the list2012boolean isSelectableInList(final JFileChooser fc, final File f) {2013if (f == null) return false;2014if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) return fc.isTraversable(f);2015return fc.accept(f);2016}20172018void approveSelection(final JFileChooser fc) {2019fc.approveSelection();2020}20212022JButton getDefaultButton(final JFileChooser fc) {2023return fApproveButton;2024}20252026// Default to the textfield, panels without one should subclass2027JComponent getFocusComponent(final JFileChooser fc) {2028return filenameTextField;2029}20302031String getApproveButtonText(final JFileChooser fc) {2032// Fallback to "choose"2033return this.getApproveButtonText(fc, chooseButtonText);2034}20352036// Try to get the custom text. If none, use the fallback2037String getApproveButtonText(final JFileChooser fc, final String fallbackText) {2038final String buttonText = fc.getApproveButtonText();2039return buttonText != null2040? buttonText2041: fallbackText;2042}20432044int getApproveButtonMnemonic(final JFileChooser fc) {2045// Don't use a default2046return fc.getApproveButtonMnemonic();2047}20482049// No fallback2050String getApproveButtonToolTipText(final JFileChooser fc) {2051return getApproveButtonToolTipText(fc, null);2052}20532054String getApproveButtonToolTipText(final JFileChooser fc, final String fallbackText) {2055final String tooltipText = fc.getApproveButtonToolTipText();2056return tooltipText != null2057? tooltipText2058: fallbackText;2059}20602061String getCancelButtonToolTipText(final JFileChooser fc) {2062return cancelChooseButtonToolTipText;2063}2064}20652066// Custom FILES_ONLY dialog2067/*2068NavServices Save appearance with Open behavior2069Approve button label = Open when list has focus and a directory is selected, Custom otherwise2070No OpenDirectory button - Approve button is overloaded2071Default button / double click = Approve2072Has text field2073List - everything is enabled2074*/2075class CustomFilePanel extends FCSubpanel {2076void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2077fTextfieldPanel.setVisible(true); // do we really want one in multi-select? It's confusing2078fOpenButton.setVisible(false);2079fNewFolderButton.setVisible(true);2080}20812082// If the list has focus, the mode depends on the selection2083// - directory = open, file = approve2084// If something else has focus and we have text, it's approve2085// otherwise, it depends on selection again.2086boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {2087final boolean selectionIsDirectory = (f != null && fc.isTraversable(f));2088if (fFileList.hasFocus()) return selectionIsDirectory;2089else if (textfieldIsValid()) return false;2090return selectionIsDirectory;2091}20922093// The approve button is overloaded to mean OpenDirectory or Save2094void approveSelection(final JFileChooser fc) {2095File f = getFirstSelectedItem();2096if (inOpenDirectoryMode(fc, f)) {2097openDirectory(f);2098} else {2099f = makeFile(fc, getFileName());2100if (f != null) {2101selectionInProgress = true;2102getFileChooser().setSelectedFile(f);2103selectionInProgress = false;2104}2105getFileChooser().approveSelection();2106}2107}21082109// The approve button should be enabled2110// - if something in the list can be opened2111// - if the textfield has something in it2112void updateButtonState(final JFileChooser fc, final File f) {2113boolean enabled = true;2114if (!inOpenDirectoryMode(fc, f)) {2115enabled = (f != null) || textfieldIsValid();2116}2117getApproveButton(fc).setEnabled(enabled);21182119// The OpenDirectory button should be disabled if there's no directory selected2120fOpenButton.setEnabled(f != null && fc.isTraversable(f));21212122// Update the default button, since we may have disabled the current default.2123setDefaultButtonForMode(fc);2124}21252126// everything's enabled, because we don't know what they're doing with them2127boolean isSelectableInList(final JFileChooser fc, final File f) {2128if (f == null) return false;2129return fc.accept(f);2130}21312132String getApproveButtonToolTipText(final JFileChooser fc) {2133// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...2134if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;2135return super.getApproveButtonToolTipText(fc);2136}2137}21382139// All Save dialogs2140/*2141NavServices Save2142Approve button label = Open when list has focus and a directory is selected, Save otherwise2143No OpenDirectory button - Approve button is overloaded2144Default button / double click = Approve2145Has text field2146Has NewFolder button (by text field)2147List - only traversables are enabled2148List is always SINGLE_SELECT2149*/2150// Subclasses CustomFilePanel because they look alike and have some common behavior2151class SaveFilePanel extends CustomFilePanel {2152void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2153fTextfieldPanel.setVisible(true);2154fOpenButton.setVisible(false);2155fNewFolderButton.setVisible(true);2156}21572158// only traversables are enabled, regardless of mode2159// because all you can do is select the next folder to open2160boolean isSelectableInList(final JFileChooser fc, final File f) {2161return fc.accept(f) && fc.isTraversable(f);2162}21632164// The approve button means 'approve the file name in the text field.'2165void approveSelection(final JFileChooser fc) {2166final File f = makeFile(fc, getFileName());2167if (f != null) {2168selectionInProgress = true;2169getFileChooser().setSelectedFile(f);2170selectionInProgress = false;2171getFileChooser().approveSelection();2172}2173}21742175// The approve button should be enabled if the textfield has something in it2176void updateButtonState(final JFileChooser fc, final File f) {2177final boolean enabled = textfieldIsValid();2178getApproveButton(fc).setEnabled(enabled);2179}21802181String getApproveButtonText(final JFileChooser fc) {2182// Get the custom text, or fallback to "Save"2183return this.getApproveButtonText(fc, saveButtonText);2184}21852186int getApproveButtonMnemonic(final JFileChooser fc) {2187return saveButtonMnemonic;2188}21892190String getApproveButtonToolTipText(final JFileChooser fc) {2191// The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...2192if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;2193return this.getApproveButtonToolTipText(fc, saveButtonToolTipText);2194}21952196String getCancelButtonToolTipText(final JFileChooser fc) {2197return cancelSaveButtonToolTipText;2198}2199}22002201// Open FILES_ONLY2202/*2203NSOpenPanel-style2204Approve button label = Open2205Default button / double click = Approve2206No text field2207No NewFolder button2208List - all items are enabled2209*/2210class OpenFilePanel extends FCSubpanel {2211void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2212fTextfieldPanel.setVisible(false);2213fOpenButton.setVisible(false);2214fNewFolderButton.setVisible(false);2215setDefaultButtonForMode(fc);2216}22172218boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {2219return (f != null && fc.isTraversable(f));2220}22212222// Default to the list2223JComponent getFocusComponent(final JFileChooser fc) {2224return fFileList;2225}22262227void updateButtonState(final JFileChooser fc, final File f) {2228// Button is disabled if there's nothing selected2229final boolean enabled = (f != null) && !fc.isTraversable(f);2230getApproveButton(fc).setEnabled(enabled);2231}22322233// all items are enabled2234boolean isSelectableInList(final JFileChooser fc, final File f) {2235return f != null && fc.accept(f);2236}22372238String getApproveButtonText(final JFileChooser fc) {2239// Get the custom text, or fallback to "Open"2240return this.getApproveButtonText(fc, openButtonText);2241}22422243int getApproveButtonMnemonic(final JFileChooser fc) {2244return openButtonMnemonic;2245}22462247String getApproveButtonToolTipText(final JFileChooser fc) {2248return this.getApproveButtonToolTipText(fc, openButtonToolTipText);2249}22502251String getCancelButtonToolTipText(final JFileChooser fc) {2252return cancelOpenButtonToolTipText;2253}2254}22552256// used by open and custom panels for Directory only or files and directories2257abstract class DirOrAnyPanel extends FCSubpanel {2258void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2259fOpenButton.setVisible(false);2260}22612262JButton getDefaultButton(final JFileChooser fc) {2263return getApproveButton(fc);2264}22652266void updateButtonState(final JFileChooser fc, final File f) {2267// Button is disabled if there's nothing selected2268// Approve button is handled by the subclasses2269// getApproveButton(fc).setEnabled(f != null);22702271// The OpenDirectory button should be disabled if there's no directory selected2272// - we only check the first item22732274fOpenButton.setEnabled(false);2275setDefaultButtonForMode(fc);2276}2277}22782279// Open FILES_AND_DIRECTORIES or DIRECTORIES_ONLY2280/*2281NavServices Choose2282Approve button label = Choose/Custom2283Has OpenDirectory button2284Default button / double click = OpenDirectory2285No text field2286List - files are disabled in DIRECTORIES_ONLY2287*/2288class OpenDirOrAnyPanel extends DirOrAnyPanel {2289void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2290super.installPanel(fc, controlButtonsAreShown);2291fTextfieldPanel.setVisible(false);2292fNewFolderButton.setVisible(false);2293}22942295// Default to the list2296JComponent getFocusComponent(final JFileChooser fc) {2297return fFileList;2298}22992300int getApproveButtonMnemonic(final JFileChooser fc) {2301return chooseButtonMnemonic;2302}23032304String getApproveButtonToolTipText(final JFileChooser fc) {2305String fallbackText;2306if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) fallbackText = chooseFolderButtonToolTipText;2307else fallbackText = chooseItemButtonToolTipText;2308return this.getApproveButtonToolTipText(fc, fallbackText);2309}23102311void updateButtonState(final JFileChooser fc, final File f) {2312// Button is disabled if there's nothing selected2313getApproveButton(fc).setEnabled(f != null);2314super.updateButtonState(fc, f);2315}2316}23172318// Custom FILES_AND_DIRECTORIES or DIRECTORIES_ONLY2319/*2320No NavServices equivalent2321Approve button label = user defined or Choose2322Has OpenDirectory button2323Default button / double click = OpenDirectory2324Has text field2325Has NewFolder button (by text field)2326List - files are disabled in DIRECTORIES_ONLY2327*/2328class CustomDirOrAnyPanel extends DirOrAnyPanel {2329void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {2330super.installPanel(fc, controlButtonsAreShown);2331fTextfieldPanel.setVisible(true);2332fNewFolderButton.setVisible(true);2333}23342335// If there's text, make a file and select it2336void approveSelection(final JFileChooser fc) {2337final File f = makeFile(fc, getFileName());2338if (f != null) {2339selectionInProgress = true;2340getFileChooser().setSelectedFile(f);2341selectionInProgress = false;2342}2343getFileChooser().approveSelection();2344}23452346void updateButtonState(final JFileChooser fc, final File f) {2347// Button is disabled if there's nothing selected2348getApproveButton(fc).setEnabled(f != null || textfieldIsValid());2349super.updateButtonState(fc, f);2350}2351}23522353// See FileRenderer - documents in Save dialogs draw disabled, so they shouldn't be selected2354@SuppressWarnings("serial") // Superclass is not serializable across versions2355class MacListSelectionModel extends DefaultListSelectionModel {2356AquaFileSystemModel fModel;23572358MacListSelectionModel(final AquaFileSystemModel model) {2359fModel = model;2360}23612362// Can the file be selected in this mode?2363// (files are visible even if they can't be selected)2364boolean isSelectableInListIndex(final int index) {2365final File file = (File)fModel.getValueAt(index, 0);2366return (file != null && isSelectableInList(file));2367}23682369// Make sure everything in the selection interval is valid2370void verifySelectionInterval(int index0, int index1, boolean isSetSelection) {2371if (index0 > index1) {2372final int tmp = index1;2373index1 = index0;2374index0 = tmp;2375}2376int start = index0;2377int end;2378do {2379// Find the first selectable file in the range2380for (; start <= index1; start++) {2381if (isSelectableInListIndex(start)) break;2382}2383end = -1;2384// Find the last selectable file in the range2385for (int i = start; i <= index1; i++) {2386if (!isSelectableInListIndex(i)) {2387break;2388}2389end = i;2390}2391// Select the range2392if (end >= 0) {2393// If setting the selection, do "set" the first time to clear the old one2394// after that do "add" to extend it2395if (isSetSelection) {2396super.setSelectionInterval(start, end);2397isSetSelection = false;2398} else {2399super.addSelectionInterval(start, end);2400}2401start = end + 1;2402} else {2403break;2404}2405} while (start <= index1);2406}24072408public void setAnchorSelectionIndex(final int anchorIndex) {2409if (isSelectableInListIndex(anchorIndex)) super.setAnchorSelectionIndex(anchorIndex);2410}24112412public void setLeadSelectionIndex(final int leadIndex) {2413if (isSelectableInListIndex(leadIndex)) super.setLeadSelectionIndex(leadIndex);2414}24152416public void setSelectionInterval(final int index0, final int index1) {2417if (index0 == -1 || index1 == -1) { return; }24182419if ((getSelectionMode() == SINGLE_SELECTION) || (index0 == index1)) {2420if (isSelectableInListIndex(index1)) super.setSelectionInterval(index1, index1);2421} else {2422verifySelectionInterval(index0, index1, true);2423}2424}24252426public void addSelectionInterval(final int index0, final int index1) {2427if (index0 == -1 || index1 == -1) { return; }24282429if (index0 == index1) {2430if (isSelectableInListIndex(index1)) super.addSelectionInterval(index1, index1);2431return;2432}24332434if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {2435setSelectionInterval(index0, index1);2436return;2437}24382439verifySelectionInterval(index0, index1, false);2440}2441}24422443// Convenience, to translate from the JList directory view to the Mac-style JTable2444// & minimize diffs between this and BasicFileChooserUI2445@SuppressWarnings("serial") // Superclass is not serializable across versions2446class JTableExtension extends JTable {2447public void setSelectedIndex(final int index) {2448getSelectionModel().setSelectionInterval(index, index);2449}24502451public void removeSelectedIndex(final int index) {2452getSelectionModel().removeSelectionInterval(index, index);2453}24542455public void ensureIndexIsVisible(final int index) {2456final Rectangle cellBounds = getCellRect(index, 0, false);2457if (cellBounds != null) {2458scrollRectToVisible(cellBounds);2459}2460}24612462public int locationToIndex(final Point location) {2463return rowAtPoint(location);2464}2465}2466}246724682469