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