1 /*
   2  * Copyright (c) 2003, 2009, 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 package sun.swing;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.beans.PropertyChangeEvent;
  30 import java.beans.PropertyChangeListener;
  31 import java.io.*;
  32 import java.text.DateFormat;
  33 import java.text.MessageFormat;
  34 import java.util.*;
  35 import java.util.List;
  36 import java.util.concurrent.Callable;
  37 
  38 import javax.accessibility.AccessibleContext;
  39 import javax.swing.*;
  40 import javax.swing.border.*;
  41 import javax.swing.event.*;
  42 import javax.swing.filechooser.*;
  43 import javax.swing.plaf.basic.*;
  44 import javax.swing.table.*;
  45 import javax.swing.text.*;
  46 
  47 import sun.awt.shell.*;
  48 
  49 /**
  50  * <b>WARNING:</b> This class is an implementation detail and is only
  51  * public so that it can be used by two packages. You should NOT consider
  52  * this public API.
  53  * <p>
  54  * This component is intended to be used in a subclass of
  55  * javax.swing.plaf.basic.BasicFileChooserUI. It realies heavily on the
  56  * implementation of BasicFileChooserUI, and is intended to be API compatible
  57  * with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI.
  58  *
  59  * @author Leif Samuelsson
  60  */
  61 public class FilePane extends JPanel implements PropertyChangeListener {
  62     // Constants for actions. These are used for the actions' ACTION_COMMAND_KEY
  63     // and as keys in the action maps for FilePane and the corresponding UI classes
  64 
  65     public final static String ACTION_APPROVE_SELECTION = "approveSelection";
  66     public final static String ACTION_CANCEL            = "cancelSelection";
  67     public final static String ACTION_EDIT_FILE_NAME    = "editFileName";
  68     public final static String ACTION_REFRESH           = "refresh";
  69     public final static String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up";
  70     public final static String ACTION_NEW_FOLDER        = "New Folder";
  71     public final static String ACTION_VIEW_LIST         = "viewTypeList";
  72     public final static String ACTION_VIEW_DETAILS      = "viewTypeDetails";
  73 
  74     private Action[] actions;
  75 
  76     // "enums" for setViewType()
  77     public  static final int VIEWTYPE_LIST     = 0;
  78     public  static final int VIEWTYPE_DETAILS  = 1;
  79     private static final int VIEWTYPE_COUNT    = 2;
  80 
  81     private int viewType = -1;
  82     private JPanel[] viewPanels = new JPanel[VIEWTYPE_COUNT];
  83     private JPanel currentViewPanel;
  84     private String[] viewTypeActionNames;
  85 
  86     private String filesListAccessibleName = null;
  87     private String filesDetailsAccessibleName = null;
  88 
  89     private JPopupMenu contextMenu;
  90     private JMenu viewMenu;
  91 
  92     private String viewMenuLabelText;
  93     private String refreshActionLabelText;
  94     private String newFolderActionLabelText;
  95 
  96     private String kiloByteString;
  97     private String megaByteString;
  98     private String gigaByteString;
  99 
 100     private String renameErrorTitleText;
 101     private String renameErrorText;
 102     private String renameErrorFileExistsText;
 103 
 104     private static final Cursor waitCursor =
 105         Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
 106 
 107     private final KeyListener detailsKeyListener = new KeyAdapter() {
 108         private final long timeFactor;
 109 
 110         private final StringBuilder typedString = new StringBuilder();
 111 
 112         private long lastTime = 1000L;
 113 
 114         {
 115             Long l = (Long) UIManager.get("Table.timeFactor");
 116             timeFactor = (l != null) ? l : 1000L;
 117         }
 118 
 119         /**
 120          * Moves the keyboard focus to the first element whose prefix matches
 121          * the sequence of alphanumeric keys pressed by the user with delay
 122          * less than value of <code>timeFactor</code>. Subsequent same key
 123          * presses move the keyboard focus to the next object that starts with
 124          * the same letter until another key is pressed, then it is treated
 125          * as the prefix with appropriate number of the same letters followed
 126          * by first typed another letter.
 127          */
 128         public void keyTyped(KeyEvent e) {
 129             BasicDirectoryModel model = getModel();
 130             int rowCount = model.getSize();
 131 
 132             if (detailsTable == null || rowCount == 0 ||
 133                     e.isAltDown() || e.isControlDown() || e.isMetaDown()) {
 134                 return;
 135             }
 136 
 137             InputMap inputMap = detailsTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 138             KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
 139 
 140             if (inputMap != null && inputMap.get(key) != null) {
 141                 return;
 142             }
 143 
 144             int startIndex = detailsTable.getSelectionModel().getLeadSelectionIndex();
 145 
 146             if (startIndex < 0) {
 147                 startIndex = 0;
 148             }
 149 
 150             if (startIndex >= rowCount) {
 151                 startIndex = rowCount - 1;
 152             }
 153 
 154             char c = e.getKeyChar();
 155 
 156             long time = e.getWhen();
 157 
 158             if (time - lastTime < timeFactor) {
 159                 if (typedString.length() == 1 && typedString.charAt(0) == c) {
 160                     // Subsequent same key presses move the keyboard focus to the next
 161                     // object that starts with the same letter.
 162                     startIndex++;
 163                 } else {
 164                     typedString.append(c);
 165                 }
 166             } else {
 167                 startIndex++;
 168 
 169                 typedString.setLength(0);
 170                 typedString.append(c);
 171             }
 172 
 173             lastTime = time;
 174 
 175             if (startIndex >= rowCount) {
 176                 startIndex = 0;
 177             }
 178 
 179             // Find next file
 180             int index = getNextMatch(startIndex, rowCount - 1);
 181 
 182             if (index < 0 && startIndex > 0) { // wrap
 183                 index = getNextMatch(0, startIndex - 1);
 184             }
 185 
 186             if (index >= 0) {
 187                 detailsTable.getSelectionModel().setSelectionInterval(index, index);
 188 
 189                 Rectangle cellRect = detailsTable.getCellRect(index,
 190                         detailsTable.convertColumnIndexToView(COLUMN_FILENAME), false);
 191                 detailsTable.scrollRectToVisible(cellRect);
 192             }
 193         }
 194 
 195         private int getNextMatch(int startIndex, int finishIndex) {
 196             BasicDirectoryModel model = getModel();
 197             JFileChooser fileChooser = getFileChooser();
 198             DetailsTableRowSorter rowSorter = getRowSorter();
 199 
 200             String prefix = typedString.toString().toLowerCase();
 201 
 202             // Search element
 203             for (int index = startIndex; index <= finishIndex; index++) {
 204                 File file = (File) model.getElementAt(rowSorter.convertRowIndexToModel(index));
 205 
 206                 String fileName = fileChooser.getName(file).toLowerCase();
 207 
 208                 if (fileName.startsWith(prefix)) {
 209                     return index;
 210                 }
 211             }
 212 
 213             return -1;
 214         }
 215     };
 216 
 217     private FocusListener editorFocusListener = new FocusAdapter() {
 218         public void focusLost(FocusEvent e) {
 219             if (! e.isTemporary()) {
 220                 applyEdit();
 221             }
 222         }
 223     };
 224 
 225     private static FocusListener repaintListener = new FocusListener() {
 226         public void focusGained(FocusEvent fe) {
 227             repaintSelection(fe.getSource());
 228         }
 229 
 230         public void focusLost(FocusEvent fe) {
 231             repaintSelection(fe.getSource());
 232         }
 233 
 234         private void repaintSelection(Object source) {
 235             if (source instanceof JList) {
 236                 repaintListSelection((JList)source);
 237             } else if (source instanceof JTable) {
 238                 repaintTableSelection((JTable)source);
 239             }
 240         }
 241 
 242         private void repaintListSelection(JList list) {
 243             int[] indices = list.getSelectedIndices();
 244             for (int i : indices) {
 245                 Rectangle bounds = list.getCellBounds(i, i);
 246                 list.repaint(bounds);
 247             }
 248         }
 249 
 250         private void repaintTableSelection(JTable table) {
 251             int minRow = table.getSelectionModel().getMinSelectionIndex();
 252             int maxRow = table.getSelectionModel().getMaxSelectionIndex();
 253             if (minRow == -1 || maxRow == -1) {
 254                 return;
 255             }
 256 
 257             int col0 = table.convertColumnIndexToView(COLUMN_FILENAME);
 258 
 259             Rectangle first = table.getCellRect(minRow, col0, false);
 260             Rectangle last = table.getCellRect(maxRow, col0, false);
 261             Rectangle dirty = first.union(last);
 262             table.repaint(dirty);
 263         }
 264     };
 265 
 266     private boolean smallIconsView = false;
 267     private Border  listViewBorder;
 268     private Color   listViewBackground;
 269     private boolean listViewWindowsStyle;
 270     private boolean readOnly;
 271     private boolean fullRowSelection = false;
 272 
 273     private ListSelectionModel listSelectionModel;
 274     private JList list;
 275     private JTable detailsTable;
 276 
 277     private static final int COLUMN_FILENAME = 0;
 278 
 279     // Provides a way to recognize a newly created folder, so it can
 280     // be selected when it appears in the model.
 281     private File newFolderFile;
 282 
 283     // Used for accessing methods in the corresponding UI class
 284     private FileChooserUIAccessor fileChooserUIAccessor;
 285     private DetailsTableModel detailsTableModel;
 286     private DetailsTableRowSorter rowSorter;
 287 
 288     public FilePane(FileChooserUIAccessor fileChooserUIAccessor) {
 289         super(new BorderLayout());
 290 
 291         this.fileChooserUIAccessor = fileChooserUIAccessor;
 292 
 293         installDefaults();
 294         createActionMap();
 295     }
 296 
 297     public void uninstallUI() {
 298         if (getModel() != null) {
 299             getModel().removePropertyChangeListener(this);
 300         }
 301     }
 302 
 303     protected JFileChooser getFileChooser() {
 304         return fileChooserUIAccessor.getFileChooser();
 305     }
 306 
 307     protected BasicDirectoryModel getModel() {
 308         return fileChooserUIAccessor.getModel();
 309     }
 310 
 311     public int getViewType() {
 312         return viewType;
 313     }
 314 
 315     public void setViewType(int viewType) {
 316         if (viewType == this.viewType) {
 317             return;
 318         }
 319 
 320         int oldValue = this.viewType;
 321         this.viewType = viewType;
 322 
 323         JPanel createdViewPanel = null;
 324         Component newFocusOwner = null;
 325 
 326         switch (viewType) {
 327           case VIEWTYPE_LIST:
 328             if (viewPanels[viewType] == null) {
 329                 createdViewPanel = fileChooserUIAccessor.createList();
 330                 if (createdViewPanel == null) {
 331                     createdViewPanel = createList();
 332                 }
 333 
 334                 list = (JList) findChildComponent(createdViewPanel, JList.class);
 335                 if (listSelectionModel == null) {
 336                     listSelectionModel = list.getSelectionModel();
 337                     if (detailsTable != null) {
 338                         detailsTable.setSelectionModel(listSelectionModel);
 339                     }
 340                 } else {
 341                     list.setSelectionModel(listSelectionModel);
 342                 }
 343             }
 344             list.setLayoutOrientation(JList.VERTICAL_WRAP);
 345             newFocusOwner = list;
 346             break;
 347 
 348           case VIEWTYPE_DETAILS:
 349             if (viewPanels[viewType] == null) {
 350                 createdViewPanel = fileChooserUIAccessor.createDetailsView();
 351                 if (createdViewPanel == null) {
 352                     createdViewPanel = createDetailsView();
 353                 }
 354 
 355                 detailsTable = (JTable) findChildComponent(createdViewPanel, JTable.class);
 356                 detailsTable.setRowHeight(Math.max(detailsTable.getFont().getSize() + 4, 16 + 1));
 357                 if (listSelectionModel != null) {
 358                     detailsTable.setSelectionModel(listSelectionModel);
 359                 }
 360             }
 361             newFocusOwner = detailsTable;
 362             break;
 363         }
 364 
 365         if (createdViewPanel != null) {
 366             viewPanels[viewType] = createdViewPanel;
 367             recursivelySetInheritsPopupMenu(createdViewPanel, true);
 368         }
 369 
 370         boolean isFocusOwner = false;
 371 
 372         if (currentViewPanel != null) {
 373             Component owner = DefaultKeyboardFocusManager.
 374                     getCurrentKeyboardFocusManager().getPermanentFocusOwner();
 375 
 376             isFocusOwner = owner == detailsTable || owner == list;
 377 
 378             remove(currentViewPanel);
 379         }
 380 
 381         currentViewPanel = viewPanels[viewType];
 382         add(currentViewPanel, BorderLayout.CENTER);
 383 
 384         if (isFocusOwner && newFocusOwner != null) {
 385             newFocusOwner.requestFocusInWindow();
 386         }
 387 
 388         revalidate();
 389         repaint();
 390         updateViewMenu();
 391         firePropertyChange("viewType", oldValue, viewType);
 392     }
 393 
 394     class ViewTypeAction extends AbstractAction {
 395         private int viewType;
 396 
 397         ViewTypeAction(int viewType) {
 398             super(viewTypeActionNames[viewType]);
 399             this.viewType = viewType;
 400 
 401             String cmd;
 402             switch (viewType) {
 403                 case VIEWTYPE_LIST:    cmd = ACTION_VIEW_LIST;    break;
 404                 case VIEWTYPE_DETAILS: cmd = ACTION_VIEW_DETAILS; break;
 405                 default:               cmd = (String)getValue(Action.NAME);
 406             }
 407             putValue(Action.ACTION_COMMAND_KEY, cmd);
 408         }
 409 
 410         public void actionPerformed(ActionEvent e) {
 411             setViewType(viewType);
 412         }
 413     }
 414 
 415     public Action getViewTypeAction(int viewType) {
 416         return new ViewTypeAction(viewType);
 417     }
 418 
 419     private static void recursivelySetInheritsPopupMenu(Container container, boolean b) {
 420         if (container instanceof JComponent) {
 421             ((JComponent)container).setInheritsPopupMenu(b);
 422         }
 423         int n = container.getComponentCount();
 424         for (int i = 0; i < n; i++) {
 425             recursivelySetInheritsPopupMenu((Container)container.getComponent(i), b);
 426         }
 427     }
 428 
 429     protected void installDefaults() {
 430         Locale l = getFileChooser().getLocale();
 431 
 432         listViewBorder       = UIManager.getBorder("FileChooser.listViewBorder");
 433         listViewBackground   = UIManager.getColor("FileChooser.listViewBackground");
 434         listViewWindowsStyle = UIManager.getBoolean("FileChooser.listViewWindowsStyle");
 435         readOnly             = UIManager.getBoolean("FileChooser.readOnly");
 436 
 437         // TODO: On windows, get the following localized strings from the OS
 438 
 439         viewMenuLabelText =
 440                         UIManager.getString("FileChooser.viewMenuLabelText", l);
 441         refreshActionLabelText =
 442                         UIManager.getString("FileChooser.refreshActionLabelText", l);
 443         newFolderActionLabelText =
 444                         UIManager.getString("FileChooser.newFolderActionLabelText", l);
 445 
 446         viewTypeActionNames = new String[VIEWTYPE_COUNT];
 447         viewTypeActionNames[VIEWTYPE_LIST] =
 448                         UIManager.getString("FileChooser.listViewActionLabelText", l);
 449         viewTypeActionNames[VIEWTYPE_DETAILS] =
 450                         UIManager.getString("FileChooser.detailsViewActionLabelText", l);
 451 
 452         kiloByteString = UIManager.getString("FileChooser.fileSizeKiloBytes", l);
 453         megaByteString = UIManager.getString("FileChooser.fileSizeMegaBytes", l);
 454         gigaByteString = UIManager.getString("FileChooser.fileSizeGigaBytes", l);
 455         fullRowSelection = UIManager.getBoolean("FileView.fullRowSelection");
 456 
 457         filesListAccessibleName = UIManager.getString("FileChooser.filesListAccessibleName", l);
 458         filesDetailsAccessibleName = UIManager.getString("FileChooser.filesDetailsAccessibleName", l);
 459 
 460         renameErrorTitleText = UIManager.getString("FileChooser.renameErrorTitleText", l);
 461         renameErrorText = UIManager.getString("FileChooser.renameErrorText", l);
 462         renameErrorFileExistsText = UIManager.getString("FileChooser.renameErrorFileExistsText", l);
 463     }
 464 
 465     /**
 466      * Fetches the command list for the FilePane. These commands
 467      * are useful for binding to events, such as in a keymap.
 468      *
 469      * @return the command list
 470      */
 471     public Action[] getActions() {
 472         if (actions == null) {
 473             class FilePaneAction extends AbstractAction {
 474                 FilePaneAction(String name) {
 475                     this(name, name);
 476                 }
 477 
 478                 FilePaneAction(String name, String cmd) {
 479                     super(name);
 480                     putValue(Action.ACTION_COMMAND_KEY, cmd);
 481                 }
 482 
 483                 public void actionPerformed(ActionEvent e) {
 484                     String cmd = (String)getValue(Action.ACTION_COMMAND_KEY);
 485 
 486                     if (cmd == ACTION_CANCEL) {
 487                         if (editFile != null) {
 488                            cancelEdit();
 489                         } else {
 490                            getFileChooser().cancelSelection();
 491                         }
 492                     } else if (cmd == ACTION_EDIT_FILE_NAME) {
 493                         JFileChooser fc = getFileChooser();
 494                         int index = listSelectionModel.getMinSelectionIndex();
 495                         if (index >= 0 && editFile == null &&
 496                             (!fc.isMultiSelectionEnabled() ||
 497                              fc.getSelectedFiles().length <= 1)) {
 498 
 499                             editFileName(index);
 500                         }
 501                     } else if (cmd == ACTION_REFRESH) {
 502                         getFileChooser().rescanCurrentDirectory();
 503                     }
 504                 }
 505 
 506                 public boolean isEnabled() {
 507                     String cmd = (String)getValue(Action.ACTION_COMMAND_KEY);
 508                     if (cmd == ACTION_CANCEL) {
 509                         return getFileChooser().isEnabled();
 510                     } else if (cmd == ACTION_EDIT_FILE_NAME) {
 511                         return !readOnly && getFileChooser().isEnabled();
 512                     } else {
 513                         return true;
 514                     }
 515                 }
 516             }
 517 
 518             ArrayList<Action> actionList = new ArrayList<Action>(8);
 519             Action action;
 520 
 521             actionList.add(new FilePaneAction(ACTION_CANCEL));
 522             actionList.add(new FilePaneAction(ACTION_EDIT_FILE_NAME));
 523             actionList.add(new FilePaneAction(refreshActionLabelText, ACTION_REFRESH));
 524 
 525             action = fileChooserUIAccessor.getApproveSelectionAction();
 526             if (action != null) {
 527                 actionList.add(action);
 528             }
 529             action = fileChooserUIAccessor.getChangeToParentDirectoryAction();
 530             if (action != null) {
 531                 actionList.add(action);
 532             }
 533             action = getNewFolderAction();
 534             if (action != null) {
 535                 actionList.add(action);
 536             }
 537             action = getViewTypeAction(VIEWTYPE_LIST);
 538             if (action != null) {
 539                 actionList.add(action);
 540             }
 541             action = getViewTypeAction(VIEWTYPE_DETAILS);
 542             if (action != null) {
 543                 actionList.add(action);
 544             }
 545             actions = actionList.toArray(new Action[actionList.size()]);
 546         }
 547 
 548         return actions;
 549     }
 550 
 551     protected void createActionMap() {
 552         addActionsToMap(super.getActionMap(), getActions());
 553     }
 554 
 555 
 556     public static void addActionsToMap(ActionMap map, Action[] actions) {
 557         if (map != null && actions != null) {
 558             for (Action a : actions) {
 559                 String cmd = (String)a.getValue(Action.ACTION_COMMAND_KEY);
 560                 if (cmd == null) {
 561                     cmd = (String)a.getValue(Action.NAME);
 562                 }
 563                 map.put(cmd, a);
 564             }
 565         }
 566     }
 567 
 568 
 569     private void updateListRowCount(JList list) {
 570         if (smallIconsView) {
 571             list.setVisibleRowCount(getModel().getSize() / 3);
 572         } else {
 573             list.setVisibleRowCount(-1);
 574         }
 575     }
 576 
 577     public JPanel createList() {
 578         JPanel p = new JPanel(new BorderLayout());
 579         final JFileChooser fileChooser = getFileChooser();
 580         final JList<Object> list = new JList<Object>() {
 581             public int getNextMatch(String prefix, int startIndex, Position.Bias bias) {
 582                 ListModel model = getModel();
 583                 int max = model.getSize();
 584                 if (prefix == null || startIndex < 0 || startIndex >= max) {
 585                     throw new IllegalArgumentException();
 586                 }
 587                 // start search from the next element before/after the selected element
 588                 boolean backwards = (bias == Position.Bias.Backward);
 589                 for (int i = startIndex; backwards ? i >= 0 : i < max; i += (backwards ?  -1 : 1)) {
 590                     String filename = fileChooser.getName((File)model.getElementAt(i));
 591                     if (filename.regionMatches(true, 0, prefix, 0, prefix.length())) {
 592                         return i;
 593                     }
 594                 }
 595                 return -1;
 596             }
 597         };
 598         list.setCellRenderer(new FileRenderer());
 599         list.setLayoutOrientation(JList.VERTICAL_WRAP);
 600 
 601         // 4835633 : tell BasicListUI that this is a file list
 602         list.putClientProperty("List.isFileList", Boolean.TRUE);
 603 
 604         if (listViewWindowsStyle) {
 605             list.addFocusListener(repaintListener);
 606         }
 607 
 608         updateListRowCount(list);
 609 
 610         getModel().addListDataListener(new ListDataListener() {
 611             public void intervalAdded(ListDataEvent e) {
 612                 updateListRowCount(list);
 613             }
 614             public void intervalRemoved(ListDataEvent e) {
 615                 updateListRowCount(list);
 616             }
 617             public void contentsChanged(ListDataEvent e) {
 618                 if (isShowing()) {
 619                     clearSelection();
 620                 }
 621                 updateListRowCount(list);
 622             }
 623         });
 624 
 625         getModel().addPropertyChangeListener(this);
 626 
 627         if (fileChooser.isMultiSelectionEnabled()) {
 628             list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 629         } else {
 630             list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 631         }
 632         list.setModel(new SortableListModel());
 633 
 634         list.addListSelectionListener(createListSelectionListener());
 635         list.addMouseListener(getMouseHandler());
 636 
 637         JScrollPane scrollpane = new JScrollPane(list);
 638         if (listViewBackground != null) {
 639             list.setBackground(listViewBackground);
 640         }
 641         if (listViewBorder != null) {
 642             scrollpane.setBorder(listViewBorder);
 643         }
 644 
 645         list.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesListAccessibleName);
 646 
 647         p.add(scrollpane, BorderLayout.CENTER);
 648         return p;
 649     }
 650 
 651     /**
 652      * This model allows for sorting JList
 653      */
 654     private class SortableListModel extends AbstractListModel<Object>
 655             implements TableModelListener, RowSorterListener {
 656 
 657         public SortableListModel() {
 658             getDetailsTableModel().addTableModelListener(this);
 659             getRowSorter().addRowSorterListener(this);
 660         }
 661 
 662         public int getSize() {
 663             return getModel().getSize();
 664         }
 665 
 666         public Object getElementAt(int index) {
 667             // JList doesn't support RowSorter so far, so we put it into the list model
 668             return getModel().getElementAt(getRowSorter().convertRowIndexToModel(index));
 669         }
 670 
 671         public void tableChanged(TableModelEvent e) {
 672             fireContentsChanged(this, 0, getSize());
 673         }
 674 
 675         public void sorterChanged(RowSorterEvent e) {
 676             fireContentsChanged(this, 0, getSize());
 677         }
 678     }
 679 
 680     private DetailsTableModel getDetailsTableModel() {
 681         if(detailsTableModel == null) {
 682             detailsTableModel = new DetailsTableModel(getFileChooser());
 683         }
 684         return detailsTableModel;
 685     }
 686 
 687     class DetailsTableModel extends AbstractTableModel implements ListDataListener {
 688         JFileChooser chooser;
 689         BasicDirectoryModel directoryModel;
 690 
 691         ShellFolderColumnInfo[] columns;
 692         int[] columnMap;
 693 
 694         DetailsTableModel(JFileChooser fc) {
 695             this.chooser = fc;
 696             directoryModel = getModel();
 697             directoryModel.addListDataListener(this);
 698 
 699             updateColumnInfo();
 700         }
 701 
 702         void updateColumnInfo() {
 703             File dir = chooser.getCurrentDirectory();
 704             if (dir != null && usesShellFolder(chooser)) {
 705                 try {
 706                     dir = ShellFolder.getShellFolder(dir);
 707                 } catch (FileNotFoundException e) {
 708                     // Leave dir without changing
 709                 }
 710             }
 711 
 712             ShellFolderColumnInfo[] allColumns = ShellFolder.getFolderColumns(dir);
 713 
 714             ArrayList<ShellFolderColumnInfo> visibleColumns =
 715                     new ArrayList<ShellFolderColumnInfo>();
 716             columnMap = new int[allColumns.length];
 717             for (int i = 0; i < allColumns.length; i++) {
 718                 ShellFolderColumnInfo column = allColumns[i];
 719                 if (column.isVisible()) {
 720                     columnMap[visibleColumns.size()] = i;
 721                     visibleColumns.add(column);
 722                 }
 723             }
 724 
 725             columns = new ShellFolderColumnInfo[visibleColumns.size()];
 726             visibleColumns.toArray(columns);
 727             columnMap = Arrays.copyOf(columnMap, columns.length);
 728 
 729             List<? extends RowSorter.SortKey> sortKeys =
 730                     (rowSorter == null) ? null : rowSorter.getSortKeys();
 731             fireTableStructureChanged();
 732             restoreSortKeys(sortKeys);
 733         }
 734 
 735         private void restoreSortKeys(List<? extends RowSorter.SortKey> sortKeys) {
 736             if (sortKeys != null) {
 737                 // check if preserved sortKeys are valid for this folder
 738                 for (int i = 0; i < sortKeys.size(); i++) {
 739                     RowSorter.SortKey sortKey = sortKeys.get(i);
 740                     if (sortKey.getColumn() >= columns.length) {
 741                         sortKeys = null;
 742                         break;
 743                     }
 744                 }
 745                 if (sortKeys != null) {
 746                     rowSorter.setSortKeys(sortKeys);
 747                 }
 748             }
 749         }
 750 
 751         public int getRowCount() {
 752             return directoryModel.getSize();
 753         }
 754 
 755         public int getColumnCount() {
 756             return columns.length;
 757         }
 758 
 759         public Object getValueAt(int row, int col) {
 760             // Note: It is very important to avoid getting info on drives, as
 761             // this will trigger "No disk in A:" and similar dialogs.
 762             //
 763             // Use (f.exists() && !chooser.getFileSystemView().isFileSystemRoot(f)) to
 764             // determine if it is safe to call methods directly on f.
 765             return getFileColumnValue((File)directoryModel.getElementAt(row), col);
 766         }
 767 
 768         private Object getFileColumnValue(File f, int col) {
 769             return (col == COLUMN_FILENAME)
 770                     ? f // always return the file itself for the 1st column
 771                     : ShellFolder.getFolderColumnValue(f, columnMap[col]);
 772         }
 773 
 774         public void setValueAt(Object value, int row, int col) {
 775             if (col == COLUMN_FILENAME) {
 776                 final JFileChooser chooser = getFileChooser();
 777                 File f = (File)getValueAt(row, col);
 778                 if (f != null) {
 779                     String oldDisplayName = chooser.getName(f);
 780                     String oldFileName = f.getName();
 781                     String newDisplayName = ((String)value).trim();
 782                     String newFileName;
 783 
 784                     if (!newDisplayName.equals(oldDisplayName)) {
 785                         newFileName = newDisplayName;
 786                         //Check if extension is hidden from user
 787                         int i1 = oldFileName.length();
 788                         int i2 = oldDisplayName.length();
 789                         if (i1 > i2 && oldFileName.charAt(i2) == '.') {
 790                             newFileName = newDisplayName + oldFileName.substring(i2);
 791                         }
 792 
 793                         // rename
 794                         FileSystemView fsv = chooser.getFileSystemView();
 795                         final File f2 = fsv.createFileObject(f.getParentFile(), newFileName);
 796                         if (f2.exists()) {
 797                             JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText,
 798                                     oldFileName), renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
 799                         } else {
 800                             if (FilePane.this.getModel().renameFile(f, f2)) {
 801                                 if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
 802                                     // The setSelectedFile method produces a new setValueAt invocation while the JTable
 803                                     // is editing. Postpone file selection to be sure that edit mode of the JTable
 804                                     // is completed
 805                                     SwingUtilities.invokeLater(new Runnable() {
 806                                         public void run() {
 807                                             if (chooser.isMultiSelectionEnabled()) {
 808                                                 chooser.setSelectedFiles(new File[]{f2});
 809                                             } else {
 810                                                 chooser.setSelectedFile(f2);
 811                                             }
 812                                         }
 813                                     });
 814                                 } else {
 815                                     // Could be because of delay in updating Desktop folder
 816                                     // chooser.setSelectedFile(null);
 817                                 }
 818                             } else {
 819                                 JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName),
 820                                         renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
 821                             }
 822                         }
 823                     }
 824                 }
 825             }
 826         }
 827 
 828         public boolean isCellEditable(int row, int column) {
 829             File currentDirectory = getFileChooser().getCurrentDirectory();
 830             return (!readOnly && column == COLUMN_FILENAME && canWrite(currentDirectory));
 831         }
 832 
 833         public void contentsChanged(ListDataEvent e) {
 834             // Update the selection after the model has been updated
 835             new DelayedSelectionUpdater();
 836             fireTableDataChanged();
 837         }
 838 
 839         public void intervalAdded(ListDataEvent e) {
 840             int i0 = e.getIndex0();
 841             int i1 = e.getIndex1();
 842             if (i0 == i1) {
 843                 File file = (File)getModel().getElementAt(i0);
 844                 if (file.equals(newFolderFile)) {
 845                     new DelayedSelectionUpdater(file);
 846                     newFolderFile = null;
 847                 }
 848             }
 849 
 850             fireTableRowsInserted(e.getIndex0(), e.getIndex1());
 851         }
 852         public void intervalRemoved(ListDataEvent e) {
 853             fireTableRowsDeleted(e.getIndex0(), e.getIndex1());
 854         }
 855 
 856         public ShellFolderColumnInfo[] getColumns() {
 857             return columns;
 858         }
 859     }
 860 
 861 
 862     private void updateDetailsColumnModel(JTable table) {
 863         if (table != null) {
 864             ShellFolderColumnInfo[] columns = detailsTableModel.getColumns();
 865 
 866             TableColumnModel columnModel = new DefaultTableColumnModel();
 867             for (int i = 0; i < columns.length; i++) {
 868                 ShellFolderColumnInfo dataItem = columns[i];
 869                 TableColumn column = new TableColumn(i);
 870 
 871                 String title = dataItem.getTitle();
 872                 if (title != null && title.startsWith("FileChooser.") && title.endsWith("HeaderText")) {
 873                     // the column must have a string resource that we try to get
 874                     String uiTitle = UIManager.getString(title, table.getLocale());
 875                     if (uiTitle != null) {
 876                         title = uiTitle;
 877                     }
 878                 }
 879                 column.setHeaderValue(title);
 880 
 881                 Integer width = dataItem.getWidth();
 882                 if (width != null) {
 883                     column.setPreferredWidth(width);
 884                     // otherwise we let JTable to decide the actual width
 885                 }
 886 
 887                 columnModel.addColumn(column);
 888             }
 889 
 890             // Install cell editor for editing file name
 891             if (!readOnly && columnModel.getColumnCount() > COLUMN_FILENAME) {
 892                 columnModel.getColumn(COLUMN_FILENAME).
 893                         setCellEditor(getDetailsTableCellEditor());
 894             }
 895 
 896             table.setColumnModel(columnModel);
 897         }
 898     }
 899 
 900     private DetailsTableRowSorter getRowSorter() {
 901         if (rowSorter == null) {
 902             rowSorter = new DetailsTableRowSorter();
 903         }
 904         return rowSorter;
 905     }
 906 
 907     private class DetailsTableRowSorter extends TableRowSorter<TableModel> {
 908         public DetailsTableRowSorter() {
 909             setModelWrapper(new SorterModelWrapper());
 910         }
 911 
 912         public void updateComparators(ShellFolderColumnInfo [] columns) {
 913             for (int i = 0; i < columns.length; i++) {
 914                 Comparator c = columns[i].getComparator();
 915                 if (c != null) {
 916                     c = new DirectoriesFirstComparatorWrapper(i, c);
 917                 }
 918                 setComparator(i, c);
 919             }
 920         }
 921 
 922         @Override
 923         public void sort() {
 924             ShellFolder.invoke(new Callable<Void>() {
 925                 public Void call() {
 926                     DetailsTableRowSorter.super.sort();
 927                     return null;
 928                 }
 929             });
 930         }
 931 
 932         public void modelStructureChanged() {
 933             super.modelStructureChanged();
 934             updateComparators(detailsTableModel.getColumns());
 935         }
 936 
 937         private class SorterModelWrapper extends ModelWrapper<TableModel, Integer> {
 938             public TableModel getModel() {
 939                 return getDetailsTableModel();
 940             }
 941 
 942             public int getColumnCount() {
 943                 return getDetailsTableModel().getColumnCount();
 944             }
 945 
 946             public int getRowCount() {
 947                 return getDetailsTableModel().getRowCount();
 948             }
 949 
 950             public Object getValueAt(int row, int column) {
 951                 return FilePane.this.getModel().getElementAt(row);
 952             }
 953 
 954             public Integer getIdentifier(int row) {
 955                 return row;
 956             }
 957         }
 958     }
 959 
 960     /**
 961      * This class sorts directories before files, comparing directory to
 962      * directory and file to file using the wrapped comparator.
 963      */
 964     private class DirectoriesFirstComparatorWrapper implements Comparator<File> {
 965         private Comparator comparator;
 966         private int column;
 967 
 968         public DirectoriesFirstComparatorWrapper(int column, Comparator comparator) {
 969             this.column = column;
 970             this.comparator = comparator;
 971         }
 972 
 973         public int compare(File f1, File f2) {
 974             if (f1 != null && f2 != null) {
 975                 boolean traversable1 = getFileChooser().isTraversable(f1);
 976                 boolean traversable2 = getFileChooser().isTraversable(f2);
 977                 // directories go first
 978                 if (traversable1 && !traversable2) {
 979                     return -1;
 980                 }
 981                 if (!traversable1 && traversable2) {
 982                     return 1;
 983                 }
 984             }
 985             if (detailsTableModel.getColumns()[column].isCompareByColumn()) {
 986                 return comparator.compare(
 987                         getDetailsTableModel().getFileColumnValue(f1, column),
 988                         getDetailsTableModel().getFileColumnValue(f2, column)
 989                 );
 990             }
 991             // For this column we need to pass the file itself (not a
 992             // column value) to the comparator
 993             return comparator.compare(f1, f2);
 994         }
 995     }
 996 
 997     private DetailsTableCellEditor tableCellEditor;
 998 
 999     private DetailsTableCellEditor getDetailsTableCellEditor() {
1000         if (tableCellEditor == null) {
1001             tableCellEditor = new DetailsTableCellEditor(new JTextField());
1002         }
1003         return tableCellEditor;
1004     }
1005 
1006     private class DetailsTableCellEditor extends DefaultCellEditor {
1007         private final JTextField tf;
1008 
1009         public DetailsTableCellEditor(JTextField tf) {
1010             super(tf);
1011             this.tf = tf;
1012             tf.setName("Table.editor");
1013             tf.addFocusListener(editorFocusListener);
1014         }
1015 
1016         public Component getTableCellEditorComponent(JTable table, Object value,
1017                                                      boolean isSelected, int row, int column) {
1018             Component comp = super.getTableCellEditorComponent(table, value,
1019                     isSelected, row, column);
1020             if (value instanceof File) {
1021                 tf.setText(getFileChooser().getName((File) value));
1022                 tf.selectAll();
1023             }
1024             return comp;
1025         }
1026     }
1027 
1028 
1029     class DetailsTableCellRenderer extends DefaultTableCellRenderer {
1030         JFileChooser chooser;
1031         DateFormat df;
1032 
1033         DetailsTableCellRenderer(JFileChooser chooser) {
1034             this.chooser = chooser;
1035             df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT,
1036                                                 chooser.getLocale());
1037         }
1038 
1039         public void setBounds(int x, int y, int width, int height) {
1040         if (getHorizontalAlignment() == SwingConstants.LEADING &&
1041                     !fullRowSelection) {
1042                 // Restrict width to actual text
1043                 width = Math.min(width, this.getPreferredSize().width+4);
1044             } else {
1045                 x -= 4;
1046             }
1047             super.setBounds(x, y, width, height);
1048         }
1049 
1050 
1051         public Insets getInsets(Insets i) {
1052             // Provide some space between columns
1053             i = super.getInsets(i);
1054             i.left  += 4;
1055             i.right += 4;
1056             return i;
1057         }
1058 
1059         public Component getTableCellRendererComponent(JTable table, Object value,
1060                               boolean isSelected, boolean hasFocus, int row, int column) {
1061 
1062             if ((table.convertColumnIndexToModel(column) != COLUMN_FILENAME ||
1063                     (listViewWindowsStyle && !table.isFocusOwner())) &&
1064                     !fullRowSelection) {
1065                 isSelected = false;
1066             }
1067 
1068             super.getTableCellRendererComponent(table, value, isSelected,
1069                                                        hasFocus, row, column);
1070 
1071             setIcon(null);
1072 
1073             int modelColumn = table.convertColumnIndexToModel(column);
1074             ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn];
1075 
1076             Integer alignment = columnInfo.getAlignment();
1077             if (alignment == null) {
1078                 alignment = (value instanceof Number)
1079                         ? SwingConstants.RIGHT
1080                         : SwingConstants.LEADING;
1081             }
1082 
1083             setHorizontalAlignment(alignment);
1084 
1085             // formatting cell text
1086             // TODO: it's rather a temporary trick, to be revised
1087             String text;
1088 
1089             if (value == null) {
1090                 text = "";
1091 
1092             } else if (value instanceof File) {
1093                 File file = (File)value;
1094                 text = chooser.getName(file);
1095                 Icon icon = chooser.getIcon(file);
1096                 setIcon(icon);
1097 
1098             } else if (value instanceof Long) {
1099                 long len = ((Long) value) / 1024L;
1100                 if (listViewWindowsStyle) {
1101                     text = MessageFormat.format(kiloByteString, len + 1);
1102                 } else if (len < 1024L) {
1103                     text = MessageFormat.format(kiloByteString, (len == 0L) ? 1L : len);
1104                 } else {
1105                     len /= 1024L;
1106                     if (len < 1024L) {
1107                         text = MessageFormat.format(megaByteString, len);
1108                     } else {
1109                         len /= 1024L;
1110                         text = MessageFormat.format(gigaByteString, len);
1111                     }
1112                 }
1113 
1114             } else if (value instanceof Date) {
1115                 text = df.format((Date)value);
1116 
1117             } else {
1118                 text = value.toString();
1119             }
1120 
1121             setText(text);
1122 
1123             return this;
1124         }
1125     }
1126 
1127     public JPanel createDetailsView() {
1128         final JFileChooser chooser = getFileChooser();
1129 
1130         JPanel p = new JPanel(new BorderLayout());
1131 
1132         final JTable detailsTable = new JTable(getDetailsTableModel()) {
1133             // Handle Escape key events here
1134             protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
1135                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE && getCellEditor() == null) {
1136                     // We are not editing, forward to filechooser.
1137                     chooser.dispatchEvent(e);
1138                     return true;
1139                 }
1140                 return super.processKeyBinding(ks, e, condition, pressed);
1141             }
1142 
1143             public void tableChanged(TableModelEvent e) {
1144                 super.tableChanged(e);
1145 
1146                 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
1147                     // update header with possibly changed column set
1148                     updateDetailsColumnModel(this);
1149                 }
1150             }
1151         };
1152 
1153         detailsTable.setRowSorter(getRowSorter());
1154         detailsTable.setAutoCreateColumnsFromModel(false);
1155         detailsTable.setComponentOrientation(chooser.getComponentOrientation());
1156         detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1157         detailsTable.setShowGrid(false);
1158         detailsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
1159         detailsTable.addKeyListener(detailsKeyListener);
1160 
1161         Font font = list.getFont();
1162         detailsTable.setFont(font);
1163         detailsTable.setIntercellSpacing(new Dimension(0, 0));
1164 
1165         TableCellRenderer headerRenderer =
1166                 new AlignableTableHeaderRenderer(detailsTable.getTableHeader().getDefaultRenderer());
1167         detailsTable.getTableHeader().setDefaultRenderer(headerRenderer);
1168         TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser);
1169         detailsTable.setDefaultRenderer(Object.class, cellRenderer);
1170 
1171         // So that drag can be started on a mouse press
1172         detailsTable.getColumnModel().getSelectionModel().
1173             setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1174 
1175         detailsTable.addMouseListener(getMouseHandler());
1176         // No need to addListSelectionListener because selections are forwarded
1177         // to our JList.
1178 
1179         // 4835633 : tell BasicTableUI that this is a file list
1180         detailsTable.putClientProperty("Table.isFileList", Boolean.TRUE);
1181 
1182         if (listViewWindowsStyle) {
1183             detailsTable.addFocusListener(repaintListener);
1184         }
1185 
1186         // TAB/SHIFT-TAB should transfer focus and ENTER should select an item.
1187         // We don't want them to navigate within the table
1188         ActionMap am = SwingUtilities.getUIActionMap(detailsTable);
1189         am.remove("selectNextRowCell");
1190         am.remove("selectPreviousRowCell");
1191         am.remove("selectNextColumnCell");
1192         am.remove("selectPreviousColumnCell");
1193         detailsTable.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
1194                      null);
1195         detailsTable.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
1196                      null);
1197 
1198         JScrollPane scrollpane = new JScrollPane(detailsTable);
1199         scrollpane.setComponentOrientation(chooser.getComponentOrientation());
1200         LookAndFeel.installColors(scrollpane.getViewport(), "Table.background", "Table.foreground");
1201 
1202         // Adjust width of first column so the table fills the viewport when
1203         // first displayed (temporary listener).
1204         scrollpane.addComponentListener(new ComponentAdapter() {
1205             public void componentResized(ComponentEvent e) {
1206                 JScrollPane sp = (JScrollPane)e.getComponent();
1207                 fixNameColumnWidth(sp.getViewport().getSize().width);
1208                 sp.removeComponentListener(this);
1209             }
1210         });
1211 
1212         // 4835633.
1213         // If the mouse is pressed in the area below the Details view table, the
1214         // event is not dispatched to the Table MouseListener but to the
1215         // scrollpane.  Listen for that here so we can clear the selection.
1216         scrollpane.addMouseListener(new MouseAdapter() {
1217             public void mousePressed(MouseEvent e) {
1218                 JScrollPane jsp = ((JScrollPane)e.getComponent());
1219                 JTable table = (JTable)jsp.getViewport().getView();
1220 
1221                 if (!e.isShiftDown() || table.getSelectionModel().getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
1222                     clearSelection();
1223                     TableCellEditor tce = table.getCellEditor();
1224                     if (tce != null) {
1225                         tce.stopCellEditing();
1226                     }
1227                 }
1228             }
1229         });
1230 
1231         detailsTable.setForeground(list.getForeground());
1232         detailsTable.setBackground(list.getBackground());
1233 
1234         if (listViewBorder != null) {
1235             scrollpane.setBorder(listViewBorder);
1236         }
1237         p.add(scrollpane, BorderLayout.CENTER);
1238 
1239         detailsTableModel.fireTableStructureChanged();
1240 
1241         detailsTable.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesDetailsAccessibleName);
1242 
1243         return p;
1244     } // createDetailsView
1245 
1246     private class AlignableTableHeaderRenderer implements TableCellRenderer {
1247         TableCellRenderer wrappedRenderer;
1248 
1249         public AlignableTableHeaderRenderer(TableCellRenderer wrappedRenderer) {
1250             this.wrappedRenderer = wrappedRenderer;
1251         }
1252 
1253         public Component getTableCellRendererComponent(
1254                                 JTable table, Object value, boolean isSelected,
1255                                 boolean hasFocus, int row, int column) {
1256 
1257             Component c = wrappedRenderer.getTableCellRendererComponent(
1258                                 table, value, isSelected, hasFocus, row, column);
1259 
1260             int modelColumn = table.convertColumnIndexToModel(column);
1261             ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn];
1262 
1263             Integer alignment = columnInfo.getAlignment();
1264             if (alignment == null) {
1265                 alignment = SwingConstants.CENTER;
1266             }
1267             if (c instanceof JLabel) {
1268                 ((JLabel) c).setHorizontalAlignment(alignment);
1269             }
1270 
1271             return c;
1272         }
1273     }
1274 
1275     private void fixNameColumnWidth(int viewWidth) {
1276         TableColumn nameCol = detailsTable.getColumnModel().getColumn(COLUMN_FILENAME);
1277         int tableWidth = detailsTable.getPreferredSize().width;
1278 
1279         if (tableWidth < viewWidth) {
1280             nameCol.setPreferredWidth(nameCol.getPreferredWidth() + viewWidth - tableWidth);
1281         }
1282     }
1283 
1284     private class DelayedSelectionUpdater implements Runnable {
1285         File editFile;
1286 
1287         DelayedSelectionUpdater() {
1288             this(null);
1289         }
1290 
1291         DelayedSelectionUpdater(File editFile) {
1292             this.editFile = editFile;
1293             if (isShowing()) {
1294                 SwingUtilities.invokeLater(this);
1295             }
1296         }
1297 
1298         public void run() {
1299             setFileSelected();
1300             if (editFile != null) {
1301                 editFileName(getRowSorter().convertRowIndexToView(
1302                         getModel().indexOf(editFile)));
1303                 editFile = null;
1304             }
1305         }
1306     }
1307 
1308 
1309     /**
1310      * Creates a selection listener for the list of files and directories.
1311      *
1312      * @return a <code>ListSelectionListener</code>
1313      */
1314     public ListSelectionListener createListSelectionListener() {
1315         return fileChooserUIAccessor.createListSelectionListener();
1316     }
1317 
1318     int lastIndex = -1;
1319     File editFile = null;
1320 
1321     private int getEditIndex() {
1322         return lastIndex;
1323     }
1324 
1325     private void setEditIndex(int i) {
1326         lastIndex = i;
1327     }
1328 
1329     private void resetEditIndex() {
1330         lastIndex = -1;
1331     }
1332 
1333     private void cancelEdit() {
1334         if (editFile != null) {
1335             editFile = null;
1336             list.remove(editCell);
1337             repaint();
1338         } else if (detailsTable != null && detailsTable.isEditing()) {
1339             detailsTable.getCellEditor().cancelCellEditing();
1340         }
1341     }
1342 
1343     JTextField editCell = null;
1344 
1345     /**
1346      * @param index visual index of the file to be edited
1347      */
1348     private void editFileName(int index) {
1349         JFileChooser chooser = getFileChooser();
1350         File currentDirectory = chooser.getCurrentDirectory();
1351 
1352         if (readOnly || !canWrite(currentDirectory)) {
1353             return;
1354         }
1355 
1356         ensureIndexIsVisible(index);
1357         switch (viewType) {
1358           case VIEWTYPE_LIST:
1359             editFile = (File)getModel().getElementAt(getRowSorter().convertRowIndexToModel(index));
1360             Rectangle r = list.getCellBounds(index, index);
1361             if (editCell == null) {
1362                 editCell = new JTextField();
1363                 editCell.setName("Tree.cellEditor");
1364                 editCell.addActionListener(new EditActionListener());
1365                 editCell.addFocusListener(editorFocusListener);
1366                 editCell.setNextFocusableComponent(list);
1367             }
1368             list.add(editCell);
1369             editCell.setText(chooser.getName(editFile));
1370             ComponentOrientation orientation = list.getComponentOrientation();
1371             editCell.setComponentOrientation(orientation);
1372 
1373             Icon icon = chooser.getIcon(editFile);
1374 
1375             // PENDING - grab padding (4) below from defaults table.
1376             int editX = icon == null ? 20 : icon.getIconWidth() + 4;
1377 
1378             if (orientation.isLeftToRight()) {
1379                 editCell.setBounds(editX + r.x, r.y, r.width - editX, r.height);
1380             } else {
1381                 editCell.setBounds(r.x, r.y, r.width - editX, r.height);
1382             }
1383             editCell.requestFocus();
1384             editCell.selectAll();
1385             break;
1386 
1387           case VIEWTYPE_DETAILS:
1388             detailsTable.editCellAt(index, COLUMN_FILENAME);
1389             break;
1390         }
1391     }
1392 
1393 
1394     class EditActionListener implements ActionListener {
1395         public void actionPerformed(ActionEvent e) {
1396             applyEdit();
1397         }
1398     }
1399 
1400     private void applyEdit() {
1401         if (editFile != null && editFile.exists()) {
1402             JFileChooser chooser = getFileChooser();
1403             String oldDisplayName = chooser.getName(editFile);
1404             String oldFileName = editFile.getName();
1405             String newDisplayName = editCell.getText().trim();
1406             String newFileName;
1407 
1408             if (!newDisplayName.equals(oldDisplayName)) {
1409                 newFileName = newDisplayName;
1410                 //Check if extension is hidden from user
1411                 int i1 = oldFileName.length();
1412                 int i2 = oldDisplayName.length();
1413                 if (i1 > i2 && oldFileName.charAt(i2) == '.') {
1414                     newFileName = newDisplayName + oldFileName.substring(i2);
1415                 }
1416 
1417                 // rename
1418                 FileSystemView fsv = chooser.getFileSystemView();
1419                 File f2 = fsv.createFileObject(editFile.getParentFile(), newFileName);
1420                 if (f2.exists()) {
1421                     JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText, oldFileName),
1422                             renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
1423                 } else {
1424                     if (getModel().renameFile(editFile, f2)) {
1425                         if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
1426                             if (chooser.isMultiSelectionEnabled()) {
1427                                 chooser.setSelectedFiles(new File[]{f2});
1428                             } else {
1429                                 chooser.setSelectedFile(f2);
1430                             }
1431                         } else {
1432                             //Could be because of delay in updating Desktop folder
1433                             //chooser.setSelectedFile(null);
1434                         }
1435                     } else {
1436                         JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName),
1437                                 renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
1438                     }
1439                 }
1440             }
1441         }
1442         if (detailsTable != null && detailsTable.isEditing()) {
1443             detailsTable.getCellEditor().stopCellEditing();
1444         }
1445         cancelEdit();
1446     }
1447 
1448     protected Action newFolderAction;
1449 
1450     public Action getNewFolderAction() {
1451         if (!readOnly && newFolderAction == null) {
1452             newFolderAction = new AbstractAction(newFolderActionLabelText) {
1453                 private Action basicNewFolderAction;
1454 
1455                 // Initializer
1456                 {
1457                     putValue(Action.ACTION_COMMAND_KEY, FilePane.ACTION_NEW_FOLDER);
1458 
1459                     File currentDirectory = getFileChooser().getCurrentDirectory();
1460                     if (currentDirectory != null) {
1461                         setEnabled(canWrite(currentDirectory));
1462                     }
1463                 }
1464 
1465                 public void actionPerformed(ActionEvent ev) {
1466                     if (basicNewFolderAction == null) {
1467                         basicNewFolderAction = fileChooserUIAccessor.getNewFolderAction();
1468                     }
1469                     JFileChooser fc = getFileChooser();
1470                     File oldFile = fc.getSelectedFile();
1471                     basicNewFolderAction.actionPerformed(ev);
1472                     File newFile = fc.getSelectedFile();
1473                     if (newFile != null && !newFile.equals(oldFile) && newFile.isDirectory()) {
1474                         newFolderFile = newFile;
1475                     }
1476                 }
1477             };
1478         }
1479         return newFolderAction;
1480     }
1481 
1482     protected class FileRenderer extends DefaultListCellRenderer  {
1483 
1484         public Component getListCellRendererComponent(JList list, Object value,
1485                                                       int index, boolean isSelected,
1486                                                       boolean cellHasFocus) {
1487 
1488             if (listViewWindowsStyle && !list.isFocusOwner()) {
1489                 isSelected = false;
1490             }
1491 
1492             super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1493             File file = (File) value;
1494             String fileName = getFileChooser().getName(file);
1495             setText(fileName);
1496             setFont(list.getFont());
1497 
1498             Icon icon = getFileChooser().getIcon(file);
1499             if (icon != null) {
1500                 setIcon(icon);
1501             } else {
1502                 if (getFileChooser().getFileSystemView().isTraversable(file)) {
1503                     setText(fileName+File.separator);
1504                 }
1505             }
1506 
1507             return this;
1508         }
1509     }
1510 
1511 
1512     void setFileSelected() {
1513         if (getFileChooser().isMultiSelectionEnabled() && !isDirectorySelected()) {
1514             File[] files = getFileChooser().getSelectedFiles(); // Should be selected
1515             Object[] selectedObjects = list.getSelectedValues(); // Are actually selected
1516 
1517             listSelectionModel.setValueIsAdjusting(true);
1518             try {
1519                 int lead = listSelectionModel.getLeadSelectionIndex();
1520                 int anchor = listSelectionModel.getAnchorSelectionIndex();
1521 
1522                 Arrays.sort(files);
1523                 Arrays.sort(selectedObjects);
1524 
1525                 int shouldIndex = 0;
1526                 int actuallyIndex = 0;
1527 
1528                 // Remove files that shouldn't be selected and add files which should be selected
1529                 // Note: Assume files are already sorted in compareTo order.
1530                 while (shouldIndex < files.length &&
1531                        actuallyIndex < selectedObjects.length) {
1532                     int comparison = files[shouldIndex].compareTo((File)selectedObjects[actuallyIndex]);
1533                     if (comparison < 0) {
1534                         doSelectFile(files[shouldIndex++]);
1535                     } else if (comparison > 0) {
1536                         doDeselectFile(selectedObjects[actuallyIndex++]);
1537                     } else {
1538                         // Do nothing
1539                         shouldIndex++;
1540                         actuallyIndex++;
1541                     }
1542 
1543                 }
1544 
1545                 while (shouldIndex < files.length) {
1546                     doSelectFile(files[shouldIndex++]);
1547                 }
1548 
1549                 while (actuallyIndex < selectedObjects.length) {
1550                     doDeselectFile(selectedObjects[actuallyIndex++]);
1551                 }
1552 
1553                 // restore the anchor and lead
1554                 if (listSelectionModel instanceof DefaultListSelectionModel) {
1555                     ((DefaultListSelectionModel)listSelectionModel).
1556                         moveLeadSelectionIndex(lead);
1557                     listSelectionModel.setAnchorSelectionIndex(anchor);
1558                 }
1559             } finally {
1560                 listSelectionModel.setValueIsAdjusting(false);
1561             }
1562         } else {
1563             JFileChooser chooser = getFileChooser();
1564             File f;
1565             if (isDirectorySelected()) {
1566                 f = getDirectory();
1567             } else {
1568                 f = chooser.getSelectedFile();
1569             }
1570             int i;
1571             if (f != null && (i = getModel().indexOf(f)) >= 0) {
1572                 int viewIndex = getRowSorter().convertRowIndexToView(i);
1573                 listSelectionModel.setSelectionInterval(viewIndex, viewIndex);
1574                 ensureIndexIsVisible(viewIndex);
1575             } else {
1576                 clearSelection();
1577             }
1578         }
1579     }
1580 
1581     private void doSelectFile(File fileToSelect) {
1582         int index = getModel().indexOf(fileToSelect);
1583         // could be missed in the current directory if it changed
1584         if (index >= 0) {
1585             index = getRowSorter().convertRowIndexToView(index);
1586             listSelectionModel.addSelectionInterval(index, index);
1587         }
1588     }
1589 
1590     private void doDeselectFile(Object fileToDeselect) {
1591         int index = getRowSorter().convertRowIndexToView(
1592                                 getModel().indexOf(fileToDeselect));
1593         listSelectionModel.removeSelectionInterval(index, index);
1594     }
1595 
1596     /* The following methods are used by the PropertyChange Listener */
1597 
1598     private void doSelectedFileChanged(PropertyChangeEvent e) {
1599         applyEdit();
1600         File f = (File) e.getNewValue();
1601         JFileChooser fc = getFileChooser();
1602         if (f != null
1603             && ((fc.isFileSelectionEnabled() && !f.isDirectory())
1604                 || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
1605 
1606             setFileSelected();
1607         }
1608     }
1609 
1610     private void doSelectedFilesChanged(PropertyChangeEvent e) {
1611         applyEdit();
1612         File[] files = (File[]) e.getNewValue();
1613         JFileChooser fc = getFileChooser();
1614         if (files != null
1615             && files.length > 0
1616             && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
1617             setFileSelected();
1618         }
1619     }
1620 
1621     private void doDirectoryChanged(PropertyChangeEvent e) {
1622         getDetailsTableModel().updateColumnInfo();
1623 
1624         JFileChooser fc = getFileChooser();
1625         FileSystemView fsv = fc.getFileSystemView();
1626 
1627         applyEdit();
1628         resetEditIndex();
1629         ensureIndexIsVisible(0);
1630         File currentDirectory = fc.getCurrentDirectory();
1631         if (currentDirectory != null) {
1632             if (!readOnly) {
1633                 getNewFolderAction().setEnabled(canWrite(currentDirectory));
1634             }
1635             fileChooserUIAccessor.getChangeToParentDirectoryAction().setEnabled(!fsv.isRoot(currentDirectory));
1636         }
1637         if (list != null) {
1638             list.clearSelection();
1639         }
1640     }
1641 
1642     private void doFilterChanged(PropertyChangeEvent e) {
1643         applyEdit();
1644         resetEditIndex();
1645         clearSelection();
1646     }
1647 
1648     private void doFileSelectionModeChanged(PropertyChangeEvent e) {
1649         applyEdit();
1650         resetEditIndex();
1651         clearSelection();
1652     }
1653 
1654     private void doMultiSelectionChanged(PropertyChangeEvent e) {
1655         if (getFileChooser().isMultiSelectionEnabled()) {
1656             listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
1657         } else {
1658             listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1659             clearSelection();
1660             getFileChooser().setSelectedFiles(null);
1661         }
1662     }
1663 
1664     /*
1665      * Listen for filechooser property changes, such as
1666      * the selected file changing, or the type of the dialog changing.
1667      */
1668     public void propertyChange(PropertyChangeEvent e) {
1669             if (viewType == -1) {
1670                 setViewType(VIEWTYPE_LIST);
1671             }
1672 
1673         String s = e.getPropertyName();
1674         if (s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
1675             doSelectedFileChanged(e);
1676         } else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
1677             doSelectedFilesChanged(e);
1678         } else if (s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
1679             doDirectoryChanged(e);
1680         } else if (s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
1681             doFilterChanged(e);
1682         } else if (s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
1683             doFileSelectionModeChanged(e);
1684         } else if (s.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
1685             doMultiSelectionChanged(e);
1686         } else if (s.equals(JFileChooser.CANCEL_SELECTION)) {
1687             applyEdit();
1688         } else if (s.equals("busy")) {
1689             setCursor((Boolean)e.getNewValue() ? waitCursor : null);
1690         } else if (s.equals("componentOrientation")) {
1691             ComponentOrientation o = (ComponentOrientation)e.getNewValue();
1692             JFileChooser cc = (JFileChooser)e.getSource();
1693             if (o != e.getOldValue()) {
1694                 cc.applyComponentOrientation(o);
1695             }
1696             if (detailsTable != null) {
1697                 detailsTable.setComponentOrientation(o);
1698                 detailsTable.getParent().getParent().setComponentOrientation(o);
1699             }
1700         }
1701     }
1702 
1703     private void ensureIndexIsVisible(int i) {
1704         if (i >= 0) {
1705             if (list != null) {
1706                 list.ensureIndexIsVisible(i);
1707             }
1708             if (detailsTable != null) {
1709                 detailsTable.scrollRectToVisible(detailsTable.getCellRect(i, COLUMN_FILENAME, true));
1710             }
1711         }
1712     }
1713 
1714     public void ensureFileIsVisible(JFileChooser fc, File f) {
1715         int modelIndex = getModel().indexOf(f);
1716         if (modelIndex >= 0) {
1717             ensureIndexIsVisible(getRowSorter().convertRowIndexToView(modelIndex));
1718         }
1719     }
1720 
1721     public void rescanCurrentDirectory() {
1722         getModel().validateFileCache();
1723     }
1724 
1725     public void clearSelection() {
1726         if (listSelectionModel != null) {
1727             listSelectionModel.clearSelection();
1728             if (listSelectionModel instanceof DefaultListSelectionModel) {
1729                 ((DefaultListSelectionModel)listSelectionModel).moveLeadSelectionIndex(0);
1730                 listSelectionModel.setAnchorSelectionIndex(0);
1731             }
1732         }
1733     }
1734 
1735     public JMenu getViewMenu() {
1736         if (viewMenu == null) {
1737             viewMenu = new JMenu(viewMenuLabelText);
1738             ButtonGroup viewButtonGroup = new ButtonGroup();
1739 
1740             for (int i = 0; i < VIEWTYPE_COUNT; i++) {
1741                 JRadioButtonMenuItem mi =
1742                     new JRadioButtonMenuItem(new ViewTypeAction(i));
1743                 viewButtonGroup.add(mi);
1744                 viewMenu.add(mi);
1745             }
1746             updateViewMenu();
1747         }
1748         return viewMenu;
1749     }
1750 
1751     private void updateViewMenu() {
1752         if (viewMenu != null) {
1753             Component[] comps = viewMenu.getMenuComponents();
1754             for (Component comp : comps) {
1755                 if (comp instanceof JRadioButtonMenuItem) {
1756                     JRadioButtonMenuItem mi = (JRadioButtonMenuItem) comp;
1757                     if (((ViewTypeAction)mi.getAction()).viewType == viewType) {
1758                         mi.setSelected(true);
1759                     }
1760                 }
1761             }
1762         }
1763     }
1764 
1765     public JPopupMenu getComponentPopupMenu() {
1766         JPopupMenu popupMenu = getFileChooser().getComponentPopupMenu();
1767         if (popupMenu != null) {
1768             return popupMenu;
1769         }
1770 
1771         JMenu viewMenu = getViewMenu();
1772         if (contextMenu == null) {
1773             contextMenu = new JPopupMenu();
1774             if (viewMenu != null) {
1775                 contextMenu.add(viewMenu);
1776                 if (listViewWindowsStyle) {
1777                     contextMenu.addSeparator();
1778                 }
1779             }
1780             ActionMap actionMap = getActionMap();
1781             Action refreshAction   = actionMap.get(ACTION_REFRESH);
1782             Action newFolderAction = actionMap.get(ACTION_NEW_FOLDER);
1783             if (refreshAction != null) {
1784                 contextMenu.add(refreshAction);
1785                 if (listViewWindowsStyle && newFolderAction != null) {
1786                     contextMenu.addSeparator();
1787                 }
1788             }
1789             if (newFolderAction != null) {
1790                 contextMenu.add(newFolderAction);
1791             }
1792         }
1793         if (viewMenu != null) {
1794             viewMenu.getPopupMenu().setInvoker(viewMenu);
1795         }
1796         return contextMenu;
1797     }
1798 
1799 
1800     private Handler handler;
1801 
1802     protected Handler getMouseHandler() {
1803         if (handler == null) {
1804             handler = new Handler();
1805         }
1806         return handler;
1807     }
1808 
1809     private class Handler implements MouseListener {
1810         private MouseListener doubleClickListener;
1811 
1812         public void mouseClicked(MouseEvent evt) {
1813             JComponent source = (JComponent)evt.getSource();
1814 
1815             int index;
1816             if (source instanceof JList) {
1817                 index = SwingUtilities2.loc2IndexFileList(list, evt.getPoint());
1818             } else if (source instanceof JTable) {
1819                 JTable table = (JTable)source;
1820                 Point p = evt.getPoint();
1821                 index = table.rowAtPoint(p);
1822 
1823                 boolean pointOutsidePrefSize =
1824                         SwingUtilities2.pointOutsidePrefSize(
1825                             table, index, table.columnAtPoint(p), p);
1826 
1827                 if (pointOutsidePrefSize && !fullRowSelection) {
1828                     return;
1829                 }
1830 
1831                 // Translate point from table to list
1832                 if (index >= 0 && list != null &&
1833                     listSelectionModel.isSelectedIndex(index)) {
1834 
1835                     // Make a new event with the list as source, placing the
1836                     // click in the corresponding list cell.
1837                     Rectangle r = list.getCellBounds(index, index);
1838                     evt = new MouseEvent(list, evt.getID(),
1839                                          evt.getWhen(), evt.getModifiers(),
1840                                          r.x + 1, r.y + r.height/2,
1841                                          evt.getXOnScreen(),
1842                                          evt.getYOnScreen(),
1843                                          evt.getClickCount(), evt.isPopupTrigger(),
1844                                          evt.getButton());
1845                 }
1846             } else {
1847                 return;
1848             }
1849 
1850             if (index >= 0 && SwingUtilities.isLeftMouseButton(evt)) {
1851                 JFileChooser fc = getFileChooser();
1852 
1853                 // For single click, we handle editing file name
1854                 if (evt.getClickCount() == 1 && source instanceof JList) {
1855                     if ((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1)
1856                         && index >= 0 && listSelectionModel.isSelectedIndex(index)
1857                         && getEditIndex() == index && editFile == null) {
1858 
1859                         editFileName(index);
1860                     } else {
1861                         if (index >= 0) {
1862                             setEditIndex(index);
1863                         } else {
1864                             resetEditIndex();
1865                         }
1866                     }
1867                 } else if (evt.getClickCount() == 2) {
1868                     // on double click (open or drill down one directory) be
1869                     // sure to clear the edit index
1870                     resetEditIndex();
1871                 }
1872             }
1873 
1874             // Forward event to Basic
1875             if (getDoubleClickListener() != null) {
1876                 getDoubleClickListener().mouseClicked(evt);
1877             }
1878         }
1879 
1880         public void mouseEntered(MouseEvent evt) {
1881             JComponent source = (JComponent)evt.getSource();
1882             if (source instanceof JTable) {
1883                 JTable table = (JTable)evt.getSource();
1884 
1885                 TransferHandler th1 = getFileChooser().getTransferHandler();
1886                 TransferHandler th2 = table.getTransferHandler();
1887                 if (th1 != th2) {
1888                     table.setTransferHandler(th1);
1889                 }
1890 
1891                 boolean dragEnabled = getFileChooser().getDragEnabled();
1892                 if (dragEnabled != table.getDragEnabled()) {
1893                     table.setDragEnabled(dragEnabled);
1894                 }
1895             } else if (source instanceof JList) {
1896                 // Forward event to Basic
1897                 if (getDoubleClickListener() != null) {
1898                     getDoubleClickListener().mouseEntered(evt);
1899                 }
1900             }
1901         }
1902 
1903         public void mouseExited(MouseEvent evt) {
1904             if (evt.getSource() instanceof JList) {
1905                 // Forward event to Basic
1906                 if (getDoubleClickListener() != null) {
1907                     getDoubleClickListener().mouseExited(evt);
1908                 }
1909             }
1910         }
1911 
1912         public void mousePressed(MouseEvent evt) {
1913             if (evt.getSource() instanceof JList) {
1914                 // Forward event to Basic
1915                 if (getDoubleClickListener() != null) {
1916                     getDoubleClickListener().mousePressed(evt);
1917                 }
1918             }
1919         }
1920 
1921         public void mouseReleased(MouseEvent evt) {
1922             if (evt.getSource() instanceof JList) {
1923                 // Forward event to Basic
1924                 if (getDoubleClickListener() != null) {
1925                     getDoubleClickListener().mouseReleased(evt);
1926                 }
1927             }
1928         }
1929 
1930         private MouseListener getDoubleClickListener() {
1931             // Lazy creation of Basic's listener
1932             if (doubleClickListener == null && list != null) {
1933                 doubleClickListener =
1934                     fileChooserUIAccessor.createDoubleClickListener(list);
1935             }
1936             return doubleClickListener;
1937         }
1938     }
1939 
1940     /**
1941      * Property to remember whether a directory is currently selected in the UI.
1942      *
1943      * @return <code>true</code> iff a directory is currently selected.
1944      */
1945     protected boolean isDirectorySelected() {
1946         return fileChooserUIAccessor.isDirectorySelected();
1947     }
1948 
1949 
1950     /**
1951      * Property to remember the directory that is currently selected in the UI.
1952      *
1953      * @return the value of the <code>directory</code> property
1954      * @see javax.swing.plaf.basic.BasicFileChooserUI#setDirectory
1955      */
1956     protected File getDirectory() {
1957         return fileChooserUIAccessor.getDirectory();
1958     }
1959 
1960     private Component findChildComponent(Container container, Class cls) {
1961         int n = container.getComponentCount();
1962         for (int i = 0; i < n; i++) {
1963             Component comp = container.getComponent(i);
1964             if (cls.isInstance(comp)) {
1965                 return comp;
1966             } else if (comp instanceof Container) {
1967                 Component c = findChildComponent((Container)comp, cls);
1968                 if (c != null) {
1969                     return c;
1970                 }
1971             }
1972         }
1973         return null;
1974     }
1975 
1976     public boolean canWrite(File f) {
1977         // Return false for non FileSystem files or if file doesn't exist.
1978         if (!f.exists()) {
1979             return false;
1980         }
1981 
1982         if (f instanceof ShellFolder) {
1983             return f.canWrite();
1984         } else {
1985             if (usesShellFolder(getFileChooser())) {
1986                 try {
1987                     return ShellFolder.getShellFolder(f).canWrite();
1988                 } catch (FileNotFoundException ex) {
1989                     // File doesn't exist
1990                     return false;
1991                 }
1992             } else {
1993                 // Ordinary file
1994                 return f.canWrite();
1995             }
1996         }
1997     }
1998 
1999     /**
2000      * Returns true if specified FileChooser should use ShellFolder
2001      */
2002     public static boolean usesShellFolder(JFileChooser chooser) {
2003         Boolean prop = (Boolean) chooser.getClientProperty("FileChooser.useShellFolder");
2004 
2005         return prop == null ? chooser.getFileSystemView().equals(FileSystemView.getFileSystemView())
2006                 : prop.booleanValue();
2007     }
2008 
2009     // This interface is used to access methods in the FileChooserUI
2010     // that are not public.
2011     public interface FileChooserUIAccessor {
2012         public JFileChooser getFileChooser();
2013         public BasicDirectoryModel getModel();
2014         public JPanel createList();
2015         public JPanel createDetailsView();
2016         public boolean isDirectorySelected();
2017         public File getDirectory();
2018         public Action getApproveSelectionAction();
2019         public Action getChangeToParentDirectoryAction();
2020         public Action getNewFolderAction();
2021         public MouseListener createDoubleClickListener(JList list);
2022         public ListSelectionListener createListSelectionListener();
2023     }
2024 }