       1 /*
       2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.apple.laf;
      27
      28 import java.awt.*;
      29 import java.awt.datatransfer.*;
      30 import java.awt.dnd.*;
      31 import java.awt.event.*;
      32 import java.beans.*;
      33 import java.io.File;
      34 import java.net.URI;
      35 import java.text.DateFormat;
      36 import java.util.*;
      37
      38 import javax.swing.*;
      39 import javax.swing.border.Border;
      40 import javax.swing.event.*;
      41 import javax.swing.filechooser.*;
      42 import javax.swing.plaf.*;
      43 import javax.swing.table.*;
      44
      45 import sun.swing.SwingUtilities2;
      46
      47 public class AquaFileChooserUI extends FileChooserUI {
      48     /* FileView icons */
      49     protected Icon directoryIcon = null;
      50     protected Icon fileIcon = null;
      51     protected Icon computerIcon = null;
      52     protected Icon hardDriveIcon = null;
      53     protected Icon floppyDriveIcon = null;
      54
      55     protected Icon upFolderIcon = null;
      56     protected Icon homeFolderIcon = null;
      57     protected Icon listViewIcon = null;
      58     protected Icon detailsViewIcon = null;
      59
      60     protected int saveButtonMnemonic = 0;
      61     protected int openButtonMnemonic = 0;
      62     protected int cancelButtonMnemonic = 0;
      63     protected int updateButtonMnemonic = 0;
      64     protected int helpButtonMnemonic = 0;
      65     protected int chooseButtonMnemonic = 0;
      66
      67     private String saveTitleText = null;
      68     private String openTitleText = null;
      69     String newFolderTitleText = null;
      70
      71     protected String saveButtonText = null;
      72     protected String openButtonText = null;
      73     protected String cancelButtonText = null;
      74     protected String updateButtonText = null;
      75     protected String helpButtonText = null;
      76     protected String newFolderButtonText = null;
      77     protected String chooseButtonText = null;
      78
      79     //private String newFolderErrorSeparator = null;
      80     String newFolderErrorText = null;
      81     String newFolderExistsErrorText = null;
      82     protected String fileDescriptionText = null;
      83     protected String directoryDescriptionText = null;
      84
      85     protected String saveButtonToolTipText = null;
      86     protected String openButtonToolTipText = null;
      87     protected String cancelButtonToolTipText = null;
      88     protected String updateButtonToolTipText = null;
      89     protected String helpButtonToolTipText = null;
      90     protected String chooseItemButtonToolTipText = null; // Choose anything
      91     protected String chooseFolderButtonToolTipText = null; // Choose folder
      92     protected String directoryComboBoxToolTipText = null;
      93     protected String filenameTextFieldToolTipText = null;
      94     protected String filterComboBoxToolTipText = null;
      95     protected String openDirectoryButtonToolTipText = null;
      96
      97     protected String cancelOpenButtonToolTipText = null;
      98     protected String cancelSaveButtonToolTipText = null;
      99     protected String cancelChooseButtonToolTipText = null;
     100     protected String cancelNewFolderButtonToolTipText = null;
     101
     102     protected String desktopName = null;
     103     String newFolderDialogPrompt = null;
     104     String newFolderDefaultName = null;
     105     private String newFileDefaultName = null;
     106     String createButtonText = null;
     107
     108     JFileChooser filechooser = null;
     109
     110     private MouseListener doubleClickListener = null;
     111     private PropertyChangeListener propertyChangeListener = null;
     112     private AncestorListener ancestorListener = null;
     113     private DropTarget dragAndDropTarget = null;
     114
     115     private static final AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter();
     116
     117     private AquaFileSystemModel model;
     118
     119     final AquaFileView fileView = new AquaFileView(this);
     120
     121     boolean selectionInProgress = false;
     122
     123     // The accessoryPanel is a container to place the JFileChooser accessory component
     124     private JPanel accessoryPanel = null;
     125
     126     //
     127     // ComponentUI Interface Implementation methods
     128     //
     129     public static ComponentUI createUI(final JComponent c) {
     130         return new AquaFileChooserUI((JFileChooser)c);
     131     }
     132
     133     public AquaFileChooserUI(final JFileChooser filechooser) {
     134         super();
     135     }
     136
     137     public void installUI(final JComponent c) {
     138         accessoryPanel = new JPanel(new BorderLayout());
     139         filechooser = (JFileChooser)c;
     140
     141         createModel();
     142
     143         installDefaults(filechooser);
     144         installComponents(filechooser);
     145         installListeners(filechooser);
     146
     147         AquaUtils.enforceComponentOrientation(filechooser, ComponentOrientation.getOrientation(Locale.getDefault()));
     148     }
     149
     150     public void uninstallUI(final JComponent c) {
     151         uninstallListeners(filechooser);
     152         uninstallComponents(filechooser);
     153         uninstallDefaults(filechooser);
     154
     155         if (accessoryPanel != null) {
     156             accessoryPanel.removeAll();
     157         }
     158
     159         accessoryPanel = null;
     160         getFileChooser().removeAll();
     161     }
     162
     163     protected void installListeners(final JFileChooser fc) {
     164         doubleClickListener = createDoubleClickListener(fc, fFileList);
     165         fFileList.addMouseListener(doubleClickListener);
     166
     167         propertyChangeListener = createPropertyChangeListener(fc);
     168         if (propertyChangeListener != null) {
     169             fc.addPropertyChangeListener(propertyChangeListener);
     170         }
     171         if (model != null) fc.addPropertyChangeListener(model);
     172
     173         ancestorListener = new AncestorListener(){
     174             public void ancestorAdded(final AncestorEvent e) {
     175                 // Request defaultness for the appropriate button based on mode
     176                 setFocusForMode(getFileChooser());
     177                 // Request defaultness for the appropriate button based on mode
     178                 setDefaultButtonForMode(getFileChooser());
     179             }
     180
     181             public void ancestorRemoved(final AncestorEvent e) {
     182             }
     183
     184             public void ancestorMoved(final AncestorEvent e) {
     185             }
     186         };
     187         fc.addAncestorListener(ancestorListener);
     188
     189         fc.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
     190         dragAndDropTarget = new DropTarget(fc, DnDConstants.ACTION_COPY, new DnDHandler(), true);
     191         fc.setDropTarget(dragAndDropTarget);
     192     }
     193
     194     protected void uninstallListeners(final JFileChooser fc) {
     195         if (propertyChangeListener != null) {
     196             fc.removePropertyChangeListener(propertyChangeListener);
     197         }
     198         fFileList.removeMouseListener(doubleClickListener);
     199         fc.removePropertyChangeListener(model);
     200         fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
     201         fc.removeAncestorListener(ancestorListener);
     202         fc.setDropTarget(null);
     203         ancestorListener = null;
     204     }
     205
     206     protected void installDefaults(final JFileChooser fc) {
     207         installIcons(fc);
     208         installStrings(fc);
     209         setPackageIsTraversable(fc.getClientProperty(PACKAGE_TRAVERSABLE_PROPERTY));
     210         setApplicationIsTraversable(fc.getClientProperty(APPLICATION_TRAVERSABLE_PROPERTY));
     211     }
     212
     213     protected void installIcons(final JFileChooser fc) {
     214         directoryIcon = UIManager.getIcon("FileView.directoryIcon");
     215         fileIcon = UIManager.getIcon("FileView.fileIcon");
     216         computerIcon = UIManager.getIcon("FileView.computerIcon");
     217         hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon");
     218     }
     219
     220     String getString(final String uiKey, final String fallback) {
     221         final String result = UIManager.getString(uiKey);
     222         return (result == null ? fallback : result);
     223     }
     224
     225     protected void installStrings(final JFileChooser fc) {
     226         // Exist in basic.properties (though we might want to override)
     227         fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText");
     228         directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText");
     229         newFolderErrorText = getString("FileChooser.newFolderErrorText", "Error occurred during folder creation");
     230
     231         saveButtonText = UIManager.getString("FileChooser.saveButtonText");
     232         openButtonText = UIManager.getString("FileChooser.openButtonText");
     233         cancelButtonText = UIManager.getString("FileChooser.cancelButtonText");
     234         updateButtonText = UIManager.getString("FileChooser.updateButtonText");
     235         helpButtonText = UIManager.getString("FileChooser.helpButtonText");
     236
     237         saveButtonMnemonic = UIManager.getInt("FileChooser.saveButtonMnemonic");
     238         openButtonMnemonic = UIManager.getInt("FileChooser.openButtonMnemonic");
     239         cancelButtonMnemonic = UIManager.getInt("FileChooser.cancelButtonMnemonic");
     240         updateButtonMnemonic = UIManager.getInt("FileChooser.updateButtonMnemonic");
     241         helpButtonMnemonic = UIManager.getInt("FileChooser.helpButtonMnemonic");
     242         chooseButtonMnemonic = UIManager.getInt("FileChooser.chooseButtonMnemonic");
     243
     244         saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText");
     245         openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText");
     246         cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText");
     247         updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText");
     248         helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText");
     249
     250         // Mac-specific, but fallback to basic if it's missing
     251         saveTitleText = getString("FileChooser.saveTitleText", saveButtonText);
     252         openTitleText = getString("FileChooser.openTitleText", openButtonText);
     253
     254         // Mac-specific, required
     255         newFolderExistsErrorText = getString("FileChooser.newFolderExistsErrorText", "That name is already taken");
     256         chooseButtonText = getString("FileChooser.chooseButtonText", "Choose");
     257         newFolderButtonText = getString("FileChooser.newFolderButtonText", "New");
     258         newFolderTitleText = getString("FileChooser.newFolderTitleText", "New Folder");
     259
     260         if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
     261             fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
     262         } else {
     263             fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
     264         }
     265
     266         filesOfTypeLabelText = getString("FileChooser.filesOfTypeLabelText", "Format:");
     267
     268         desktopName = getString("FileChooser.desktopName", "Desktop");
     269         newFolderDialogPrompt = getString("FileChooser.newFolderPromptText", "Name of new folder:");
     270         newFolderDefaultName = getString("FileChooser.untitledFolderName", "untitled folder");
     271         newFileDefaultName = getString("FileChooser.untitledFileName", "untitled");
     272         createButtonText = getString("FileChooser.createButtonText", "Create");
     273
     274         fColumnNames[1] = getString("FileChooser.byDateText", "Date Modified");
     275         fColumnNames[0] = getString("FileChooser.byNameText", "Name");
     276
     277         // Mac-specific, optional
     278         chooseItemButtonToolTipText = UIManager.getString("FileChooser.chooseItemButtonToolTipText");
     279         chooseFolderButtonToolTipText = UIManager.getString("FileChooser.chooseFolderButtonToolTipText");
     280         openDirectoryButtonToolTipText = UIManager.getString("FileChooser.openDirectoryButtonToolTipText");
     281
     282         directoryComboBoxToolTipText = UIManager.getString("FileChooser.directoryComboBoxToolTipText");
     283         filenameTextFieldToolTipText = UIManager.getString("FileChooser.filenameTextFieldToolTipText");
     284         filterComboBoxToolTipText = UIManager.getString("FileChooser.filterComboBoxToolTipText");
     285
     286         cancelOpenButtonToolTipText = UIManager.getString("FileChooser.cancelOpenButtonToolTipText");
     287         cancelSaveButtonToolTipText = UIManager.getString("FileChooser.cancelSaveButtonToolTipText");
     288         cancelChooseButtonToolTipText = UIManager.getString("FileChooser.cancelChooseButtonToolTipText");
     289         cancelNewFolderButtonToolTipText = UIManager.getString("FileChooser.cancelNewFolderButtonToolTipText");
     290
     291         newFolderTitleText = UIManager.getString("FileChooser.newFolderTitleText");
     292         newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText");
     293         newFolderAccessibleName = getString("FileChooser.newFolderAccessibleName", newFolderTitleText);
     294     }
     295
     296     protected void uninstallDefaults(final JFileChooser fc) {
     297         uninstallIcons(fc);
     298         uninstallStrings(fc);
     299     }
     300
     301     protected void uninstallIcons(final JFileChooser fc) {
     302         directoryIcon = null;
     303         fileIcon = null;
     304         computerIcon = null;
     305         hardDriveIcon = null;
     306         floppyDriveIcon = null;
     307
     308         upFolderIcon = null;
     309         homeFolderIcon = null;
     310         detailsViewIcon = null;
     311         listViewIcon = null;
     312     }
     313
     314     protected void uninstallStrings(final JFileChooser fc) {
     315         saveTitleText = null;
     316         openTitleText = null;
     317         newFolderTitleText = null;
     318
     319         saveButtonText = null;
     320         openButtonText = null;
     321         cancelButtonText = null;
     322         updateButtonText = null;
     323         helpButtonText = null;
     324         newFolderButtonText = null;
     325         chooseButtonText = null;
     326
     327         cancelOpenButtonToolTipText = null;
     328         cancelSaveButtonToolTipText = null;
     329         cancelChooseButtonToolTipText = null;
     330         cancelNewFolderButtonToolTipText = null;
     331
     332         saveButtonToolTipText = null;
     333         openButtonToolTipText = null;
     334         cancelButtonToolTipText = null;
     335         updateButtonToolTipText = null;
     336         helpButtonToolTipText = null;
     337         chooseItemButtonToolTipText = null;
     338         chooseFolderButtonToolTipText = null;
     339         openDirectoryButtonToolTipText = null;
     340         directoryComboBoxToolTipText = null;
     341         filenameTextFieldToolTipText = null;
     342         filterComboBoxToolTipText = null;
     343
     344         newFolderDefaultName = null;
     345         newFileDefaultName = null;
     346
     347         desktopName = null;
     348     }
     349
     350     protected void createModel() {
     351     }
     352
     353     AquaFileSystemModel getModel() {
     354         return model;
     355     }
     356
     357     /*
     358      * Listen for filechooser property changes, such as
     359      * the selected file changing, or the type of the dialog changing.
     360      */
     361     // Taken almost verbatim from Metal
     362     protected PropertyChangeListener createPropertyChangeListener(final JFileChooser fc) {
     363         return new PropertyChangeListener(){
     364             public void propertyChange(final PropertyChangeEvent e) {
     365                 final String prop = e.getPropertyName();
     366                 if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
     367                     final File f = (File)e.getNewValue();
     368                     if (f != null) {
     369                         // Select the file in the list if the selected file didn't change as
     370                         // a result of a list click.
     371                         if (!selectionInProgress && getModel().contains(f)) {
     372                             fFileList.setSelectedIndex(getModel().indexOf(f));
     373                         }
     374
     375                         // [3643835] Need to populate the text field here.  No-op on Open dialogs
     376                         // Note that this was removed for 3514735, but should not have been.
     377                         if (!f.isDirectory()) {
     378                             setFileName(getFileChooser().getName(f));
     379                         }
     380                     }
     381                     updateButtonState(getFileChooser());
     382                 } else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
     383                     JFileChooser fileChooser = getFileChooser();
     384                     if (!fileChooser.isDirectorySelectionEnabled()) {
     385                         final File[] files = (File[]) e.getNewValue();
     386                         if (files != null) {
     387                             for (int selectedRow : fFileList.getSelectedRows()) {
     388                                 File file = (File) fFileList.getValueAt(selectedRow, 0);
     389                                 if (fileChooser.isTraversable(file)) {
     390                                     fFileList.removeSelectedIndex(selectedRow);
     391                                 }
     392                             }
     393                         }
     394                     }
     395                 } else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
     396                     fFileList.clearSelection();
     397                     final File currentDirectory = getFileChooser().getCurrentDirectory();
     398                     if (currentDirectory != null) {
     399                         fDirectoryComboBoxModel.addItem(currentDirectory);
     400                         // Enable the newFolder action if the current directory
     401                         // is writable.
     402                         // PENDING(jeff) - broken - fix
     403                         getAction(kNewFolder).setEnabled(currentDirectory.canWrite());
     404                     }
     405                     updateButtonState(getFileChooser());
     406                 } else if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
     407                     fFileList.clearSelection();
     408                     setBottomPanelForMode(getFileChooser()); // Also updates approve button
     409                 } else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {
     410                     if (getAccessoryPanel() != null) {
     411                         if (e.getOldValue() != null) {
     412                             getAccessoryPanel().remove((JComponent)e.getOldValue());
     413                         }
     414                         final JComponent accessory = (JComponent)e.getNewValue();
     415                         if (accessory != null) {
     416                             getAccessoryPanel().add(accessory, BorderLayout.CENTER);
     417                         }
     418                     }
     419                 } else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) {
     420                     updateApproveButton(getFileChooser());
     421                     getFileChooser().invalidate();
     422                 } else if (prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY) {
     423                     if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) {
     424                         fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
     425                     } else {
     426                         fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
     427                     }
     428                     fTextFieldLabel.setText(fileNameLabelText);
     429
     430                     // Mac doesn't show the text field or "new folder" button in 'Open' dialogs
     431                     setBottomPanelForMode(getFileChooser()); // Also updates approve button
     432                 } else if (prop.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
     433                     getApproveButton(getFileChooser()).setMnemonic(getApproveButtonMnemonic(getFileChooser()));
     434                 } else if (prop.equals(PACKAGE_TRAVERSABLE_PROPERTY)) {
     435                     setPackageIsTraversable(e.getNewValue());
     436                 } else if (prop.equals(APPLICATION_TRAVERSABLE_PROPERTY)) {
     437                     setApplicationIsTraversable(e.getNewValue());
     438                 } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
     439                     if (getFileChooser().isMultiSelectionEnabled()) {
     440                         fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
     441                     } else {
     442                         fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
     443                     }
     444                 } else if (prop.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
     445                     doControlButtonsChanged(e);
     446                 }
     447             }
     448         };
     449     }
     450
     451     void setPackageIsTraversable(final Object o) {
     452         int newProp = -1;
     453         if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
     454         if (newProp != -1) fPackageIsTraversable = newProp;
     455         else fPackageIsTraversable = sGlobalPackageIsTraversable;
     456     }
     457
     458     void setApplicationIsTraversable(final Object o) {
     459         int newProp = -1;
     460         if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
     461         if (newProp != -1) fApplicationIsTraversable = newProp;
     462         else fApplicationIsTraversable = sGlobalApplicationIsTraversable;
     463     }
     464
     465     void doControlButtonsChanged(final PropertyChangeEvent e) {
     466         if (getFileChooser().getControlButtonsAreShown()) {
     467             fBottomPanel.add(fDirectoryPanelSpacer);
     468             fBottomPanel.add(fDirectoryPanel);
     469         } else {
     470             fBottomPanel.remove(fDirectoryPanelSpacer);
     471             fBottomPanel.remove(fDirectoryPanel);
     472         }
     473     }
     474
     475     public String getFileName() {
     476         if (filenameTextField != null) { return filenameTextField.getText(); }
     477         return null;
     478     }
     479
     480     public String getDirectoryName() {
     481         // PENDING(jeff) - get the name from the directory combobox
     482         return null;
     483     }
     484
     485     public void setFileName(final String filename) {
     486         if (filenameTextField != null) {
     487             filenameTextField.setText(filename);
     488         }
     489     }
     490
     491     public void setDirectoryName(final String dirname) {
     492         // PENDING(jeff) - set the name in the directory combobox
     493     }
     494
     495     public void rescanCurrentDirectory(final JFileChooser fc) {
     496         getModel().invalidateFileCache();
     497         getModel().validateFileCache();
     498     }
     499
     500     public void ensureFileIsVisible(final JFileChooser fc, final File f) {
     501         if (f == null) {
     502             fFileList.requestFocusInWindow();
     503             fFileList.ensureIndexIsVisible(-1);
     504             return;
     505         }
     506
     507         getModel().runWhenDone(new Runnable() {
     508             public void run() {
     509                 fFileList.requestFocusInWindow();
     510                 fFileList.ensureIndexIsVisible(getModel().indexOf(f));
     511             }
     512         });
     513     }
     514
     515     public JFileChooser getFileChooser() {
     516         return filechooser;
     517     }
     518
     519     public JPanel getAccessoryPanel() {
     520         return accessoryPanel;
     521     }
     522
     523     protected JButton getApproveButton(final JFileChooser fc) {
     524         return fApproveButton;
     525     }
     526
     527     public int getApproveButtonMnemonic(final JFileChooser fc) {
     528         return fSubPanel.getApproveButtonMnemonic(fc);
     529     }
     530
     531     public String getApproveButtonToolTipText(final JFileChooser fc) {
     532         return fSubPanel.getApproveButtonToolTipText(fc);
     533     }
     534
     535     public String getApproveButtonText(final JFileChooser fc) {
     536         return fSubPanel.getApproveButtonText(fc);
     537     }
     538
     539     protected String getCancelButtonToolTipText(final JFileChooser fc) {
     540         return fSubPanel.getCancelButtonToolTipText(fc);
     541     }
     542
     543     // If the item's not selectable, it'll be visible but disabled in the list
     544     boolean isSelectableInList(final File f) {
     545         return fSubPanel.isSelectableInList(getFileChooser(), f);
     546     }
     547
     548     // Is this a file that the JFileChooser wants?
     549     // Directories can be selected in the list regardless of mode
     550     boolean isSelectableForMode(final JFileChooser fc, final File f) {
     551         if (f == null) return false;
     552         final int mode = fc.getFileSelectionMode();
     553         if (mode == JFileChooser.FILES_AND_DIRECTORIES) return true;
     554         boolean traversable = fc.isTraversable(f);
     555         if (mode == JFileChooser.DIRECTORIES_ONLY) return traversable;
     556         return !traversable;
     557     }
     558
     559     // ********************************************
     560     // ************ Create Listeners **************
     561     // ********************************************
     562
     563     // From Basic
     564     public ListSelectionListener createListSelectionListener(final JFileChooser fc) {
     565         return new SelectionListener();
     566     }
     567
     568     protected class SelectionListener implements ListSelectionListener {
     569         public void valueChanged(final ListSelectionEvent e) {
     570             if (e.getValueIsAdjusting()) return;
     571
     572             File f = null;
     573             final int selectedRow = fFileList.getSelectedRow();
     574             final JFileChooser chooser = getFileChooser();
     575             boolean isSave = (chooser.getDialogType() == JFileChooser.SAVE_DIALOG);
     576             if (selectedRow >= 0) {
     577                 f = (File)fFileList.getValueAt(selectedRow, 0);
     578             }
     579
     580             // Save dialog lists can't be multi select, because all we're selecting is the next folder to open
     581             selectionInProgress = true;
     582             if (!isSave && chooser.isMultiSelectionEnabled()) {
     583                 final int[] rows = fFileList.getSelectedRows();
     584                 int selectableCount = 0;
     585                 // Double-check that all the list selections are valid for this mode
     586                 // Directories can be selected in the list regardless of mode
     587                 if (rows.length > 0) {
     588                     for (final int element : rows) {
     589                         if (isSelectableForMode(chooser, (File)fFileList.getValueAt(element, 0))) selectableCount++;
     590                     }
     591                 }
     592                 if (selectableCount > 0) {
     593                     final File[] files = new File[selectableCount];
     594                     for (int i = 0, si = 0; i < rows.length; i++) {
     595                         f = (File)fFileList.getValueAt(rows[i], 0);
     596                         if (isSelectableForMode(chooser, f)) {
     597                             if (fileView.isAlias(f)) {
     598                                 f = fileView.resolveAlias(f);
     599                             }
     600                             files[si++] = f;
     601                         }
     602                     }
     603                     chooser.setSelectedFiles(files);
     604                 } else {
     605                     chooser.setSelectedFiles(null);
     606                 }
     607             } else {
     608                 chooser.setSelectedFiles(null);
     609                 chooser.setSelectedFile(f);
     610             }
     611             selectionInProgress = false;
     612         }
     613     }
     614
     615     // When the Save textfield has the focus, the button should say "Save"
     616     // Otherwise, it depends on the list selection
     617     protected class SaveTextFocusListener implements FocusListener {
     618         public void focusGained(final FocusEvent e) {
     619             updateButtonState(getFileChooser());
     620         }
     621
     622         // Do nothing, we might be losing focus due to window deactivation
     623         public void focusLost(final FocusEvent e) {
     624
     625         }
     626     }
     627
     628     // When the Save textfield is empty and the button says "Save", it should be disabled
     629     // Otherwise, it depends on the list selection
     630     protected class SaveTextDocumentListener implements DocumentListener {
     631         public void insertUpdate(final DocumentEvent e) {
     632             textChanged();
     633         }
     634
     635         public void removeUpdate(final DocumentEvent e) {
     636             textChanged();
     637         }
     638
     639         public void changedUpdate(final DocumentEvent e) {
     640
     641         }
     642
     643         void textChanged() {
     644             updateButtonState(getFileChooser());
     645         }
     646     }
     647
     648     // Opens the File object if it's a traversable directory
     649     protected boolean openDirectory(final File f) {
     650         if (getFileChooser().isTraversable(f)) {
     651             fFileList.clearSelection();
     652             // Resolve any aliases
     653             final File original = fileView.resolveAlias(f);
     654             getFileChooser().setCurrentDirectory(original);
     655             updateButtonState(getFileChooser());
     656             return true;
     657         }
     658         return false;
     659     }
     660
     661     // From Basic
     662     protected class DoubleClickListener extends MouseAdapter {
     663         JTableExtension list;
     664
     665         public DoubleClickListener(final JTableExtension list) {
     666             this.list = list;
     667         }
     668
     669         public void mouseClicked(final MouseEvent e) {
     670             if (e.getClickCount() != 2) return;
     671
     672             final int index = list.locationToIndex(e.getPoint());
     673             if (index < 0) return;
     674
     675             final File f = (File)((AquaFileSystemModel)list.getModel()).getElementAt(index);
     676             if (openDirectory(f)) return;
     677
     678             if (!isSelectableInList(f)) return;
     679             getFileChooser().approveSelection();
     680         }
     681     }
     682
     683     protected MouseListener createDoubleClickListener(final JFileChooser fc, final JTableExtension list) {
     684         return new DoubleClickListener(list);
     685     }
     686
     687     // listens for drag events onto the JFileChooser and sets the selected file or directory
     688     class DnDHandler extends DropTargetAdapter {
     689         public void dragEnter(final DropTargetDragEvent dtde) {
     690             tryToAcceptDrag(dtde);
     691         }
     692
     693         public void dragOver(final DropTargetDragEvent dtde) {
     694             tryToAcceptDrag(dtde);
     695         }
     696
     697         public void dropActionChanged(final DropTargetDragEvent dtde) {
     698             tryToAcceptDrag(dtde);
     699         }
     700
     701         public void drop(final DropTargetDropEvent dtde) {
     702             if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
     703                 handleFileDropEvent(dtde);
     704                 return;
     705             }
     706
     707             if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
     708                 handleStringDropEvent(dtde);
     709                 return;
     710             }
     711         }
     712
     713         protected void tryToAcceptDrag(final DropTargetDragEvent dtde) {
     714             if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
     715                 dtde.acceptDrag(DnDConstants.ACTION_COPY);
     716                 return;
     717             }
     718
     719             dtde.rejectDrag();
     720         }
     721
     722         protected void handleFileDropEvent(final DropTargetDropEvent dtde) {
     723             dtde.acceptDrop(dtde.getDropAction());
     724             final Transferable transferable = dtde.getTransferable();
     725
     726             try {
     727                 @SuppressWarnings("unchecked")
     728                 final java.util.List<File> fileList = (java.util.List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);
     729                 dropFiles(fileList.toArray(new File[fileList.size()]));
     730                 dtde.dropComplete(true);
     731             } catch (final Exception e) {
     732                 dtde.dropComplete(false);
     733             }
     734         }
     735
     736         protected void handleStringDropEvent(final DropTargetDropEvent dtde) {
     737             dtde.acceptDrop(dtde.getDropAction());
     738             final Transferable transferable = dtde.getTransferable();
     739
     740             final String stringData;
     741             try {
     742                 stringData = (String)transferable.getTransferData(DataFlavor.stringFlavor);
     743             } catch (final Exception e) {
     744                 dtde.dropComplete(false);
     745                 return;
     746             }
     747
     748             try {
     749                 final File fileAsPath = new File(stringData);
     750                 if (fileAsPath.exists()) {
     751                     dropFiles(new File[] {fileAsPath});
     752                     dtde.dropComplete(true);
     753                     return;
     754                 }
     755             } catch (final Exception e) {
     756                 // try again
     757             }
     758
     759             try {
     760                 final File fileAsURI = new File(new URI(stringData));
     761                 if (fileAsURI.exists()) {
     762                     dropFiles(new File[] {fileAsURI});
     763                     dtde.dropComplete(true);
     764                     return;
     765                 }
     766             } catch (final Exception e) {
     767                 // nothing more to do
     768             }
     769
     770             dtde.dropComplete(false);
     771         }
     772
     773         protected void dropFiles(final File[] files) {
     774             final JFileChooser jfc = getFileChooser();
     775
     776             if (files.length == 1) {
     777                 if (files[0].isDirectory()) {
     778                     jfc.setCurrentDirectory(files[0]);
     779                     return;
     780                 }
     781
     782                 if (!isSelectableForMode(jfc, files[0])) {
     783                     return;
     784                 }
     785             }
     786
     787             jfc.setSelectedFiles(files);
     788             for (final File file : files) {
     789                 jfc.ensureFileIsVisible(file);
     790             }
     791             getModel().runWhenDone(new Runnable() {
     792                 public void run() {
     793                     final AquaFileSystemModel fileSystemModel = getModel();
     794                     for (final File element : files) {
     795                         final int index = fileSystemModel.indexOf(element);
     796                         if (index >= 0) fFileList.addRowSelectionInterval(index, index);
     797                     }
     798                 }
     799             });
     800         }
     801     }
     802
     803     // FileChooser UI PLAF methods
     804
     805     /**
     806      * Returns the default accept all file filter
     807      */
     808     public FileFilter getAcceptAllFileFilter(final JFileChooser fc) {
     809         return acceptAllFileFilter;
     810     }
     811
     812     public FileView getFileView(final JFileChooser fc) {
     813         return fileView;
     814     }
     815
     816     /**
     817      * Returns the title of this dialog
     818      */
     819     public String getDialogTitle(final JFileChooser fc) {
     820         if (fc.getDialogTitle() == null) {
     821             if (getFileChooser().getDialogType() == JFileChooser.OPEN_DIALOG) {
     822                 return openTitleText;
     823             } else if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) { return saveTitleText; }
     824         }
     825         return fc.getDialogTitle();
     826     }
     827
     828     // Utility to get the first selected item regardless of whether we're single or multi select
     829     File getFirstSelectedItem() {
     830         // Get the selected item
     831         File selectedFile = null;
     832         final int index = fFileList.getSelectedRow();
     833         if (index >= 0) {
     834             selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
     835         }
     836         return selectedFile;
     837     }
     838
     839     // Make a file from the filename
     840     File makeFile(final JFileChooser fc, final String filename) {
     841         File selectedFile = null;
     842         // whitespace is legal on Macs, even on beginning and end of filename
     843         if (filename != null && !filename.equals("")) {
     844             final FileSystemView fs = fc.getFileSystemView();
     845             selectedFile = fs.createFileObject(filename);
     846             if (!selectedFile.isAbsolute()) {
     847                 selectedFile = fs.createFileObject(fc.getCurrentDirectory(), filename);
     848             }
     849         }
     850         return selectedFile;
     851     }
     852
     853     // Utility to tell if the textfield has anything in it
     854     boolean textfieldIsValid() {
     855         final String s = getFileName();
     856         return (s != null && !s.equals(""));
     857     }
     858
     859     // Action to attach to the file list so we can override the default action
     860     // of the table for the return key, which is to select the next line.
     861     @SuppressWarnings("serial") // Superclass is not serializable across versions
     862     protected class DefaultButtonAction extends AbstractAction {
     863         public void actionPerformed(final ActionEvent e) {
     864             final JRootPane root = AquaFileChooserUI.this.getFileChooser().getRootPane();
     865             final JFileChooser fc = AquaFileChooserUI.this.getFileChooser();
     866             final JButton owner = root.getDefaultButton();
     867             if (owner != null && SwingUtilities.getRootPane(owner) == root && owner.isEnabled()) {
     868                 owner.doClick(20);
     869             } else if (!fc.getControlButtonsAreShown()) {
     870                 final JButton defaultButton = AquaFileChooserUI.this.fSubPanel.getDefaultButton(fc);
     871
     872                 if (defaultButton != null) {
     873                     defaultButton.doClick(20);
     874                 }
     875             } else {
     876                 Toolkit.getDefaultToolkit().beep();
     877             }
     878         }
     879
     880         public boolean isEnabled() {
     881             return true;
     882         }
     883     }
     884
     885     /**
     886      * Creates a new folder.
     887      */
     888     @SuppressWarnings("serial") // Superclass is not serializable across versions
     889     protected class NewFolderAction extends AbstractAction {
     890         protected NewFolderAction() {
     891             super(newFolderAccessibleName);
     892         }
     893
     894         // Muchlike showInputDialog, but we give it options instead of selectionValues
     895         private Object showNewFolderDialog(final Component parentComponent, final Object message, final String title, final int messageType, final Icon icon, final Object[] options, final Object initialSelectionValue) {
     896             final JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.OK_CANCEL_OPTION, icon, options, null);
     897
     898             pane.setWantsInput(true);
     899             pane.setInitialSelectionValue(initialSelectionValue);
     900
     901             final JDialog dialog = pane.createDialog(parentComponent, title);
     902
     903             pane.selectInitialValue();
     904             dialog.setVisible(true);
     905             dialog.dispose();
     906
     907             final Object value = pane.getValue();
     908
     909             if (value == null || value.equals(cancelButtonText)) {
     910                 return null;
     911             }
     912             return pane.getInputValue();
     913         }
     914
     915         public void actionPerformed(final ActionEvent e) {
     916             final JFileChooser fc = getFileChooser();
     917             final File currentDirectory = fc.getCurrentDirectory();
     918             File newFolder = null;
     919             final String[] options = {createButtonText, cancelButtonText};
     920             final String filename = (String)showNewFolderDialog(fc, //parentComponent
     921                     newFolderDialogPrompt, // message
     922                     newFolderTitleText, // title
     923                     JOptionPane.PLAIN_MESSAGE, // messageType
     924                     null, // icon
     925                     options, // selectionValues
     926                     newFolderDefaultName); // initialSelectionValue
     927
     928             if (filename != null) {
     929                 try {
     930                     newFolder = fc.getFileSystemView().createFileObject(currentDirectory, filename);
     931                     if (newFolder.exists()) {
     932                         JOptionPane.showMessageDialog(fc, newFolderExistsErrorText, "", JOptionPane.ERROR_MESSAGE);
     933                         return;
     934                     }
     935
     936                     newFolder.mkdirs();
     937                 } catch(final Exception exc) {
     938                     JOptionPane.showMessageDialog(fc, newFolderErrorText, "", JOptionPane.ERROR_MESSAGE);
     939                     return;
     940                 }
     941
     942                 openDirectory(newFolder);
     943             }
     944         }
     945     }
     946
     947     /**
     948      * Responds to an Open, Save, or Choose request
     949      */
     950     @SuppressWarnings("serial") // Superclass is not serializable across versions
     951     protected class ApproveSelectionAction extends AbstractAction {
     952         public void actionPerformed(final ActionEvent e) {
     953             fSubPanel.approveSelection(getFileChooser());
     954         }
     955     }
     956
     957     /**
     958      * Responds to an OpenDirectory request
     959      */
     960     @SuppressWarnings("serial") // Superclass is not serializable across versions
     961     protected class OpenSelectionAction extends AbstractAction {
     962         public void actionPerformed(final ActionEvent e) {
     963             final int index = fFileList.getSelectedRow();
     964             if (index >= 0) {
     965                 final File selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
     966                 if (selectedFile != null) openDirectory(selectedFile);
     967             }
     968         }
     969     }
     970
     971     /**
     972      * Responds to a cancel request.
     973      */
     974     @SuppressWarnings("serial") // Superclass is not serializable across versions
     975     protected class CancelSelectionAction extends AbstractAction {
     976         public void actionPerformed(final ActionEvent e) {
     977             getFileChooser().cancelSelection();
     978         }
     979
     980         public boolean isEnabled() {
     981             return getFileChooser().isEnabled();
     982         }
     983     }
     984
     985     /**
     986      * Rescans the files in the current directory
     987      */
     988     @SuppressWarnings("serial") // Superclass is not serializable across versions
     989     protected class UpdateAction extends AbstractAction {
     990         public void actionPerformed(final ActionEvent e) {
     991             final JFileChooser fc = getFileChooser();
     992             fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName()));
     993             fc.rescanCurrentDirectory();
     994         }
     995     }
     996
     997     // *****************************************
     998     // ***** default AcceptAll file filter *****
     999     // *****************************************
    1000     private static class AcceptAllFileFilter extends FileFilter {
    1001         public AcceptAllFileFilter() {
    1002         }
    1003
    1004         public boolean accept(final File f) {
    1005             return true;
    1006         }
    1007
    1008         public String getDescription() {
    1009             return UIManager.getString("FileChooser.acceptAllFileFilterText");
    1010         }
    1011     }
    1012
    1013     // Penultimate superclass is JLabel
    1014     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1015     protected class MacFCTableCellRenderer extends DefaultTableCellRenderer {
    1016         boolean fIsSelected = false;
    1017
    1018         public MacFCTableCellRenderer(final Font f) {
    1019             super();
    1020             setFont(f);
    1021             setIconTextGap(10);
    1022         }
    1023
    1024         public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {
    1025             super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks
    1026             fIsSelected = isSelected;
    1027             return this;
    1028         }
    1029
    1030         public boolean isSelected() {
    1031             return fIsSelected && isEnabled();
    1032         }
    1033
    1034         protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon, final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {
    1035             return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());
    1036         }
    1037
    1038         protected void paintComponent(final Graphics g) {
    1039             final String text = getText();
    1040             Icon icon = getIcon();
    1041             if (icon != null && !isEnabled()) {
    1042                 final Icon disabledIcon = getDisabledIcon();
    1043                 if (disabledIcon != null) icon = disabledIcon;
    1044             }
    1045
    1046             if ((icon == null) && (text == null)) { return; }
    1047
    1048             // from ComponentUI update
    1049             g.setColor(getBackground());
    1050             g.fillRect(0, 0, getWidth(), getHeight());
    1051
    1052             // from BasicLabelUI paint
    1053             final FontMetrics fm = g.getFontMetrics();
    1054             Insets paintViewInsets = getInsets(null);
    1055             paintViewInsets.left += 10;
    1056
    1057             Rectangle paintViewR = new Rectangle(paintViewInsets.left, paintViewInsets.top, getWidth() - (paintViewInsets.left + paintViewInsets.right), getHeight() - (paintViewInsets.top + paintViewInsets.bottom));
    1058
    1059             Rectangle paintIconR = new Rectangle();
    1060             Rectangle paintTextR = new Rectangle();
    1061
    1062             final String clippedText = layoutCL(this, fm, text, icon, paintViewR, paintIconR, paintTextR);
    1063
    1064             if (icon != null) {
    1065                 icon.paintIcon(this, g, paintIconR.x + 5, paintIconR.y);
    1066             }
    1067
    1068             if (text != null) {
    1069                 final int textX = paintTextR.x;
    1070                 final int textY = paintTextR.y + fm.getAscent() + 1;
    1071                 if (isEnabled()) {
    1072                     // Color background = fIsSelected ? getForeground() : getBackground();
    1073                     final Color background = getBackground();
    1074
    1075                     g.setColor(background);
    1076                     g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);
    1077
    1078                     g.setColor(getForeground());
    1079                     SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
    1080                 } else {
    1081                     final Color background = getBackground();
    1082                     g.setColor(background);
    1083                     g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);
    1084
    1085                     g.setColor(background.brighter());
    1086                     SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
    1087                     g.setColor(background.darker());
    1088                     SwingUtilities2.drawString(filechooser, g, clippedText, textX + 1, textY + 1);
    1089                 }
    1090             }
    1091         }
    1092
    1093     }
    1094
    1095     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1096     protected class FileRenderer extends MacFCTableCellRenderer {
    1097         public FileRenderer(final Font f) {
    1098             super(f);
    1099         }
    1100
    1101         public Component getTableCellRendererComponent(final JTable list,
    1102                                                        final Object value,
    1103                                                        final boolean isSelected,
    1104                                                        final boolean cellHasFocus,
    1105                                                        final int index,
    1106                                                        final int col) {
    1107             super.getTableCellRendererComponent(list, value, isSelected, false,
    1108                                                 index,
    1109                                                 col); // No focus border, thanks
    1110             final File file = (File)value;
    1111             final JFileChooser fc = getFileChooser();
    1112             setText(fc.getName(file));
    1113             setIcon(fc.getIcon(file));
    1114             setEnabled(isSelectableInList(file));
    1115             return this;
    1116         }
    1117     }
    1118
    1119     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1120     protected class DateRenderer extends MacFCTableCellRenderer {
    1121         public DateRenderer(final Font f) {
    1122             super(f);
    1123         }
    1124
    1125         public Component getTableCellRendererComponent(final JTable list,
    1126                                                        final Object value,
    1127                                                        final boolean isSelected,
    1128                                                        final boolean cellHasFocus,
    1129                                                        final int index,
    1130                                                        final int col) {
    1131             super.getTableCellRendererComponent(list, value, isSelected, false,
    1132                                                 index, col);
    1133             final File file = (File)fFileList.getValueAt(index, 0);
    1134             setEnabled(isSelectableInList(file));
    1135             final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);
    1136             final Date date = (Date)value;
    1137
    1138             if (date != null) {
    1139                 setText(formatter.format(date));
    1140             } else {
    1141                 setText("");
    1142             }
    1143
    1144             return this;
    1145         }
    1146     }
    1147
    1148     @Override
    1149     public Dimension getPreferredSize(final JComponent c) {
    1150         return new Dimension(PREF_WIDTH, PREF_HEIGHT);
    1151     }
    1152
    1153     @Override
    1154     public Dimension getMinimumSize(final JComponent c) {
    1155         return new Dimension(MIN_WIDTH, MIN_HEIGHT);
    1156     }
    1157
    1158     @Override
    1159     public Dimension getMaximumSize(final JComponent c) {
    1160         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    1161     }
    1162
    1163     @SuppressWarnings("serial") // anonymous class
    1164     protected ListCellRenderer<File> createDirectoryComboBoxRenderer(final JFileChooser fc) {
    1165         return new AquaComboBoxRendererInternal<File>(directoryComboBox) {
    1166             public Component getListCellRendererComponent(final JList<? extends File> list,
    1167                                                           final File directory,
    1168                                                           final int index,
    1169                                                           final boolean isSelected,
    1170                                                           final boolean cellHasFocus) {
    1171                 super.getListCellRendererComponent(list, directory, index, isSelected, cellHasFocus);
    1172                 if (directory == null) {
    1173                     setText("");
    1174                     return this;
    1175                 }
    1176
    1177                 final JFileChooser chooser = getFileChooser();
    1178                 setText(chooser.getName(directory));
    1179                 setIcon(chooser.getIcon(directory));
    1180                 return this;
    1181             }
    1182         };
    1183     }
    1184
    1185     //
    1186     // DataModel for DirectoryComboxbox
    1187     //
    1188     protected DirectoryComboBoxModel createDirectoryComboBoxModel(final JFileChooser fc) {
    1189         return new DirectoryComboBoxModel();
    1190     }
    1191
    1192     /**
    1193      * Data model for a type-face selection combo-box.
    1194      */
    1195     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1196     protected class DirectoryComboBoxModel extends AbstractListModel<File> implements ComboBoxModel<File> {
    1197         Vector<File> fDirectories = new Vector<File>();
    1198         int topIndex = -1;
    1199         int fPathCount = 0;
    1200
    1201         File fSelectedDirectory = null;
    1202
    1203         public DirectoryComboBoxModel() {
    1204             super();
    1205             // Add the current directory to the model, and make it the
    1206             // selectedDirectory
    1207             addItem(getFileChooser().getCurrentDirectory());
    1208         }
    1209
    1210         /**
    1211          * Removes the selected directory, and clears out the
    1212          * path file entries leading up to that directory.
    1213          */
    1214         private void removeSelectedDirectory() {
    1215             fDirectories.removeAllElements();
    1216             fPathCount = 0;
    1217             fSelectedDirectory = null;
    1218             // dump();
    1219         }
    1220
    1221         /**
    1222          * Adds the directory to the model and sets it to be selected,
    1223          * additionally clears out the previous selected directory and
    1224          * the paths leading up to it, if any.
    1225          */
    1226         void addItem(final File directory) {
    1227             if (directory == null) { return; }
    1228             if (fSelectedDirectory != null) {
    1229                 removeSelectedDirectory();
    1230             }
    1231
    1232             // create File instances of each directory leading up to the top
    1233             File f = directory.getAbsoluteFile();
    1234             final Vector<File> path = new Vector<File>(10);
    1235             while (f.getParent() != null) {
    1236                 path.addElement(f);
    1237                 f = getFileChooser().getFileSystemView().createFileObject(f.getParent());
    1238             };
    1239
    1240             // Add root file (the desktop) to the model
    1241             final File[] roots = getFileChooser().getFileSystemView().getRoots();
    1242             for (final File element : roots) {
    1243                 path.addElement(element);
    1244             }
    1245             fPathCount = path.size();
    1246
    1247             // insert all the path fDirectories leading up to the
    1248             // selected directory in reverse order (current directory at top)
    1249             for (int i = 0; i < path.size(); i++) {
    1250                 fDirectories.addElement(path.elementAt(i));
    1251             }
    1252
    1253             setSelectedItem(fDirectories.elementAt(0));
    1254
    1255             // dump();
    1256         }
    1257
    1258         public void setSelectedItem(final Object selectedDirectory) {
    1259             this.fSelectedDirectory = (File)selectedDirectory;
    1260             fireContentsChanged(this, -1, -1);
    1261         }
    1262
    1263         public Object getSelectedItem() {
    1264             return fSelectedDirectory;
    1265         }
    1266
    1267         public int getSize() {
    1268             return fDirectories.size();
    1269         }
    1270
    1271         public File getElementAt(final int index) {
    1272             return fDirectories.elementAt(index);
    1273         }
    1274     }
    1275
    1276     //
    1277     // Renderer for Types ComboBox
    1278     //
    1279     @SuppressWarnings("serial") // anonymous class
    1280     protected ListCellRenderer<FileFilter> createFilterComboBoxRenderer() {
    1281         return new AquaComboBoxRendererInternal<FileFilter>(filterComboBox) {
    1282             public Component getListCellRendererComponent(final JList<? extends FileFilter> list,
    1283                                                           final FileFilter filter,
    1284                                                           final int index,
    1285                                                           final boolean isSelected,
    1286                                                           final boolean cellHasFocus) {
    1287                 super.getListCellRendererComponent(list, filter, index, isSelected, cellHasFocus);
    1288                 if (filter != null) setText(filter.getDescription());
    1289                 return this;
    1290             }
    1291         };
    1292     }
    1293
    1294     //
    1295     // DataModel for Types Comboxbox
    1296     //
    1297     protected FilterComboBoxModel createFilterComboBoxModel() {
    1298         return new FilterComboBoxModel();
    1299     }
    1300
    1301     /**
    1302      * Data model for a type-face selection combo-box.
    1303      */
    1304     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1305     protected class FilterComboBoxModel extends AbstractListModel<FileFilter> implements ComboBoxModel<FileFilter>,
    1306             PropertyChangeListener {
    1307         protected FileFilter[] filters;
    1308         Object oldFileFilter = getFileChooser().getFileFilter();
    1309
    1310         protected FilterComboBoxModel() {
    1311             super();
    1312             filters = getFileChooser().getChoosableFileFilters();
    1313         }
    1314
    1315         public void propertyChange(PropertyChangeEvent e) {
    1316             String prop = e.getPropertyName();
    1317             if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
    1318                 filters = (FileFilter[]) e.getNewValue();
    1319                 fireContentsChanged(this, -1, -1);
    1320             } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
    1321                 setSelectedItem(e.getNewValue());
    1322             }
    1323         }
    1324
    1325         public void setSelectedItem(Object filter) {
    1326             if (filter != null && !isSelectedFileFilterInModel(filter)) {
    1327                 oldFileFilter = filter;
    1328                 getFileChooser().setFileFilter((FileFilter) filter);
    1329                 fireContentsChanged(this, -1, -1);
    1330             }
    1331         }
    1332
    1333         private boolean isSelectedFileFilterInModel(Object filter) {
    1334             return Objects.equals(filter, oldFileFilter);
    1335         }
    1336
    1337         public Object getSelectedItem() {
    1338             // Ensure that the current filter is in the list.
    1339             // NOTE: we shouldnt' have to do this, since JFileChooser adds
    1340             // the filter to the choosable filters list when the filter
    1341             // is set. Lets be paranoid just in case someone overrides
    1342             // setFileFilter in JFileChooser.
    1343             FileFilter currentFilter = getFileChooser().getFileFilter();
    1344             boolean found = false;
    1345             if(currentFilter != null) {
    1346                 for (FileFilter filter : filters) {
    1347                     if (filter == currentFilter) {
    1348                         found = true;
    1349                     }
    1350                 }
    1351                 if(found == false) {
    1352                     getFileChooser().addChoosableFileFilter(currentFilter);
    1353                 }
    1354             }
    1355             return getFileChooser().getFileFilter();
    1356         }
    1357
    1358         public int getSize() {
    1359             if(filters != null) {
    1360                 return filters.length;
    1361             } else {
    1362                 return 0;
    1363             }
    1364         }
    1365
    1366         public FileFilter getElementAt(int index) {
    1367             if(index > getSize() - 1) {
    1368                 // This shouldn't happen. Try to recover gracefully.
    1369                 return getFileChooser().getFileFilter();
    1370             }
    1371             if(filters != null) {
    1372                 return filters[index];
    1373             } else {
    1374                 return null;
    1375             }
    1376         }
    1377     }
    1378
    1379     private boolean containsFileFilter(Object fileFilter) {
    1380         return Objects.equals(fileFilter, getFileChooser().getFileFilter());
    1381     }
    1382
    1383     /**
    1384      * Acts when FilterComboBox has changed the selected item.
    1385      */
    1386     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1387     protected class FilterComboBoxAction extends AbstractAction {
    1388         protected FilterComboBoxAction() {
    1389             super("FilterComboBoxAction");
    1390         }
    1391
    1392         public void actionPerformed(final ActionEvent e) {
    1393             Object selectedFilter = filterComboBox.getSelectedItem();
    1394             if (!containsFileFilter(selectedFilter)) {
    1395                 getFileChooser().setFileFilter((FileFilter) selectedFilter);
    1396             }
    1397         }
    1398     }
    1399
    1400     /**
    1401      * Acts when DirectoryComboBox has changed the selected item.
    1402      */
    1403     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1404     protected class DirectoryComboBoxAction extends AbstractAction {
    1405         protected DirectoryComboBoxAction() {
    1406             super("DirectoryComboBoxAction");
    1407         }
    1408
    1409         public void actionPerformed(final ActionEvent e) {
    1410             getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem());
    1411         }
    1412     }
    1413
    1414     // Sorting Table operations
    1415     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1416     class JSortingTableHeader extends JTableHeader {
    1417         public JSortingTableHeader(final TableColumnModel cm) {
    1418             super(cm);
    1419             setReorderingAllowed(true); // This causes mousePress to call setDraggedColumn
    1420         }
    1421
    1422         // One sort state for each column.  Both are ascending by default
    1423         final boolean fSortAscending[] = {true, true};
    1424
    1425         // Instead of dragging, it selects which one to sort by
    1426         public void setDraggedColumn(final TableColumn aColumn) {
    1427             if (aColumn != null) {
    1428                 final int colIndex = aColumn.getModelIndex();
    1429                 if (colIndex != fSortColumn) {
    1430                     filechooser.firePropertyChange(AquaFileSystemModel.SORT_BY_CHANGED, fSortColumn, colIndex);
    1431                     fSortColumn = colIndex;
    1432                 } else {
    1433                     fSortAscending[colIndex] = !fSortAscending[colIndex];
    1434                     filechooser.firePropertyChange(AquaFileSystemModel.SORT_ASCENDING_CHANGED, !fSortAscending[colIndex], fSortAscending[colIndex]);
    1435                 }
    1436                 // Need to repaint the highlighted column.
    1437                 repaint();
    1438             }
    1439         }
    1440
    1441         // This stops mouseDrags from moving the column
    1442         public TableColumn getDraggedColumn() {
    1443             return null;
    1444         }
    1445
    1446         protected TableCellRenderer createDefaultRenderer() {
    1447             final DefaultTableCellRenderer label = new AquaTableCellRenderer();
    1448             label.setHorizontalAlignment(SwingConstants.LEFT);
    1449             return label;
    1450         }
    1451
    1452         @SuppressWarnings("serial") // Superclass is not serializable across versions
    1453         class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {
    1454             public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
    1455                 if (localTable != null) {
    1456                     final JTableHeader header = localTable.getTableHeader();
    1457                     if (header != null) {
    1458                         setForeground(header.getForeground());
    1459                         setBackground(header.getBackground());
    1460                         setFont(UIManager.getFont("TableHeader.font"));
    1461                     }
    1462                 }
    1463
    1464                 setText((value == null) ? "" : value.toString());
    1465
    1466                 // Modify the table "border" to draw smaller, and with the titles in the right position
    1467                 // and sort indicators, just like an NSSave/Open panel.
    1468                 final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();
    1469                 cellBorder.setSelected(column == fSortColumn);
    1470                 final int horizontalShift = (column == 0 ? 35 : 10);
    1471                 cellBorder.setHorizontalShift(horizontalShift);
    1472
    1473                 if (column == fSortColumn) {
    1474                     cellBorder.setSortOrder(fSortAscending[column] ? AquaTableHeaderBorder.SORT_ASCENDING : AquaTableHeaderBorder.SORT_DECENDING);
    1475                 } else {
    1476                     cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);
    1477                 }
    1478                 setBorder(cellBorder);
    1479                 return this;
    1480             }
    1481         }
    1482     }
    1483
    1484     public void installComponents(final JFileChooser fc) {
    1485         JPanel tPanel; // temp panel
    1486         // set to a Y BoxLayout. The chooser will be laid out top to bottom.
    1487         fc.setLayout(new BoxLayout(fc, BoxLayout.Y_AXIS));
    1488         fc.add(Box.createRigidArea(vstrut10));
    1489
    1490         // construct the top panel
    1491
    1492         final JPanel topPanel = new JPanel();
    1493         topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
    1494         fc.add(topPanel);
    1495         fc.add(Box.createRigidArea(vstrut10));
    1496
    1497         // Add the textfield pane
    1498
    1499         fTextfieldPanel = new JPanel();
    1500         fTextfieldPanel.setLayout(new BorderLayout());
    1501         // setBottomPanelForMode will make this visible if we need it
    1502         fTextfieldPanel.setVisible(false);
    1503         topPanel.add(fTextfieldPanel);
    1504
    1505         tPanel = new JPanel();
    1506         tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.Y_AXIS));
    1507         final JPanel labelArea = new JPanel();
    1508         labelArea.setLayout(new FlowLayout(FlowLayout.CENTER));
    1509         fTextFieldLabel = new JLabel(fileNameLabelText);
    1510         labelArea.add(fTextFieldLabel);
    1511
    1512         // text field
    1513         filenameTextField = new JTextField();
    1514         fTextFieldLabel.setLabelFor(filenameTextField);
    1515         filenameTextField.addActionListener(getAction(kOpen));
    1516         filenameTextField.addFocusListener(new SaveTextFocusListener());
    1517         final Dimension minSize = filenameTextField.getMinimumSize();
    1518         Dimension d = new Dimension(250, (int)minSize.getHeight());
    1519         filenameTextField.setPreferredSize(d);
    1520         filenameTextField.setMaximumSize(d);
    1521         labelArea.add(filenameTextField);
    1522         final File f = fc.getSelectedFile();
    1523         if (f != null) {
    1524             setFileName(fc.getName(f));
    1525         } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
    1526             setFileName(newFileDefaultName);
    1527         }
    1528
    1529         tPanel.add(labelArea);
    1530         // separator line
    1531         @SuppressWarnings("serial") // anonymous class
    1532         final JSeparator sep = new JSeparator(){
    1533             public Dimension getPreferredSize() {
    1534                 return new Dimension(((JComponent)getParent()).getWidth(), 3);
    1535             }
    1536         };
    1537         tPanel.add(Box.createRigidArea(new Dimension(1, 8)));
    1538         tPanel.add(sep);
    1539         tPanel.add(Box.createRigidArea(new Dimension(1, 7)));
    1540         fTextfieldPanel.add(tPanel, BorderLayout.CENTER);
    1541
    1542         // DirectoryComboBox, left-justified, 200x20 not including drop shadow
    1543         directoryComboBox = new JComboBox<>();
    1544         directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight");
    1545         fDirectoryComboBoxModel = createDirectoryComboBoxModel(fc);
    1546         directoryComboBox.setModel(fDirectoryComboBoxModel);
    1547         directoryComboBox.addActionListener(directoryComboBoxAction);
    1548         directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
    1549         directoryComboBox.setToolTipText(directoryComboBoxToolTipText);
    1550         d = new Dimension(250, (int)directoryComboBox.getMinimumSize().getHeight());
    1551         directoryComboBox.setPreferredSize(d);
    1552         directoryComboBox.setMaximumSize(d);
    1553         topPanel.add(directoryComboBox);
    1554
    1555         // ************************************** //
    1556         // ** Add the directory/Accessory pane ** //
    1557         // ************************************** //
    1558         final JPanel centerPanel = new JPanel(new BorderLayout());
    1559         fc.add(centerPanel);
    1560
    1561         // Accessory pane (equiv to Preview pane in NavServices)
    1562         final JComponent accessory = fc.getAccessory();
    1563         if (accessory != null) {
    1564             getAccessoryPanel().add(accessory);
    1565         }
    1566         centerPanel.add(getAccessoryPanel(), BorderLayout.LINE_START);
    1567
    1568         // Directory list(table), right-justified, resizable
    1569         final JPanel p = createList(fc);
    1570         p.setMinimumSize(LIST_MIN_SIZE);
    1571         centerPanel.add(p, BorderLayout.CENTER);
    1572
    1573         // ********************************** //
    1574         // **** Construct the bottom panel ** //
    1575         // ********************************** //
    1576         fBottomPanel = new JPanel();
    1577         fBottomPanel.setLayout(new BoxLayout(fBottomPanel, BoxLayout.Y_AXIS));
    1578         fc.add(fBottomPanel);
    1579
    1580         // Filter label and combobox.
    1581         // I know it's unMaclike, but the filter goes on Directory_only too.
    1582         tPanel = new JPanel();
    1583         tPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
    1584         tPanel.setBorder(AquaGroupBorder.getTitlelessBorder());
    1585         final JLabel formatLabel = new JLabel(filesOfTypeLabelText);
    1586         tPanel.add(formatLabel);
    1587
    1588         // Combobox
    1589         filterComboBoxModel = createFilterComboBoxModel();
    1590         fc.addPropertyChangeListener(filterComboBoxModel);
    1591         filterComboBox = new JComboBox<>(filterComboBoxModel);
    1592         formatLabel.setLabelFor(filterComboBox);
    1593         filterComboBox.setRenderer(createFilterComboBoxRenderer());
    1594         d = new Dimension(220, (int)filterComboBox.getMinimumSize().getHeight());
    1595         filterComboBox.setPreferredSize(d);
    1596         filterComboBox.setMaximumSize(d);
    1597         filterComboBox.addActionListener(filterComboBoxAction);
    1598         filterComboBox.setOpaque(false);
    1599         tPanel.add(filterComboBox);
    1600
    1601         fBottomPanel.add(tPanel);
    1602
    1603         // fDirectoryPanel: New, Open, Cancel, Approve buttons, right-justified, 82x22
    1604         // (sometimes the NewFolder and OpenFolder buttons are invisible)
    1605         fDirectoryPanel = new JPanel();
    1606         fDirectoryPanel.setLayout(new BoxLayout(fDirectoryPanel, BoxLayout.PAGE_AXIS));
    1607         JPanel directoryPanel = new JPanel(new BorderLayout());
    1608         JPanel newFolderButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
    1609         newFolderButtonPanel.add(Box.createHorizontalStrut(20));
    1610         fNewFolderButton = createNewFolderButton(); // Because we hide it depending on style
    1611         newFolderButtonPanel.add(fNewFolderButton);
    1612         directoryPanel.add(newFolderButtonPanel, BorderLayout.LINE_START);
    1613         JPanel approveCancelButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));
    1614         fOpenButton = createButton(kOpenDirectory, openButtonText);
    1615         approveCancelButtonPanel.add(fOpenButton);
    1616         approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
    1617         fCancelButton = createButton(kCancel, null);
    1618         approveCancelButtonPanel.add(fCancelButton);
    1619         approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
    1620         // The ApproveSelection button
    1621         fApproveButton = new JButton();
    1622         fApproveButton.addActionListener(fApproveSelectionAction);
    1623         approveCancelButtonPanel.add(fApproveButton);
    1624         approveCancelButtonPanel.add(Box.createHorizontalStrut(20));
    1625         directoryPanel.add(approveCancelButtonPanel, BorderLayout.LINE_END);
    1626         fDirectoryPanel.add(Box.createVerticalStrut(5));
    1627         fDirectoryPanel.add(directoryPanel);
    1628         fDirectoryPanel.add(Box.createVerticalStrut(12));
    1629         fDirectoryPanelSpacer = Box.createRigidArea(hstrut10);
    1630
    1631         if (fc.getControlButtonsAreShown()) {
    1632             fBottomPanel.add(fDirectoryPanelSpacer);
    1633             fBottomPanel.add(fDirectoryPanel);
    1634         }
    1635
    1636         setBottomPanelForMode(fc); // updates ApproveButtonText etc
    1637
    1638         // don't create til after the FCSubpanel and buttons are made
    1639         filenameTextField.getDocument().addDocumentListener(new SaveTextDocumentListener());
    1640     }
    1641
    1642     void setDefaultButtonForMode(final JFileChooser fc) {
    1643         final JButton defaultButton = fSubPanel.getDefaultButton(fc);
    1644         final JRootPane root = defaultButton.getRootPane();
    1645         if (root != null) {
    1646             root.setDefaultButton(defaultButton);
    1647         }
    1648     }
    1649
    1650     // Macs start with their focus in text areas if they have them,
    1651     // lists otherwise (the other plafs start with the focus on approveButton)
    1652     void setFocusForMode(final JFileChooser fc) {
    1653         final JComponent focusComponent = fSubPanel.getFocusComponent(fc);
    1654         if (focusComponent != null) {
    1655             focusComponent.requestFocus();
    1656         }
    1657     }
    1658
    1659     // Enable/disable buttons as needed for the current selection/focus state
    1660     void updateButtonState(final JFileChooser fc) {
    1661         fSubPanel.updateButtonState(fc, getFirstSelectedItem());
    1662         updateApproveButton(fc);
    1663     }
    1664
    1665     void updateApproveButton(final JFileChooser chooser) {
    1666         fApproveButton.setText(getApproveButtonText(chooser));
    1667         fApproveButton.setToolTipText(getApproveButtonToolTipText(chooser));
    1668         fApproveButton.setMnemonic(getApproveButtonMnemonic(chooser));
    1669         fCancelButton.setToolTipText(getCancelButtonToolTipText(chooser));
    1670     }
    1671
    1672     // Lazy-init the subpanels
    1673     synchronized FCSubpanel getSaveFilePanel() {
    1674         if (fSaveFilePanel == null) fSaveFilePanel = new SaveFilePanel();
    1675         return fSaveFilePanel;
    1676     }
    1677
    1678     synchronized FCSubpanel getOpenFilePanel() {
    1679         if (fOpenFilePanel == null) fOpenFilePanel = new OpenFilePanel();
    1680         return fOpenFilePanel;
    1681     }
    1682
    1683     synchronized FCSubpanel getOpenDirOrAnyPanel() {
    1684         if (fOpenDirOrAnyPanel == null) fOpenDirOrAnyPanel = new OpenDirOrAnyPanel();
    1685         return fOpenDirOrAnyPanel;
    1686     }
    1687
    1688     synchronized FCSubpanel getCustomFilePanel() {
    1689         if (fCustomFilePanel == null) fCustomFilePanel = new CustomFilePanel();
    1690         return fCustomFilePanel;
    1691     }
    1692
    1693     synchronized FCSubpanel getCustomDirOrAnyPanel() {
    1694         if (fCustomDirOrAnyPanel == null) fCustomDirOrAnyPanel = new CustomDirOrAnyPanel();
    1695         return fCustomDirOrAnyPanel;
    1696     }
    1697
    1698     void setBottomPanelForMode(final JFileChooser fc) {
    1699         if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) fSubPanel = getSaveFilePanel();
    1700         else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) {
    1701             if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getOpenFilePanel();
    1702             else fSubPanel = getOpenDirOrAnyPanel();
    1703         } else if (fc.getDialogType() == JFileChooser.CUSTOM_DIALOG) {
    1704             if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getCustomFilePanel();
    1705             else fSubPanel = getCustomDirOrAnyPanel();
    1706         }
    1707
    1708         fSubPanel.installPanel(fc, true);
    1709         updateApproveButton(fc);
    1710         updateButtonState(fc);
    1711         setDefaultButtonForMode(fc);
    1712         setFocusForMode(fc);
    1713         fc.invalidate();
    1714     }
    1715
    1716     // fTextfieldPanel and fDirectoryPanel both have NewFolder buttons; only one should be visible at a time
    1717     JButton createNewFolderButton() {
    1718         final JButton b = new JButton(newFolderButtonText);
    1719         b.setToolTipText(newFolderToolTipText);
    1720         b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
    1721         b.setHorizontalTextPosition(SwingConstants.LEFT);
    1722         b.setAlignmentX(Component.LEFT_ALIGNMENT);
    1723         b.setAlignmentY(Component.CENTER_ALIGNMENT);
    1724         b.addActionListener(getAction(kNewFolder));
    1725         return b;
    1726     }
    1727
    1728     JButton createButton(final int which, String label) {
    1729         if (label == null) label = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[0]);
    1730         final int mnemonic = UIManager.getInt(sDataPrefix + sButtonKinds[which] + sButtonData[1]);
    1731         final String tipText = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[2]);
    1732         final JButton b = new JButton(label);
    1733         b.setMnemonic(mnemonic);
    1734         b.setToolTipText(tipText);
    1735         b.addActionListener(getAction(which));
    1736         return b;
    1737     }
    1738
    1739     AbstractAction getAction(final int which) {
    1740         return fButtonActions[which];
    1741     }
    1742
    1743     public void uninstallComponents(final JFileChooser fc) { //$ Metal (on which this is based) doesn't uninstall its components.
    1744     }
    1745
    1746     // Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field
    1747     // with that file's display name.
    1748     protected class FileListMouseListener extends MouseAdapter {
    1749         public void mouseClicked(final MouseEvent e) {
    1750             final Point p = e.getPoint();
    1751             final int row = fFileList.rowAtPoint(p);
    1752             final int column = fFileList.columnAtPoint(p);
    1753
    1754             // The autoscroller can generate drag events outside the Table's range.
    1755             if ((column == -1) || (row == -1)) { return; }
    1756
    1757             final File clickedFile = (File)(fFileList.getValueAt(row, 0));
    1758
    1759             // rdar://problem/3734130 -- don't populate the text field if this file isn't selectable in this mode.
    1760             if (isSelectableForMode(getFileChooser(), clickedFile)) {
    1761                 // [3188387] Populate the file name field with the selected file name
    1762                 // [3484163] It should also use the display name, not the actual name.
    1763                 setFileName(fileView.getName(clickedFile));
    1764             }
    1765         }
    1766     }
    1767
    1768     protected JPanel createList(final JFileChooser fc) {
    1769         // The first part is similar to MetalFileChooserUI.createList - same kind of listeners
    1770         final JPanel p = new JPanel(new BorderLayout());
    1771         fFileList = new JTableExtension();
    1772         fFileList.setToolTipText(null); // Workaround for 2487689
    1773         fFileList.addMouseListener(new FileListMouseListener());
    1774         model = new AquaFileSystemModel(fc, fFileList, fColumnNames);
    1775         final MacListSelectionModel listSelectionModel = new MacListSelectionModel(model);
    1776
    1777         if (getFileChooser().isMultiSelectionEnabled()) {
    1778             listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    1779         } else {
    1780             listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    1781         }
    1782
    1783         fFileList.setModel(model);
    1784         fFileList.setSelectionModel(listSelectionModel);
    1785         fFileList.getSelectionModel().addListSelectionListener(createListSelectionListener(fc));
    1786
    1787         // Now we're different, because we're a table, not a list
    1788         fc.addPropertyChangeListener(model);
    1789         fFileList.addFocusListener(new SaveTextFocusListener());
    1790         final JTableHeader th = new JSortingTableHeader(fFileList.getColumnModel());
    1791         fFileList.setTableHeader(th);
    1792         fFileList.setRowMargin(0);
    1793         fFileList.setIntercellSpacing(new Dimension(0, 1));
    1794         fFileList.setShowVerticalLines(false);
    1795         fFileList.setShowHorizontalLines(false);
    1796         final Font f = fFileList.getFont(); //ThemeFont.GetThemeFont(AppearanceConstants.kThemeViewsFont);
    1797         //fc.setFont(f);
    1798         //fFileList.setFont(f);
    1799         fFileList.setDefaultRenderer(File.class, new FileRenderer(f));
    1800         fFileList.setDefaultRenderer(Date.class, new DateRenderer(f));
    1801         final FontMetrics fm = fFileList.getFontMetrics(f);
    1802
    1803         // Row height isn't based on the renderers.  It defaults to 16 so we have to set it
    1804         fFileList.setRowHeight(Math.max(fm.getHeight(), fileIcon.getIconHeight() + 2));
    1805
    1806         // Add a binding for the file list that triggers return and escape
    1807         fFileList.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
    1808         // Add a binding for the file list that triggers the default button (see DefaultButtonAction)
    1809         fFileList.registerKeyboardAction(new DefaultButtonAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
    1810         fFileList.setDropTarget(dragAndDropTarget);
    1811
    1812         final JScrollPane scrollpane = new JScrollPane(fFileList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    1813         scrollpane.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
    1814         scrollpane.setCorner(ScrollPaneConstants.UPPER_TRAILING_CORNER, new ScrollPaneCornerPanel());
    1815         p.add(scrollpane, BorderLayout.CENTER);
    1816         return p;
    1817     }
    1818
    1819     @SuppressWarnings("serial") // Superclass is not serializable across versions
    1820     protected class ScrollPaneCornerPanel extends JPanel {
    1821         final Border border = UIManager.getBorder("TableHeader.cellBorder");
    1822
    1823         protected void paintComponent(final Graphics g) {
    1824             border.paintBorder(this, g, 0, 0, getWidth() + 1, getHeight());
    1825         }
    1826     }
    1827
    1828     JComboBox<File> directoryComboBox;
    1829     DirectoryComboBoxModel fDirectoryComboBoxModel;
    1830     private final Action directoryComboBoxAction = new DirectoryComboBoxAction();
    1831
    1832     JTextField filenameTextField;
    1833
    1834     JTableExtension fFileList;
    1835
    1836     private FilterComboBoxModel filterComboBoxModel;
    1837     JComboBox<FileFilter> filterComboBox;
    1838     private final Action filterComboBoxAction = new FilterComboBoxAction();
    1839
    1840     private static final Dimension hstrut10 = new Dimension(10, 1);
    1841     private static final Dimension vstrut10 = new Dimension(1, 10);
    1842
    1843     private static final int PREF_WIDTH = 550;
    1844     private static final int PREF_HEIGHT = 400;
    1845     private static final int MIN_WIDTH = 400;
    1846     private static final int MIN_HEIGHT = 250;
    1847     private static final int LIST_MIN_WIDTH = 400;
    1848     private static final int LIST_MIN_HEIGHT = 100;
    1849     private static final Dimension LIST_MIN_SIZE = new Dimension(LIST_MIN_WIDTH, LIST_MIN_HEIGHT);
    1850
    1851     static String fileNameLabelText = null;
    1852     JLabel fTextFieldLabel = null;
    1853
    1854     private static String filesOfTypeLabelText = null;
    1855
    1856     private static String newFolderToolTipText = null;
    1857     static String newFolderAccessibleName = null;
    1858
    1859     private static final String[] fColumnNames = new String[2];
    1860
    1861     JPanel fTextfieldPanel; // Filename textfield for Save or Custom
    1862     private JPanel fDirectoryPanel; // NewFolder/OpenFolder/Cancel/Approve buttons
    1863     private Component fDirectoryPanelSpacer;
    1864     private JPanel fBottomPanel; // The panel that holds fDirectoryPanel and filterComboBox
    1865
    1866     private FCSubpanel fSaveFilePanel = null;
    1867     private FCSubpanel fOpenFilePanel = null;
    1868     private FCSubpanel fOpenDirOrAnyPanel = null;
    1869     private FCSubpanel fCustomFilePanel = null;
    1870     private FCSubpanel fCustomDirOrAnyPanel = null;
    1871
    1872     FCSubpanel fSubPanel = null; // Current FCSubpanel
    1873
    1874     JButton fApproveButton; // mode-specific behavior is managed by FCSubpanel.approveSelection
    1875     JButton fOpenButton; // for Directories
    1876     JButton fNewFolderButton; // for fDirectoryPanel
    1877
    1878     // ToolTip text varies with type of dialog
    1879     private JButton fCancelButton;
    1880
    1881     private final ApproveSelectionAction fApproveSelectionAction = new ApproveSelectionAction();
    1882     protected int fSortColumn = 0;
    1883     protected int fPackageIsTraversable = -1;
    1884     protected int fApplicationIsTraversable = -1;
    1885
    1886     protected static final int sGlobalPackageIsTraversable;
    1887     protected static final int sGlobalApplicationIsTraversable;
    1888
    1889     protected static final String PACKAGE_TRAVERSABLE_PROPERTY = "JFileChooser.packageIsTraversable";
    1890     protected static final String APPLICATION_TRAVERSABLE_PROPERTY = "JFileChooser.appBundleIsTraversable";
    1891     protected static final String[] sTraversableProperties = {"always", // Bundle is always traversable
    1892             "never", // Bundle is never traversable
    1893             "conditional"}; // Bundle is traversable on command click
    1894     protected static final int kOpenAlways = 0, kOpenNever = 1, kOpenConditional = 2;
    1895
    1896     AbstractAction[] fButtonActions = {fApproveSelectionAction, fApproveSelectionAction, new CancelSelectionAction(), new OpenSelectionAction(), null, new NewFolderAction()};
    1897
    1898     static int parseTraversableProperty(final String s) {
    1899         if (s == null) return -1;
    1900         for (int i = 0; i < sTraversableProperties.length; i++) {
    1901             if (s.equals(sTraversableProperties[i])) return i;
    1902         }
    1903         return -1;
    1904     }
    1905
    1906     static {
    1907         Object o = UIManager.get(PACKAGE_TRAVERSABLE_PROPERTY);
    1908         if (o != null && o instanceof String) sGlobalPackageIsTraversable = parseTraversableProperty((String)o);
    1909         else sGlobalPackageIsTraversable = kOpenConditional;
    1910
    1911         o = UIManager.get(APPLICATION_TRAVERSABLE_PROPERTY);
    1912         if (o != null && o instanceof String) sGlobalApplicationIsTraversable = parseTraversableProperty((String)o);
    1913         else sGlobalApplicationIsTraversable = kOpenConditional;
    1914     }
    1915     static final String sDataPrefix = "FileChooser.";
    1916     static final String[] sButtonKinds = {"openButton", "saveButton", "cancelButton", "openDirectoryButton", "helpButton", "newFolderButton"};
    1917     static final String[] sButtonData = {"Text", "Mnemonic", "ToolTipText"};
    1918     static final int kOpen = 0, kSave = 1, kCancel = 2, kOpenDirectory = 3, kHelp = 4, kNewFolder = 5;
    1919
    1920     /*-------
    1921
    1922      Possible states: Save, {Open, Custom}x{Files, File and Directory, Directory}
    1923      --------- */
    1924
    1925     // This class returns the values for the Custom type, to avoid duplicating code in the two Custom subclasses
    1926     abstract class FCSubpanel {
    1927         // Install the appropriate panels for this mode
    1928         abstract void installPanel(JFileChooser fc, boolean controlButtonsAreShown);
    1929
    1930         abstract void updateButtonState(JFileChooser fc, File f);
    1931
    1932         // Can this item be selected?
    1933         // if not, it's disabled in the list
    1934         boolean isSelectableInList(final JFileChooser fc, final File f) {
    1935             if (f == null) return false;
    1936             if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) return fc.isTraversable(f);
    1937             return fc.accept(f);
    1938         }
    1939
    1940         void approveSelection(final JFileChooser fc) {
    1941             fc.approveSelection();
    1942         }
    1943
    1944         JButton getDefaultButton(final JFileChooser fc) {
    1945             return fApproveButton;
    1946         }
    1947
    1948         // Default to the textfield, panels without one should subclass
    1949         JComponent getFocusComponent(final JFileChooser fc) {
    1950             return filenameTextField;
    1951         }
    1952
    1953         String getApproveButtonText(final JFileChooser fc) {
    1954             // Fallback to "choose"
    1955             return this.getApproveButtonText(fc, chooseButtonText);
    1956         }
    1957
    1958         // Try to get the custom text.  If none, use the fallback
    1959         String getApproveButtonText(final JFileChooser fc, final String fallbackText) {
    1960             final String buttonText = fc.getApproveButtonText();
    1961             if (buttonText != null) {
    1962                 buttonText.trim();
    1963                 if (!buttonText.equals("")) return buttonText;
    1964             }
    1965             return fallbackText;
    1966         }
    1967
    1968         int getApproveButtonMnemonic(final JFileChooser fc) {
    1969             // Don't use a default
    1970             return fc.getApproveButtonMnemonic();
    1971         }
    1972
    1973         // No fallback
    1974         String getApproveButtonToolTipText(final JFileChooser fc) {
    1975             return getApproveButtonToolTipText(fc, null);
    1976         }
    1977
    1978         String getApproveButtonToolTipText(final JFileChooser fc, final String fallbackText) {
    1979             final String tooltipText = fc.getApproveButtonToolTipText();
    1980             if (tooltipText != null) {
    1981                 tooltipText.trim();
    1982                 if (!tooltipText.equals("")) return tooltipText;
    1983             }
    1984             return fallbackText;
    1985         }
    1986
    1987         String getCancelButtonToolTipText(final JFileChooser fc) {
    1988             return cancelChooseButtonToolTipText;
    1989         }
    1990     }
    1991
    1992     // Custom FILES_ONLY dialog
    1993     /*
    1994      NavServices Save appearance with Open behavior
    1995      Approve button label = Open when list has focus and a directory is selected, Custom otherwise
    1996      No OpenDirectory button - Approve button is overloaded
    1997      Default button / double click = Approve
    1998      Has text field
    1999      List - everything is enabled
    2000      */
    2001     class CustomFilePanel extends FCSubpanel {
    2002         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2003             fTextfieldPanel.setVisible(true); // do we really want one in multi-select?  It's confusing
    2004             fOpenButton.setVisible(false);
    2005             fNewFolderButton.setVisible(true);
    2006         }
    2007
    2008         // If the list has focus, the mode depends on the selection
    2009         // - directory = open, file = approve
    2010         // If something else has focus and we have text, it's approve
    2011         // otherwise, it depends on selection again.
    2012         boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
    2013             final boolean selectionIsDirectory = (f != null && fc.isTraversable(f));
    2014             if (fFileList.hasFocus()) return selectionIsDirectory;
    2015             else if (textfieldIsValid()) return false;
    2016             return selectionIsDirectory;
    2017         }
    2018
    2019         // The approve button is overloaded to mean OpenDirectory or Save
    2020         void approveSelection(final JFileChooser fc) {
    2021             File f = getFirstSelectedItem();
    2022             if (inOpenDirectoryMode(fc, f)) {
    2023                 openDirectory(f);
    2024             } else {
    2025                 f = makeFile(fc, getFileName());
    2026                 if (f != null) {
    2027                     selectionInProgress = true;
    2028                     getFileChooser().setSelectedFile(f);
    2029                     selectionInProgress = false;
    2030                 }
    2031                 getFileChooser().approveSelection();
    2032             }
    2033         }
    2034
    2035         // The approve button should be enabled
    2036         // - if something in the list can be opened
    2037         // - if the textfield has something in it
    2038         void updateButtonState(final JFileChooser fc, final File f) {
    2039             boolean enabled = true;
    2040             if (!inOpenDirectoryMode(fc, f)) {
    2041                 enabled = (f != null) || textfieldIsValid();
    2042             }
    2043             getApproveButton(fc).setEnabled(enabled);
    2044
    2045             // The OpenDirectory button should be disabled if there's no directory selected
    2046             fOpenButton.setEnabled(f != null && fc.isTraversable(f));
    2047
    2048             // Update the default button, since we may have disabled the current default.
    2049             setDefaultButtonForMode(fc);
    2050         }
    2051
    2052         // everything's enabled, because we don't know what they're doing with them
    2053         boolean isSelectableInList(final JFileChooser fc, final File f) {
    2054             if (f == null) return false;
    2055             return fc.accept(f);
    2056         }
    2057
    2058         String getApproveButtonToolTipText(final JFileChooser fc) {
    2059             // The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
    2060             if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
    2061             return super.getApproveButtonToolTipText(fc);
    2062         }
    2063     }
    2064
    2065     // All Save dialogs
    2066     /*
    2067      NavServices Save
    2068      Approve button label = Open when list has focus and a directory is selected, Save otherwise
    2069      No OpenDirectory button - Approve button is overloaded
    2070      Default button / double click = Approve
    2071      Has text field
    2072      Has NewFolder button (by text field)
    2073      List - only traversables are enabled
    2074      List is always SINGLE_SELECT
    2075      */
    2076     // Subclasses CustomFilePanel because they look alike and have some common behavior
    2077     class SaveFilePanel extends CustomFilePanel {
    2078         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2079             fTextfieldPanel.setVisible(true);
    2080             fOpenButton.setVisible(false);
    2081             fNewFolderButton.setVisible(true);
    2082         }
    2083
    2084         // only traversables are enabled, regardless of mode
    2085         // because all you can do is select the next folder to open
    2086         boolean isSelectableInList(final JFileChooser fc, final File f) {
    2087             return fc.accept(f) && fc.isTraversable(f);
    2088         }
    2089
    2090         // The approve button means 'approve the file name in the text field.'
    2091         void approveSelection(final JFileChooser fc) {
    2092             final File f = makeFile(fc, getFileName());
    2093             if (f != null) {
    2094                 selectionInProgress = true;
    2095                 getFileChooser().setSelectedFile(f);
    2096                 selectionInProgress = false;
    2097                 getFileChooser().approveSelection();
    2098             }
    2099         }
    2100
    2101         // The approve button should be enabled if the textfield has something in it
    2102         void updateButtonState(final JFileChooser fc, final File f) {
    2103             final boolean enabled = textfieldIsValid();
    2104             getApproveButton(fc).setEnabled(enabled);
    2105         }
    2106
    2107         String getApproveButtonText(final JFileChooser fc) {
    2108             // Get the custom text, or fallback to "Save"
    2109             return this.getApproveButtonText(fc, saveButtonText);
    2110         }
    2111
    2112         int getApproveButtonMnemonic(final JFileChooser fc) {
    2113             return saveButtonMnemonic;
    2114         }
    2115
    2116         String getApproveButtonToolTipText(final JFileChooser fc) {
    2117             // The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
    2118             if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
    2119             return this.getApproveButtonToolTipText(fc, saveButtonToolTipText);
    2120         }
    2121
    2122         String getCancelButtonToolTipText(final JFileChooser fc) {
    2123             return cancelSaveButtonToolTipText;
    2124         }
    2125     }
    2126
    2127     // Open FILES_ONLY
    2128     /*
    2129      NSOpenPanel-style
    2130      Approve button label = Open
    2131      Default button / double click = Approve
    2132      No text field
    2133      No NewFolder button
    2134      List - all items are enabled
    2135      */
    2136     class OpenFilePanel extends FCSubpanel {
    2137         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2138             fTextfieldPanel.setVisible(false);
    2139             fOpenButton.setVisible(false);
    2140             fNewFolderButton.setVisible(false);
    2141             setDefaultButtonForMode(fc);
    2142         }
    2143
    2144         boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
    2145             return (f != null && fc.isTraversable(f));
    2146         }
    2147
    2148         // Default to the list
    2149         JComponent getFocusComponent(final JFileChooser fc) {
    2150             return fFileList;
    2151         }
    2152
    2153         void updateButtonState(final JFileChooser fc, final File f) {
    2154             // Button is disabled if there's nothing selected
    2155             final boolean enabled = (f != null) && !fc.isTraversable(f);
    2156             getApproveButton(fc).setEnabled(enabled);
    2157         }
    2158
    2159         // all items are enabled
    2160         boolean isSelectableInList(final JFileChooser fc, final File f) {
    2161             return f != null && fc.accept(f);
    2162         }
    2163
    2164         String getApproveButtonText(final JFileChooser fc) {
    2165             // Get the custom text, or fallback to "Open"
    2166             return this.getApproveButtonText(fc, openButtonText);
    2167         }
    2168
    2169         int getApproveButtonMnemonic(final JFileChooser fc) {
    2170             return openButtonMnemonic;
    2171         }
    2172
    2173         String getApproveButtonToolTipText(final JFileChooser fc) {
    2174             return this.getApproveButtonToolTipText(fc, openButtonToolTipText);
    2175         }
    2176
    2177         String getCancelButtonToolTipText(final JFileChooser fc) {
    2178             return cancelOpenButtonToolTipText;
    2179         }
    2180     }
    2181
    2182     // used by open and custom panels for Directory only or files and directories
    2183     abstract class DirOrAnyPanel extends FCSubpanel {
    2184         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2185             fOpenButton.setVisible(false);
    2186         }
    2187
    2188         JButton getDefaultButton(final JFileChooser fc) {
    2189             return getApproveButton(fc);
    2190         }
    2191
    2192         void updateButtonState(final JFileChooser fc, final File f) {
    2193             // Button is disabled if there's nothing selected
    2194             // Approve button is handled by the subclasses
    2195             // getApproveButton(fc).setEnabled(f != null);
    2196
    2197             // The OpenDirectory button should be disabled if there's no directory selected
    2198             // - we only check the first item
    2199
    2200             fOpenButton.setEnabled(false);
    2201             setDefaultButtonForMode(fc);
    2202         }
    2203     }
    2204
    2205     // Open FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
    2206     /*
    2207      NavServices Choose
    2208      Approve button label = Choose/Custom
    2209      Has OpenDirectory button
    2210      Default button / double click = OpenDirectory
    2211      No text field
    2212      List - files are disabled in DIRECTORIES_ONLY
    2213      */
    2214     class OpenDirOrAnyPanel extends DirOrAnyPanel {
    2215         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2216             super.installPanel(fc, controlButtonsAreShown);
    2217             fTextfieldPanel.setVisible(false);
    2218             fNewFolderButton.setVisible(false);
    2219         }
    2220
    2221         // Default to the list
    2222         JComponent getFocusComponent(final JFileChooser fc) {
    2223             return fFileList;
    2224         }
    2225
    2226         int getApproveButtonMnemonic(final JFileChooser fc) {
    2227             return chooseButtonMnemonic;
    2228         }
    2229
    2230         String getApproveButtonToolTipText(final JFileChooser fc) {
    2231             String fallbackText;
    2232             if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) fallbackText = chooseFolderButtonToolTipText;
    2233             else fallbackText = chooseItemButtonToolTipText;
    2234             return this.getApproveButtonToolTipText(fc, fallbackText);
    2235         }
    2236
    2237         void updateButtonState(final JFileChooser fc, final File f) {
    2238             // Button is disabled if there's nothing selected
    2239             getApproveButton(fc).setEnabled(f != null);
    2240             super.updateButtonState(fc, f);
    2241         }
    2242     }
    2243
    2244     // Custom FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
    2245     /*
    2246      No NavServices equivalent
    2247      Approve button label = user defined or Choose
    2248      Has OpenDirectory button
    2249      Default button / double click = OpenDirectory
    2250      Has text field
    2251      Has NewFolder button (by text field)
    2252      List - files are disabled in DIRECTORIES_ONLY
    2253      */
    2254     class CustomDirOrAnyPanel extends DirOrAnyPanel {
    2255         void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
    2256             super.installPanel(fc, controlButtonsAreShown);
    2257             fTextfieldPanel.setVisible(true);
    2258             fNewFolderButton.setVisible(true);
    2259         }
    2260
    2261         // If there's text, make a file and select it
    2262         void approveSelection(final JFileChooser fc) {
    2263             final File f = makeFile(fc, getFileName());
    2264             if (f != null) {
    2265                 selectionInProgress = true;
    2266                 getFileChooser().setSelectedFile(f);
    2267                 selectionInProgress = false;
    2268             }
    2269             getFileChooser().approveSelection();
    2270         }
    2271
    2272         void updateButtonState(final JFileChooser fc, final File f) {
    2273             // Button is disabled if there's nothing selected
    2274             getApproveButton(fc).setEnabled(f != null || textfieldIsValid());
    2275             super.updateButtonState(fc, f);
    2276         }
    2277     }
    2278
    2279     // See FileRenderer - documents in Save dialogs draw disabled, so they shouldn't be selected
    2280     @SuppressWarnings("serial") // Superclass is not serializable across versions
    2281     class MacListSelectionModel extends DefaultListSelectionModel {
    2282         AquaFileSystemModel fModel;
    2283
    2284         MacListSelectionModel(final AquaFileSystemModel model) {
    2285             fModel = model;
    2286         }
    2287
    2288         // Can the file be selected in this mode?
    2289         // (files are visible even if they can't be selected)
    2290         boolean isSelectableInListIndex(final int index) {
    2291             final File file = (File)fModel.getValueAt(index, 0);
    2292             return (file != null && isSelectableInList(file));
    2293         }
    2294
    2295         // Make sure everything in the selection interval is valid
    2296         void verifySelectionInterval(int index0, int index1, boolean isSetSelection) {
    2297             if (index0 > index1) {
    2298                 final int tmp = index1;
    2299                 index1 = index0;
    2300                 index0 = tmp;
    2301             }
    2302             int start = index0;
    2303             int end;
    2304             do {
    2305                 // Find the first selectable file in the range
    2306                 for (; start <= index1; start++) {
    2307                     if (isSelectableInListIndex(start)) break;
    2308                 }
    2309                 end = -1;
    2310                 // Find the last selectable file in the range
    2311                 for (int i = start; i <= index1; i++) {
    2312                     if (!isSelectableInListIndex(i)) {
    2313                         break;
    2314                     }
    2315                     end = i;
    2316                 }
    2317                 // Select the range
    2318                 if (end >= 0) {
    2319                     // If setting the selection, do "set" the first time to clear the old one
    2320                     // after that do "add" to extend it
    2321                     if (isSetSelection) {
    2322                         super.setSelectionInterval(start, end);
    2323                         isSetSelection = false;
    2324                     } else {
    2325                         super.addSelectionInterval(start, end);
    2326                     }
    2327                     start = end + 1;
    2328                 } else {
    2329                     break;
    2330                 }
    2331             } while (start <= index1);
    2332         }
    2333
    2334         public void setAnchorSelectionIndex(final int anchorIndex) {
    2335             if (isSelectableInListIndex(anchorIndex)) super.setAnchorSelectionIndex(anchorIndex);
    2336         }
    2337
    2338         public void setLeadSelectionIndex(final int leadIndex) {
    2339             if (isSelectableInListIndex(leadIndex)) super.setLeadSelectionIndex(leadIndex);
    2340         }
    2341
    2342         public void setSelectionInterval(final int index0, final int index1) {
    2343             if (index0 == -1 || index1 == -1) { return; }
    2344
    2345             if ((getSelectionMode() == SINGLE_SELECTION) || (index0 == index1)) {
    2346                 if (isSelectableInListIndex(index1)) super.setSelectionInterval(index1, index1);
    2347             } else {
    2348                 verifySelectionInterval(index0, index1, true);
    2349             }
    2350         }
    2351
    2352         public void addSelectionInterval(final int index0, final int index1) {
    2353             if (index0 == -1 || index1 == -1) { return; }
    2354
    2355             if (index0 == index1) {
    2356                 if (isSelectableInListIndex(index1)) super.addSelectionInterval(index1, index1);
    2357                 return;
    2358             }
    2359
    2360             if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
    2361                 setSelectionInterval(index0, index1);
    2362                 return;
    2363             }
    2364
    2365             verifySelectionInterval(index0, index1, false);
    2366         }
    2367     }
    2368
    2369     // Convenience, to translate from the JList directory view to the Mac-style JTable
    2370     //   & minimize diffs between this and BasicFileChooserUI
    2371     @SuppressWarnings("serial") // Superclass is not serializable across versions
    2372     class JTableExtension extends JTable {
    2373         public void setSelectedIndex(final int index) {
    2374             getSelectionModel().setSelectionInterval(index, index);
    2375         }
    2376
    2377         public void removeSelectedIndex(final int index) {
    2378             getSelectionModel().removeSelectionInterval(index, index);
    2379         }
    2380
    2381         public void ensureIndexIsVisible(final int index) {
    2382             final Rectangle cellBounds = getCellRect(index, 0, false);
    2383             if (cellBounds != null) {
    2384                 scrollRectToVisible(cellBounds);
    2385             }
    2386         }
    2387
    2388         public int locationToIndex(final Point location) {
    2389             return rowAtPoint(location);
    2390         }
    2391     }
    2392 }