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