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