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