blob: c605a54a88e8aa82993c9d527dd5f21c61079f67 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001
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 */
26package sun.swing;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.beans.PropertyChangeEvent;
31import java.beans.PropertyChangeListener;
32import java.io.*;
33import java.text.DateFormat;
34import java.text.MessageFormat;
35import java.util.*;
36import java.util.List;
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
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}