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