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 }