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