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