1 /* 2 * Copyright (c) 1998, 2014, 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 javax.swing.plaf.basic; 27 28 import javax.swing.*; 29 import javax.swing.filechooser.*; 30 import javax.swing.filechooser.FileFilter; 31 import javax.swing.event.*; 32 import javax.swing.plaf.*; 33 import java.awt.*; 34 import java.awt.event.*; 35 import java.awt.datatransfer.*; 36 import java.beans.*; 37 import java.io.*; 38 import java.util.*; 39 import java.util.List; 40 import java.util.regex.*; 41 import sun.awt.shell.ShellFolder; 42 import sun.swing.*; 43 import sun.swing.SwingUtilities2; 44 45 /** 46 * Basic L&F implementation of a FileChooser. 47 * 48 * @author Jeff Dinkins 49 */ 50 public class BasicFileChooserUI extends FileChooserUI { 51 52 /* FileView icons */ 53 protected Icon directoryIcon = null; 54 protected Icon fileIcon = null; 55 protected Icon computerIcon = null; 56 protected Icon hardDriveIcon = null; 57 protected Icon floppyDriveIcon = null; 58 59 protected Icon newFolderIcon = null; 60 protected Icon upFolderIcon = null; 61 protected Icon homeFolderIcon = null; 62 protected Icon listViewIcon = null; 63 protected Icon detailsViewIcon = null; 64 protected Icon viewMenuIcon = null; 65 66 protected int saveButtonMnemonic = 0; 67 protected int openButtonMnemonic = 0; 68 protected int cancelButtonMnemonic = 0; 69 protected int updateButtonMnemonic = 0; 70 protected int helpButtonMnemonic = 0; 71 72 /** 73 * The mnemonic keycode used for the approve button when a directory 74 * is selected and the current selection mode is FILES_ONLY. 75 * 76 * @since 1.4 77 */ 78 protected int directoryOpenButtonMnemonic = 0; 79 80 protected String saveButtonText = null; 81 protected String openButtonText = null; 82 protected String cancelButtonText = null; 83 protected String updateButtonText = null; 84 protected String helpButtonText = null; 85 86 /** 87 * The label text displayed on the approve button when a directory 88 * is selected and the current selection mode is FILES_ONLY. 89 * 90 * @since 1.4 91 */ 92 protected String directoryOpenButtonText = null; 93 94 private String openDialogTitleText = null; 95 private String saveDialogTitleText = null; 96 97 protected String saveButtonToolTipText = null; 98 protected String openButtonToolTipText = null; 99 protected String cancelButtonToolTipText = null; 100 protected String updateButtonToolTipText = null; 101 protected String helpButtonToolTipText = null; 102 103 /** 104 * The tooltip text displayed on the approve button when a directory 105 * is selected and the current selection mode is FILES_ONLY. 106 * 107 * @since 1.4 108 */ 109 protected String directoryOpenButtonToolTipText = null; 110 111 // Some generic FileChooser functions 112 private Action approveSelectionAction = new ApproveSelectionAction(); 113 private Action cancelSelectionAction = new CancelSelectionAction(); 114 private Action updateAction = new UpdateAction(); 115 private Action newFolderAction; 116 private Action goHomeAction = new GoHomeAction(); 117 private Action changeToParentDirectoryAction = new ChangeToParentDirectoryAction(); 118 119 private String newFolderErrorSeparator = null; 120 private String newFolderErrorText = null; 121 private String newFolderParentDoesntExistTitleText = null; 122 private String newFolderParentDoesntExistText = null; 123 private String fileDescriptionText = null; 124 private String directoryDescriptionText = null; 125 126 private JFileChooser filechooser = null; 127 128 private boolean directorySelected = false; 129 private File directory = null; 130 131 private PropertyChangeListener propertyChangeListener = null; 132 private AcceptAllFileFilter acceptAllFileFilter = new AcceptAllFileFilter(); 133 private FileFilter actualFileFilter = null; 134 private GlobFilter globFilter = null; 135 private BasicDirectoryModel model = null; 136 private BasicFileView fileView = new BasicFileView(); 137 private boolean usesSingleFilePane; 138 private boolean readOnly; 139 140 // The accessoryPanel is a container to place the JFileChooser accessory component 141 private JPanel accessoryPanel = null; 142 private Handler handler; 143 144 /** 145 * Creates a {@code BasicFileChooserUI} implementation 146 * for the specified component. By default 147 * the {@code BasicLookAndFeel} class uses 148 * {@code createUI} methods of all basic UIs classes 149 * to instantiate UIs. 150 * 151 * @param c the {@code JFileChooser} which needs a UI 152 * @return the {@code BasicFileChooserUI} object 153 * 154 * @see UIDefaults#getUI(JComponent) 155 * @since 1.7 156 */ 157 public static ComponentUI createUI(JComponent c) { 158 return new BasicFileChooserUI((JFileChooser) c); 159 } 160 161 public BasicFileChooserUI(JFileChooser b) { 162 } 163 164 public void installUI(JComponent c) { 165 accessoryPanel = new JPanel(new BorderLayout()); 166 filechooser = (JFileChooser) c; 167 168 createModel(); 169 170 clearIconCache(); 171 172 installDefaults(filechooser); 173 installComponents(filechooser); 174 installListeners(filechooser); 175 filechooser.applyComponentOrientation(filechooser.getComponentOrientation()); 176 } 177 178 public void uninstallUI(JComponent c) { 179 uninstallListeners(filechooser); 180 uninstallComponents(filechooser); 181 uninstallDefaults(filechooser); 182 183 if(accessoryPanel != null) { 184 accessoryPanel.removeAll(); 185 } 186 187 accessoryPanel = null; 188 getFileChooser().removeAll(); 189 190 handler = null; 191 } 192 193 public void installComponents(JFileChooser fc) { 194 } 195 196 public void uninstallComponents(JFileChooser fc) { 197 } 198 199 protected void installListeners(JFileChooser fc) { 200 propertyChangeListener = createPropertyChangeListener(fc); 201 if(propertyChangeListener != null) { 202 fc.addPropertyChangeListener(propertyChangeListener); 203 } 204 fc.addPropertyChangeListener(getModel()); 205 206 InputMap inputMap = getInputMap(JComponent. 207 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 208 SwingUtilities.replaceUIInputMap(fc, JComponent. 209 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap); 210 ActionMap actionMap = getActionMap(); 211 SwingUtilities.replaceUIActionMap(fc, actionMap); 212 } 213 214 InputMap getInputMap(int condition) { 215 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 216 return (InputMap)DefaultLookup.get(getFileChooser(), this, 217 "FileChooser.ancestorInputMap"); 218 } 219 return null; 220 } 221 222 ActionMap getActionMap() { 223 return createActionMap(); 224 } 225 226 ActionMap createActionMap() { 227 ActionMap map = new ActionMapUIResource(); 228 229 Action refreshAction = new UIAction(FilePane.ACTION_REFRESH) { 230 public void actionPerformed(ActionEvent evt) { 231 getFileChooser().rescanCurrentDirectory(); 232 } 233 }; 234 235 map.put(FilePane.ACTION_APPROVE_SELECTION, getApproveSelectionAction()); 236 map.put(FilePane.ACTION_CANCEL, getCancelSelectionAction()); 237 map.put(FilePane.ACTION_REFRESH, refreshAction); 238 map.put(FilePane.ACTION_CHANGE_TO_PARENT_DIRECTORY, 239 getChangeToParentDirectoryAction()); 240 return map; 241 } 242 243 244 protected void uninstallListeners(JFileChooser fc) { 245 if(propertyChangeListener != null) { 246 fc.removePropertyChangeListener(propertyChangeListener); 247 } 248 fc.removePropertyChangeListener(getModel()); 249 SwingUtilities.replaceUIInputMap(fc, JComponent. 250 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 251 SwingUtilities.replaceUIActionMap(fc, null); 252 } 253 254 255 protected void installDefaults(JFileChooser fc) { 256 installIcons(fc); 257 installStrings(fc); 258 usesSingleFilePane = UIManager.getBoolean("FileChooser.usesSingleFilePane"); 259 readOnly = UIManager.getBoolean("FileChooser.readOnly"); 260 TransferHandler th = fc.getTransferHandler(); 261 if (th == null || th instanceof UIResource) { 262 fc.setTransferHandler(defaultTransferHandler); 263 } 264 LookAndFeel.installProperty(fc, "opaque", Boolean.FALSE); 265 } 266 267 protected void installIcons(JFileChooser fc) { 268 directoryIcon = UIManager.getIcon("FileView.directoryIcon"); 269 fileIcon = UIManager.getIcon("FileView.fileIcon"); 270 computerIcon = UIManager.getIcon("FileView.computerIcon"); 271 hardDriveIcon = UIManager.getIcon("FileView.hardDriveIcon"); 272 floppyDriveIcon = UIManager.getIcon("FileView.floppyDriveIcon"); 273 274 newFolderIcon = UIManager.getIcon("FileChooser.newFolderIcon"); 275 upFolderIcon = UIManager.getIcon("FileChooser.upFolderIcon"); 276 homeFolderIcon = UIManager.getIcon("FileChooser.homeFolderIcon"); 277 detailsViewIcon = UIManager.getIcon("FileChooser.detailsViewIcon"); 278 listViewIcon = UIManager.getIcon("FileChooser.listViewIcon"); 279 viewMenuIcon = UIManager.getIcon("FileChooser.viewMenuIcon"); 280 } 281 282 protected void installStrings(JFileChooser fc) { 283 284 Locale l = fc.getLocale(); 285 newFolderErrorText = UIManager.getString("FileChooser.newFolderErrorText",l); 286 newFolderErrorSeparator = UIManager.getString("FileChooser.newFolderErrorSeparator",l); 287 288 newFolderParentDoesntExistTitleText = UIManager.getString("FileChooser.newFolderParentDoesntExistTitleText", l); 289 newFolderParentDoesntExistText = UIManager.getString("FileChooser.newFolderParentDoesntExistText", l); 290 291 fileDescriptionText = UIManager.getString("FileChooser.fileDescriptionText",l); 292 directoryDescriptionText = UIManager.getString("FileChooser.directoryDescriptionText",l); 293 294 saveButtonText = UIManager.getString("FileChooser.saveButtonText",l); 295 openButtonText = UIManager.getString("FileChooser.openButtonText",l); 296 saveDialogTitleText = UIManager.getString("FileChooser.saveDialogTitleText",l); 297 openDialogTitleText = UIManager.getString("FileChooser.openDialogTitleText",l); 298 cancelButtonText = UIManager.getString("FileChooser.cancelButtonText",l); 299 updateButtonText = UIManager.getString("FileChooser.updateButtonText",l); 300 helpButtonText = UIManager.getString("FileChooser.helpButtonText",l); 301 directoryOpenButtonText = UIManager.getString("FileChooser.directoryOpenButtonText",l); 302 303 saveButtonMnemonic = getMnemonic("FileChooser.saveButtonMnemonic", l); 304 openButtonMnemonic = getMnemonic("FileChooser.openButtonMnemonic", l); 305 cancelButtonMnemonic = getMnemonic("FileChooser.cancelButtonMnemonic", l); 306 updateButtonMnemonic = getMnemonic("FileChooser.updateButtonMnemonic", l); 307 helpButtonMnemonic = getMnemonic("FileChooser.helpButtonMnemonic", l); 308 directoryOpenButtonMnemonic = getMnemonic("FileChooser.directoryOpenButtonMnemonic", l); 309 310 saveButtonToolTipText = UIManager.getString("FileChooser.saveButtonToolTipText",l); 311 openButtonToolTipText = UIManager.getString("FileChooser.openButtonToolTipText",l); 312 cancelButtonToolTipText = UIManager.getString("FileChooser.cancelButtonToolTipText",l); 313 updateButtonToolTipText = UIManager.getString("FileChooser.updateButtonToolTipText",l); 314 helpButtonToolTipText = UIManager.getString("FileChooser.helpButtonToolTipText",l); 315 directoryOpenButtonToolTipText = UIManager.getString("FileChooser.directoryOpenButtonToolTipText",l); 316 } 317 318 protected void uninstallDefaults(JFileChooser fc) { 319 uninstallIcons(fc); 320 uninstallStrings(fc); 321 if (fc.getTransferHandler() instanceof UIResource) { 322 fc.setTransferHandler(null); 323 } 324 } 325 326 protected void uninstallIcons(JFileChooser fc) { 327 directoryIcon = null; 328 fileIcon = null; 329 computerIcon = null; 330 hardDriveIcon = null; 331 floppyDriveIcon = null; 332 333 newFolderIcon = null; 334 upFolderIcon = null; 335 homeFolderIcon = null; 336 detailsViewIcon = null; 337 listViewIcon = null; 338 viewMenuIcon = null; 339 } 340 341 protected void uninstallStrings(JFileChooser fc) { 342 saveButtonText = null; 343 openButtonText = null; 344 cancelButtonText = null; 345 updateButtonText = null; 346 helpButtonText = null; 347 directoryOpenButtonText = null; 348 349 saveButtonToolTipText = null; 350 openButtonToolTipText = null; 351 cancelButtonToolTipText = null; 352 updateButtonToolTipText = null; 353 helpButtonToolTipText = null; 354 directoryOpenButtonToolTipText = null; 355 } 356 357 protected void createModel() { 358 if (model != null) { 359 model.invalidateFileCache(); 360 } 361 model = new BasicDirectoryModel(getFileChooser()); 362 } 363 364 public BasicDirectoryModel getModel() { 365 return model; 366 } 367 368 public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { 369 return null; 370 } 371 372 public String getFileName() { 373 return null; 374 } 375 376 public String getDirectoryName() { 377 return null; 378 } 379 380 public void setFileName(String filename) { 381 } 382 383 public void setDirectoryName(String dirname) { 384 } 385 386 public void rescanCurrentDirectory(JFileChooser fc) { 387 } 388 389 public void ensureFileIsVisible(JFileChooser fc, File f) { 390 } 391 392 public JFileChooser getFileChooser() { 393 return filechooser; 394 } 395 396 public JPanel getAccessoryPanel() { 397 return accessoryPanel; 398 } 399 400 protected JButton getApproveButton(JFileChooser fc) { 401 return null; 402 } 403 404 public JButton getDefaultButton(JFileChooser fc) { 405 return getApproveButton(fc); 406 } 407 408 public String getApproveButtonToolTipText(JFileChooser fc) { 409 String tooltipText = fc.getApproveButtonToolTipText(); 410 if(tooltipText != null) { 411 return tooltipText; 412 } 413 414 if(fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 415 return openButtonToolTipText; 416 } else if(fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 417 return saveButtonToolTipText; 418 } 419 return null; 420 } 421 422 public void clearIconCache() { 423 fileView.clearIconCache(); 424 } 425 426 427 // ******************************************** 428 // ************ Create Listeners ************** 429 // ******************************************** 430 431 private Handler getHandler() { 432 if (handler == null) { 433 handler = new Handler(); 434 } 435 return handler; 436 } 437 438 protected MouseListener createDoubleClickListener(JFileChooser fc, 439 JList<?> list) { 440 return new Handler(list); 441 } 442 443 public ListSelectionListener createListSelectionListener(JFileChooser fc) { 444 return getHandler(); 445 } 446 447 private class Handler implements MouseListener, ListSelectionListener { 448 JList<?> list; 449 450 Handler() { 451 } 452 453 Handler(JList<?> list) { 454 this.list = list; 455 } 456 457 public void mouseClicked(MouseEvent evt) { 458 // Note: we can't depend on evt.getSource() because of backward 459 // compatibility 460 if (list != null && 461 SwingUtilities.isLeftMouseButton(evt) && 462 (evt.getClickCount()%2 == 0)) { 463 464 int index = SwingUtilities2.loc2IndexFileList(list, evt.getPoint()); 465 if (index >= 0) { 466 File f = (File)list.getModel().getElementAt(index); 467 try { 468 // Strip trailing ".." 469 f = ShellFolder.getNormalizedFile(f); 470 } catch (IOException ex) { 471 // That's ok, we'll use f as is 472 } 473 if(getFileChooser().isTraversable(f)) { 474 list.clearSelection(); 475 changeDirectory(f); 476 } else { 477 getFileChooser().approveSelection(); 478 } 479 } 480 } 481 } 482 483 public void mouseEntered(MouseEvent evt) { 484 if (list != null) { 485 TransferHandler th1 = getFileChooser().getTransferHandler(); 486 TransferHandler th2 = list.getTransferHandler(); 487 if (th1 != th2) { 488 list.setTransferHandler(th1); 489 } 490 if (getFileChooser().getDragEnabled() != list.getDragEnabled()) { 491 list.setDragEnabled(getFileChooser().getDragEnabled()); 492 } 493 } 494 } 495 496 public void mouseExited(MouseEvent evt) { 497 } 498 499 public void mousePressed(MouseEvent evt) { 500 } 501 502 public void mouseReleased(MouseEvent evt) { 503 } 504 505 @SuppressWarnings("deprecation") 506 public void valueChanged(ListSelectionEvent evt) { 507 if(!evt.getValueIsAdjusting()) { 508 JFileChooser chooser = getFileChooser(); 509 FileSystemView fsv = chooser.getFileSystemView(); 510 @SuppressWarnings("unchecked") 511 JList<?> list = (JList)evt.getSource(); 512 513 int fsm = chooser.getFileSelectionMode(); 514 boolean useSetDirectory = usesSingleFilePane && 515 (fsm == JFileChooser.FILES_ONLY); 516 517 if (chooser.isMultiSelectionEnabled()) { 518 File[] files = null; 519 Object[] objects = list.getSelectedValues(); 520 if (objects != null) { 521 if (objects.length == 1 522 && ((File)objects[0]).isDirectory() 523 && chooser.isTraversable(((File)objects[0])) 524 && (useSetDirectory || !fsv.isFileSystem(((File)objects[0])))) { 525 setDirectorySelected(true); 526 setDirectory(((File)objects[0])); 527 } else { 528 ArrayList<File> fList = new ArrayList<File>(objects.length); 529 for (Object object : objects) { 530 File f = (File) object; 531 boolean isDir = f.isDirectory(); 532 if ((chooser.isFileSelectionEnabled() && !isDir) 533 || (chooser.isDirectorySelectionEnabled() 534 && fsv.isFileSystem(f) 535 && isDir)) { 536 fList.add(f); 537 } 538 } 539 if (fList.size() > 0) { 540 files = fList.toArray(new File[fList.size()]); 541 } 542 setDirectorySelected(false); 543 } 544 } 545 chooser.setSelectedFiles(files); 546 } else { 547 File file = (File)list.getSelectedValue(); 548 if (file != null 549 && file.isDirectory() 550 && chooser.isTraversable(file) 551 && (useSetDirectory || !fsv.isFileSystem(file))) { 552 553 setDirectorySelected(true); 554 setDirectory(file); 555 if (usesSingleFilePane) { 556 chooser.setSelectedFile(null); 557 } 558 } else { 559 setDirectorySelected(false); 560 if (file != null) { 561 chooser.setSelectedFile(file); 562 } 563 } 564 } 565 } 566 } 567 } 568 569 protected class DoubleClickListener extends MouseAdapter { 570 // NOTE: This class exists only for backward compatibility. All 571 // its functionality has been moved into Handler. If you need to add 572 // new functionality add it to the Handler, but make sure this 573 // class calls into the Handler. 574 Handler handler; 575 public DoubleClickListener(JList<?> list) { 576 handler = new Handler(list); 577 } 578 579 /** 580 * The JList used for representing the files is created by subclasses, but the 581 * selection is monitored in this class. The TransferHandler installed in the 582 * JFileChooser is also installed in the file list as it is used as the actual 583 * transfer source. The list is updated on a mouse enter to reflect the current 584 * data transfer state of the file chooser. 585 */ 586 public void mouseEntered(MouseEvent e) { 587 handler.mouseEntered(e); 588 } 589 590 public void mouseClicked(MouseEvent e) { 591 handler.mouseClicked(e); 592 } 593 } 594 595 protected class SelectionListener implements ListSelectionListener { 596 // NOTE: This class exists only for backward compatibility. All 597 // its functionality has been moved into Handler. If you need to add 598 // new functionality add it to the Handler, but make sure this 599 // class calls into the Handler. 600 public void valueChanged(ListSelectionEvent e) { 601 getHandler().valueChanged(e); 602 } 603 } 604 605 /** 606 * Property to remember whether a directory is currently selected in the UI. 607 * 608 * @return <code>true</code> iff a directory is currently selected. 609 * @since 1.4 610 */ 611 protected boolean isDirectorySelected() { 612 return directorySelected; 613 } 614 615 /** 616 * Property to remember whether a directory is currently selected in the UI. 617 * This is normally called by the UI on a selection event. 618 * 619 * @param b iff a directory is currently selected. 620 * @since 1.4 621 */ 622 protected void setDirectorySelected(boolean b) { 623 directorySelected = b; 624 } 625 626 /** 627 * Property to remember the directory that is currently selected in the UI. 628 * 629 * @return the value of the <code>directory</code> property 630 * @see #setDirectory 631 * @since 1.4 632 */ 633 protected File getDirectory() { 634 return directory; 635 } 636 637 /** 638 * Property to remember the directory that is currently selected in the UI. 639 * This is normally called by the UI on a selection event. 640 * 641 * @param f the <code>File</code> object representing the directory that is 642 * currently selected 643 * @since 1.4 644 */ 645 protected void setDirectory(File f) { 646 directory = f; 647 } 648 649 /** 650 * Returns the mnemonic for the given key. 651 */ 652 private int getMnemonic(String key, Locale l) { 653 return SwingUtilities2.getUIDefaultsInt(key, l); 654 } 655 656 // ******************************************************* 657 // ************ FileChooser UI PLAF methods ************** 658 // ******************************************************* 659 660 /** 661 * Returns the default accept all file filter 662 */ 663 public FileFilter getAcceptAllFileFilter(JFileChooser fc) { 664 return acceptAllFileFilter; 665 } 666 667 668 public FileView getFileView(JFileChooser fc) { 669 return fileView; 670 } 671 672 673 /** 674 * Returns the title of this dialog 675 */ 676 public String getDialogTitle(JFileChooser fc) { 677 String dialogTitle = fc.getDialogTitle(); 678 if (dialogTitle != null) { 679 return dialogTitle; 680 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 681 return openDialogTitleText; 682 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 683 return saveDialogTitleText; 684 } else { 685 return getApproveButtonText(fc); 686 } 687 } 688 689 690 public int getApproveButtonMnemonic(JFileChooser fc) { 691 int mnemonic = fc.getApproveButtonMnemonic(); 692 if (mnemonic > 0) { 693 return mnemonic; 694 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 695 return openButtonMnemonic; 696 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 697 return saveButtonMnemonic; 698 } else { 699 return mnemonic; 700 } 701 } 702 703 public String getApproveButtonText(JFileChooser fc) { 704 String buttonText = fc.getApproveButtonText(); 705 if (buttonText != null) { 706 return buttonText; 707 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 708 return openButtonText; 709 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 710 return saveButtonText; 711 } else { 712 return null; 713 } 714 } 715 716 717 // ***************************** 718 // ***** Directory Actions ***** 719 // ***************************** 720 721 public Action getNewFolderAction() { 722 if (newFolderAction == null) { 723 newFolderAction = new NewFolderAction(); 724 // Note: Don't return null for readOnly, it might 725 // break older apps. 726 if (readOnly) { 727 newFolderAction.setEnabled(false); 728 } 729 } 730 return newFolderAction; 731 } 732 733 public Action getGoHomeAction() { 734 return goHomeAction; 735 } 736 737 public Action getChangeToParentDirectoryAction() { 738 return changeToParentDirectoryAction; 739 } 740 741 public Action getApproveSelectionAction() { 742 return approveSelectionAction; 743 } 744 745 public Action getCancelSelectionAction() { 746 return cancelSelectionAction; 747 } 748 749 public Action getUpdateAction() { 750 return updateAction; 751 } 752 753 754 /** 755 * Creates a new folder. 756 */ 757 @SuppressWarnings("serial") // Superclass is not serializable across versions 758 protected class NewFolderAction extends AbstractAction { 759 protected NewFolderAction() { 760 super(FilePane.ACTION_NEW_FOLDER); 761 } 762 public void actionPerformed(ActionEvent e) { 763 if (readOnly) { 764 return; 765 } 766 JFileChooser fc = getFileChooser(); 767 File currentDirectory = fc.getCurrentDirectory(); 768 769 if (!currentDirectory.exists()) { 770 JOptionPane.showMessageDialog( 771 fc, 772 newFolderParentDoesntExistText, 773 newFolderParentDoesntExistTitleText, JOptionPane.WARNING_MESSAGE); 774 return; 775 } 776 777 File newFolder; 778 try { 779 newFolder = fc.getFileSystemView().createNewFolder(currentDirectory); 780 if (fc.isMultiSelectionEnabled()) { 781 fc.setSelectedFiles(new File[] { newFolder }); 782 } else { 783 fc.setSelectedFile(newFolder); 784 } 785 } catch (IOException exc) { 786 JOptionPane.showMessageDialog( 787 fc, 788 newFolderErrorText + newFolderErrorSeparator + exc, 789 newFolderErrorText, JOptionPane.ERROR_MESSAGE); 790 return; 791 } 792 793 fc.rescanCurrentDirectory(); 794 } 795 } 796 797 /** 798 * Acts on the "home" key event or equivalent event. 799 */ 800 @SuppressWarnings("serial") // Superclass is not serializable across versions 801 protected class GoHomeAction extends AbstractAction { 802 protected GoHomeAction() { 803 super("Go Home"); 804 } 805 public void actionPerformed(ActionEvent e) { 806 JFileChooser fc = getFileChooser(); 807 changeDirectory(fc.getFileSystemView().getHomeDirectory()); 808 } 809 } 810 811 @SuppressWarnings("serial") // Superclass is not serializable across versions 812 protected class ChangeToParentDirectoryAction extends AbstractAction { 813 protected ChangeToParentDirectoryAction() { 814 super("Go Up"); 815 putValue(Action.ACTION_COMMAND_KEY, FilePane.ACTION_CHANGE_TO_PARENT_DIRECTORY); 816 } 817 public void actionPerformed(ActionEvent e) { 818 getFileChooser().changeToParentDirectory(); 819 } 820 } 821 822 /** 823 * Responds to an Open or Save request 824 */ 825 @SuppressWarnings("serial") // Superclass is not serializable across versions 826 protected class ApproveSelectionAction extends AbstractAction { 827 protected ApproveSelectionAction() { 828 super(FilePane.ACTION_APPROVE_SELECTION); 829 } 830 public void actionPerformed(ActionEvent e) { 831 if (isDirectorySelected()) { 832 File dir = getDirectory(); 833 if (dir != null) { 834 try { 835 // Strip trailing ".." 836 dir = ShellFolder.getNormalizedFile(dir); 837 } catch (IOException ex) { 838 // Ok, use f as is 839 } 840 changeDirectory(dir); 841 return; 842 } 843 } 844 845 JFileChooser chooser = getFileChooser(); 846 847 String filename = getFileName(); 848 FileSystemView fs = chooser.getFileSystemView(); 849 File dir = chooser.getCurrentDirectory(); 850 851 if (filename != null) { 852 // Remove whitespaces from end of filename 853 int i = filename.length() - 1; 854 855 while (i >=0 && filename.charAt(i) <= ' ') { 856 i--; 857 } 858 859 filename = filename.substring(0, i + 1); 860 } 861 862 if (filename == null || filename.length() == 0) { 863 // no file selected, multiple selection off, therefore cancel the approve action 864 resetGlobFilter(); 865 return; 866 } 867 868 File selectedFile = null; 869 File[] selectedFiles = null; 870 871 // Unix: Resolve '~' to user's home directory 872 if (File.separatorChar == '/') { 873 if (filename.startsWith("~/")) { 874 filename = System.getProperty("user.home") + filename.substring(1); 875 } else if (filename.equals("~")) { 876 filename = System.getProperty("user.home"); 877 } 878 } 879 880 if (chooser.isMultiSelectionEnabled() && filename.length() > 1 && 881 filename.charAt(0) == '"' && filename.charAt(filename.length() - 1) == '"') { 882 List<File> fList = new ArrayList<File>(); 883 884 String[] files = filename.substring(1, filename.length() - 1).split("\" \""); 885 // Optimize searching files by names in "children" array 886 Arrays.sort(files); 887 888 File[] children = null; 889 int childIndex = 0; 890 891 for (String str : files) { 892 File file = fs.createFileObject(str); 893 if (!file.isAbsolute()) { 894 if (children == null) { 895 children = fs.getFiles(dir, false); 896 Arrays.sort(children); 897 } 898 for (int k = 0; k < children.length; k++) { 899 int l = (childIndex + k) % children.length; 900 if (children[l].getName().equals(str)) { 901 file = children[l]; 902 childIndex = l + 1; 903 break; 904 } 905 } 906 } 907 fList.add(file); 908 } 909 910 if (!fList.isEmpty()) { 911 selectedFiles = fList.toArray(new File[fList.size()]); 912 } 913 resetGlobFilter(); 914 } else { 915 selectedFile = fs.createFileObject(filename); 916 if (!selectedFile.isAbsolute()) { 917 selectedFile = fs.getChild(dir, filename); 918 } 919 // check for wildcard pattern 920 FileFilter currentFilter = chooser.getFileFilter(); 921 if (!selectedFile.exists() && isGlobPattern(filename)) { 922 changeDirectory(selectedFile.getParentFile()); 923 if (globFilter == null) { 924 globFilter = new GlobFilter(); 925 } 926 try { 927 globFilter.setPattern(selectedFile.getName()); 928 if (!(currentFilter instanceof GlobFilter)) { 929 actualFileFilter = currentFilter; 930 } 931 chooser.setFileFilter(null); 932 chooser.setFileFilter(globFilter); 933 return; 934 } catch (PatternSyntaxException pse) { 935 // Not a valid glob pattern. Abandon filter. 936 } 937 } 938 939 resetGlobFilter(); 940 941 // Check for directory change action 942 boolean isDir = (selectedFile != null && selectedFile.isDirectory()); 943 boolean isTrav = (selectedFile != null && chooser.isTraversable(selectedFile)); 944 boolean isDirSelEnabled = chooser.isDirectorySelectionEnabled(); 945 boolean isFileSelEnabled = chooser.isFileSelectionEnabled(); 946 boolean isCtrl = (e != null && (e.getModifiers() & 947 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0); 948 949 if (isDir && isTrav && (isCtrl || !isDirSelEnabled)) { 950 changeDirectory(selectedFile); 951 return; 952 } else if ((isDir || !isFileSelEnabled) 953 && (!isDir || !isDirSelEnabled) 954 && (!isDirSelEnabled || selectedFile.exists())) { 955 selectedFile = null; 956 } 957 } 958 959 if (selectedFiles != null || selectedFile != null) { 960 if (selectedFiles != null || chooser.isMultiSelectionEnabled()) { 961 if (selectedFiles == null) { 962 selectedFiles = new File[] { selectedFile }; 963 } 964 chooser.setSelectedFiles(selectedFiles); 965 // Do it again. This is a fix for bug 4949273 to force the 966 // selected value in case the ListSelectionModel clears it 967 // for non-existing file names. 968 chooser.setSelectedFiles(selectedFiles); 969 } else { 970 chooser.setSelectedFile(selectedFile); 971 } 972 chooser.approveSelection(); 973 } else { 974 if (chooser.isMultiSelectionEnabled()) { 975 chooser.setSelectedFiles(null); 976 } else { 977 chooser.setSelectedFile(null); 978 } 979 chooser.cancelSelection(); 980 } 981 } 982 } 983 984 985 private void resetGlobFilter() { 986 if (actualFileFilter != null) { 987 JFileChooser chooser = getFileChooser(); 988 FileFilter currentFilter = chooser.getFileFilter(); 989 if (currentFilter != null && currentFilter.equals(globFilter)) { 990 chooser.setFileFilter(actualFileFilter); 991 chooser.removeChoosableFileFilter(globFilter); 992 } 993 actualFileFilter = null; 994 } 995 } 996 997 private static boolean isGlobPattern(String filename) { 998 return ((File.separatorChar == '\\' && (filename.indexOf('*') >= 0 999 || filename.indexOf('?') >= 0)) 1000 || (File.separatorChar == '/' && (filename.indexOf('*') >= 0 1001 || filename.indexOf('?') >= 0 1002 || filename.indexOf('[') >= 0))); 1003 } 1004 1005 1006 /* A file filter which accepts file patterns containing 1007 * the special wildcards *? on Windows and *?[] on Unix. 1008 */ 1009 class GlobFilter extends FileFilter { 1010 Pattern pattern; 1011 String globPattern; 1012 1013 public void setPattern(String globPattern) { 1014 char[] gPat = globPattern.toCharArray(); 1015 char[] rPat = new char[gPat.length * 2]; 1016 boolean isWin32 = (File.separatorChar == '\\'); 1017 boolean inBrackets = false; 1018 int j = 0; 1019 1020 this.globPattern = globPattern; 1021 1022 if (isWin32) { 1023 // On windows, a pattern ending with *.* is equal to ending with * 1024 int len = gPat.length; 1025 if (globPattern.endsWith("*.*")) { 1026 len -= 2; 1027 } 1028 for (int i = 0; i < len; i++) { 1029 switch(gPat[i]) { 1030 case '*': 1031 rPat[j++] = '.'; 1032 rPat[j++] = '*'; 1033 break; 1034 1035 case '?': 1036 rPat[j++] = '.'; 1037 break; 1038 1039 case '\\': 1040 rPat[j++] = '\\'; 1041 rPat[j++] = '\\'; 1042 break; 1043 1044 default: 1045 if ("+()^$.{}[]".indexOf(gPat[i]) >= 0) { 1046 rPat[j++] = '\\'; 1047 } 1048 rPat[j++] = gPat[i]; 1049 break; 1050 } 1051 } 1052 } else { 1053 for (int i = 0; i < gPat.length; i++) { 1054 switch(gPat[i]) { 1055 case '*': 1056 if (!inBrackets) { 1057 rPat[j++] = '.'; 1058 } 1059 rPat[j++] = '*'; 1060 break; 1061 1062 case '?': 1063 rPat[j++] = inBrackets ? '?' : '.'; 1064 break; 1065 1066 case '[': 1067 inBrackets = true; 1068 rPat[j++] = gPat[i]; 1069 1070 if (i < gPat.length - 1) { 1071 switch (gPat[i+1]) { 1072 case '!': 1073 case '^': 1074 rPat[j++] = '^'; 1075 i++; 1076 break; 1077 1078 case ']': 1079 rPat[j++] = gPat[++i]; 1080 break; 1081 } 1082 } 1083 break; 1084 1085 case ']': 1086 rPat[j++] = gPat[i]; 1087 inBrackets = false; 1088 break; 1089 1090 case '\\': 1091 if (i == 0 && gPat.length > 1 && gPat[1] == '~') { 1092 rPat[j++] = gPat[++i]; 1093 } else { 1094 rPat[j++] = '\\'; 1095 if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) { 1096 rPat[j++] = gPat[++i]; 1097 } else { 1098 rPat[j++] = '\\'; 1099 } 1100 } 1101 break; 1102 1103 default: 1104 //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) { 1105 if (!Character.isLetterOrDigit(gPat[i])) { 1106 rPat[j++] = '\\'; 1107 } 1108 rPat[j++] = gPat[i]; 1109 break; 1110 } 1111 } 1112 } 1113 this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE); 1114 } 1115 1116 public boolean accept(File f) { 1117 if (f == null) { 1118 return false; 1119 } 1120 if (f.isDirectory()) { 1121 return true; 1122 } 1123 return pattern.matcher(f.getName()).matches(); 1124 } 1125 1126 public String getDescription() { 1127 return globPattern; 1128 } 1129 } 1130 1131 /** 1132 * Responds to a cancel request. 1133 */ 1134 @SuppressWarnings("serial") // Superclass is not serializable across versions 1135 protected class CancelSelectionAction extends AbstractAction { 1136 public void actionPerformed(ActionEvent e) { 1137 getFileChooser().cancelSelection(); 1138 } 1139 } 1140 1141 /** 1142 * Rescans the files in the current directory 1143 */ 1144 @SuppressWarnings("serial") // Superclass is not serializable across versions 1145 protected class UpdateAction extends AbstractAction { 1146 public void actionPerformed(ActionEvent e) { 1147 JFileChooser fc = getFileChooser(); 1148 fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName())); 1149 fc.rescanCurrentDirectory(); 1150 } 1151 } 1152 1153 1154 private void changeDirectory(File dir) { 1155 JFileChooser fc = getFileChooser(); 1156 // Traverse shortcuts on Windows 1157 if (dir != null && FilePane.usesShellFolder(fc)) { 1158 try { 1159 ShellFolder shellFolder = ShellFolder.getShellFolder(dir); 1160 1161 if (shellFolder.isLink()) { 1162 File linkedTo = shellFolder.getLinkLocation(); 1163 1164 // If linkedTo is null we try to use dir 1165 if (linkedTo != null) { 1166 if (fc.isTraversable(linkedTo)) { 1167 dir = linkedTo; 1168 } else { 1169 return; 1170 } 1171 } else { 1172 dir = shellFolder; 1173 } 1174 } 1175 } catch (FileNotFoundException ex) { 1176 return; 1177 } 1178 } 1179 fc.setCurrentDirectory(dir); 1180 if (fc.getFileSelectionMode() == JFileChooser.FILES_AND_DIRECTORIES && 1181 fc.getFileSystemView().isFileSystem(dir)) { 1182 1183 setFileName(dir.getAbsolutePath()); 1184 } 1185 } 1186 1187 1188 // ***************************************** 1189 // ***** default AcceptAll file filter ***** 1190 // ***************************************** 1191 protected class AcceptAllFileFilter extends FileFilter { 1192 1193 public AcceptAllFileFilter() { 1194 } 1195 1196 public boolean accept(File f) { 1197 return true; 1198 } 1199 1200 public String getDescription() { 1201 return UIManager.getString("FileChooser.acceptAllFileFilterText"); 1202 } 1203 } 1204 1205 1206 // *********************** 1207 // * FileView operations * 1208 // *********************** 1209 protected class BasicFileView extends FileView { 1210 /* FileView type descriptions */ 1211 // PENDING(jeff) - pass in the icon cache size 1212 protected Hashtable<File,Icon> iconCache = new Hashtable<File,Icon>(); 1213 1214 public BasicFileView() { 1215 } 1216 1217 public void clearIconCache() { 1218 iconCache = new Hashtable<File,Icon>(); 1219 } 1220 1221 public String getName(File f) { 1222 // Note: Returns display name rather than file name 1223 String fileName = null; 1224 if(f != null) { 1225 fileName = getFileChooser().getFileSystemView().getSystemDisplayName(f); 1226 } 1227 return fileName; 1228 } 1229 1230 1231 public String getDescription(File f) { 1232 return f.getName(); 1233 } 1234 1235 public String getTypeDescription(File f) { 1236 String type = getFileChooser().getFileSystemView().getSystemTypeDescription(f); 1237 if (type == null) { 1238 if (f.isDirectory()) { 1239 type = directoryDescriptionText; 1240 } else { 1241 type = fileDescriptionText; 1242 } 1243 } 1244 return type; 1245 } 1246 1247 public Icon getCachedIcon(File f) { 1248 return iconCache.get(f); 1249 } 1250 1251 public void cacheIcon(File f, Icon i) { 1252 if(f == null || i == null) { 1253 return; 1254 } 1255 iconCache.put(f, i); 1256 } 1257 1258 public Icon getIcon(File f) { 1259 Icon icon = getCachedIcon(f); 1260 if(icon != null) { 1261 return icon; 1262 } 1263 icon = fileIcon; 1264 if (f != null) { 1265 FileSystemView fsv = getFileChooser().getFileSystemView(); 1266 1267 if (fsv.isFloppyDrive(f)) { 1268 icon = floppyDriveIcon; 1269 } else if (fsv.isDrive(f)) { 1270 icon = hardDriveIcon; 1271 } else if (fsv.isComputerNode(f)) { 1272 icon = computerIcon; 1273 } else if (f.isDirectory()) { 1274 icon = directoryIcon; 1275 } 1276 } 1277 cacheIcon(f, icon); 1278 return icon; 1279 } 1280 1281 public Boolean isHidden(File f) { 1282 String name = f.getName(); 1283 if(name != null && name.charAt(0) == '.') { 1284 return Boolean.TRUE; 1285 } else { 1286 return Boolean.FALSE; 1287 } 1288 } 1289 } 1290 1291 private static final TransferHandler defaultTransferHandler = new FileTransferHandler(); 1292 1293 /** 1294 * Data transfer support for the file chooser. Since files are currently presented 1295 * as a list, the list support is reused with the added flavor of DataFlavor.javaFileListFlavor 1296 */ 1297 @SuppressWarnings("serial") // JDK-implementation class 1298 static class FileTransferHandler extends TransferHandler implements UIResource { 1299 1300 /** 1301 * Create a Transferable to use as the source for a data transfer. 1302 * 1303 * @param c The component holding the data to be transfered. This 1304 * argument is provided to enable sharing of TransferHandlers by 1305 * multiple components. 1306 * @return The representation of the data to be transfered. 1307 * 1308 */ 1309 @SuppressWarnings("deprecation") 1310 protected Transferable createTransferable(JComponent c) { 1311 Object[] values = null; 1312 if (c instanceof JList) { 1313 values = ((JList)c).getSelectedValues(); 1314 } else if (c instanceof JTable) { 1315 JTable table = (JTable)c; 1316 int[] rows = table.getSelectedRows(); 1317 if (rows != null) { 1318 values = new Object[rows.length]; 1319 for (int i=0; i<rows.length; i++) { 1320 values[i] = table.getValueAt(rows[i], 0); 1321 } 1322 } 1323 } 1324 if (values == null || values.length == 0) { 1325 return null; 1326 } 1327 1328 StringBuilder plainBuf = new StringBuilder(); 1329 StringBuilder htmlBuf = new StringBuilder(); 1330 1331 htmlBuf.append("<html>\n<body>\n<ul>\n"); 1332 1333 for (Object obj : values) { 1334 String val = ((obj == null) ? "" : obj.toString()); 1335 plainBuf.append(val).append('\n'); 1336 htmlBuf.append(" <li>").append(val).append('\n'); 1337 } 1338 1339 // remove the last newline 1340 plainBuf.deleteCharAt(plainBuf.length() - 1); 1341 htmlBuf.append("</ul>\n</body>\n</html>"); 1342 1343 return new FileTransferable(plainBuf.toString(), htmlBuf.toString(), values); 1344 } 1345 1346 public int getSourceActions(JComponent c) { 1347 return COPY; 1348 } 1349 1350 static class FileTransferable extends BasicTransferable { 1351 1352 Object[] fileData; 1353 1354 FileTransferable(String plainData, String htmlData, Object[] fileData) { 1355 super(plainData, htmlData); 1356 this.fileData = fileData; 1357 } 1358 1359 /** 1360 * Best format of the file chooser is DataFlavor.javaFileListFlavor. 1361 */ 1362 protected DataFlavor[] getRicherFlavors() { 1363 DataFlavor[] flavors = new DataFlavor[1]; 1364 flavors[0] = DataFlavor.javaFileListFlavor; 1365 return flavors; 1366 } 1367 1368 /** 1369 * The only richer format supported is the file list flavor 1370 */ 1371 protected Object getRicherData(DataFlavor flavor) { 1372 if (DataFlavor.javaFileListFlavor.equals(flavor)) { 1373 ArrayList<Object> files = new ArrayList<Object>(); 1374 for (Object file : this.fileData) { 1375 files.add(file); 1376 } 1377 return files; 1378 } 1379 return null; 1380 } 1381 1382 } 1383 } 1384 }