alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (AquaFileChooserUI.java)

This example Java source code file (AquaFileChooserUI.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

abstractaction, actionevent, awt, dimension, event, fcsubpanel, file, filechooser, filefilter, gui, icon, jbutton, jcomponent, jfilechooser, jpanel, object, rectangle, string, swing, text

The AquaFileChooserUI.java Java example source code

/*
 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.apple.laf;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.beans.*;
import java.io.File;
import java.net.URI;
import java.text.DateFormat;
import java.util.*;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.*;
import javax.swing.table.*;

import sun.swing.SwingUtilities2;

public class AquaFileChooserUI extends FileChooserUI {
    /* FileView icons */
    protected Icon directoryIcon = null;
    protected Icon fileIcon = null;
    protected Icon computerIcon = null;
    protected Icon hardDriveIcon = null;
    protected Icon floppyDriveIcon = null;

    protected Icon upFolderIcon = null;
    protected Icon homeFolderIcon = null;
    protected Icon listViewIcon = null;
    protected Icon detailsViewIcon = null;

    protected int saveButtonMnemonic = 0;
    protected int openButtonMnemonic = 0;
    protected int cancelButtonMnemonic = 0;
    protected int updateButtonMnemonic = 0;
    protected int helpButtonMnemonic = 0;
    protected int chooseButtonMnemonic = 0;

    private String saveTitleText = null;
    private String openTitleText = null;
    String newFolderTitleText = null;

    protected String saveButtonText = null;
    protected String openButtonText = null;
    protected String cancelButtonText = null;
    protected String updateButtonText = null;
    protected String helpButtonText = null;
    protected String newFolderButtonText = null;
    protected String chooseButtonText = null;

    //private String newFolderErrorSeparator = null;
    String newFolderErrorText = null;
    String newFolderExistsErrorText = null;
    protected String fileDescriptionText = null;
    protected String directoryDescriptionText = null;

    protected String saveButtonToolTipText = null;
    protected String openButtonToolTipText = null;
    protected String cancelButtonToolTipText = null;
    protected String updateButtonToolTipText = null;
    protected String helpButtonToolTipText = null;
    protected String chooseItemButtonToolTipText = null; // Choose anything
    protected String chooseFolderButtonToolTipText = null; // Choose folder
    protected String directoryComboBoxToolTipText = null;
    protected String filenameTextFieldToolTipText = null;
    protected String filterComboBoxToolTipText = null;
    protected String openDirectoryButtonToolTipText = null;

    protected String cancelOpenButtonToolTipText = null;
    protected String cancelSaveButtonToolTipText = null;
    protected String cancelChooseButtonToolTipText = null;
    protected String cancelNewFolderButtonToolTipText = null;

    protected String desktopName = null;
    String newFolderDialogPrompt = null;
    String newFolderDefaultName = null;
    private String newFileDefaultName = null;
    String createButtonText = null;

    JFileChooser filechooser = null;

    private MouseListener doubleClickListener = null;
    private PropertyChangeListener propertyChangeListener = null;
    private AncestorListener ancestorListener = null;
    private DropTarget dragAndDropTarget = null;

    private final AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter();

    private AquaFileSystemModel model;

    final AquaFileView fileView = new AquaFileView(this);

    boolean selectionInProgress = false;

    // The accessoryPanel is a container to place the JFileChooser accessory component
    private JPanel accessoryPanel = null;

    //
    // ComponentUI Interface Implementation methods
    //
    public static ComponentUI createUI(final JComponent c) {
        return new AquaFileChooserUI((JFileChooser)c);
    }

    public AquaFileChooserUI(final JFileChooser filechooser) {
        super();
    }

    public void installUI(final JComponent c) {
        accessoryPanel = new JPanel(new BorderLayout());
        filechooser = (JFileChooser)c;

        createModel();

        installDefaults(filechooser);
        installComponents(filechooser);
        installListeners(filechooser);

        AquaUtils.enforceComponentOrientation(filechooser, ComponentOrientation.getOrientation(Locale.getDefault()));
    }

    public void uninstallUI(final JComponent c) {
        uninstallListeners(filechooser);
        uninstallComponents(filechooser);
        uninstallDefaults(filechooser);

        if (accessoryPanel != null) {
            accessoryPanel.removeAll();
        }

        accessoryPanel = null;
        getFileChooser().removeAll();
    }

    protected void installListeners(final JFileChooser fc) {
        doubleClickListener = createDoubleClickListener(fc, fFileList);
        fFileList.addMouseListener(doubleClickListener);

        propertyChangeListener = createPropertyChangeListener(fc);
        if (propertyChangeListener != null) {
            fc.addPropertyChangeListener(propertyChangeListener);
        }
        if (model != null) fc.addPropertyChangeListener(model);

        ancestorListener = new AncestorListener(){
            public void ancestorAdded(final AncestorEvent e) {
                // Request defaultness for the appropriate button based on mode
                setFocusForMode(getFileChooser());
                // Request defaultness for the appropriate button based on mode
                setDefaultButtonForMode(getFileChooser());
            }

            public void ancestorRemoved(final AncestorEvent e) {
            }

            public void ancestorMoved(final AncestorEvent e) {
            }
        };
        fc.addAncestorListener(ancestorListener);

        fc.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        dragAndDropTarget = new DropTarget(fc, DnDConstants.ACTION_COPY, new DnDHandler(), true);
        fc.setDropTarget(dragAndDropTarget);
    }

    protected void uninstallListeners(final JFileChooser fc) {
        if (propertyChangeListener != null) {
            fc.removePropertyChangeListener(propertyChangeListener);
        }
        fFileList.removeMouseListener(doubleClickListener);
        fc.removePropertyChangeListener(model);
        fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
        fc.removeAncestorListener(ancestorListener);
        fc.setDropTarget(null);
        ancestorListener = null;
    }

    protected void installDefaults(final JFileChooser fc) {
        installIcons(fc);
        installStrings(fc);
        setPackageIsTraversable(fc.getClientProperty(PACKAGE_TRAVERSABLE_PROPERTY));
        setApplicationIsTraversable(fc.getClientProperty(APPLICATION_TRAVERSABLE_PROPERTY));
    }

    protected void installIcons(final JFileChooser fc) {
        directoryIcon = UIManager.getIcon("FileView.directoryIcon");
        fileIcon = UIManager.getIcon("FileView.fileIcon");
        computerIcon = UIManager.getIcon("FileView.computerIcon");
        hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon");
    }

    String getString(final String uiKey, final String fallback) {
        final String result = UIManager.getString(uiKey);
        return (result == null ? fallback : result);
    }

    protected void installStrings(final JFileChooser fc) {
        // Exist in basic.properties (though we might want to override)
        fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText");
        directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText");
        newFolderErrorText = getString("FileChooser.newFolderErrorText", "Error occurred during folder creation");

        saveButtonText = UIManager.getString("FileChooser.saveButtonText");
        openButtonText = UIManager.getString("FileChooser.openButtonText");
        cancelButtonText = UIManager.getString("FileChooser.cancelButtonText");
        updateButtonText = UIManager.getString("FileChooser.updateButtonText");
        helpButtonText = UIManager.getString("FileChooser.helpButtonText");

        saveButtonMnemonic = UIManager.getInt("FileChooser.saveButtonMnemonic");
        openButtonMnemonic = UIManager.getInt("FileChooser.openButtonMnemonic");
        cancelButtonMnemonic = UIManager.getInt("FileChooser.cancelButtonMnemonic");
        updateButtonMnemonic = UIManager.getInt("FileChooser.updateButtonMnemonic");
        helpButtonMnemonic = UIManager.getInt("FileChooser.helpButtonMnemonic");
        chooseButtonMnemonic = UIManager.getInt("FileChooser.chooseButtonMnemonic");

        saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText");
        openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText");
        cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText");
        updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText");
        helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText");

        // Mac-specific, but fallback to basic if it's missing
        saveTitleText = getString("FileChooser.saveTitleText", saveButtonText);
        openTitleText = getString("FileChooser.openTitleText", openButtonText);

        // Mac-specific, required
        newFolderExistsErrorText = getString("FileChooser.newFolderExistsErrorText", "That name is already taken");
        chooseButtonText = getString("FileChooser.chooseButtonText", "Choose");
        newFolderButtonText = getString("FileChooser.newFolderButtonText", "New");
        newFolderTitleText = getString("FileChooser.newFolderTitleText", "New Folder");

        if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
            fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
        } else {
            fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
        }

        filesOfTypeLabelText = getString("FileChooser.filesOfTypeLabelText", "Format:");

        desktopName = getString("FileChooser.desktopName", "Desktop");
        newFolderDialogPrompt = getString("FileChooser.newFolderPromptText", "Name of new folder:");
        newFolderDefaultName = getString("FileChooser.untitledFolderName", "untitled folder");
        newFileDefaultName = getString("FileChooser.untitledFileName", "untitled");
        createButtonText = getString("FileChooser.createButtonText", "Create");

        fColumnNames[1] = getString("FileChooser.byDateText", "Date Modified");
        fColumnNames[0] = getString("FileChooser.byNameText", "Name");

        // Mac-specific, optional
        chooseItemButtonToolTipText = UIManager.getString("FileChooser.chooseItemButtonToolTipText");
        chooseFolderButtonToolTipText = UIManager.getString("FileChooser.chooseFolderButtonToolTipText");
        openDirectoryButtonToolTipText = UIManager.getString("FileChooser.openDirectoryButtonToolTipText");

        directoryComboBoxToolTipText = UIManager.getString("FileChooser.directoryComboBoxToolTipText");
        filenameTextFieldToolTipText = UIManager.getString("FileChooser.filenameTextFieldToolTipText");
        filterComboBoxToolTipText = UIManager.getString("FileChooser.filterComboBoxToolTipText");

        cancelOpenButtonToolTipText = UIManager.getString("FileChooser.cancelOpenButtonToolTipText");
        cancelSaveButtonToolTipText = UIManager.getString("FileChooser.cancelSaveButtonToolTipText");
        cancelChooseButtonToolTipText = UIManager.getString("FileChooser.cancelChooseButtonToolTipText");
        cancelNewFolderButtonToolTipText = UIManager.getString("FileChooser.cancelNewFolderButtonToolTipText");

        newFolderTitleText = UIManager.getString("FileChooser.newFolderTitleText");
        newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText");
        newFolderAccessibleName = getString("FileChooser.newFolderAccessibleName", newFolderTitleText);
    }

    protected void uninstallDefaults(final JFileChooser fc) {
        uninstallIcons(fc);
        uninstallStrings(fc);
    }

    protected void uninstallIcons(final JFileChooser fc) {
        directoryIcon = null;
        fileIcon = null;
        computerIcon = null;
        hardDriveIcon = null;
        floppyDriveIcon = null;

        upFolderIcon = null;
        homeFolderIcon = null;
        detailsViewIcon = null;
        listViewIcon = null;
    }

    protected void uninstallStrings(final JFileChooser fc) {
        saveTitleText = null;
        openTitleText = null;
        newFolderTitleText = null;

        saveButtonText = null;
        openButtonText = null;
        cancelButtonText = null;
        updateButtonText = null;
        helpButtonText = null;
        newFolderButtonText = null;
        chooseButtonText = null;

        cancelOpenButtonToolTipText = null;
        cancelSaveButtonToolTipText = null;
        cancelChooseButtonToolTipText = null;
        cancelNewFolderButtonToolTipText = null;

        saveButtonToolTipText = null;
        openButtonToolTipText = null;
        cancelButtonToolTipText = null;
        updateButtonToolTipText = null;
        helpButtonToolTipText = null;
        chooseItemButtonToolTipText = null;
        chooseFolderButtonToolTipText = null;
        openDirectoryButtonToolTipText = null;
        directoryComboBoxToolTipText = null;
        filenameTextFieldToolTipText = null;
        filterComboBoxToolTipText = null;

        newFolderDefaultName = null;
        newFileDefaultName = null;

        desktopName = null;
    }

    protected void createModel() {
    }

    AquaFileSystemModel getModel() {
        return model;
    }

    /*
     * Listen for filechooser property changes, such as
     * the selected file changing, or the type of the dialog changing.
     */
    // Taken almost verbatim from Metal
    protected PropertyChangeListener createPropertyChangeListener(final JFileChooser fc) {
        return new PropertyChangeListener(){
            public void propertyChange(final PropertyChangeEvent e) {
                final String prop = e.getPropertyName();
                if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
                    final File f = (File)e.getNewValue();
                    if (f != null) {
                        // Select the file in the list if the selected file didn't change as
                        // a result of a list click.
                        if (!selectionInProgress && getModel().contains(f)) {
                            fFileList.setSelectedIndex(getModel().indexOf(f));
                        }

                        // [3643835] Need to populate the text field here.  No-op on Open dialogs
                        // Note that this was removed for 3514735, but should not have been.
                        if (!f.isDirectory()) {
                            setFileName(getFileChooser().getName(f));
                        }
                    }
                    updateButtonState(getFileChooser());
                } else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
                    JFileChooser fileChooser = getFileChooser();
                    if (!fileChooser.isDirectorySelectionEnabled()) {
                        final File[] files = (File[]) e.getNewValue();
                        if (files != null) {
                            for (int selectedRow : fFileList.getSelectedRows()) {
                                File file = (File) fFileList.getValueAt(selectedRow, 0);
                                if (fileChooser.isTraversable(file)) {
                                    fFileList.removeSelectedIndex(selectedRow);
                                }
                            }
                        }
                    }
                } else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
                    fFileList.clearSelection();
                    final File currentDirectory = getFileChooser().getCurrentDirectory();
                    if (currentDirectory != null) {
                        fDirectoryComboBoxModel.addItem(currentDirectory);
                        // Enable the newFolder action if the current directory
                        // is writable.
                        // PENDING(jeff) - broken - fix
                        getAction(kNewFolder).setEnabled(currentDirectory.canWrite());
                    }
                    updateButtonState(getFileChooser());
                } else if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
                    fFileList.clearSelection();
                    setBottomPanelForMode(getFileChooser()); // Also updates approve button
                } else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {
                    if (getAccessoryPanel() != null) {
                        if (e.getOldValue() != null) {
                            getAccessoryPanel().remove((JComponent)e.getOldValue());
                        }
                        final JComponent accessory = (JComponent)e.getNewValue();
                        if (accessory != null) {
                            getAccessoryPanel().add(accessory, BorderLayout.CENTER);
                        }
                    }
                } else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) {
                    updateApproveButton(getFileChooser());
                    getFileChooser().invalidate();
                } else if (prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY) {
                    if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) {
                        fileNameLabelText = getString("FileChooser.saveDialogFileNameLabelText", "Save As:");
                    } else {
                        fileNameLabelText = getString("FileChooser.fileNameLabelText", "Name:");
                    }
                    fTextFieldLabel.setText(fileNameLabelText);

                    // Mac doesn't show the text field or "new folder" button in 'Open' dialogs
                    setBottomPanelForMode(getFileChooser()); // Also updates approve button
                } else if (prop.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
                    getApproveButton(getFileChooser()).setMnemonic(getApproveButtonMnemonic(getFileChooser()));
                } else if (prop.equals(PACKAGE_TRAVERSABLE_PROPERTY)) {
                    setPackageIsTraversable(e.getNewValue());
                } else if (prop.equals(APPLICATION_TRAVERSABLE_PROPERTY)) {
                    setApplicationIsTraversable(e.getNewValue());
                } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
                    if (getFileChooser().isMultiSelectionEnabled()) {
                        fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
                    } else {
                        fFileList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                    }
                } else if (prop.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
                    doControlButtonsChanged(e);
                }
            }
        };
    }

    void setPackageIsTraversable(final Object o) {
        int newProp = -1;
        if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
        if (newProp != -1) fPackageIsTraversable = newProp;
        else fPackageIsTraversable = sGlobalPackageIsTraversable;
    }

    void setApplicationIsTraversable(final Object o) {
        int newProp = -1;
        if (o != null && o instanceof String) newProp = parseTraversableProperty((String)o);
        if (newProp != -1) fApplicationIsTraversable = newProp;
        else fApplicationIsTraversable = sGlobalApplicationIsTraversable;
    }

    void doControlButtonsChanged(final PropertyChangeEvent e) {
        if (getFileChooser().getControlButtonsAreShown()) {
            fBottomPanel.add(fDirectoryPanelSpacer);
            fBottomPanel.add(fDirectoryPanel);
        } else {
            fBottomPanel.remove(fDirectoryPanelSpacer);
            fBottomPanel.remove(fDirectoryPanel);
        }
    }

    public String getFileName() {
        if (filenameTextField != null) { return filenameTextField.getText(); }
        return null;
    }

    public String getDirectoryName() {
        // PENDING(jeff) - get the name from the directory combobox
        return null;
    }

    public void setFileName(final String filename) {
        if (filenameTextField != null) {
            filenameTextField.setText(filename);
        }
    }

    public void setDirectoryName(final String dirname) {
        // PENDING(jeff) - set the name in the directory combobox
    }

    public void rescanCurrentDirectory(final JFileChooser fc) {
        getModel().invalidateFileCache();
        getModel().validateFileCache();
    }

    public void ensureFileIsVisible(final JFileChooser fc, final File f) {
        if (f == null) {
            fFileList.requestFocusInWindow();
            fFileList.ensureIndexIsVisible(-1);
            return;
        }

        getModel().runWhenDone(new Runnable() {
            public void run() {
                fFileList.requestFocusInWindow();
                fFileList.ensureIndexIsVisible(getModel().indexOf(f));
            }
        });
    }

    public JFileChooser getFileChooser() {
        return filechooser;
    }

    public JPanel getAccessoryPanel() {
        return accessoryPanel;
    }

    protected JButton getApproveButton(final JFileChooser fc) {
        return fApproveButton;
    }

    public int getApproveButtonMnemonic(final JFileChooser fc) {
        return fSubPanel.getApproveButtonMnemonic(fc);
    }

    public String getApproveButtonToolTipText(final JFileChooser fc) {
        return fSubPanel.getApproveButtonToolTipText(fc);
    }

    public String getApproveButtonText(final JFileChooser fc) {
        return fSubPanel.getApproveButtonText(fc);
    }

    protected String getCancelButtonToolTipText(final JFileChooser fc) {
        return fSubPanel.getCancelButtonToolTipText(fc);
    }

    // If the item's not selectable, it'll be visible but disabled in the list
    boolean isSelectableInList(final File f) {
        return fSubPanel.isSelectableInList(getFileChooser(), f);
    }

    // Is this a file that the JFileChooser wants?
    // Directories can be selected in the list regardless of mode
    boolean isSelectableForMode(final JFileChooser fc, final File f) {
        if (f == null) return false;
        final int mode = fc.getFileSelectionMode();
        if (mode == JFileChooser.FILES_AND_DIRECTORIES) return true;
        boolean traversable = fc.isTraversable(f);
        if (mode == JFileChooser.DIRECTORIES_ONLY) return traversable;
        return !traversable;
    }

    // ********************************************
    // ************ Create Listeners **************
    // ********************************************

    // From Basic
    public ListSelectionListener createListSelectionListener(final JFileChooser fc) {
        return new SelectionListener();
    }

    protected class SelectionListener implements ListSelectionListener {
        public void valueChanged(final ListSelectionEvent e) {
            if (e.getValueIsAdjusting()) return;

            File f = null;
            final int selectedRow = fFileList.getSelectedRow();
            final JFileChooser chooser = getFileChooser();
            boolean isSave = (chooser.getDialogType() == JFileChooser.SAVE_DIALOG);
            if (selectedRow >= 0) {
                f = (File)fFileList.getValueAt(selectedRow, 0);
            }

            // Save dialog lists can't be multi select, because all we're selecting is the next folder to open
            selectionInProgress = true;
            if (!isSave && chooser.isMultiSelectionEnabled()) {
                final int[] rows = fFileList.getSelectedRows();
                int selectableCount = 0;
                // Double-check that all the list selections are valid for this mode
                // Directories can be selected in the list regardless of mode
                if (rows.length > 0) {
                    for (final int element : rows) {
                        if (isSelectableForMode(chooser, (File)fFileList.getValueAt(element, 0))) selectableCount++;
                    }
                }
                if (selectableCount > 0) {
                    final File[] files = new File[selectableCount];
                    for (int i = 0, si = 0; i < rows.length; i++) {
                        f = (File)fFileList.getValueAt(rows[i], 0);
                        if (isSelectableForMode(chooser, f)) {
                            if (fileView.isAlias(f)) {
                                f = fileView.resolveAlias(f);
                            }
                            files[si++] = f;
                        }
                    }
                    chooser.setSelectedFiles(files);
                } else {
                    chooser.setSelectedFiles(null);
                }
            } else {
                chooser.setSelectedFiles(null);
                chooser.setSelectedFile(f);
            }
            selectionInProgress = false;
        }
    }

    // When the Save textfield has the focus, the button should say "Save"
    // Otherwise, it depends on the list selection
    protected class SaveTextFocusListener implements FocusListener {
        public void focusGained(final FocusEvent e) {
            updateButtonState(getFileChooser());
        }

        // Do nothing, we might be losing focus due to window deactivation
        public void focusLost(final FocusEvent e) {

        }
    }

    // When the Save textfield is empty and the button says "Save", it should be disabled
    // Otherwise, it depends on the list selection
    protected class SaveTextDocumentListener implements DocumentListener {
        public void insertUpdate(final DocumentEvent e) {
            textChanged();
        }

        public void removeUpdate(final DocumentEvent e) {
            textChanged();
        }

        public void changedUpdate(final DocumentEvent e) {

        }

        void textChanged() {
            updateButtonState(getFileChooser());
        }
    }

    // Opens the File object if it's a traversable directory
    protected boolean openDirectory(final File f) {
        if (getFileChooser().isTraversable(f)) {
            fFileList.clearSelection();
            // Resolve any aliases
            final File original = fileView.resolveAlias(f);
            getFileChooser().setCurrentDirectory(original);
            updateButtonState(getFileChooser());
            return true;
        }
        return false;
    }

    // From Basic
    protected class DoubleClickListener extends MouseAdapter {
        JTableExtension list;

        public DoubleClickListener(final JTableExtension list) {
            this.list = list;
        }

        public void mouseClicked(final MouseEvent e) {
            if (e.getClickCount() != 2) return;

            final int index = list.locationToIndex(e.getPoint());
            if (index < 0) return;

            final File f = (File)((AquaFileSystemModel)list.getModel()).getElementAt(index);
            if (openDirectory(f)) return;

            if (!isSelectableInList(f)) return;
            getFileChooser().approveSelection();
        }
    }

    protected MouseListener createDoubleClickListener(final JFileChooser fc, final JTableExtension list) {
        return new DoubleClickListener(list);
    }

    // listens for drag events onto the JFileChooser and sets the selected file or directory
    class DnDHandler extends DropTargetAdapter {
        public void dragEnter(final DropTargetDragEvent dtde) {
            tryToAcceptDrag(dtde);
        }

        public void dragOver(final DropTargetDragEvent dtde) {
            tryToAcceptDrag(dtde);
        }

        public void dropActionChanged(final DropTargetDragEvent dtde) {
            tryToAcceptDrag(dtde);
        }

        public void drop(final DropTargetDropEvent dtde) {
            if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                handleFileDropEvent(dtde);
                return;
            }

            if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                handleStringDropEvent(dtde);
                return;
            }
        }

        protected void tryToAcceptDrag(final DropTargetDragEvent dtde) {
            if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                dtde.acceptDrag(DnDConstants.ACTION_COPY);
                return;
            }

            dtde.rejectDrag();
        }

        protected void handleFileDropEvent(final DropTargetDropEvent dtde) {
            dtde.acceptDrop(dtde.getDropAction());
            final Transferable transferable = dtde.getTransferable();

            try {
                final java.util.List<File> fileList = (java.util.List)transferable.getTransferData(DataFlavor.javaFileListFlavor);
                dropFiles(fileList.toArray(new File[fileList.size()]));
                dtde.dropComplete(true);
            } catch (final Exception e) {
                dtde.dropComplete(false);
            }
        }

        protected void handleStringDropEvent(final DropTargetDropEvent dtde) {
            dtde.acceptDrop(dtde.getDropAction());
            final Transferable transferable = dtde.getTransferable();

            final String stringData;
            try {
                stringData = (String)transferable.getTransferData(DataFlavor.stringFlavor);
            } catch (final Exception e) {
                dtde.dropComplete(false);
                return;
            }

            try {
                final File fileAsPath = new File(stringData);
                if (fileAsPath.exists()) {
                    dropFiles(new File[] {fileAsPath});
                    dtde.dropComplete(true);
                    return;
                }
            } catch (final Exception e) {
                // try again
            }

            try {
                final File fileAsURI = new File(new URI(stringData));
                if (fileAsURI.exists()) {
                    dropFiles(new File[] {fileAsURI});
                    dtde.dropComplete(true);
                    return;
                }
            } catch (final Exception e) {
                // nothing more to do
            }

            dtde.dropComplete(false);
        }

        protected void dropFiles(final File[] files) {
            final JFileChooser jfc = getFileChooser();

            if (files.length == 1) {
                if (files[0].isDirectory()) {
                    jfc.setCurrentDirectory(files[0]);
                    return;
                }

                if (!isSelectableForMode(jfc, files[0])) {
                    return;
                }
            }

            jfc.setSelectedFiles(files);
            for (final File file : files) {
                jfc.ensureFileIsVisible(file);
            }
            getModel().runWhenDone(new Runnable() {
                public void run() {
                    final AquaFileSystemModel fileSystemModel = getModel();
                    for (final File element : files) {
                        final int index = fileSystemModel.indexOf(element);
                        if (index >= 0) fFileList.addRowSelectionInterval(index, index);
                    }
                }
            });
        }
    }

    // FileChooser UI PLAF methods

    /**
     * Returns the default accept all file filter
     */
    public FileFilter getAcceptAllFileFilter(final JFileChooser fc) {
        return acceptAllFileFilter;
    }

    public FileView getFileView(final JFileChooser fc) {
        return fileView;
    }

    /**
     * Returns the title of this dialog
     */
    public String getDialogTitle(final JFileChooser fc) {
        if (fc.getDialogTitle() == null) {
            if (getFileChooser().getDialogType() == JFileChooser.OPEN_DIALOG) {
                return openTitleText;
            } else if (getFileChooser().getDialogType() == JFileChooser.SAVE_DIALOG) { return saveTitleText; }
        }
        return fc.getDialogTitle();
    }

    // Utility to get the first selected item regardless of whether we're single or multi select
    File getFirstSelectedItem() {
        // Get the selected item
        File selectedFile = null;
        final int index = fFileList.getSelectedRow();
        if (index >= 0) {
            selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
        }
        return selectedFile;
    }

    // Make a file from the filename
    File makeFile(final JFileChooser fc, final String filename) {
        File selectedFile = null;
        // whitespace is legal on Macs, even on beginning and end of filename
        if (filename != null && !filename.equals("")) {
            final FileSystemView fs = fc.getFileSystemView();
            selectedFile = fs.createFileObject(filename);
            if (!selectedFile.isAbsolute()) {
                selectedFile = fs.createFileObject(fc.getCurrentDirectory(), filename);
            }
        }
        return selectedFile;
    }

    // Utility to tell if the textfield has anything in it
    boolean textfieldIsValid() {
        final String s = getFileName();
        return (s != null && !s.equals(""));
    }

    // Action to attach to the file list so we can override the default action
    // of the table for the return key, which is to select the next line.
    protected class DefaultButtonAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            final JRootPane root = AquaFileChooserUI.this.getFileChooser().getRootPane();
            final JFileChooser fc = AquaFileChooserUI.this.getFileChooser();
            final JButton owner = root.getDefaultButton();
            if (owner != null && SwingUtilities.getRootPane(owner) == root && owner.isEnabled()) {
                owner.doClick(20);
            } else if (!fc.getControlButtonsAreShown()) {
                final JButton defaultButton = AquaFileChooserUI.this.fSubPanel.getDefaultButton(fc);

                if (defaultButton != null) {
                    defaultButton.doClick(20);
                }
            } else {
                Toolkit.getDefaultToolkit().beep();
            }
        }

        public boolean isEnabled() {
            return true;
        }
    }

    /**
     * Creates a new folder.
     */
    protected class NewFolderAction extends AbstractAction {
        protected NewFolderAction() {
            super(newFolderAccessibleName);
        }

        // Muchlike showInputDialog, but we give it options instead of selectionValues
        private Object showNewFolderDialog(final Component parentComponent, final Object message, final String title, final int messageType, final Icon icon, final Object[] options, final Object initialSelectionValue) {
            final JOptionPane pane = new JOptionPane(message, messageType, JOptionPane.OK_CANCEL_OPTION, icon, options, null);

            pane.setWantsInput(true);
            pane.setInitialSelectionValue(initialSelectionValue);

            final JDialog dialog = pane.createDialog(parentComponent, title);

            pane.selectInitialValue();
            dialog.setVisible(true);
            dialog.dispose();

            final Object value = pane.getValue();

            if (value == null || value.equals(cancelButtonText)) {
                return null;
            }
            return pane.getInputValue();
        }

        public void actionPerformed(final ActionEvent e) {
            final JFileChooser fc = getFileChooser();
            final File currentDirectory = fc.getCurrentDirectory();
            File newFolder = null;
            final String[] options = {createButtonText, cancelButtonText};
            final String filename = (String)showNewFolderDialog(fc, //parentComponent
                    newFolderDialogPrompt, // message
                    newFolderTitleText, // title
                    JOptionPane.PLAIN_MESSAGE, // messageType
                    null, // icon
                    options, // selectionValues
                    newFolderDefaultName); // initialSelectionValue

            if (filename != null) {
                try {
                    newFolder = fc.getFileSystemView().createFileObject(currentDirectory, filename);
                    if (newFolder.exists()) {
                        JOptionPane.showMessageDialog(fc, newFolderExistsErrorText, "", JOptionPane.ERROR_MESSAGE);
                        return;
                    }

                    newFolder.mkdirs();
                } catch(final Exception exc) {
                    JOptionPane.showMessageDialog(fc, newFolderErrorText, "", JOptionPane.ERROR_MESSAGE);
                    return;
                }

                openDirectory(newFolder);
            }
        }
    }

    /**
     * Responds to an Open, Save, or Choose request
     */
    protected class ApproveSelectionAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            fSubPanel.approveSelection(getFileChooser());
        }
    }

    /**
     * Responds to an OpenDirectory request
     */
    protected class OpenSelectionAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            final int index = fFileList.getSelectedRow();
            if (index >= 0) {
                final File selectedFile = (File)((AquaFileSystemModel)fFileList.getModel()).getElementAt(index);
                if (selectedFile != null) openDirectory(selectedFile);
            }
        }
    }

    /**
     * Responds to a cancel request.
     */
    protected class CancelSelectionAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            getFileChooser().cancelSelection();
        }

        public boolean isEnabled() {
            return getFileChooser().isEnabled();
        }
    }

    /**
     * Rescans the files in the current directory
     */
    protected class UpdateAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            final JFileChooser fc = getFileChooser();
            fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName()));
            fc.rescanCurrentDirectory();
        }
    }

    // *****************************************
    // ***** default AcceptAll file filter *****
    // *****************************************
    protected class AcceptAllFileFilter extends FileFilter {
        public AcceptAllFileFilter() {
        }

        public boolean accept(final File f) {
            return true;
        }

        public String getDescription() {
            return UIManager.getString("FileChooser.acceptAllFileFilterText");
        }
    }

    // Penultimate superclass is JLabel
    protected class MacFCTableCellRenderer extends DefaultTableCellRenderer {
        boolean fIsSelected = false;

        public MacFCTableCellRenderer(final Font f) {
            super();
            setFont(f);
            setIconTextGap(10);
        }

        public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {
            super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks
            fIsSelected = isSelected;
            return this;
        }

        public boolean isSelected() {
            return fIsSelected && isEnabled();
        }

        protected String layoutCL(final JLabel label, final FontMetrics fontMetrics, final String text, final Icon icon, final Rectangle viewR, final Rectangle iconR, final Rectangle textR) {
            return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());
        }

        protected void paintComponent(final Graphics g) {
            final String text = getText();
            Icon icon = getIcon();
            if (icon != null && !isEnabled()) {
                final Icon disabledIcon = getDisabledIcon();
                if (disabledIcon != null) icon = disabledIcon;
            }

            if ((icon == null) && (text == null)) { return; }

            // from ComponentUI update
            g.setColor(getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());

            // from BasicLabelUI paint
            final FontMetrics fm = g.getFontMetrics();
            Insets paintViewInsets = getInsets(null);
            paintViewInsets.left += 10;

            Rectangle paintViewR = new Rectangle(paintViewInsets.left, paintViewInsets.top, getWidth() - (paintViewInsets.left + paintViewInsets.right), getHeight() - (paintViewInsets.top + paintViewInsets.bottom));

            Rectangle paintIconR = new Rectangle();
            Rectangle paintTextR = new Rectangle();

            final String clippedText = layoutCL(this, fm, text, icon, paintViewR, paintIconR, paintTextR);

            if (icon != null) {
                icon.paintIcon(this, g, paintIconR.x + 5, paintIconR.y);
            }

            if (text != null) {
                final int textX = paintTextR.x;
                final int textY = paintTextR.y + fm.getAscent() + 1;
                if (isEnabled()) {
                    // Color background = fIsSelected ? getForeground() : getBackground();
                    final Color background = getBackground();

                    g.setColor(background);
                    g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);

                    g.setColor(getForeground());
                    SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
                } else {
                    final Color background = getBackground();
                    g.setColor(background);
                    g.fillRect(textX - 1, paintTextR.y, paintTextR.width + 2, fm.getAscent() + 2);

                    g.setColor(background.brighter());
                    SwingUtilities2.drawString(filechooser, g, clippedText, textX, textY);
                    g.setColor(background.darker());
                    SwingUtilities2.drawString(filechooser, g, clippedText, textX + 1, textY + 1);
                }
            }
        }

    }

    protected class FileRenderer extends MacFCTableCellRenderer {
        public FileRenderer(final Font f) {
            super(f);
        }

        public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {
            super.getTableCellRendererComponent(list, value, isSelected, false, index, col); // No focus border, thanks
            final File file = (File)value;
            final JFileChooser fc = getFileChooser();
            setText(fc.getName(file));
            setIcon(fc.getIcon(file));
            setEnabled(isSelectableInList(file));
            return this;
        }
    }

    protected class DateRenderer extends MacFCTableCellRenderer {
        public DateRenderer(final Font f) {
            super(f);
        }

        public Component getTableCellRendererComponent(final JTable list, final Object value, final boolean isSelected, final boolean cellHasFocus, final int index, final int col) {
            super.getTableCellRendererComponent(list, value, isSelected, false, index, col);
            final File file = (File)fFileList.getValueAt(index, 0);
            setEnabled(isSelectableInList(file));
            final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);
            final Date date = (Date)value;

            if (date != null) {
                setText(formatter.format(date));
            } else {
                setText("");
            }

            return this;
        }
    }

    public Dimension getPreferredSize(final JComponent c) {
        return PREF_SIZE;
    }

    public Dimension getMinimumSize(final JComponent c) {
        return MIN_SIZE;
    }

    public Dimension getMaximumSize(final JComponent c) {
        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    protected ListCellRenderer createDirectoryComboBoxRenderer(final JFileChooser fc) {
        return new AquaComboBoxRendererInternal(directoryComboBox) {
            public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                final File directory = (File)value;
                if (directory == null) {
                    setText("");
                    return this;
                }

                final JFileChooser chooser = getFileChooser();
                setText(chooser.getName(directory));
                setIcon(chooser.getIcon(directory));
                return this;
            }
        };
    }

    //
    // DataModel for DirectoryComboxbox
    //
    protected DirectoryComboBoxModel createDirectoryComboBoxModel(final JFileChooser fc) {
        return new DirectoryComboBoxModel();
    }

    /**
     * Data model for a type-face selection combo-box.
     */
    protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel {
        Vector<File> fDirectories = new Vector();
        int topIndex = -1;
        int fPathCount = 0;

        File fSelectedDirectory = null;

        public DirectoryComboBoxModel() {
            super();
            // Add the current directory to the model, and make it the
            // selectedDirectory
            addItem(getFileChooser().getCurrentDirectory());
        }

        /**
         * Removes the selected directory, and clears out the
         * path file entries leading up to that directory.
         */
        private void removeSelectedDirectory() {
            fDirectories.removeAllElements();
            fPathCount = 0;
            fSelectedDirectory = null;
            // dump();
        }

        /**
         * Adds the directory to the model and sets it to be selected,
         * additionally clears out the previous selected directory and
         * the paths leading up to it, if any.
         */
        void addItem(final File directory) {
            if (directory == null) { return; }
            if (fSelectedDirectory != null) {
                removeSelectedDirectory();
            }

            // create File instances of each directory leading up to the top
            File f = directory.getAbsoluteFile();
            final Vector<File> path = new Vector(10);
            while (f.getParent() != null) {
                path.addElement(f);
                f = getFileChooser().getFileSystemView().createFileObject(f.getParent());
            };

            // Add root file (the desktop) to the model
            final File[] roots = getFileChooser().getFileSystemView().getRoots();
            for (final File element : roots) {
                path.addElement(element);
            }
            fPathCount = path.size();

            // insert all the path fDirectories leading up to the
            // selected directory in reverse order (current directory at top)
            for (int i = 0; i < path.size(); i++) {
                fDirectories.addElement(path.elementAt(i));
            }

            setSelectedItem(fDirectories.elementAt(0));

            // dump();
        }

        public void setSelectedItem(final Object selectedDirectory) {
            this.fSelectedDirectory = (File)selectedDirectory;
            fireContentsChanged(this, -1, -1);
        }

        public Object getSelectedItem() {
            return fSelectedDirectory;
        }

        public int getSize() {
            return fDirectories.size();
        }

        public Object getElementAt(final int index) {
            return fDirectories.elementAt(index);
        }
    }

    //
    // Renderer for Types ComboBox
    //
    protected ListCellRenderer createFilterComboBoxRenderer() {
        return new AquaComboBoxRendererInternal(filterComboBox) {
            public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                final FileFilter filter = (FileFilter)value;
                if (filter != null) setText(filter.getDescription());
                return this;
            }
        };
    }

    //
    // DataModel for Types Comboxbox
    //
    protected FilterComboBoxModel createFilterComboBoxModel() {
        return new FilterComboBoxModel();
    }

    /**
     * Data model for a type-face selection combo-box.
     */
    protected class FilterComboBoxModel extends DefaultListModel implements ComboBoxModel, PropertyChangeListener {
        int selectedIndex = -1;

        protected FilterComboBoxModel() {
            super();
            final FileFilter filters[] = getFileChooser().getChoosableFileFilters();
            for (int i = 0; i < filters.length; i++) {
                this.add(i, filters[i]);
            }
        }

        public void propertyChange(final PropertyChangeEvent e) {
            final String prop = e.getPropertyName();
            if (prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
                this.clear();
                final FileFilter filters[] = (FileFilter[])e.getNewValue();

                for (int i = 0; i < filters.length; i++) {
                    this.add(i, filters[i]);
                }

                fireContentsChanged(this, -1, -1);
            } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
                final FileFilter currentFilter = (FileFilter)e.getNewValue();
                FileFilter filters[] = getFileChooser().getChoosableFileFilters();

                boolean found = false;
                if (currentFilter != null) {
                    for (final FileFilter element : filters) {
                        if (element == currentFilter) {
                            found = true;
                        }
                    }
                    if (found == false) {
                        getFileChooser().addChoosableFileFilter(currentFilter);
                    }
                }

                filters = getFileChooser().getChoosableFileFilters();
                setSelectedItem(e.getNewValue());
            }
        }

        public void setSelectedItem(final Object filter) {
            if (filter != null) {
                selectedIndex = this.indexOf(filter);
                fireContentsChanged(this, -1, -1);
            }
        }

        public Object getSelectedItem() {
            final Object returnValue = null;

            if (this.size() > 0) {
                if ((selectedIndex != -1) && (selectedIndex < size())) { return this.get(selectedIndex); }
            }

            return returnValue;
        }
    }

    /**
     * Acts when FilterComboBox has changed the selected item.
     */
    protected class FilterComboBoxAction extends AbstractAction {
        protected FilterComboBoxAction() {
            super("FilterComboBoxAction");
        }

        public void actionPerformed(final ActionEvent e) {
            getFileChooser().setFileFilter((FileFilter)filterComboBox.getSelectedItem());
        }
    }

    /**
     * Acts when DirectoryComboBox has changed the selected item.
     */
    protected class DirectoryComboBoxAction extends AbstractAction {
        protected DirectoryComboBoxAction() {
            super("DirectoryComboBoxAction");
        }

        public void actionPerformed(final ActionEvent e) {
            getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem());
        }
    }

    // Sorting Table operations
    class JSortingTableHeader extends JTableHeader {
        public JSortingTableHeader(final TableColumnModel cm) {
            super(cm);
            setReorderingAllowed(true); // This causes mousePress to call setDraggedColumn
        }

        // One sort state for each column.  Both are ascending by default
        final boolean fSortAscending[] = {true, true};

        // Instead of dragging, it selects which one to sort by
        public void setDraggedColumn(final TableColumn aColumn) {
            if (aColumn != null) {
                final int colIndex = aColumn.getModelIndex();
                if (colIndex != fSortColumn) {
                    filechooser.firePropertyChange(AquaFileSystemModel.SORT_BY_CHANGED, fSortColumn, colIndex);
                    fSortColumn = colIndex;
                } else {
                    fSortAscending[colIndex] = !fSortAscending[colIndex];
                    filechooser.firePropertyChange(AquaFileSystemModel.SORT_ASCENDING_CHANGED, !fSortAscending[colIndex], fSortAscending[colIndex]);
                }
                // Need to repaint the highlighted column.
                repaint();
            }
        }

        // This stops mouseDrags from moving the column
        public TableColumn getDraggedColumn() {
            return null;
        }

        protected TableCellRenderer createDefaultRenderer() {
            final DefaultTableCellRenderer label = new AquaTableCellRenderer();
            label.setHorizontalAlignment(SwingConstants.LEFT);
            return label;
        }

        class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {
            public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
                if (localTable != null) {
                    final JTableHeader header = localTable.getTableHeader();
                    if (header != null) {
                        setForeground(header.getForeground());
                        setBackground(header.getBackground());
                        setFont(UIManager.getFont("TableHeader.font"));
                    }
                }

                setText((value == null) ? "" : value.toString());

                // Modify the table "border" to draw smaller, and with the titles in the right position
                // and sort indicators, just like an NSSave/Open panel.
                final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();
                cellBorder.setSelected(column == fSortColumn);
                final int horizontalShift = (column == 0 ? 35 : 10);
                cellBorder.setHorizontalShift(horizontalShift);

                if (column == fSortColumn) {
                    cellBorder.setSortOrder(fSortAscending[column] ? AquaTableHeaderBorder.SORT_ASCENDING : AquaTableHeaderBorder.SORT_DECENDING);
                } else {
                    cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);
                }
                setBorder(cellBorder);
                return this;
            }
        }
    }

    public void installComponents(final JFileChooser fc) {
        JPanel tPanel; // temp panel
        // set to a Y BoxLayout. The chooser will be laid out top to bottom.
        fc.setLayout(new BoxLayout(fc, BoxLayout.Y_AXIS));
        fc.add(Box.createRigidArea(vstrut10));

        // construct the top panel

        final JPanel topPanel = new JPanel();
        topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
        fc.add(topPanel);
        fc.add(Box.createRigidArea(vstrut10));

        // Add the textfield pane

        fTextfieldPanel = new JPanel();
        fTextfieldPanel.setLayout(new BorderLayout());
        // setBottomPanelForMode will make this visible if we need it
        fTextfieldPanel.setVisible(false);
        topPanel.add(fTextfieldPanel);

        tPanel = new JPanel();
        tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.Y_AXIS));
        final JPanel labelArea = new JPanel();
        labelArea.setLayout(new FlowLayout(FlowLayout.CENTER));
        fTextFieldLabel = new JLabel(fileNameLabelText);
        labelArea.add(fTextFieldLabel);

        // text field
        filenameTextField = new JTextField();
        fTextFieldLabel.setLabelFor(filenameTextField);
        filenameTextField.addActionListener(getAction(kOpen));
        filenameTextField.addFocusListener(new SaveTextFocusListener());
        final Dimension minSize = filenameTextField.getMinimumSize();
        Dimension d = new Dimension(250, (int)minSize.getHeight());
        filenameTextField.setPreferredSize(d);
        filenameTextField.setMaximumSize(d);
        labelArea.add(filenameTextField);
        final File f = fc.getSelectedFile();
        if (f != null) {
            setFileName(fc.getName(f));
        } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) {
            setFileName(newFileDefaultName);
        }

        tPanel.add(labelArea);
        // separator line
        final JSeparator sep = new JSeparator(){
            public Dimension getPreferredSize() {
                return new Dimension(((JComponent)getParent()).getWidth(), 3);
            }
        };
        tPanel.add(Box.createRigidArea(new Dimension(1, 8)));
        tPanel.add(sep);
        tPanel.add(Box.createRigidArea(new Dimension(1, 7)));
        fTextfieldPanel.add(tPanel, BorderLayout.CENTER);

        // DirectoryComboBox, left-justified, 200x20 not including drop shadow
        directoryComboBox = new JComboBox();
        directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight");
        fDirectoryComboBoxModel = createDirectoryComboBoxModel(fc);
        directoryComboBox.setModel(fDirectoryComboBoxModel);
        directoryComboBox.addActionListener(directoryComboBoxAction);
        directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
        directoryComboBox.setToolTipText(directoryComboBoxToolTipText);
        d = new Dimension(250, (int)directoryComboBox.getMinimumSize().getHeight());
        directoryComboBox.setPreferredSize(d);
        directoryComboBox.setMaximumSize(d);
        topPanel.add(directoryComboBox);

        // ************************************** //
        // ** Add the directory/Accessory pane ** //
        // ************************************** //
        final JPanel centerPanel = new JPanel(new BorderLayout());
        fc.add(centerPanel);

        // Accessory pane (equiv to Preview pane in NavServices)
        final JComponent accessory = fc.getAccessory();
        if (accessory != null) {
            getAccessoryPanel().add(accessory);
        }
        centerPanel.add(getAccessoryPanel(), BorderLayout.LINE_START);

        // Directory list(table), right-justified, resizable
        final JPanel p = createList(fc);
        p.setMinimumSize(LIST_MIN_SIZE);
        centerPanel.add(p, BorderLayout.CENTER);

        // ********************************** //
        // **** Construct the bottom panel ** //
        // ********************************** //
        fBottomPanel = new JPanel();
        fBottomPanel.setLayout(new BoxLayout(fBottomPanel, BoxLayout.Y_AXIS));
        fc.add(fBottomPanel);

        // Filter label and combobox.
        // I know it's unMaclike, but the filter goes on Directory_only too.
        tPanel = new JPanel();
        tPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
        tPanel.setBorder(AquaGroupBorder.getTitlelessBorder());
        final JLabel formatLabel = new JLabel(filesOfTypeLabelText);
        tPanel.add(formatLabel);

        // Combobox
        filterComboBoxModel = createFilterComboBoxModel();
        fc.addPropertyChangeListener(filterComboBoxModel);
        filterComboBox = new JComboBox(filterComboBoxModel);
        formatLabel.setLabelFor(filterComboBox);
        filterComboBox.setRenderer(createFilterComboBoxRenderer());
        d = new Dimension(220, (int)filterComboBox.getMinimumSize().getHeight());
        filterComboBox.setPreferredSize(d);
        filterComboBox.setMaximumSize(d);
        filterComboBox.addActionListener(filterComboBoxAction);
        filterComboBox.setOpaque(false);
        tPanel.add(filterComboBox);

        fBottomPanel.add(tPanel);

        // fDirectoryPanel: New, Open, Cancel, Approve buttons, right-justified, 82x22
        // (sometimes the NewFolder and OpenFolder buttons are invisible)
        fDirectoryPanel = new JPanel();
        fDirectoryPanel.setLayout(new BoxLayout(fDirectoryPanel, BoxLayout.PAGE_AXIS));
        JPanel directoryPanel = new JPanel(new BorderLayout());
        JPanel newFolderButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
        newFolderButtonPanel.add(Box.createHorizontalStrut(20));
        fNewFolderButton = createNewFolderButton(); // Because we hide it depending on style
        newFolderButtonPanel.add(fNewFolderButton);
        directoryPanel.add(newFolderButtonPanel, BorderLayout.LINE_START);
        JPanel approveCancelButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));
        fOpenButton = createButton(kOpenDirectory, openButtonText);
        approveCancelButtonPanel.add(fOpenButton);
        approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
        fCancelButton = createButton(kCancel, null);
        approveCancelButtonPanel.add(fCancelButton);
        approveCancelButtonPanel.add(Box.createHorizontalStrut(8));
        // The ApproveSelection button
        fApproveButton = new JButton();
        fApproveButton.addActionListener(fApproveSelectionAction);
        approveCancelButtonPanel.add(fApproveButton);
        approveCancelButtonPanel.add(Box.createHorizontalStrut(20));
        directoryPanel.add(approveCancelButtonPanel, BorderLayout.LINE_END);
        fDirectoryPanel.add(Box.createVerticalStrut(5));
        fDirectoryPanel.add(directoryPanel);
        fDirectoryPanel.add(Box.createVerticalStrut(12));
        fDirectoryPanelSpacer = Box.createRigidArea(hstrut10);

        if (fc.getControlButtonsAreShown()) {
            fBottomPanel.add(fDirectoryPanelSpacer);
            fBottomPanel.add(fDirectoryPanel);
        }

        setBottomPanelForMode(fc); // updates ApproveButtonText etc

        // don't create til after the FCSubpanel and buttons are made
        filenameTextField.getDocument().addDocumentListener(new SaveTextDocumentListener());
    }

    void setDefaultButtonForMode(final JFileChooser fc) {
        final JButton defaultButton = fSubPanel.getDefaultButton(fc);
        final JRootPane root = defaultButton.getRootPane();
        if (root != null) {
            root.setDefaultButton(defaultButton);
        }
    }

    // Macs start with their focus in text areas if they have them,
    // lists otherwise (the other plafs start with the focus on approveButton)
    void setFocusForMode(final JFileChooser fc) {
        final JComponent focusComponent = fSubPanel.getFocusComponent(fc);
        if (focusComponent != null) {
            focusComponent.requestFocus();
        }
    }

    // Enable/disable buttons as needed for the current selection/focus state
    void updateButtonState(final JFileChooser fc) {
        fSubPanel.updateButtonState(fc, getFirstSelectedItem());
        updateApproveButton(fc);
    }

    void updateApproveButton(final JFileChooser chooser) {
        fApproveButton.setText(getApproveButtonText(chooser));
        fApproveButton.setToolTipText(getApproveButtonToolTipText(chooser));
        fApproveButton.setMnemonic(getApproveButtonMnemonic(chooser));
        fCancelButton.setToolTipText(getCancelButtonToolTipText(chooser));
    }

    // Lazy-init the subpanels
    synchronized FCSubpanel getSaveFilePanel() {
        if (fSaveFilePanel == null) fSaveFilePanel = new SaveFilePanel();
        return fSaveFilePanel;
    }

    synchronized FCSubpanel getOpenFilePanel() {
        if (fOpenFilePanel == null) fOpenFilePanel = new OpenFilePanel();
        return fOpenFilePanel;
    }

    synchronized FCSubpanel getOpenDirOrAnyPanel() {
        if (fOpenDirOrAnyPanel == null) fOpenDirOrAnyPanel = new OpenDirOrAnyPanel();
        return fOpenDirOrAnyPanel;
    }

    synchronized FCSubpanel getCustomFilePanel() {
        if (fCustomFilePanel == null) fCustomFilePanel = new CustomFilePanel();
        return fCustomFilePanel;
    }

    synchronized FCSubpanel getCustomDirOrAnyPanel() {
        if (fCustomDirOrAnyPanel == null) fCustomDirOrAnyPanel = new CustomDirOrAnyPanel();
        return fCustomDirOrAnyPanel;
    }

    void setBottomPanelForMode(final JFileChooser fc) {
        if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) fSubPanel = getSaveFilePanel();
        else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) {
            if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getOpenFilePanel();
            else fSubPanel = getOpenDirOrAnyPanel();
        } else if (fc.getDialogType() == JFileChooser.CUSTOM_DIALOG) {
            if (fc.getFileSelectionMode() == JFileChooser.FILES_ONLY) fSubPanel = getCustomFilePanel();
            else fSubPanel = getCustomDirOrAnyPanel();
        }

        fSubPanel.installPanel(fc, true);
        updateApproveButton(fc);
        updateButtonState(fc);
        setDefaultButtonForMode(fc);
        setFocusForMode(fc);
        fc.invalidate();
    }

    // fTextfieldPanel and fDirectoryPanel both have NewFolder buttons; only one should be visible at a time
    JButton createNewFolderButton() {
        final JButton b = new JButton(newFolderButtonText);
        b.setToolTipText(newFolderToolTipText);
        b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
        b.setHorizontalTextPosition(SwingConstants.LEFT);
        b.setAlignmentX(Component.LEFT_ALIGNMENT);
        b.setAlignmentY(Component.CENTER_ALIGNMENT);
        b.addActionListener(getAction(kNewFolder));
        return b;
    }

    JButton createButton(final int which, String label) {
        if (label == null) label = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[0]);
        final int mnemonic = UIManager.getInt(sDataPrefix + sButtonKinds[which] + sButtonData[1]);
        final String tipText = UIManager.getString(sDataPrefix + sButtonKinds[which] + sButtonData[2]);
        final JButton b = new JButton(label);
        b.setMnemonic(mnemonic);
        b.setToolTipText(tipText);
        b.addActionListener(getAction(which));
        return b;
    }

    AbstractAction getAction(final int which) {
        return fButtonActions[which];
    }

    public void uninstallComponents(final JFileChooser fc) { //$ Metal (on which this is based) doesn't uninstall its components.
    }

    // Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field
    // with that file's display name.
    protected class FileListMouseListener extends MouseAdapter {
        public void mouseClicked(final MouseEvent e) {
            final Point p = e.getPoint();
            final int row = fFileList.rowAtPoint(p);
            final int column = fFileList.columnAtPoint(p);

            // The autoscroller can generate drag events outside the Table's range.
            if ((column == -1) || (row == -1)) { return; }

            final File clickedFile = (File)(fFileList.getValueAt(row, 0));

            // rdar://problem/3734130 -- don't populate the text field if this file isn't selectable in this mode.
            if (isSelectableForMode(getFileChooser(), clickedFile)) {
                // [3188387] Populate the file name field with the selected file name
                // [3484163] It should also use the display name, not the actual name.
                setFileName(fileView.getName(clickedFile));
            }
        }
    }

    protected JPanel createList(final JFileChooser fc) {
        // The first part is similar to MetalFileChooserUI.createList - same kind of listeners
        final JPanel p = new JPanel(new BorderLayout());
        fFileList = new JTableExtension();
        fFileList.setToolTipText(null); // Workaround for 2487689
        fFileList.addMouseListener(new FileListMouseListener());
        model = new AquaFileSystemModel(fc, fFileList, fColumnNames);
        final MacListSelectionModel listSelectionModel = new MacListSelectionModel(model);

        if (getFileChooser().isMultiSelectionEnabled()) {
            listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        } else {
            listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        }

        fFileList.setModel(model);
        fFileList.setSelectionModel(listSelectionModel);
        fFileList.getSelectionModel().addListSelectionListener(createListSelectionListener(fc));

        // Now we're different, because we're a table, not a list
        fc.addPropertyChangeListener(model);
        fFileList.addFocusListener(new SaveTextFocusListener());
        final JTableHeader th = new JSortingTableHeader(fFileList.getColumnModel());
        fFileList.setTableHeader(th);
        fFileList.setRowMargin(0);
        fFileList.setIntercellSpacing(new Dimension(0, 1));
        fFileList.setShowVerticalLines(false);
        fFileList.setShowHorizontalLines(false);
        final Font f = fFileList.getFont(); //ThemeFont.GetThemeFont(AppearanceConstants.kThemeViewsFont);
        //fc.setFont(f);
        //fFileList.setFont(f);
        fFileList.setDefaultRenderer(File.class, new FileRenderer(f));
        fFileList.setDefaultRenderer(Date.class, new DateRenderer(f));
        final FontMetrics fm = fFileList.getFontMetrics(f);

        // Row height isn't based on the renderers.  It defaults to 16 so we have to set it
        fFileList.setRowHeight(Math.max(fm.getHeight(), fileIcon.getIconHeight() + 2));

        // Add a binding for the file list that triggers return and escape
        fFileList.registerKeyboardAction(new CancelSelectionAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
        // Add a binding for the file list that triggers the default button (see DefaultButtonAction)
        fFileList.registerKeyboardAction(new DefaultButtonAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
        fFileList.setDropTarget(dragAndDropTarget);

        final JScrollPane scrollpane = new JScrollPane(fFileList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollpane.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
        scrollpane.setCorner(ScrollPaneConstants.UPPER_TRAILING_CORNER, new ScrollPaneCornerPanel());
        p.add(scrollpane, BorderLayout.CENTER);
        return p;
    }

    protected class ScrollPaneCornerPanel extends JPanel {
        final Border border = UIManager.getBorder("TableHeader.cellBorder");

        protected void paintComponent(final Graphics g) {
            border.paintBorder(this, g, 0, 0, getWidth() + 1, getHeight());
        }
    }

    JComboBox directoryComboBox;
    DirectoryComboBoxModel fDirectoryComboBoxModel;
    private final Action directoryComboBoxAction = new DirectoryComboBoxAction();

    JTextField filenameTextField;

    JTableExtension fFileList;

    private FilterComboBoxModel filterComboBoxModel;
    JComboBox filterComboBox;
    private final Action filterComboBoxAction = new FilterComboBoxAction();

    private static final Dimension hstrut10 = new Dimension(10, 1);
    private static final Dimension vstrut10 = new Dimension(1, 10);

    private static final int PREF_WIDTH = 550;
    private static final int PREF_HEIGHT = 400;
    private static final Dimension PREF_SIZE = new Dimension(PREF_WIDTH, PREF_HEIGHT);

    private static final int MIN_WIDTH = 400;
    private static final int MIN_HEIGHT = 250;
    private static final Dimension MIN_SIZE = new Dimension(MIN_WIDTH, MIN_HEIGHT);

    private static final int LIST_MIN_WIDTH = 400;
    private static final int LIST_MIN_HEIGHT = 100;
    private static final Dimension LIST_MIN_SIZE = new Dimension(LIST_MIN_WIDTH, LIST_MIN_HEIGHT);

    static String fileNameLabelText = null;
    JLabel fTextFieldLabel = null;

    private static String filesOfTypeLabelText = null;

    private static String newFolderToolTipText = null;
    static String newFolderAccessibleName = null;

    private static final String[] fColumnNames = new String[2];

    JPanel fTextfieldPanel; // Filename textfield for Save or Custom
    private JPanel fDirectoryPanel; // NewFolder/OpenFolder/Cancel/Approve buttons
    private Component fDirectoryPanelSpacer;
    private JPanel fBottomPanel; // The panel that holds fDirectoryPanel and filterComboBox

    private FCSubpanel fSaveFilePanel = null;
    private FCSubpanel fOpenFilePanel = null;
    private FCSubpanel fOpenDirOrAnyPanel = null;
    private FCSubpanel fCustomFilePanel = null;
    private FCSubpanel fCustomDirOrAnyPanel = null;

    FCSubpanel fSubPanel = null; // Current FCSubpanel

    JButton fApproveButton; // mode-specific behavior is managed by FCSubpanel.approveSelection
    JButton fOpenButton; // for Directories
    JButton fNewFolderButton; // for fDirectoryPanel

    // ToolTip text varies with type of dialog
    private JButton fCancelButton;

    private final ApproveSelectionAction fApproveSelectionAction = new ApproveSelectionAction();
    protected int fSortColumn = 0;
    protected int fPackageIsTraversable = -1;
    protected int fApplicationIsTraversable = -1;

    protected static final int sGlobalPackageIsTraversable;
    protected static final int sGlobalApplicationIsTraversable;

    protected static final String PACKAGE_TRAVERSABLE_PROPERTY = "JFileChooser.packageIsTraversable";
    protected static final String APPLICATION_TRAVERSABLE_PROPERTY = "JFileChooser.appBundleIsTraversable";
    protected static final String[] sTraversableProperties = {"always", // Bundle is always traversable
            "never", // Bundle is never traversable
            "conditional"}; // Bundle is traversable on command click
    protected static final int kOpenAlways = 0, kOpenNever = 1, kOpenConditional = 2;

    AbstractAction[] fButtonActions = {fApproveSelectionAction, fApproveSelectionAction, new CancelSelectionAction(), new OpenSelectionAction(), null, new NewFolderAction()};

    static int parseTraversableProperty(final String s) {
        if (s == null) return -1;
        for (int i = 0; i < sTraversableProperties.length; i++) {
            if (s.equals(sTraversableProperties[i])) return i;
        }
        return -1;
    }

    static {
        Object o = UIManager.get(PACKAGE_TRAVERSABLE_PROPERTY);
        if (o != null && o instanceof String) sGlobalPackageIsTraversable = parseTraversableProperty((String)o);
        else sGlobalPackageIsTraversable = kOpenConditional;

        o = UIManager.get(APPLICATION_TRAVERSABLE_PROPERTY);
        if (o != null && o instanceof String) sGlobalApplicationIsTraversable = parseTraversableProperty((String)o);
        else sGlobalApplicationIsTraversable = kOpenConditional;
    }
    static final String sDataPrefix = "FileChooser.";
    static final String[] sButtonKinds = {"openButton", "saveButton", "cancelButton", "openDirectoryButton", "helpButton", "newFolderButton"};
    static final String[] sButtonData = {"Text", "Mnemonic", "ToolTipText"};
    static final int kOpen = 0, kSave = 1, kCancel = 2, kOpenDirectory = 3, kHelp = 4, kNewFolder = 5;

    /*-------

     Possible states: Save, {Open, Custom}x{Files, File and Directory, Directory}
     --------- */

    // This class returns the values for the Custom type, to avoid duplicating code in the two Custom subclasses
    abstract class FCSubpanel {
        // Install the appropriate panels for this mode
        abstract void installPanel(JFileChooser fc, boolean controlButtonsAreShown);

        abstract void updateButtonState(JFileChooser fc, File f);

        // Can this item be selected?
        // if not, it's disabled in the list
        boolean isSelectableInList(final JFileChooser fc, final File f) {
            if (f == null) return false;
            if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) return fc.isTraversable(f);
            return fc.accept(f);
        }

        void approveSelection(final JFileChooser fc) {
            fc.approveSelection();
        }

        JButton getDefaultButton(final JFileChooser fc) {
            return fApproveButton;
        }

        // Default to the textfield, panels without one should subclass
        JComponent getFocusComponent(final JFileChooser fc) {
            return filenameTextField;
        }

        String getApproveButtonText(final JFileChooser fc) {
            // Fallback to "choose"
            return this.getApproveButtonText(fc, chooseButtonText);
        }

        // Try to get the custom text.  If none, use the fallback
        String getApproveButtonText(final JFileChooser fc, final String fallbackText) {
            final String buttonText = fc.getApproveButtonText();
            if (buttonText != null) {
                buttonText.trim();
                if (!buttonText.equals("")) return buttonText;
            }
            return fallbackText;
        }

        int getApproveButtonMnemonic(final JFileChooser fc) {
            // Don't use a default
            return fc.getApproveButtonMnemonic();
        }

        // No fallback
        String getApproveButtonToolTipText(final JFileChooser fc) {
            return getApproveButtonToolTipText(fc, null);
        }

        String getApproveButtonToolTipText(final JFileChooser fc, final String fallbackText) {
            final String tooltipText = fc.getApproveButtonToolTipText();
            if (tooltipText != null) {
                tooltipText.trim();
                if (!tooltipText.equals("")) return tooltipText;
            }
            return fallbackText;
        }

        String getCancelButtonToolTipText(final JFileChooser fc) {
            return cancelChooseButtonToolTipText;
        }
    }

    // Custom FILES_ONLY dialog
    /*
     NavServices Save appearance with Open behavior
     Approve button label = Open when list has focus and a directory is selected, Custom otherwise
     No OpenDirectory button - Approve button is overloaded
     Default button / double click = Approve
     Has text field
     List - everything is enabled
     */
    class CustomFilePanel extends FCSubpanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            fTextfieldPanel.setVisible(true); // do we really want one in multi-select?  It's confusing
            fOpenButton.setVisible(false);
            fNewFolderButton.setVisible(true);
        }

        // If the list has focus, the mode depends on the selection
        // - directory = open, file = approve
        // If something else has focus and we have text, it's approve
        // otherwise, it depends on selection again.
        boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
            final boolean selectionIsDirectory = (f != null && fc.isTraversable(f));
            if (fFileList.hasFocus()) return selectionIsDirectory;
            else if (textfieldIsValid()) return false;
            return selectionIsDirectory;
        }

        // The approve button is overloaded to mean OpenDirectory or Save
        void approveSelection(final JFileChooser fc) {
            File f = getFirstSelectedItem();
            if (inOpenDirectoryMode(fc, f)) {
                openDirectory(f);
            } else {
                f = makeFile(fc, getFileName());
                if (f != null) {
                    selectionInProgress = true;
                    getFileChooser().setSelectedFile(f);
                    selectionInProgress = false;
                }
                getFileChooser().approveSelection();
            }
        }

        // The approve button should be enabled
        // - if something in the list can be opened
        // - if the textfield has something in it
        void updateButtonState(final JFileChooser fc, final File f) {
            boolean enabled = true;
            if (!inOpenDirectoryMode(fc, f)) {
                enabled = (f != null) || textfieldIsValid();
            }
            getApproveButton(fc).setEnabled(enabled);

            // The OpenDirectory button should be disabled if there's no directory selected
            fOpenButton.setEnabled(f != null && fc.isTraversable(f));

            // Update the default button, since we may have disabled the current default.
            setDefaultButtonForMode(fc);
        }

        // everything's enabled, because we don't know what they're doing with them
        boolean isSelectableInList(final JFileChooser fc, final File f) {
            if (f == null) return false;
            return fc.accept(f);
        }

        String getApproveButtonToolTipText(final JFileChooser fc) {
            // The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
            if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
            return super.getApproveButtonToolTipText(fc);
        }
    }

    // All Save dialogs
    /*
     NavServices Save
     Approve button label = Open when list has focus and a directory is selected, Save otherwise
     No OpenDirectory button - Approve button is overloaded
     Default button / double click = Approve
     Has text field
     Has NewFolder button (by text field)
     List - only traversables are enabled
     List is always SINGLE_SELECT
     */
    // Subclasses CustomFilePanel because they look alike and have some common behavior
    class SaveFilePanel extends CustomFilePanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            fTextfieldPanel.setVisible(true);
            fOpenButton.setVisible(false);
            fNewFolderButton.setVisible(true);
        }

        // only traversables are enabled, regardless of mode
        // because all you can do is select the next folder to open
        boolean isSelectableInList(final JFileChooser fc, final File f) {
            return fc.accept(f) && fc.isTraversable(f);
        }

        // The approve button means 'approve the file name in the text field.'
        void approveSelection(final JFileChooser fc) {
            final File f = makeFile(fc, getFileName());
            if (f != null) {
                selectionInProgress = true;
                getFileChooser().setSelectedFile(f);
                selectionInProgress = false;
                getFileChooser().approveSelection();
            }
        }

        // The approve button should be enabled if the textfield has something in it
        void updateButtonState(final JFileChooser fc, final File f) {
            final boolean enabled = textfieldIsValid();
            getApproveButton(fc).setEnabled(enabled);
        }

        String getApproveButtonText(final JFileChooser fc) {
            // Get the custom text, or fallback to "Save"
            return this.getApproveButtonText(fc, saveButtonText);
        }

        int getApproveButtonMnemonic(final JFileChooser fc) {
            return saveButtonMnemonic;
        }

        String getApproveButtonToolTipText(final JFileChooser fc) {
            // The approve Button should have openDirectoryButtonToolTipText when the selection is a folder...
            if (inOpenDirectoryMode(fc, getFirstSelectedItem())) return openDirectoryButtonToolTipText;
            return this.getApproveButtonToolTipText(fc, saveButtonToolTipText);
        }

        String getCancelButtonToolTipText(final JFileChooser fc) {
            return cancelSaveButtonToolTipText;
        }
    }

    // Open FILES_ONLY
    /*
     NSOpenPanel-style
     Approve button label = Open
     Default button / double click = Approve
     No text field
     No NewFolder button
     List - all items are enabled
     */
    class OpenFilePanel extends FCSubpanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            fTextfieldPanel.setVisible(false);
            fOpenButton.setVisible(false);
            fNewFolderButton.setVisible(false);
            setDefaultButtonForMode(fc);
        }

        boolean inOpenDirectoryMode(final JFileChooser fc, final File f) {
            return (f != null && fc.isTraversable(f));
        }

        // Default to the list
        JComponent getFocusComponent(final JFileChooser fc) {
            return fFileList;
        }

        void updateButtonState(final JFileChooser fc, final File f) {
            // Button is disabled if there's nothing selected
            final boolean enabled = (f != null) && !fc.isTraversable(f);
            getApproveButton(fc).setEnabled(enabled);
        }

        // all items are enabled
        boolean isSelectableInList(final JFileChooser fc, final File f) {
            return f != null && fc.accept(f);
        }

        String getApproveButtonText(final JFileChooser fc) {
            // Get the custom text, or fallback to "Open"
            return this.getApproveButtonText(fc, openButtonText);
        }

        int getApproveButtonMnemonic(final JFileChooser fc) {
            return openButtonMnemonic;
        }

        String getApproveButtonToolTipText(final JFileChooser fc) {
            return this.getApproveButtonToolTipText(fc, openButtonToolTipText);
        }

        String getCancelButtonToolTipText(final JFileChooser fc) {
            return cancelOpenButtonToolTipText;
        }
    }

    // used by open and custom panels for Directory only or files and directories
    abstract class DirOrAnyPanel extends FCSubpanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            fOpenButton.setVisible(false);
        }

        JButton getDefaultButton(final JFileChooser fc) {
            return getApproveButton(fc);
        }

        void updateButtonState(final JFileChooser fc, final File f) {
            // Button is disabled if there's nothing selected
            // Approve button is handled by the subclasses
            // getApproveButton(fc).setEnabled(f != null);

            // The OpenDirectory button should be disabled if there's no directory selected
            // - we only check the first item

            fOpenButton.setEnabled(false);
            setDefaultButtonForMode(fc);
        }
    }

    // Open FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
    /*
     NavServices Choose
     Approve button label = Choose/Custom
     Has OpenDirectory button
     Default button / double click = OpenDirectory
     No text field
     List - files are disabled in DIRECTORIES_ONLY
     */
    class OpenDirOrAnyPanel extends DirOrAnyPanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            super.installPanel(fc, controlButtonsAreShown);
            fTextfieldPanel.setVisible(false);
            fNewFolderButton.setVisible(false);
        }

        // Default to the list
        JComponent getFocusComponent(final JFileChooser fc) {
            return fFileList;
        }

        int getApproveButtonMnemonic(final JFileChooser fc) {
            return chooseButtonMnemonic;
        }

        String getApproveButtonToolTipText(final JFileChooser fc) {
            String fallbackText;
            if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) fallbackText = chooseFolderButtonToolTipText;
            else fallbackText = chooseItemButtonToolTipText;
            return this.getApproveButtonToolTipText(fc, fallbackText);
        }

        void updateButtonState(final JFileChooser fc, final File f) {
            // Button is disabled if there's nothing selected
            getApproveButton(fc).setEnabled(f != null);
            super.updateButtonState(fc, f);
        }
    }

    // Custom FILES_AND_DIRECTORIES or DIRECTORIES_ONLY
    /*
     No NavServices equivalent
     Approve button label = user defined or Choose
     Has OpenDirectory button
     Default button / double click = OpenDirectory
     Has text field
     Has NewFolder button (by text field)
     List - files are disabled in DIRECTORIES_ONLY
     */
    class CustomDirOrAnyPanel extends DirOrAnyPanel {
        void installPanel(final JFileChooser fc, final boolean controlButtonsAreShown) {
            super.installPanel(fc, controlButtonsAreShown);
            fTextfieldPanel.setVisible(true);
            fNewFolderButton.setVisible(true);
        }

        // If there's text, make a file and select it
        void approveSelection(final JFileChooser fc) {
            final File f = makeFile(fc, getFileName());
            if (f != null) {
                selectionInProgress = true;
                getFileChooser().setSelectedFile(f);
                selectionInProgress = false;
            }
            getFileChooser().approveSelection();
        }

        void updateButtonState(final JFileChooser fc, final File f) {
            // Button is disabled if there's nothing selected
            getApproveButton(fc).setEnabled(f != null || textfieldIsValid());
            super.updateButtonState(fc, f);
        }
    }

    // See FileRenderer - documents in Save dialogs draw disabled, so they shouldn't be selected
    class MacListSelectionModel extends DefaultListSelectionModel {
        AquaFileSystemModel fModel;

        MacListSelectionModel(final AquaFileSystemModel model) {
            fModel = model;
        }

        // Can the file be selected in this mode?
        // (files are visible even if they can't be selected)
        boolean isSelectableInListIndex(final int index) {
            final File file = (File)fModel.getValueAt(index, 0);
            return (file != null && isSelectableInList(file));
        }

        // Make sure everything in the selection interval is valid
        void verifySelectionInterval(int index0, int index1, boolean isSetSelection) {
            if (index0 > index1) {
                final int tmp = index1;
                index1 = index0;
                index0 = tmp;
            }
            int start = index0;
            int end;
            do {
                // Find the first selectable file in the range
                for (; start <= index1; start++) {
                    if (isSelectableInListIndex(start)) break;
                }
                end = -1;
                // Find the last selectable file in the range
                for (int i = start; i <= index1; i++) {
                    if (!isSelectableInListIndex(i)) {
                        break;
                    }
                    end = i;
                }
                // Select the range
                if (end >= 0) {
                    // If setting the selection, do "set" the first time to clear the old one
                    // after that do "add" to extend it
                    if (isSetSelection) {
                        super.setSelectionInterval(start, end);
                        isSetSelection = false;
                    } else {
                        super.addSelectionInterval(start, end);
                    }
                    start = end + 1;
                } else {
                    break;
                }
            } while (start <= index1);
        }

        public void setAnchorSelectionIndex(final int anchorIndex) {
            if (isSelectableInListIndex(anchorIndex)) super.setAnchorSelectionIndex(anchorIndex);
        }

        public void setLeadSelectionIndex(final int leadIndex) {
            if (isSelectableInListIndex(leadIndex)) super.setLeadSelectionIndex(leadIndex);
        }

        public void setSelectionInterval(final int index0, final int index1) {
            if (index0 == -1 || index1 == -1) { return; }

            if ((getSelectionMode() == SINGLE_SELECTION) || (index0 == index1)) {
                if (isSelectableInListIndex(index1)) super.setSelectionInterval(index1, index1);
            } else {
                verifySelectionInterval(index0, index1, true);
            }
        }

        public void addSelectionInterval(final int index0, final int index1) {
            if (index0 == -1 || index1 == -1) { return; }

            if (index0 == index1) {
                if (isSelectableInListIndex(index1)) super.addSelectionInterval(index1, index1);
                return;
            }

            if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
                setSelectionInterval(index0, index1);
                return;
            }

            verifySelectionInterval(index0, index1, false);
        }
    }

    // Convenience, to translate from the JList directory view to the Mac-style JTable
    //   & minimize diffs between this and BasicFileChooserUI
    class JTableExtension extends JTable {
        public void setSelectedIndex(final int index) {
            getSelectionModel().setSelectionInterval(index, index);
        }

        public void removeSelectedIndex(final int index) {
            getSelectionModel().removeSelectionInterval(index, index);
        }

        public void ensureIndexIsVisible(final int index) {
            final Rectangle cellBounds = getCellRect(index, 0, false);
            if (cellBounds != null) {
                scrollRectToVisible(cellBounds);
            }
        }

        public int locationToIndex(final Point location) {
            return rowAtPoint(location);
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java AquaFileChooserUI.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.