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