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