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