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