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 public void valueChanged(ListSelectionEvent evt) { 506 if(!evt.getValueIsAdjusting()) { 507 JFileChooser chooser = getFileChooser(); 508 FileSystemView fsv = chooser.getFileSystemView(); 509 @SuppressWarnings("unchecked") 510 JList<?> list = (JList)evt.getSource(); 511 512 int fsm = chooser.getFileSelectionMode(); 513 boolean useSetDirectory = usesSingleFilePane && 514 (fsm == JFileChooser.FILES_ONLY); 515 516 if (chooser.isMultiSelectionEnabled()) { 517 File[] files = null; 518 Object[] objects = list.getSelectedValues(); 519 if (objects != null) { 520 if (objects.length == 1 521 && ((File)objects[0]).isDirectory() 522 && chooser.isTraversable(((File)objects[0])) 523 && (useSetDirectory || !fsv.isFileSystem(((File)objects[0])))) { 524 setDirectorySelected(true); 525 setDirectory(((File)objects[0])); 526 } else { 527 ArrayList<File> fList = new ArrayList<File>(objects.length); 528 for (Object object : objects) { 529 File f = (File) object; 530 boolean isDir = f.isDirectory(); 531 if ((chooser.isFileSelectionEnabled() && !isDir) 532 || (chooser.isDirectorySelectionEnabled() 533 && fsv.isFileSystem(f) 534 && isDir)) { 535 fList.add(f); 536 } 537 } 538 if (fList.size() > 0) { 539 files = fList.toArray(new File[fList.size()]); 540 } 541 setDirectorySelected(false); 542 } 543 } 544 chooser.setSelectedFiles(files); 545 } else { 546 File file = (File)list.getSelectedValue(); 547 if (file != null 548 && file.isDirectory() 549 && chooser.isTraversable(file) 550 && (useSetDirectory || !fsv.isFileSystem(file))) { 551 552 setDirectorySelected(true); 553 setDirectory(file); 554 if (usesSingleFilePane) { 555 chooser.setSelectedFile(null); 556 } 557 } else { 558 setDirectorySelected(false); 559 if (file != null) { 560 chooser.setSelectedFile(file); 561 } 562 } 563 } 564 } 565 } 566 } 567 568 protected class DoubleClickListener extends MouseAdapter { 569 // NOTE: This class exists only for backward compatibility. All 570 // its functionality has been moved into Handler. If you need to add 571 // new functionality add it to the Handler, but make sure this 572 // class calls into the Handler. 573 Handler handler; 574 public DoubleClickListener(JList<?> list) { 575 handler = new Handler(list); 576 } 577 578 /** 579 * The JList used for representing the files is created by subclasses, but the 580 * selection is monitored in this class. The TransferHandler installed in the 581 * JFileChooser is also installed in the file list as it is used as the actual 582 * transfer source. The list is updated on a mouse enter to reflect the current 583 * data transfer state of the file chooser. 584 */ 585 public void mouseEntered(MouseEvent e) { 586 handler.mouseEntered(e); 587 } 588 589 public void mouseClicked(MouseEvent e) { 590 handler.mouseClicked(e); 591 } 592 } 593 594 protected class SelectionListener implements ListSelectionListener { 595 // NOTE: This class exists only for backward compatibility. All 596 // its functionality has been moved into Handler. If you need to add 597 // new functionality add it to the Handler, but make sure this 598 // class calls into the Handler. 599 public void valueChanged(ListSelectionEvent e) { 600 getHandler().valueChanged(e); 601 } 602 } 603 604 /** 605 * Property to remember whether a directory is currently selected in the UI. 606 * 607 * @return <code>true</code> iff a directory is currently selected. 608 * @since 1.4 609 */ 610 protected boolean isDirectorySelected() { 611 return directorySelected; 612 } 613 614 /** 615 * Property to remember whether a directory is currently selected in the UI. 616 * This is normally called by the UI on a selection event. 617 * 618 * @param b iff a directory is currently selected. 619 * @since 1.4 620 */ 621 protected void setDirectorySelected(boolean b) { 622 directorySelected = b; 623 } 624 625 /** 626 * Property to remember the directory that is currently selected in the UI. 627 * 628 * @return the value of the <code>directory</code> property 629 * @see #setDirectory 630 * @since 1.4 631 */ 632 protected File getDirectory() { 633 return directory; 634 } 635 636 /** 637 * Property to remember the directory that is currently selected in the UI. 638 * This is normally called by the UI on a selection event. 639 * 640 * @param f the <code>File</code> object representing the directory that is 641 * currently selected 642 * @since 1.4 643 */ 644 protected void setDirectory(File f) { 645 directory = f; 646 } 647 648 /** 649 * Returns the mnemonic for the given key. 650 */ 651 private int getMnemonic(String key, Locale l) { 652 return SwingUtilities2.getUIDefaultsInt(key, l); 653 } 654 655 // ******************************************************* 656 // ************ FileChooser UI PLAF methods ************** 657 // ******************************************************* 658 659 /** 660 * Returns the default accept all file filter 661 */ 662 public FileFilter getAcceptAllFileFilter(JFileChooser fc) { 663 return acceptAllFileFilter; 664 } 665 666 667 public FileView getFileView(JFileChooser fc) { 668 return fileView; 669 } 670 671 672 /** 673 * Returns the title of this dialog 674 */ 675 public String getDialogTitle(JFileChooser fc) { 676 String dialogTitle = fc.getDialogTitle(); 677 if (dialogTitle != null) { 678 return dialogTitle; 679 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 680 return openDialogTitleText; 681 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 682 return saveDialogTitleText; 683 } else { 684 return getApproveButtonText(fc); 685 } 686 } 687 688 689 public int getApproveButtonMnemonic(JFileChooser fc) { 690 int mnemonic = fc.getApproveButtonMnemonic(); 691 if (mnemonic > 0) { 692 return mnemonic; 693 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 694 return openButtonMnemonic; 695 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 696 return saveButtonMnemonic; 697 } else { 698 return mnemonic; 699 } 700 } 701 702 public String getApproveButtonText(JFileChooser fc) { 703 String buttonText = fc.getApproveButtonText(); 704 if (buttonText != null) { 705 return buttonText; 706 } else if (fc.getDialogType() == JFileChooser.OPEN_DIALOG) { 707 return openButtonText; 708 } else if (fc.getDialogType() == JFileChooser.SAVE_DIALOG) { 709 return saveButtonText; 710 } else { 711 return null; 712 } 713 } 714 715 716 // ***************************** 717 // ***** Directory Actions ***** 718 // ***************************** 719 720 public Action getNewFolderAction() { 721 if (newFolderAction == null) { 722 newFolderAction = new NewFolderAction(); 723 // Note: Don't return null for readOnly, it might 724 // break older apps. 725 if (readOnly) { 726 newFolderAction.setEnabled(false); 727 } 728 } 729 return newFolderAction; 730 } 731 732 public Action getGoHomeAction() { 733 return goHomeAction; 734 } 735 736 public Action getChangeToParentDirectoryAction() { 737 return changeToParentDirectoryAction; 738 } 739 740 public Action getApproveSelectionAction() { 741 return approveSelectionAction; 742 } 743 744 public Action getCancelSelectionAction() { 745 return cancelSelectionAction; 746 } 747 748 public Action getUpdateAction() { 749 return updateAction; 750 } 751 752 753 /** 754 * Creates a new folder. 755 */ 756 @SuppressWarnings("serial") // Superclass is not serializable across versions 757 protected class NewFolderAction extends AbstractAction { 758 protected NewFolderAction() { 759 super(FilePane.ACTION_NEW_FOLDER); 760 } 761 public void actionPerformed(ActionEvent e) { 762 if (readOnly) { 763 return; 764 } 765 JFileChooser fc = getFileChooser(); 766 File currentDirectory = fc.getCurrentDirectory(); 767 768 if (!currentDirectory.exists()) { 769 JOptionPane.showMessageDialog( 770 fc, 771 newFolderParentDoesntExistText, 772 newFolderParentDoesntExistTitleText, JOptionPane.WARNING_MESSAGE); 773 return; 774 } 775 776 File newFolder; 777 try { 778 newFolder = fc.getFileSystemView().createNewFolder(currentDirectory); 779 if (fc.isMultiSelectionEnabled()) { 780 fc.setSelectedFiles(new File[] { newFolder }); 781 } else { 782 fc.setSelectedFile(newFolder); 783 } 784 } catch (IOException exc) { 785 JOptionPane.showMessageDialog( 786 fc, 787 newFolderErrorText + newFolderErrorSeparator + exc, 788 newFolderErrorText, JOptionPane.ERROR_MESSAGE); 789 return; 790 } 791 792 fc.rescanCurrentDirectory(); 793 } 794 } 795 796 /** 797 * Acts on the "home" key event or equivalent event. 798 */ 799 @SuppressWarnings("serial") // Superclass is not serializable across versions 800 protected class GoHomeAction extends AbstractAction { 801 protected GoHomeAction() { 802 super("Go Home"); 803 } 804 public void actionPerformed(ActionEvent e) { 805 JFileChooser fc = getFileChooser(); 806 changeDirectory(fc.getFileSystemView().getHomeDirectory()); 807 } 808 } 809 810 @SuppressWarnings("serial") // Superclass is not serializable across versions 811 protected class ChangeToParentDirectoryAction extends AbstractAction { 812 protected ChangeToParentDirectoryAction() { 813 super("Go Up"); 814 putValue(Action.ACTION_COMMAND_KEY, FilePane.ACTION_CHANGE_TO_PARENT_DIRECTORY); 815 } 816 public void actionPerformed(ActionEvent e) { 817 getFileChooser().changeToParentDirectory(); 818 } 819 } 820 821 /** 822 * Responds to an Open or Save request 823 */ 824 @SuppressWarnings("serial") // Superclass is not serializable across versions 825 protected class ApproveSelectionAction extends AbstractAction { 826 protected ApproveSelectionAction() { 827 super(FilePane.ACTION_APPROVE_SELECTION); 828 } 829 public void actionPerformed(ActionEvent e) { 830 if (isDirectorySelected()) { 831 File dir = getDirectory(); 832 if (dir != null) { 833 try { 834 // Strip trailing ".." 835 dir = ShellFolder.getNormalizedFile(dir); 836 } catch (IOException ex) { 837 // Ok, use f as is 838 } 839 changeDirectory(dir); 840 return; 841 } 842 } 843 844 JFileChooser chooser = getFileChooser(); 845 846 String filename = getFileName(); 847 FileSystemView fs = chooser.getFileSystemView(); 848 File dir = chooser.getCurrentDirectory(); 849 850 if (filename != null) { 851 // Remove whitespaces from end of filename 852 int i = filename.length() - 1; 853 854 while (i >=0 && filename.charAt(i) <= ' ') { 855 i--; 856 } 857 858 filename = filename.substring(0, i + 1); 859 } 860 861 if (filename == null || filename.length() == 0) { 862 // no file selected, multiple selection off, therefore cancel the approve action 863 resetGlobFilter(); 864 return; 865 } 866 867 File selectedFile = null; 868 File[] selectedFiles = null; 869 870 // Unix: Resolve '~' to user's home directory 871 if (File.separatorChar == '/') { 872 if (filename.startsWith("~/")) { 873 filename = System.getProperty("user.home") + filename.substring(1); 874 } else if (filename.equals("~")) { 875 filename = System.getProperty("user.home"); 876 } 877 } 878 879 if (chooser.isMultiSelectionEnabled() && filename.length() > 1 && 880 filename.charAt(0) == '"' && filename.charAt(filename.length() - 1) == '"') { 881 List<File> fList = new ArrayList<File>(); 882 883 String[] files = filename.substring(1, filename.length() - 1).split("\" \""); 884 // Optimize searching files by names in "children" array 885 Arrays.sort(files); 886 887 File[] children = null; 888 int childIndex = 0; 889 890 for (String str : files) { 891 File file = fs.createFileObject(str); 892 if (!file.isAbsolute()) { 893 if (children == null) { 894 children = fs.getFiles(dir, false); 895 Arrays.sort(children); 896 } 897 for (int k = 0; k < children.length; k++) { 898 int l = (childIndex + k) % children.length; 899 if (children[l].getName().equals(str)) { 900 file = children[l]; 901 childIndex = l + 1; 902 break; 903 } 904 } 905 } 906 fList.add(file); 907 } 908 909 if (!fList.isEmpty()) { 910 selectedFiles = fList.toArray(new File[fList.size()]); 911 } 912 resetGlobFilter(); 913 } else { 914 selectedFile = fs.createFileObject(filename); 915 if (!selectedFile.isAbsolute()) { 916 selectedFile = fs.getChild(dir, filename); 917 } 918 // check for wildcard pattern 919 FileFilter currentFilter = chooser.getFileFilter(); 920 if (!selectedFile.exists() && isGlobPattern(filename)) { 921 changeDirectory(selectedFile.getParentFile()); 922 if (globFilter == null) { 923 globFilter = new GlobFilter(); 924 } 925 try { 926 globFilter.setPattern(selectedFile.getName()); 927 if (!(currentFilter instanceof GlobFilter)) { 928 actualFileFilter = currentFilter; 929 } 930 chooser.setFileFilter(null); 931 chooser.setFileFilter(globFilter); 932 return; 933 } catch (PatternSyntaxException pse) { 934 // Not a valid glob pattern. Abandon filter. 935 } 936 } 937 938 resetGlobFilter(); 939 940 // Check for directory change action 941 boolean isDir = (selectedFile != null && selectedFile.isDirectory()); 942 boolean isTrav = (selectedFile != null && chooser.isTraversable(selectedFile)); 943 boolean isDirSelEnabled = chooser.isDirectorySelectionEnabled(); 944 boolean isFileSelEnabled = chooser.isFileSelectionEnabled(); 945 boolean isCtrl = (e != null && (e.getModifiers() & 946 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0); 947 948 if (isDir && isTrav && (isCtrl || !isDirSelEnabled)) { 949 changeDirectory(selectedFile); 950 return; 951 } else if ((isDir || !isFileSelEnabled) 952 && (!isDir || !isDirSelEnabled) 953 && (!isDirSelEnabled || selectedFile.exists())) { 954 selectedFile = null; 955 } 956 } 957 958 if (selectedFiles != null || selectedFile != null) { 959 if (selectedFiles != null || chooser.isMultiSelectionEnabled()) { 960 if (selectedFiles == null) { 961 selectedFiles = new File[] { selectedFile }; 962 } 963 chooser.setSelectedFiles(selectedFiles); 964 // Do it again. This is a fix for bug 4949273 to force the 965 // selected value in case the ListSelectionModel clears it 966 // for non-existing file names. 967 chooser.setSelectedFiles(selectedFiles); 968 } else { 969 chooser.setSelectedFile(selectedFile); 970 } 971 chooser.approveSelection(); 972 } else { 973 if (chooser.isMultiSelectionEnabled()) { 974 chooser.setSelectedFiles(null); 975 } else { 976 chooser.setSelectedFile(null); 977 } 978 chooser.cancelSelection(); 979 } 980 } 981 } 982 983 984 private void resetGlobFilter() { 985 if (actualFileFilter != null) { 986 JFileChooser chooser = getFileChooser(); 987 FileFilter currentFilter = chooser.getFileFilter(); 988 if (currentFilter != null && currentFilter.equals(globFilter)) { 989 chooser.setFileFilter(actualFileFilter); 990 chooser.removeChoosableFileFilter(globFilter); 991 } 992 actualFileFilter = null; 993 } 994 } 995 996 private static boolean isGlobPattern(String filename) { 997 return ((File.separatorChar == '\\' && (filename.indexOf('*') >= 0 998 || filename.indexOf('?') >= 0)) 999 || (File.separatorChar == '/' && (filename.indexOf('*') >= 0 1000 || filename.indexOf('?') >= 0 1001 || filename.indexOf('[') >= 0))); 1002 } 1003 1004 1005 /* A file filter which accepts file patterns containing 1006 * the special wildcards *? on Windows and *?[] on Unix. 1007 */ 1008 class GlobFilter extends FileFilter { 1009 Pattern pattern; 1010 String globPattern; 1011 1012 public void setPattern(String globPattern) { 1013 char[] gPat = globPattern.toCharArray(); 1014 char[] rPat = new char[gPat.length * 2]; 1015 boolean isWin32 = (File.separatorChar == '\\'); 1016 boolean inBrackets = false; 1017 int j = 0; 1018 1019 this.globPattern = globPattern; 1020 1021 if (isWin32) { 1022 // On windows, a pattern ending with *.* is equal to ending with * 1023 int len = gPat.length; 1024 if (globPattern.endsWith("*.*")) { 1025 len -= 2; 1026 } 1027 for (int i = 0; i < len; i++) { 1028 switch(gPat[i]) { 1029 case '*': 1030 rPat[j++] = '.'; 1031 rPat[j++] = '*'; 1032 break; 1033 1034 case '?': 1035 rPat[j++] = '.'; 1036 break; 1037 1038 case '\\': 1039 rPat[j++] = '\\'; 1040 rPat[j++] = '\\'; 1041 break; 1042 1043 default: 1044 if ("+()^$.{}[]".indexOf(gPat[i]) >= 0) { 1045 rPat[j++] = '\\'; 1046 } 1047 rPat[j++] = gPat[i]; 1048 break; 1049 } 1050 } 1051 } else { 1052 for (int i = 0; i < gPat.length; i++) { 1053 switch(gPat[i]) { 1054 case '*': 1055 if (!inBrackets) { 1056 rPat[j++] = '.'; 1057 } 1058 rPat[j++] = '*'; 1059 break; 1060 1061 case '?': 1062 rPat[j++] = inBrackets ? '?' : '.'; 1063 break; 1064 1065 case '[': 1066 inBrackets = true; 1067 rPat[j++] = gPat[i]; 1068 1069 if (i < gPat.length - 1) { 1070 switch (gPat[i+1]) { 1071 case '!': 1072 case '^': 1073 rPat[j++] = '^'; 1074 i++; 1075 break; 1076 1077 case ']': 1078 rPat[j++] = gPat[++i]; 1079 break; 1080 } 1081 } 1082 break; 1083 1084 case ']': 1085 rPat[j++] = gPat[i]; 1086 inBrackets = false; 1087 break; 1088 1089 case '\\': 1090 if (i == 0 && gPat.length > 1 && gPat[1] == '~') { 1091 rPat[j++] = gPat[++i]; 1092 } else { 1093 rPat[j++] = '\\'; 1094 if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) { 1095 rPat[j++] = gPat[++i]; 1096 } else { 1097 rPat[j++] = '\\'; 1098 } 1099 } 1100 break; 1101 1102 default: 1103 //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) { 1104 if (!Character.isLetterOrDigit(gPat[i])) { 1105 rPat[j++] = '\\'; 1106 } 1107 rPat[j++] = gPat[i]; 1108 break; 1109 } 1110 } 1111 } 1112 this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE); 1113 } 1114 1115 public boolean accept(File f) { 1116 if (f == null) { 1117 return false; 1118 } 1119 if (f.isDirectory()) { 1120 return true; 1121 } 1122 return pattern.matcher(f.getName()).matches(); 1123 } 1124 1125 public String getDescription() { 1126 return globPattern; 1127 } 1128 } 1129 1130 /** 1131 * Responds to a cancel request. 1132 */ 1133 @SuppressWarnings("serial") // Superclass is not serializable across versions 1134 protected class CancelSelectionAction extends AbstractAction { 1135 public void actionPerformed(ActionEvent e) { 1136 getFileChooser().cancelSelection(); 1137 } 1138 } 1139 1140 /** 1141 * Rescans the files in the current directory 1142 */ 1143 @SuppressWarnings("serial") // Superclass is not serializable across versions 1144 protected class UpdateAction extends AbstractAction { 1145 public void actionPerformed(ActionEvent e) { 1146 JFileChooser fc = getFileChooser(); 1147 fc.setCurrentDirectory(fc.getFileSystemView().createFileObject(getDirectoryName())); 1148 fc.rescanCurrentDirectory(); 1149 } 1150 } 1151 1152 1153 private void changeDirectory(File dir) { 1154 JFileChooser fc = getFileChooser(); 1155 // Traverse shortcuts on Windows 1156 if (dir != null && FilePane.usesShellFolder(fc)) { 1157 try { 1158 ShellFolder shellFolder = ShellFolder.getShellFolder(dir); 1159 1160 if (shellFolder.isLink()) { 1161 File linkedTo = shellFolder.getLinkLocation(); 1162 1163 // If linkedTo is null we try to use dir 1164 if (linkedTo != null) { 1165 if (fc.isTraversable(linkedTo)) { 1166 dir = linkedTo; 1167 } else { 1168 return; 1169 } 1170 } else { 1171 dir = shellFolder; 1172 } 1173 } 1174 } catch (FileNotFoundException ex) { 1175 return; 1176 } 1177 } 1178 fc.setCurrentDirectory(dir); 1179 if (fc.getFileSelectionMode() == JFileChooser.FILES_AND_DIRECTORIES && 1180 fc.getFileSystemView().isFileSystem(dir)) { 1181 1182 setFileName(dir.getAbsolutePath()); 1183 } 1184 } 1185 1186 1187 // ***************************************** 1188 // ***** default AcceptAll file filter ***** 1189 // ***************************************** 1190 protected class AcceptAllFileFilter extends FileFilter { 1191 1192 public AcceptAllFileFilter() { 1193 } 1194 1195 public boolean accept(File f) { 1196 return true; 1197 } 1198 1199 public String getDescription() { 1200 return UIManager.getString("FileChooser.acceptAllFileFilterText"); 1201 } 1202 } 1203 1204 1205 // *********************** 1206 // * FileView operations * 1207 // *********************** 1208 protected class BasicFileView extends FileView { 1209 /* FileView type descriptions */ 1210 // PENDING(jeff) - pass in the icon cache size 1211 protected Hashtable<File,Icon> iconCache = new Hashtable<File,Icon>(); 1212 1213 public BasicFileView() { 1214 } 1215 1216 public void clearIconCache() { 1217 iconCache = new Hashtable<File,Icon>(); 1218 } 1219 1220 public String getName(File f) { 1221 // Note: Returns display name rather than file name 1222 String fileName = null; 1223 if(f != null) { 1224 fileName = getFileChooser().getFileSystemView().getSystemDisplayName(f); 1225 } 1226 return fileName; 1227 } 1228 1229 1230 public String getDescription(File f) { 1231 return f.getName(); 1232 } 1233 1234 public String getTypeDescription(File f) { 1235 String type = getFileChooser().getFileSystemView().getSystemTypeDescription(f); 1236 if (type == null) { 1237 if (f.isDirectory()) { 1238 type = directoryDescriptionText; 1239 } else { 1240 type = fileDescriptionText; 1241 } 1242 } 1243 return type; 1244 } 1245 1246 public Icon getCachedIcon(File f) { 1247 return iconCache.get(f); 1248 } 1249 1250 public void cacheIcon(File f, Icon i) { 1251 if(f == null || i == null) { 1252 return; 1253 } 1254 iconCache.put(f, i); 1255 } 1256 1257 public Icon getIcon(File f) { 1258 Icon icon = getCachedIcon(f); 1259 if(icon != null) { 1260 return icon; 1261 } 1262 icon = fileIcon; 1263 if (f != null) { 1264 FileSystemView fsv = getFileChooser().getFileSystemView(); 1265 1266 if (fsv.isFloppyDrive(f)) { 1267 icon = floppyDriveIcon; 1268 } else if (fsv.isDrive(f)) { 1269 icon = hardDriveIcon; 1270 } else if (fsv.isComputerNode(f)) { 1271 icon = computerIcon; 1272 } else if (f.isDirectory()) { 1273 icon = directoryIcon; 1274 } 1275 } 1276 cacheIcon(f, icon); 1277 return icon; 1278 } 1279 1280 public Boolean isHidden(File f) { 1281 String name = f.getName(); 1282 if(name != null && name.charAt(0) == '.') { 1283 return Boolean.TRUE; 1284 } else { 1285 return Boolean.FALSE; 1286 } 1287 } 1288 } 1289 1290 private static final TransferHandler defaultTransferHandler = new FileTransferHandler(); 1291 1292 /** 1293 * Data transfer support for the file chooser. Since files are currently presented 1294 * as a list, the list support is reused with the added flavor of DataFlavor.javaFileListFlavor 1295 */ 1296 @SuppressWarnings("serial") // JDK-implementation class 1297 static class FileTransferHandler extends TransferHandler implements UIResource { 1298 1299 /** 1300 * Create a Transferable to use as the source for a data transfer. 1301 * 1302 * @param c The component holding the data to be transfered. This 1303 * argument is provided to enable sharing of TransferHandlers by 1304 * multiple components. 1305 * @return The representation of the data to be transfered. 1306 * 1307 */ 1308 protected Transferable createTransferable(JComponent c) { 1309 Object[] values = null; 1310 if (c instanceof JList) { 1311 values = ((JList)c).getSelectedValues(); 1312 } else if (c instanceof JTable) { 1313 JTable table = (JTable)c; 1314 int[] rows = table.getSelectedRows(); 1315 if (rows != null) { 1316 values = new Object[rows.length]; 1317 for (int i=0; i<rows.length; i++) { 1318 values[i] = table.getValueAt(rows[i], 0); 1319 } 1320 } 1321 } 1322 if (values == null || values.length == 0) { 1323 return null; 1324 } 1325 1326 StringBuilder plainBuf = new StringBuilder(); 1327 StringBuilder htmlBuf = new StringBuilder(); 1328 1329 htmlBuf.append("<html>\n<body>\n<ul>\n"); 1330 1331 for (Object obj : values) { 1332 String val = ((obj == null) ? "" : obj.toString()); 1333 plainBuf.append(val + "\n"); 1334 htmlBuf.append(" <li>" + val + "\n"); 1335 } 1336 1337 // remove the last newline 1338 plainBuf.deleteCharAt(plainBuf.length() - 1); 1339 htmlBuf.append("</ul>\n</body>\n</html>"); 1340 1341 return new FileTransferable(plainBuf.toString(), htmlBuf.toString(), values); 1342 } 1343 1344 public int getSourceActions(JComponent c) { 1345 return COPY; 1346 } 1347 1348 static class FileTransferable extends BasicTransferable { 1349 1350 Object[] fileData; 1351 1352 FileTransferable(String plainData, String htmlData, Object[] fileData) { 1353 super(plainData, htmlData); 1354 this.fileData = fileData; 1355 } 1356 1357 /** 1358 * Best format of the file chooser is DataFlavor.javaFileListFlavor. 1359 */ 1360 protected DataFlavor[] getRicherFlavors() { 1361 DataFlavor[] flavors = new DataFlavor[1]; 1362 flavors[0] = DataFlavor.javaFileListFlavor; 1363 return flavors; 1364 } 1365 1366 /** 1367 * The only richer format supported is the file list flavor 1368 */ 1369 protected Object getRicherData(DataFlavor flavor) { 1370 if (DataFlavor.javaFileListFlavor.equals(flavor)) { 1371 ArrayList<Object> files = new ArrayList<Object>(); 1372 for (Object file : this.fileData) { 1373 files.add(file); 1374 } 1375 return files; 1376 } 1377 return null; 1378 } 1379 1380 } 1381 } 1382 }