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