    rev 48858 : Work in progress for JDK-6303622
    Use generic in Swing components like JComboBox
    1. Added generic return type and parameter type for JComboBox.
    2. Modified JList related classes as well.
    3. Reverted the changes made to "getSelectedValues" as suggested by Joe.
    4. Modified files that were failing on Linux and Mac as well.

       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 File selectedDirectory) {
    1259             this.fSelectedDirectory = selectedDirectory;
    1260             fireContentsChanged(this, -1, -1);
    1261         }
    1262
    1263         public File 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((FileFilter)e.getNewValue());
    1322             }
    1323         }
    1324
    1325         public void setSelectedItem(FileFilter filter) {
    1326             if (filter != null && !isSelectedFileFilterInModel(filter)) {
    1327                 oldFileFilter = filter;
    1328                 getFileChooser().setFileFilter(filter);
    1329                 fireContentsChanged(this, -1, -1);
    1330             }
    1331         }
    1332
    1333         private boolean isSelectedFileFilterInModel(FileFilter filter) {
    1334             return Objects.equals(filter, oldFileFilter);
    1335         }
    1336
    1337         public FileFilter 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(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 }
    --- EOF ---