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