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