/* * Copyright (c) 2011, 2019, 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 static 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 { @SuppressWarnings("unchecked") final java.util.List 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.isEmpty()) { 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.isEmpty()); } // 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. @SuppressWarnings("serial") // Superclass is not serializable across versions 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. */ @SuppressWarnings("serial") // Superclass is not serializable across versions 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 */ @SuppressWarnings("serial") // Superclass is not serializable across versions protected class ApproveSelectionAction extends AbstractAction { public void actionPerformed(final ActionEvent e) { fSubPanel.approveSelection(getFileChooser()); } } /** * Responds to an OpenDirectory request */ @SuppressWarnings("serial") // Superclass is not serializable across versions 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. */ @SuppressWarnings("serial") // Superclass is not serializable across versions 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 */ @SuppressWarnings("serial") // Superclass is not serializable across versions 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 ***** // ***************************************** private static 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 @SuppressWarnings("serial") // Superclass is not serializable across versions 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); } } } } @SuppressWarnings("serial") // Superclass is not serializable across versions 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; } } @SuppressWarnings("serial") // Superclass is not serializable across versions 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; } } @Override public Dimension getPreferredSize(final JComponent c) { return new Dimension(PREF_WIDTH, PREF_HEIGHT); } @Override public Dimension getMinimumSize(final JComponent c) { return new Dimension(MIN_WIDTH, MIN_HEIGHT); } @Override public Dimension getMaximumSize(final JComponent c) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } @SuppressWarnings("serial") // anonymous class protected ListCellRenderer createDirectoryComboBoxRenderer(final JFileChooser fc) { return new AquaComboBoxRendererInternal(directoryComboBox) { public Component getListCellRendererComponent(final JList list, final File directory, final int index, final boolean isSelected, final boolean cellHasFocus) { super.getListCellRendererComponent(list, directory, index, isSelected, cellHasFocus); 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. */ @SuppressWarnings("serial") // Superclass is not serializable across versions protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel { Vector 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 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 File getElementAt(final int index) { return fDirectories.elementAt(index); } } // // Renderer for Types ComboBox // @SuppressWarnings("serial") // anonymous class protected ListCellRenderer createFilterComboBoxRenderer() { return new AquaComboBoxRendererInternal(filterComboBox) { public Component getListCellRendererComponent(final JList list, final FileFilter filter, final int index, final boolean isSelected, final boolean cellHasFocus) { super.getListCellRendererComponent(list, filter, index, isSelected, cellHasFocus); 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. */ @SuppressWarnings("serial") // Superclass is not serializable across versions protected class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener { protected FileFilter[] filters; Object oldFileFilter = getFileChooser().getFileFilter(); protected FilterComboBoxModel() { super(); filters = getFileChooser().getChoosableFileFilters(); } public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) { filters = (FileFilter[]) e.getNewValue(); fireContentsChanged(this, -1, -1); } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) { setSelectedItem(e.getNewValue()); } } public void setSelectedItem(Object filter) { if (filter != null && !isSelectedFileFilterInModel(filter)) { oldFileFilter = filter; getFileChooser().setFileFilter((FileFilter) filter); fireContentsChanged(this, -1, -1); } } private boolean isSelectedFileFilterInModel(Object filter) { return Objects.equals(filter, oldFileFilter); } public Object getSelectedItem() { // Ensure that the current filter is in the list. // NOTE: we shouldnt' have to do this, since JFileChooser adds // the filter to the choosable filters list when the filter // is set. Lets be paranoid just in case someone overrides // setFileFilter in JFileChooser. FileFilter currentFilter = getFileChooser().getFileFilter(); boolean found = false; if(currentFilter != null) { for (FileFilter filter : filters) { if (filter == currentFilter) { found = true; } } if(found == false) { getFileChooser().addChoosableFileFilter(currentFilter); } } return getFileChooser().getFileFilter(); } public int getSize() { if(filters != null) { return filters.length; } else { return 0; } } public FileFilter getElementAt(int index) { if(index > getSize() - 1) { // This shouldn't happen. Try to recover gracefully. return getFileChooser().getFileFilter(); } if(filters != null) { return filters[index]; } else { return null; } } } private boolean containsFileFilter(Object fileFilter) { return Objects.equals(fileFilter, getFileChooser().getFileFilter()); } /** * Acts when FilterComboBox has changed the selected item. */ @SuppressWarnings("serial") // Superclass is not serializable across versions protected class FilterComboBoxAction extends AbstractAction { protected FilterComboBoxAction() { super("FilterComboBoxAction"); } public void actionPerformed(final ActionEvent e) { Object selectedFilter = filterComboBox.getSelectedItem(); if (!containsFileFilter(selectedFilter)) { getFileChooser().setFileFilter((FileFilter) selectedFilter); } } } /** * Acts when DirectoryComboBox has changed the selected item. */ @SuppressWarnings("serial") // Superclass is not serializable across versions protected class DirectoryComboBoxAction extends AbstractAction { protected DirectoryComboBoxAction() { super("DirectoryComboBoxAction"); } public void actionPerformed(final ActionEvent e) { getFileChooser().setCurrentDirectory((File)directoryComboBox.getSelectedItem()); } } // Sorting Table operations @SuppressWarnings("serial") // Superclass is not serializable across versions 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; } @SuppressWarnings("serial") // Superclass is not serializable across versions 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 @SuppressWarnings("serial") // anonymous class 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; } @SuppressWarnings("serial") // Superclass is not serializable across versions 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 int MIN_WIDTH = 400; private static final int MIN_HEIGHT = 250; 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.isEmpty()) 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.isEmpty()) 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 @SuppressWarnings("serial") // Superclass is not serializable across versions 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 @SuppressWarnings("serial") // Superclass is not serializable across versions 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); } } }