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