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