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