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