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