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