History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/swing/FilePane.java (1 of 18352 files). Show all

Unified · Split

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