| /* | |
| * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.swing; | |
| import java.awt.BorderLayout; | |
| import java.awt.Color; | |
| import java.awt.Component; | |
| import java.awt.ComponentOrientation; | |
| import java.awt.Container; | |
| import java.awt.Cursor; | |
| import java.awt.DefaultKeyboardFocusManager; | |
| import java.awt.Dimension; | |
| import java.awt.Font; | |
| import java.awt.Insets; | |
| import java.awt.KeyboardFocusManager; | |
| import java.awt.Point; | |
| import java.awt.Rectangle; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.ActionListener; | |
| import java.awt.event.ComponentAdapter; | |
| import java.awt.event.ComponentEvent; | |
| import java.awt.event.FocusAdapter; | |
| import java.awt.event.FocusEvent; | |
| import java.awt.event.FocusListener; | |
| import java.awt.event.KeyAdapter; | |
| import java.awt.event.KeyEvent; | |
| import java.awt.event.KeyListener; | |
| import java.awt.event.MouseAdapter; | |
| import java.awt.event.MouseEvent; | |
| import java.awt.event.MouseListener; | |
| import java.beans.PropertyChangeEvent; | |
| import java.beans.PropertyChangeListener; | |
| import java.io.File; | |
| import java.io.FileNotFoundException; | |
| import java.text.DateFormat; | |
| import java.text.MessageFormat; | |
| import java.text.NumberFormat; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Comparator; | |
| import java.util.Date; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.concurrent.Callable; | |
| import javax.accessibility.AccessibleContext; | |
| import javax.swing.AbstractAction; | |
| import javax.swing.AbstractListModel; | |
| import javax.swing.Action; | |
| import javax.swing.ActionMap; | |
| import javax.swing.ButtonGroup; | |
| import javax.swing.DefaultCellEditor; | |
| import javax.swing.DefaultListCellRenderer; | |
| import javax.swing.DefaultListSelectionModel; | |
| import javax.swing.Icon; | |
| import javax.swing.InputMap; | |
| import javax.swing.JComponent; | |
| import javax.swing.JFileChooser; | |
| import javax.swing.JLabel; | |
| import javax.swing.JList; | |
| import javax.swing.JMenu; | |
| import javax.swing.JOptionPane; | |
| import javax.swing.JPanel; | |
| import javax.swing.JPopupMenu; | |
| import javax.swing.JRadioButtonMenuItem; | |
| import javax.swing.JScrollPane; | |
| import javax.swing.JTable; | |
| import javax.swing.JTextField; | |
| import javax.swing.KeyStroke; | |
| import javax.swing.ListModel; | |
| import javax.swing.ListSelectionModel; | |
| import javax.swing.LookAndFeel; | |
| import javax.swing.RowSorter; | |
| import javax.swing.SwingConstants; | |
| import javax.swing.SwingUtilities; | |
| import javax.swing.TransferHandler; | |
| import javax.swing.UIManager; | |
| import javax.swing.border.Border; | |
| import javax.swing.event.ListDataEvent; | |
| import javax.swing.event.ListDataListener; | |
| import javax.swing.event.ListSelectionListener; | |
| import javax.swing.event.RowSorterEvent; | |
| import javax.swing.event.RowSorterListener; | |
| import javax.swing.event.TableModelEvent; | |
| import javax.swing.event.TableModelListener; | |
| import javax.swing.filechooser.FileSystemView; | |
| import javax.swing.plaf.basic.BasicDirectoryModel; | |
| import javax.swing.table.AbstractTableModel; | |
| import javax.swing.table.DefaultTableCellRenderer; | |
| import javax.swing.table.DefaultTableColumnModel; | |
| import javax.swing.table.TableCellEditor; | |
| import javax.swing.table.TableCellRenderer; | |
| import javax.swing.table.TableColumn; | |
| import javax.swing.table.TableColumnModel; | |
| import javax.swing.table.TableModel; | |
| import javax.swing.table.TableRowSorter; | |
| import javax.swing.text.Position; | |
| import sun.awt.AWTAccessor; | |
| import sun.awt.AWTAccessor.MouseEventAccessor; | |
| import sun.awt.shell.ShellFolder; | |
| import sun.awt.shell.ShellFolderColumnInfo; | |
| /** | |
| * <b>WARNING:</b> This class is an implementation detail and is only | |
| * public so that it can be used by two packages. You should NOT consider | |
| * this public API. | |
| * <p> | |
| * This component is intended to be used in a subclass of | |
| * javax.swing.plaf.basic.BasicFileChooserUI. It relies heavily on the | |
| * implementation of BasicFileChooserUI and is intended to be API compatible | |
| * with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI. | |
| * | |
| * @author Leif Samuelsson | |
| */ | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| public class FilePane extends JPanel implements PropertyChangeListener { | |
| // Constants for actions. These are used for the actions' ACTION_COMMAND_KEY | |
| // and as keys in the action maps for FilePane and the corresponding UI classes | |
| public static final String ACTION_APPROVE_SELECTION = "approveSelection"; | |
| public static final String ACTION_CANCEL = "cancelSelection"; | |
| public static final String ACTION_EDIT_FILE_NAME = "editFileName"; | |
| public static final String ACTION_REFRESH = "refresh"; | |
| public static final String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up"; | |
| public static final String ACTION_NEW_FOLDER = "New Folder"; | |
| public static final String ACTION_VIEW_LIST = "viewTypeList"; | |
| public static final String ACTION_VIEW_DETAILS = "viewTypeDetails"; | |
| private Action[] actions; | |
| // "enums" for setViewType() | |
| public static final int VIEWTYPE_LIST = 0; | |
| public static final int VIEWTYPE_DETAILS = 1; | |
| private static final int VIEWTYPE_COUNT = 2; | |
| private int viewType = -1; | |
| private JPanel[] viewPanels = new JPanel[VIEWTYPE_COUNT]; | |
| private JPanel currentViewPanel; | |
| private String[] viewTypeActionNames; | |
| private String filesListAccessibleName = null; | |
| private String filesDetailsAccessibleName = null; | |
| private JPopupMenu contextMenu; | |
| private JMenu viewMenu; | |
| private String viewMenuLabelText; | |
| private String refreshActionLabelText; | |
| private String newFolderActionLabelText; | |
| private String kiloByteString; | |
| private String megaByteString; | |
| private String gigaByteString; | |
| private String renameErrorTitleText; | |
| private String renameErrorText; | |
| private String renameErrorFileExistsText; | |
| private static final Cursor waitCursor = | |
| Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); | |
| private final KeyListener detailsKeyListener = new KeyAdapter() { | |
| private final long timeFactor; | |
| private final StringBuilder typedString = new StringBuilder(); | |
| private long lastTime = 1000L; | |
| { | |
| Long l = (Long) UIManager.get("Table.timeFactor"); | |
| timeFactor = (l != null) ? l : 1000L; | |
| } | |
| /** | |
| * Moves the keyboard focus to the first element whose prefix matches | |
| * the sequence of alphanumeric keys pressed by the user with delay | |
| * less than value of <code>timeFactor</code>. Subsequent same key | |
| * presses move the keyboard focus to the next object that starts with | |
| * the same letter until another key is pressed, then it is treated | |
| * as the prefix with appropriate number of the same letters followed | |
| * by first typed another letter. | |
| */ | |
| public void keyTyped(KeyEvent e) { | |
| BasicDirectoryModel model = getModel(); | |
| int rowCount = model.getSize(); | |
| if (detailsTable == null || rowCount == 0 || | |
| e.isAltDown() || e.isControlDown() || e.isMetaDown()) { | |
| return; | |
| } | |
| InputMap inputMap = detailsTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); | |
| KeyStroke key = KeyStroke.getKeyStrokeForEvent(e); | |
| if (inputMap != null && inputMap.get(key) != null) { | |
| return; | |
| } | |
| int startIndex = detailsTable.getSelectionModel().getLeadSelectionIndex(); | |
| if (startIndex < 0) { | |
| startIndex = 0; | |
| } | |
| if (startIndex >= rowCount) { | |
| startIndex = rowCount - 1; | |
| } | |
| char c = e.getKeyChar(); | |
| long time = e.getWhen(); | |
| if (time - lastTime < timeFactor) { | |
| if (typedString.length() == 1 && typedString.charAt(0) == c) { | |
| // Subsequent same key presses move the keyboard focus to the next | |
| // object that starts with the same letter. | |
| startIndex++; | |
| } else { | |
| typedString.append(c); | |
| } | |
| } else { | |
| startIndex++; | |
| typedString.setLength(0); | |
| typedString.append(c); | |
| } | |
| lastTime = time; | |
| if (startIndex >= rowCount) { | |
| startIndex = 0; | |
| } | |
| // Find next file | |
| int index = getNextMatch(startIndex, rowCount - 1); | |
| if (index < 0 && startIndex > 0) { // wrap | |
| index = getNextMatch(0, startIndex - 1); | |
| } | |
| if (index >= 0) { | |
| detailsTable.getSelectionModel().setSelectionInterval(index, index); | |
| Rectangle cellRect = detailsTable.getCellRect(index, | |
| detailsTable.convertColumnIndexToView(COLUMN_FILENAME), false); | |
| detailsTable.scrollRectToVisible(cellRect); | |
| } | |
| } | |
| private int getNextMatch(int startIndex, int finishIndex) { | |
| BasicDirectoryModel model = getModel(); | |
| JFileChooser fileChooser = getFileChooser(); | |
| DetailsTableRowSorter rowSorter = getRowSorter(); | |
| String prefix = typedString.toString().toLowerCase(); | |
| // Search element | |
| for (int index = startIndex; index <= finishIndex; index++) { | |
| File file = (File) model.getElementAt(rowSorter.convertRowIndexToModel(index)); | |
| String fileName = fileChooser.getName(file).toLowerCase(); | |
| if (fileName.startsWith(prefix)) { | |
| return index; | |
| } | |
| } | |
| return -1; | |
| } | |
| }; | |
| private FocusListener editorFocusListener = new FocusAdapter() { | |
| public void focusLost(FocusEvent e) { | |
| if (! e.isTemporary()) { | |
| applyEdit(); | |
| } | |
| } | |
| }; | |
| private static FocusListener repaintListener = new FocusListener() { | |
| public void focusGained(FocusEvent fe) { | |
| repaintSelection(fe.getSource()); | |
| } | |
| public void focusLost(FocusEvent fe) { | |
| repaintSelection(fe.getSource()); | |
| } | |
| private void repaintSelection(Object source) { | |
| if (source instanceof JList) { | |
| repaintListSelection((JList)source); | |
| } else if (source instanceof JTable) { | |
| repaintTableSelection((JTable)source); | |
| } | |
| } | |
| private void repaintListSelection(JList<?> list) { | |
| int[] indices = list.getSelectedIndices(); | |
| for (int i : indices) { | |
| Rectangle bounds = list.getCellBounds(i, i); | |
| list.repaint(bounds); | |
| } | |
| } | |
| private void repaintTableSelection(JTable table) { | |
| int minRow = table.getSelectionModel().getMinSelectionIndex(); | |
| int maxRow = table.getSelectionModel().getMaxSelectionIndex(); | |
| if (minRow == -1 || maxRow == -1) { | |
| return; | |
| } | |
| int col0 = table.convertColumnIndexToView(COLUMN_FILENAME); | |
| Rectangle first = table.getCellRect(minRow, col0, false); | |
| Rectangle last = table.getCellRect(maxRow, col0, false); | |
| Rectangle dirty = first.union(last); | |
| table.repaint(dirty); | |
| } | |
| }; | |
| private boolean smallIconsView = false; | |
| private Border listViewBorder; | |
| private Color listViewBackground; | |
| private boolean listViewWindowsStyle; | |
| private boolean readOnly; | |
| private boolean fullRowSelection = false; | |
| private ListSelectionModel listSelectionModel; | |
| private JList<?> list; | |
| private JTable detailsTable; | |
| private static final int COLUMN_FILENAME = 0; | |
| // Provides a way to recognize a newly created folder, so it can | |
| // be selected when it appears in the model. | |
| private File newFolderFile; | |
| // Used for accessing methods in the corresponding UI class | |
| private FileChooserUIAccessor fileChooserUIAccessor; | |
| private DetailsTableModel detailsTableModel; | |
| private DetailsTableRowSorter rowSorter; | |
| public FilePane(FileChooserUIAccessor fileChooserUIAccessor) { | |
| super(new BorderLayout()); | |
| this.fileChooserUIAccessor = fileChooserUIAccessor; | |
| installDefaults(); | |
| createActionMap(); | |
| } | |
| public void uninstallUI() { | |
| if (getModel() != null) { | |
| getModel().removePropertyChangeListener(this); | |
| } | |
| } | |
| protected JFileChooser getFileChooser() { | |
| return fileChooserUIAccessor.getFileChooser(); | |
| } | |
| protected BasicDirectoryModel getModel() { | |
| return fileChooserUIAccessor.getModel(); | |
| } | |
| public int getViewType() { | |
| return viewType; | |
| } | |
| public void setViewType(int viewType) { | |
| if (viewType == this.viewType) { | |
| return; | |
| } | |
| int oldValue = this.viewType; | |
| this.viewType = viewType; | |
| JPanel createdViewPanel = null; | |
| Component newFocusOwner = null; | |
| switch (viewType) { | |
| case VIEWTYPE_LIST: | |
| if (viewPanels[viewType] == null) { | |
| createdViewPanel = fileChooserUIAccessor.createList(); | |
| if (createdViewPanel == null) { | |
| createdViewPanel = createList(); | |
| } | |
| list = findChildComponent(createdViewPanel, JList.class); | |
| if (listSelectionModel == null) { | |
| listSelectionModel = list.getSelectionModel(); | |
| if (detailsTable != null) { | |
| detailsTable.setSelectionModel(listSelectionModel); | |
| } | |
| } else { | |
| list.setSelectionModel(listSelectionModel); | |
| } | |
| } | |
| list.setLayoutOrientation(JList.VERTICAL_WRAP); | |
| newFocusOwner = list; | |
| break; | |
| case VIEWTYPE_DETAILS: | |
| if (viewPanels[viewType] == null) { | |
| createdViewPanel = fileChooserUIAccessor.createDetailsView(); | |
| if (createdViewPanel == null) { | |
| createdViewPanel = createDetailsView(); | |
| } | |
| detailsTable = findChildComponent(createdViewPanel, JTable.class); | |
| detailsTable.setRowHeight(Math.max(detailsTable.getFont().getSize() + 4, 16 + 1)); | |
| if (listSelectionModel != null) { | |
| detailsTable.setSelectionModel(listSelectionModel); | |
| } | |
| } | |
| newFocusOwner = detailsTable; | |
| break; | |
| } | |
| if (createdViewPanel != null) { | |
| viewPanels[viewType] = createdViewPanel; | |
| recursivelySetInheritsPopupMenu(createdViewPanel, true); | |
| } | |
| boolean isFocusOwner = false; | |
| if (currentViewPanel != null) { | |
| Component owner = DefaultKeyboardFocusManager. | |
| getCurrentKeyboardFocusManager().getPermanentFocusOwner(); | |
| isFocusOwner = owner == detailsTable || owner == list; | |
| remove(currentViewPanel); | |
| } | |
| currentViewPanel = viewPanels[viewType]; | |
| add(currentViewPanel, BorderLayout.CENTER); | |
| if (isFocusOwner && newFocusOwner != null) { | |
| newFocusOwner.requestFocusInWindow(); | |
| } | |
| revalidate(); | |
| repaint(); | |
| updateViewMenu(); | |
| firePropertyChange("viewType", oldValue, viewType); | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| class ViewTypeAction extends AbstractAction { | |
| private int viewType; | |
| ViewTypeAction(int viewType) { | |
| super(viewTypeActionNames[viewType]); | |
| this.viewType = viewType; | |
| String cmd; | |
| switch (viewType) { | |
| case VIEWTYPE_LIST: cmd = ACTION_VIEW_LIST; break; | |
| case VIEWTYPE_DETAILS: cmd = ACTION_VIEW_DETAILS; break; | |
| default: cmd = (String)getValue(Action.NAME); | |
| } | |
| putValue(Action.ACTION_COMMAND_KEY, cmd); | |
| } | |
| public void actionPerformed(ActionEvent e) { | |
| setViewType(viewType); | |
| } | |
| } | |
| public Action getViewTypeAction(int viewType) { | |
| return new ViewTypeAction(viewType); | |
| } | |
| private static void recursivelySetInheritsPopupMenu(Container container, boolean b) { | |
| if (container instanceof JComponent) { | |
| ((JComponent)container).setInheritsPopupMenu(b); | |
| } | |
| int n = container.getComponentCount(); | |
| for (int i = 0; i < n; i++) { | |
| recursivelySetInheritsPopupMenu((Container)container.getComponent(i), b); | |
| } | |
| } | |
| protected void installDefaults() { | |
| Locale l = getFileChooser().getLocale(); | |
| listViewBorder = UIManager.getBorder("FileChooser.listViewBorder"); | |
| listViewBackground = UIManager.getColor("FileChooser.listViewBackground"); | |
| listViewWindowsStyle = UIManager.getBoolean("FileChooser.listViewWindowsStyle"); | |
| readOnly = UIManager.getBoolean("FileChooser.readOnly"); | |
| // TODO: On windows, get the following localized strings from the OS | |
| viewMenuLabelText = | |
| UIManager.getString("FileChooser.viewMenuLabelText", l); | |
| refreshActionLabelText = | |
| UIManager.getString("FileChooser.refreshActionLabelText", l); | |
| newFolderActionLabelText = | |
| UIManager.getString("FileChooser.newFolderActionLabelText", l); | |
| viewTypeActionNames = new String[VIEWTYPE_COUNT]; | |
| viewTypeActionNames[VIEWTYPE_LIST] = | |
| UIManager.getString("FileChooser.listViewActionLabelText", l); | |
| viewTypeActionNames[VIEWTYPE_DETAILS] = | |
| UIManager.getString("FileChooser.detailsViewActionLabelText", l); | |
| kiloByteString = UIManager.getString("FileChooser.fileSizeKiloBytes", l); | |
| megaByteString = UIManager.getString("FileChooser.fileSizeMegaBytes", l); | |
| gigaByteString = UIManager.getString("FileChooser.fileSizeGigaBytes", l); | |
| fullRowSelection = UIManager.getBoolean("FileView.fullRowSelection"); | |
| filesListAccessibleName = UIManager.getString("FileChooser.filesListAccessibleName", l); | |
| filesDetailsAccessibleName = UIManager.getString("FileChooser.filesDetailsAccessibleName", l); | |
| renameErrorTitleText = UIManager.getString("FileChooser.renameErrorTitleText", l); | |
| renameErrorText = UIManager.getString("FileChooser.renameErrorText", l); | |
| renameErrorFileExistsText = UIManager.getString("FileChooser.renameErrorFileExistsText", l); | |
| } | |
| /** | |
| * Fetches the command list for the FilePane. These commands | |
| * are useful for binding to events, such as in a keymap. | |
| * | |
| * @return the command list | |
| */ | |
| public Action[] getActions() { | |
| if (actions == null) { | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| class FilePaneAction extends AbstractAction { | |
| FilePaneAction(String name) { | |
| this(name, name); | |
| } | |
| FilePaneAction(String name, String cmd) { | |
| super(name); | |
| putValue(Action.ACTION_COMMAND_KEY, cmd); | |
| } | |
| public void actionPerformed(ActionEvent e) { | |
| String cmd = (String)getValue(Action.ACTION_COMMAND_KEY); | |
| if (cmd == ACTION_CANCEL) { | |
| if (editFile != null) { | |
| cancelEdit(); | |
| } else { | |
| getFileChooser().cancelSelection(); | |
| } | |
| } else if (cmd == ACTION_EDIT_FILE_NAME) { | |
| JFileChooser fc = getFileChooser(); | |
| int index = listSelectionModel.getMinSelectionIndex(); | |
| if (index >= 0 && editFile == null && | |
| (!fc.isMultiSelectionEnabled() || | |
| fc.getSelectedFiles().length <= 1)) { | |
| editFileName(index); | |
| } | |
| } else if (cmd == ACTION_REFRESH) { | |
| getFileChooser().rescanCurrentDirectory(); | |
| } | |
| } | |
| public boolean isEnabled() { | |
| String cmd = (String)getValue(Action.ACTION_COMMAND_KEY); | |
| if (cmd == ACTION_CANCEL) { | |
| return getFileChooser().isEnabled(); | |
| } else if (cmd == ACTION_EDIT_FILE_NAME) { | |
| return !readOnly && getFileChooser().isEnabled(); | |
| } else { | |
| return true; | |
| } | |
| } | |
| } | |
| ArrayList<Action> actionList = new ArrayList<Action>(8); | |
| Action action; | |
| actionList.add(new FilePaneAction(ACTION_CANCEL)); | |
| actionList.add(new FilePaneAction(ACTION_EDIT_FILE_NAME)); | |
| actionList.add(new FilePaneAction(refreshActionLabelText, ACTION_REFRESH)); | |
| action = fileChooserUIAccessor.getApproveSelectionAction(); | |
| if (action != null) { | |
| actionList.add(action); | |
| } | |
| action = fileChooserUIAccessor.getChangeToParentDirectoryAction(); | |
| if (action != null) { | |
| actionList.add(action); | |
| } | |
| action = getNewFolderAction(); | |
| if (action != null) { | |
| actionList.add(action); | |
| } | |
| action = getViewTypeAction(VIEWTYPE_LIST); | |
| if (action != null) { | |
| actionList.add(action); | |
| } | |
| action = getViewTypeAction(VIEWTYPE_DETAILS); | |
| if (action != null) { | |
| actionList.add(action); | |
| } | |
| actions = actionList.toArray(new Action[actionList.size()]); | |
| } | |
| return Arrays.copyOf(actions, actions.length); | |
| } | |
| protected void createActionMap() { | |
| addActionsToMap(super.getActionMap(), getActions()); | |
| } | |
| public static void addActionsToMap(ActionMap map, Action[] actions) { | |
| if (map != null && actions != null) { | |
| for (Action a : actions) { | |
| String cmd = (String)a.getValue(Action.ACTION_COMMAND_KEY); | |
| if (cmd == null) { | |
| cmd = (String)a.getValue(Action.NAME); | |
| } | |
| map.put(cmd, a); | |
| } | |
| } | |
| } | |
| private void updateListRowCount(JList<?> list) { | |
| if (smallIconsView) { | |
| list.setVisibleRowCount(getModel().getSize() / 3); | |
| } else { | |
| list.setVisibleRowCount(-1); | |
| } | |
| } | |
| public JPanel createList() { | |
| JPanel p = new JPanel(new BorderLayout()); | |
| final JFileChooser fileChooser = getFileChooser(); | |
| final JList<Object> list = new JList<Object>() { | |
| public int getNextMatch(String prefix, int startIndex, Position.Bias bias) { | |
| ListModel<?> model = getModel(); | |
| int max = model.getSize(); | |
| if (prefix == null || startIndex < 0 || startIndex >= max) { | |
| throw new IllegalArgumentException(); | |
| } | |
| // start search from the next element before/after the selected element | |
| boolean backwards = (bias == Position.Bias.Backward); | |
| for (int i = startIndex; backwards ? i >= 0 : i < max; i += (backwards ? -1 : 1)) { | |
| String filename = fileChooser.getName((File)model.getElementAt(i)); | |
| if (filename.regionMatches(true, 0, prefix, 0, prefix.length())) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| }; | |
| list.setCellRenderer(new FileRenderer()); | |
| list.setLayoutOrientation(JList.VERTICAL_WRAP); | |
| // 4835633 : tell BasicListUI that this is a file list | |
| list.putClientProperty("List.isFileList", Boolean.TRUE); | |
| if (listViewWindowsStyle) { | |
| list.addFocusListener(repaintListener); | |
| } | |
| updateListRowCount(list); | |
| getModel().addListDataListener(new ListDataListener() { | |
| public void intervalAdded(ListDataEvent e) { | |
| updateListRowCount(list); | |
| } | |
| public void intervalRemoved(ListDataEvent e) { | |
| updateListRowCount(list); | |
| } | |
| public void contentsChanged(ListDataEvent e) { | |
| if (isShowing()) { | |
| clearSelection(); | |
| } | |
| updateListRowCount(list); | |
| } | |
| }); | |
| getModel().addPropertyChangeListener(this); | |
| if (fileChooser.isMultiSelectionEnabled()) { | |
| list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); | |
| } else { | |
| list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | |
| } | |
| list.setModel(new SortableListModel()); | |
| list.addListSelectionListener(createListSelectionListener()); | |
| list.addMouseListener(getMouseHandler()); | |
| JScrollPane scrollpane = new JScrollPane(list); | |
| if (listViewBackground != null) { | |
| list.setBackground(listViewBackground); | |
| } | |
| if (listViewBorder != null) { | |
| scrollpane.setBorder(listViewBorder); | |
| } | |
| list.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesListAccessibleName); | |
| p.add(scrollpane, BorderLayout.CENTER); | |
| return p; | |
| } | |
| /** | |
| * This model allows for sorting JList | |
| */ | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| private class SortableListModel extends AbstractListModel<Object> | |
| implements TableModelListener, RowSorterListener { | |
| public SortableListModel() { | |
| getDetailsTableModel().addTableModelListener(this); | |
| getRowSorter().addRowSorterListener(this); | |
| } | |
| public int getSize() { | |
| return getModel().getSize(); | |
| } | |
| public Object getElementAt(int index) { | |
| // JList doesn't support RowSorter so far, so we put it into the list model | |
| return getModel().getElementAt(getRowSorter().convertRowIndexToModel(index)); | |
| } | |
| public void tableChanged(TableModelEvent e) { | |
| fireContentsChanged(this, 0, getSize()); | |
| } | |
| public void sorterChanged(RowSorterEvent e) { | |
| fireContentsChanged(this, 0, getSize()); | |
| } | |
| } | |
| private DetailsTableModel getDetailsTableModel() { | |
| if(detailsTableModel == null) { | |
| detailsTableModel = new DetailsTableModel(getFileChooser()); | |
| } | |
| return detailsTableModel; | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| class DetailsTableModel extends AbstractTableModel implements ListDataListener { | |
| private final JFileChooser chooser; | |
| private final BasicDirectoryModel directoryModel; | |
| ShellFolderColumnInfo[] columns; | |
| int[] columnMap; | |
| DetailsTableModel(JFileChooser fc) { | |
| this.chooser = fc; | |
| directoryModel = getModel(); | |
| directoryModel.addListDataListener(this); | |
| updateColumnInfo(); | |
| } | |
| void updateColumnInfo() { | |
| File dir = chooser.getCurrentDirectory(); | |
| if (dir != null && usesShellFolder(chooser)) { | |
| try { | |
| dir = ShellFolder.getShellFolder(dir); | |
| } catch (FileNotFoundException e) { | |
| // Leave dir without changing | |
| } | |
| } | |
| ShellFolderColumnInfo[] allColumns = ShellFolder.getFolderColumns(dir); | |
| ArrayList<ShellFolderColumnInfo> visibleColumns = | |
| new ArrayList<ShellFolderColumnInfo>(); | |
| columnMap = new int[allColumns.length]; | |
| for (int i = 0; i < allColumns.length; i++) { | |
| ShellFolderColumnInfo column = allColumns[i]; | |
| if (column.isVisible()) { | |
| columnMap[visibleColumns.size()] = i; | |
| visibleColumns.add(column); | |
| } | |
| } | |
| columns = new ShellFolderColumnInfo[visibleColumns.size()]; | |
| visibleColumns.toArray(columns); | |
| columnMap = Arrays.copyOf(columnMap, columns.length); | |
| List<? extends RowSorter.SortKey> sortKeys = | |
| (rowSorter == null) ? null : rowSorter.getSortKeys(); | |
| fireTableStructureChanged(); | |
| restoreSortKeys(sortKeys); | |
| } | |
| private void restoreSortKeys(List<? extends RowSorter.SortKey> sortKeys) { | |
| if (sortKeys != null) { | |
| // check if preserved sortKeys are valid for this folder | |
| for (int i = 0; i < sortKeys.size(); i++) { | |
| RowSorter.SortKey sortKey = sortKeys.get(i); | |
| if (sortKey.getColumn() >= columns.length) { | |
| sortKeys = null; | |
| break; | |
| } | |
| } | |
| if (sortKeys != null) { | |
| rowSorter.setSortKeys(sortKeys); | |
| } | |
| } | |
| } | |
| public int getRowCount() { | |
| return directoryModel.getSize(); | |
| } | |
| public int getColumnCount() { | |
| return columns.length; | |
| } | |
| public Object getValueAt(int row, int col) { | |
| // Note: It is very important to avoid getting info on drives, as | |
| // this will trigger "No disk in A:" and similar dialogs. | |
| // | |
| // Use (f.exists() && !chooser.getFileSystemView().isFileSystemRoot(f)) to | |
| // determine if it is safe to call methods directly on f. | |
| return getFileColumnValue((File)directoryModel.getElementAt(row), col); | |
| } | |
| private Object getFileColumnValue(File f, int col) { | |
| return (col == COLUMN_FILENAME) | |
| ? f // always return the file itself for the 1st column | |
| : ShellFolder.getFolderColumnValue(f, columnMap[col]); | |
| } | |
| public void setValueAt(Object value, int row, int col) { | |
| if (col == COLUMN_FILENAME) { | |
| final JFileChooser chooser = getFileChooser(); | |
| File f = (File)getValueAt(row, col); | |
| if (f != null) { | |
| String oldDisplayName = chooser.getName(f); | |
| String oldFileName = f.getName(); | |
| String newDisplayName = ((String)value).trim(); | |
| String newFileName; | |
| if (!newDisplayName.equals(oldDisplayName)) { | |
| newFileName = newDisplayName; | |
| //Check if extension is hidden from user | |
| int i1 = oldFileName.length(); | |
| int i2 = oldDisplayName.length(); | |
| if (i1 > i2 && oldFileName.charAt(i2) == '.') { | |
| newFileName = newDisplayName + oldFileName.substring(i2); | |
| } | |
| // rename | |
| FileSystemView fsv = chooser.getFileSystemView(); | |
| final File f2 = fsv.createFileObject(f.getParentFile(), newFileName); | |
| if (f2.exists()) { | |
| JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText, | |
| oldFileName), renameErrorTitleText, JOptionPane.ERROR_MESSAGE); | |
| } else { | |
| if (directoryModel.renameFile(f, f2)) { | |
| if (fsv.isParent(chooser.getCurrentDirectory(), f2)) { | |
| // The setSelectedFile method produces a new setValueAt invocation while the JTable | |
| // is editing. Postpone file selection to be sure that edit mode of the JTable | |
| // is completed | |
| SwingUtilities.invokeLater(new Runnable() { | |
| public void run() { | |
| if (chooser.isMultiSelectionEnabled()) { | |
| chooser.setSelectedFiles(new File[]{f2}); | |
| } else { | |
| chooser.setSelectedFile(f2); | |
| } | |
| } | |
| }); | |
| } else { | |
| // Could be because of delay in updating Desktop folder | |
| // chooser.setSelectedFile(null); | |
| } | |
| } else { | |
| JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName), | |
| renameErrorTitleText, JOptionPane.ERROR_MESSAGE); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public boolean isCellEditable(int row, int column) { | |
| File currentDirectory = getFileChooser().getCurrentDirectory(); | |
| return (!readOnly && column == COLUMN_FILENAME && canWrite(currentDirectory)); | |
| } | |
| public void contentsChanged(ListDataEvent e) { | |
| // Update the selection after the model has been updated | |
| new DelayedSelectionUpdater(); | |
| fireTableDataChanged(); | |
| } | |
| public void intervalAdded(ListDataEvent e) { | |
| int i0 = e.getIndex0(); | |
| int i1 = e.getIndex1(); | |
| if (i0 == i1) { | |
| File file = (File)directoryModel.getElementAt(i0); | |
| if (file.equals(newFolderFile)) { | |
| new DelayedSelectionUpdater(file); | |
| newFolderFile = null; | |
| } | |
| } | |
| fireTableRowsInserted(e.getIndex0(), e.getIndex1()); | |
| } | |
| public void intervalRemoved(ListDataEvent e) { | |
| fireTableRowsDeleted(e.getIndex0(), e.getIndex1()); | |
| } | |
| public ShellFolderColumnInfo[] getColumns() { | |
| return columns; | |
| } | |
| } | |
| private void updateDetailsColumnModel(JTable table) { | |
| if (table != null) { | |
| ShellFolderColumnInfo[] columns = detailsTableModel.getColumns(); | |
| TableColumnModel columnModel = new DefaultTableColumnModel(); | |
| for (int i = 0; i < columns.length; i++) { | |
| ShellFolderColumnInfo dataItem = columns[i]; | |
| TableColumn column = new TableColumn(i); | |
| String title = dataItem.getTitle(); | |
| if (title != null && title.startsWith("FileChooser.") && title.endsWith("HeaderText")) { | |
| // the column must have a string resource that we try to get | |
| String uiTitle = UIManager.getString(title, table.getLocale()); | |
| if (uiTitle != null) { | |
| title = uiTitle; | |
| } | |
| } | |
| column.setHeaderValue(title); | |
| Integer width = dataItem.getWidth(); | |
| if (width != null) { | |
| column.setPreferredWidth(width); | |
| // otherwise we let JTable to decide the actual width | |
| } | |
| columnModel.addColumn(column); | |
| } | |
| // Install cell editor for editing file name | |
| if (!readOnly && columnModel.getColumnCount() > COLUMN_FILENAME) { | |
| columnModel.getColumn(COLUMN_FILENAME). | |
| setCellEditor(getDetailsTableCellEditor()); | |
| } | |
| table.setColumnModel(columnModel); | |
| } | |
| } | |
| private DetailsTableRowSorter getRowSorter() { | |
| if (rowSorter == null) { | |
| rowSorter = new DetailsTableRowSorter(); | |
| } | |
| return rowSorter; | |
| } | |
| private class DetailsTableRowSorter extends TableRowSorter<TableModel> { | |
| public DetailsTableRowSorter() { | |
| SorterModelWrapper modelWrapper = new SorterModelWrapper(); | |
| setModelWrapper(modelWrapper); | |
| modelWrapper.getModel().addTableModelListener( | |
| new TableModelListener() { | |
| @Override | |
| public void tableChanged(TableModelEvent e) { | |
| modelStructureChanged(); | |
| } | |
| }); | |
| } | |
| public void updateComparators(ShellFolderColumnInfo [] columns) { | |
| for (int i = 0; i < columns.length; i++) { | |
| Comparator<?> c = columns[i].getComparator(); | |
| if (c != null) { | |
| c = new DirectoriesFirstComparatorWrapper(i, c); | |
| } | |
| setComparator(i, c); | |
| } | |
| } | |
| @Override | |
| public void sort() { | |
| ShellFolder.invoke(new Callable<Void>() { | |
| public Void call() { | |
| DetailsTableRowSorter.super.sort(); | |
| return null; | |
| } | |
| }); | |
| } | |
| public void modelStructureChanged() { | |
| super.modelStructureChanged(); | |
| updateComparators(detailsTableModel.getColumns()); | |
| } | |
| private class SorterModelWrapper extends ModelWrapper<TableModel, Integer> { | |
| public TableModel getModel() { | |
| return getDetailsTableModel(); | |
| } | |
| public int getColumnCount() { | |
| return getDetailsTableModel().getColumnCount(); | |
| } | |
| public int getRowCount() { | |
| return getDetailsTableModel().getRowCount(); | |
| } | |
| public Object getValueAt(int row, int column) { | |
| return FilePane.this.getModel().getElementAt(row); | |
| } | |
| public Integer getIdentifier(int row) { | |
| return row; | |
| } | |
| } | |
| } | |
| /** | |
| * This class sorts directories before files, comparing directory to | |
| * directory and file to file using the wrapped comparator. | |
| */ | |
| private class DirectoriesFirstComparatorWrapper implements Comparator<File> { | |
| private Comparator<Object> comparator; | |
| private int column; | |
| @SuppressWarnings("unchecked") | |
| public DirectoriesFirstComparatorWrapper(int column, Comparator<?> comparator) { | |
| this.column = column; | |
| this.comparator = (Comparator<Object>)comparator; | |
| } | |
| public int compare(File f1, File f2) { | |
| if (f1 != null && f2 != null) { | |
| boolean traversable1 = getFileChooser().isTraversable(f1); | |
| boolean traversable2 = getFileChooser().isTraversable(f2); | |
| // directories go first | |
| if (traversable1 && !traversable2) { | |
| return -1; | |
| } | |
| if (!traversable1 && traversable2) { | |
| return 1; | |
| } | |
| } | |
| if (detailsTableModel.getColumns()[column].isCompareByColumn()) { | |
| return comparator.compare( | |
| getDetailsTableModel().getFileColumnValue(f1, column), | |
| getDetailsTableModel().getFileColumnValue(f2, column) | |
| ); | |
| } | |
| // For this column we need to pass the file itself (not a | |
| // column value) to the comparator | |
| return comparator.compare(f1, f2); | |
| } | |
| } | |
| private DetailsTableCellEditor tableCellEditor; | |
| private DetailsTableCellEditor getDetailsTableCellEditor() { | |
| if (tableCellEditor == null) { | |
| tableCellEditor = new DetailsTableCellEditor(new JTextField()); | |
| } | |
| return tableCellEditor; | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| private class DetailsTableCellEditor extends DefaultCellEditor { | |
| private final JTextField tf; | |
| public DetailsTableCellEditor(JTextField tf) { | |
| super(tf); | |
| this.tf = tf; | |
| tf.setName("Table.editor"); | |
| tf.addFocusListener(editorFocusListener); | |
| } | |
| public Component getTableCellEditorComponent(JTable table, Object value, | |
| boolean isSelected, int row, int column) { | |
| Component comp = super.getTableCellEditorComponent(table, value, | |
| isSelected, row, column); | |
| if (value instanceof File) { | |
| tf.setText(getFileChooser().getName((File) value)); | |
| tf.selectAll(); | |
| } | |
| return comp; | |
| } | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| class DetailsTableCellRenderer extends DefaultTableCellRenderer { | |
| private final JFileChooser chooser; | |
| private final DateFormat df; | |
| private final MessageFormat mf = new MessageFormat(""); | |
| private final NumberFormat nf = NumberFormat.getNumberInstance(); | |
| private static final double baseFileSize = 1000.0; | |
| DetailsTableCellRenderer(JFileChooser chooser) { | |
| this.chooser = chooser; | |
| df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, | |
| chooser.getLocale()); | |
| nf.setMinimumFractionDigits(1); | |
| } | |
| public void setBounds(int x, int y, int width, int height) { | |
| if (getHorizontalAlignment() == SwingConstants.LEADING && | |
| !fullRowSelection) { | |
| // Restrict width to actual text | |
| width = Math.min(width, this.getPreferredSize().width+4); | |
| } else { | |
| x -= 4; | |
| } | |
| super.setBounds(x, y, width, height); | |
| } | |
| public Insets getInsets(Insets i) { | |
| // Provide some space between columns | |
| i = super.getInsets(i); | |
| i.left += 4; | |
| i.right += 4; | |
| return i; | |
| } | |
| public Component getTableCellRendererComponent(JTable table, Object value, | |
| boolean isSelected, boolean hasFocus, int row, int column) { | |
| if ((table.convertColumnIndexToModel(column) != COLUMN_FILENAME || | |
| (listViewWindowsStyle && !table.isFocusOwner())) && | |
| !fullRowSelection) { | |
| isSelected = false; | |
| } | |
| super.getTableCellRendererComponent(table, value, isSelected, | |
| hasFocus, row, column); | |
| setIcon(null); | |
| int modelColumn = table.convertColumnIndexToModel(column); | |
| ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn]; | |
| Integer alignment = columnInfo.getAlignment(); | |
| if (alignment == null) { | |
| alignment = (value instanceof Number) | |
| ? SwingConstants.RIGHT | |
| : SwingConstants.LEADING; | |
| } | |
| setHorizontalAlignment(alignment); | |
| // formatting cell text | |
| // TODO: it's rather a temporary trick, to be revised | |
| String text; | |
| if (value == null) { | |
| text = ""; | |
| } else if (value instanceof File) { | |
| File file = (File)value; | |
| text = chooser.getName(file); | |
| Icon icon = chooser.getIcon(file); | |
| setIcon(icon); | |
| } else if (value instanceof final Long len) { | |
| /* | |
| * This code block is relevant to Linux. | |
| * File size is displayed up to 1 decimal precision. | |
| * Base-10 number system is used for formatting file size | |
| * similar to how it's formatted in file managers on Linux. | |
| * Empty file size is shown as 0.0 KB, | |
| * 1-100-byte files are shown as 0.1 KB, | |
| * 101-200-byte files are shown as 0.2 KB and so on. | |
| */ | |
| Object[] displayedFileSize = new Object[1]; | |
| if (listViewWindowsStyle) { | |
| updateMessageFormatPattern(kiloByteString); | |
| displayedFileSize[0] = roundToOneDecimalPlace(len); | |
| } else { | |
| double kbVal = roundToOneDecimalPlace(len); | |
| if (kbVal < baseFileSize) { | |
| updateMessageFormatPattern(kiloByteString); | |
| displayedFileSize[0] = kbVal; | |
| } else { | |
| double mbVal = roundToOneDecimalPlace(Math.ceil(kbVal)); | |
| if (mbVal < baseFileSize) { | |
| updateMessageFormatPattern(megaByteString); | |
| displayedFileSize[0] = mbVal; | |
| } else { | |
| updateMessageFormatPattern(gigaByteString); | |
| displayedFileSize[0] = roundToOneDecimalPlace(Math.ceil(mbVal)); | |
| } | |
| } | |
| } | |
| text = mf.format(displayedFileSize); | |
| } else if (value instanceof Date) { | |
| text = df.format((Date)value); | |
| } else { | |
| text = value.toString(); | |
| } | |
| setText(text); | |
| putClientProperty("html.disable", getFileChooser().getClientProperty("html.disable")); | |
| return this; | |
| } | |
| private void updateMessageFormatPattern(String pattern) { | |
| mf.applyPattern(pattern); | |
| mf.setFormat(0, nf); | |
| } | |
| /** | |
| * Rounds a value to one decimal place. It's used to format | |
| * file size similar to how it's formatted in file managers on Linux. | |
| * For example, the file size of 1200 bytes is rounded to 1.2 KB. | |
| * | |
| * @param fileSize the file size to round to one decimal place | |
| * @return file size rounded to one decimal place | |
| */ | |
| private static double roundToOneDecimalPlace(double fileSize) { | |
| return Math.ceil(fileSize / 100.0d) / 10.0d; | |
| } | |
| } | |
| public JPanel createDetailsView() { | |
| final JFileChooser chooser = getFileChooser(); | |
| JPanel p = new JPanel(new BorderLayout()); | |
| final JTable detailsTable = new JTable(getDetailsTableModel()) { | |
| // Handle Escape key events here | |
| protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { | |
| if (e.getKeyCode() == KeyEvent.VK_ESCAPE && getCellEditor() == null) { | |
| // We are not editing, forward to filechooser. | |
| chooser.dispatchEvent(e); | |
| return true; | |
| } | |
| return super.processKeyBinding(ks, e, condition, pressed); | |
| } | |
| public void tableChanged(TableModelEvent e) { | |
| super.tableChanged(e); | |
| if (e.getFirstRow() == TableModelEvent.HEADER_ROW) { | |
| // update header with possibly changed column set | |
| updateDetailsColumnModel(this); | |
| } | |
| } | |
| }; | |
| detailsTable.setRowSorter(getRowSorter()); | |
| detailsTable.setAutoCreateColumnsFromModel(false); | |
| detailsTable.setComponentOrientation(chooser.getComponentOrientation()); | |
| detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); | |
| detailsTable.setShowGrid(false); | |
| detailsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); | |
| detailsTable.addKeyListener(detailsKeyListener); | |
| Font font = list.getFont(); | |
| detailsTable.setFont(font); | |
| detailsTable.setIntercellSpacing(new Dimension(0, 0)); | |
| TableCellRenderer headerRenderer = | |
| new AlignableTableHeaderRenderer(detailsTable.getTableHeader().getDefaultRenderer()); | |
| detailsTable.getTableHeader().setDefaultRenderer(headerRenderer); | |
| TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser); | |
| detailsTable.setDefaultRenderer(Object.class, cellRenderer); | |
| // So that drag can be started on a mouse press | |
| detailsTable.getColumnModel().getSelectionModel(). | |
| setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | |
| detailsTable.addMouseListener(getMouseHandler()); | |
| // No need to addListSelectionListener because selections are forwarded | |
| // to our JList. | |
| // 4835633 : tell BasicTableUI that this is a file list | |
| detailsTable.putClientProperty("Table.isFileList", Boolean.TRUE); | |
| if (listViewWindowsStyle) { | |
| detailsTable.addFocusListener(repaintListener); | |
| } | |
| detailsTable.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, | |
| null); | |
| detailsTable.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, | |
| null); | |
| JScrollPane scrollpane = new JScrollPane(detailsTable); | |
| scrollpane.setComponentOrientation(chooser.getComponentOrientation()); | |
| LookAndFeel.installColors(scrollpane.getViewport(), "Table.background", "Table.foreground"); | |
| // Adjust width of first column so the table fills the viewport when | |
| // first displayed (temporary listener). | |
| scrollpane.addComponentListener(new ComponentAdapter() { | |
| public void componentResized(ComponentEvent e) { | |
| JScrollPane sp = (JScrollPane)e.getComponent(); | |
| fixNameColumnWidth(sp.getViewport().getSize().width); | |
| sp.removeComponentListener(this); | |
| } | |
| }); | |
| // 4835633. | |
| // If the mouse is pressed in the area below the Details view table, the | |
| // event is not dispatched to the Table MouseListener but to the | |
| // scrollpane. Listen for that here so we can clear the selection. | |
| scrollpane.addMouseListener(new MouseAdapter() { | |
| public void mousePressed(MouseEvent e) { | |
| JScrollPane jsp = ((JScrollPane)e.getComponent()); | |
| JTable table = (JTable)jsp.getViewport().getView(); | |
| if (!e.isShiftDown() || table.getSelectionModel().getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) { | |
| clearSelection(); | |
| TableCellEditor tce = table.getCellEditor(); | |
| if (tce != null) { | |
| tce.stopCellEditing(); | |
| } | |
| } | |
| } | |
| }); | |
| detailsTable.setForeground(list.getForeground()); | |
| detailsTable.setBackground(list.getBackground()); | |
| if (listViewBorder != null) { | |
| scrollpane.setBorder(listViewBorder); | |
| } | |
| p.add(scrollpane, BorderLayout.CENTER); | |
| detailsTableModel.fireTableStructureChanged(); | |
| detailsTable.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, filesDetailsAccessibleName); | |
| return p; | |
| } // createDetailsView | |
| private class AlignableTableHeaderRenderer implements TableCellRenderer { | |
| TableCellRenderer wrappedRenderer; | |
| public AlignableTableHeaderRenderer(TableCellRenderer wrappedRenderer) { | |
| this.wrappedRenderer = wrappedRenderer; | |
| } | |
| public Component getTableCellRendererComponent( | |
| JTable table, Object value, boolean isSelected, | |
| boolean hasFocus, int row, int column) { | |
| Component c = wrappedRenderer.getTableCellRendererComponent( | |
| table, value, isSelected, hasFocus, row, column); | |
| int modelColumn = table.convertColumnIndexToModel(column); | |
| ShellFolderColumnInfo columnInfo = detailsTableModel.getColumns()[modelColumn]; | |
| Integer alignment = columnInfo.getAlignment(); | |
| if (alignment == null) { | |
| alignment = SwingConstants.CENTER; | |
| } | |
| if (c instanceof JLabel) { | |
| ((JLabel) c).setHorizontalAlignment(alignment); | |
| } | |
| return c; | |
| } | |
| } | |
| private void fixNameColumnWidth(int viewWidth) { | |
| TableColumn nameCol = detailsTable.getColumnModel().getColumn(COLUMN_FILENAME); | |
| int tableWidth = detailsTable.getPreferredSize().width; | |
| if (tableWidth < viewWidth) { | |
| nameCol.setPreferredWidth(nameCol.getPreferredWidth() + viewWidth - tableWidth); | |
| } | |
| } | |
| private class DelayedSelectionUpdater implements Runnable { | |
| File editFile; | |
| DelayedSelectionUpdater() { | |
| this(null); | |
| } | |
| DelayedSelectionUpdater(File editFile) { | |
| this.editFile = editFile; | |
| if (isShowing()) { | |
| SwingUtilities.invokeLater(this); | |
| } | |
| } | |
| public void run() { | |
| setFileSelected(); | |
| if (editFile != null) { | |
| editFileName(getRowSorter().convertRowIndexToView( | |
| getModel().indexOf(editFile))); | |
| editFile = null; | |
| } | |
| } | |
| } | |
| /** | |
| * Creates a selection listener for the list of files and directories. | |
| * | |
| * @return a <code>ListSelectionListener</code> | |
| */ | |
| public ListSelectionListener createListSelectionListener() { | |
| return fileChooserUIAccessor.createListSelectionListener(); | |
| } | |
| int lastIndex = -1; | |
| File editFile = null; | |
| private int getEditIndex() { | |
| return lastIndex; | |
| } | |
| private void setEditIndex(int i) { | |
| lastIndex = i; | |
| } | |
| private void resetEditIndex() { | |
| lastIndex = -1; | |
| } | |
| private void cancelEdit() { | |
| if (editFile != null) { | |
| editFile = null; | |
| list.remove(editCell); | |
| repaint(); | |
| } else if (detailsTable != null && detailsTable.isEditing()) { | |
| detailsTable.getCellEditor().cancelCellEditing(); | |
| } | |
| } | |
| JTextField editCell = null; | |
| /** | |
| * @param index visual index of the file to be edited | |
| */ | |
| @SuppressWarnings("deprecation") | |
| private void editFileName(int index) { | |
| JFileChooser chooser = getFileChooser(); | |
| File currentDirectory = chooser.getCurrentDirectory(); | |
| if (readOnly || !canWrite(currentDirectory)) { | |
| return; | |
| } | |
| ensureIndexIsVisible(index); | |
| switch (viewType) { | |
| case VIEWTYPE_LIST: | |
| editFile = (File)getModel().getElementAt(getRowSorter().convertRowIndexToModel(index)); | |
| Rectangle r = list.getCellBounds(index, index); | |
| if (editCell == null) { | |
| editCell = new JTextField(); | |
| editCell.setName("Tree.cellEditor"); | |
| editCell.addActionListener(new EditActionListener()); | |
| editCell.addFocusListener(editorFocusListener); | |
| editCell.setNextFocusableComponent(list); | |
| } | |
| list.add(editCell); | |
| editCell.setText(chooser.getName(editFile)); | |
| ComponentOrientation orientation = list.getComponentOrientation(); | |
| editCell.setComponentOrientation(orientation); | |
| Icon icon = chooser.getIcon(editFile); | |
| // PENDING - grab padding (4) below from defaults table. | |
| int editX = icon == null ? 20 : icon.getIconWidth() + 4; | |
| if (orientation.isLeftToRight()) { | |
| editCell.setBounds(editX + r.x, r.y, r.width - editX, r.height); | |
| } else { | |
| editCell.setBounds(r.x, r.y, r.width - editX, r.height); | |
| } | |
| editCell.requestFocus(); | |
| editCell.selectAll(); | |
| break; | |
| case VIEWTYPE_DETAILS: | |
| detailsTable.editCellAt(index, COLUMN_FILENAME); | |
| break; | |
| } | |
| } | |
| class EditActionListener implements ActionListener { | |
| public void actionPerformed(ActionEvent e) { | |
| applyEdit(); | |
| } | |
| } | |
| private void applyEdit() { | |
| if (editFile != null && editFile.exists()) { | |
| JFileChooser chooser = getFileChooser(); | |
| String oldDisplayName = chooser.getName(editFile); | |
| String oldFileName = editFile.getName(); | |
| String newDisplayName = editCell.getText().trim(); | |
| String newFileName; | |
| if (!newDisplayName.equals(oldDisplayName)) { | |
| newFileName = newDisplayName; | |
| //Check if extension is hidden from user | |
| int i1 = oldFileName.length(); | |
| int i2 = oldDisplayName.length(); | |
| if (i1 > i2 && oldFileName.charAt(i2) == '.') { | |
| newFileName = newDisplayName + oldFileName.substring(i2); | |
| } | |
| // rename | |
| FileSystemView fsv = chooser.getFileSystemView(); | |
| File f2 = fsv.createFileObject(editFile.getParentFile(), newFileName); | |
| if (f2.exists()) { | |
| JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText, oldFileName), | |
| renameErrorTitleText, JOptionPane.ERROR_MESSAGE); | |
| } else { | |
| if (getModel().renameFile(editFile, f2)) { | |
| if (fsv.isParent(chooser.getCurrentDirectory(), f2)) { | |
| if (chooser.isMultiSelectionEnabled()) { | |
| chooser.setSelectedFiles(new File[]{f2}); | |
| } else { | |
| chooser.setSelectedFile(f2); | |
| } | |
| } else { | |
| //Could be because of delay in updating Desktop folder | |
| //chooser.setSelectedFile(null); | |
| } | |
| } else { | |
| JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorText, oldFileName), | |
| renameErrorTitleText, JOptionPane.ERROR_MESSAGE); | |
| } | |
| } | |
| } | |
| } | |
| if (detailsTable != null && detailsTable.isEditing()) { | |
| detailsTable.getCellEditor().stopCellEditing(); | |
| } | |
| cancelEdit(); | |
| } | |
| protected Action newFolderAction; | |
| public Action getNewFolderAction() { | |
| if (!readOnly && newFolderAction == null) { | |
| newFolderAction = new AbstractAction(newFolderActionLabelText) { | |
| private Action basicNewFolderAction; | |
| // Initializer | |
| { | |
| putValue(Action.ACTION_COMMAND_KEY, FilePane.ACTION_NEW_FOLDER); | |
| File currentDirectory = getFileChooser().getCurrentDirectory(); | |
| if (currentDirectory != null) { | |
| setEnabled(canWrite(currentDirectory)); | |
| } | |
| } | |
| public void actionPerformed(ActionEvent ev) { | |
| if (basicNewFolderAction == null) { | |
| basicNewFolderAction = fileChooserUIAccessor.getNewFolderAction(); | |
| } | |
| JFileChooser fc = getFileChooser(); | |
| File oldFile = fc.getSelectedFile(); | |
| basicNewFolderAction.actionPerformed(ev); | |
| File newFile = fc.getSelectedFile(); | |
| if (newFile != null && !newFile.equals(oldFile) && newFile.isDirectory()) { | |
| newFolderFile = newFile; | |
| } | |
| } | |
| }; | |
| } | |
| return newFolderAction; | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| protected class FileRenderer extends DefaultListCellRenderer { | |
| public Component getListCellRendererComponent(JList<?> list, Object value, | |
| int index, boolean isSelected, | |
| boolean cellHasFocus) { | |
| if (listViewWindowsStyle && !list.isFocusOwner()) { | |
| isSelected = false; | |
| } | |
| super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); | |
| File file = (File) value; | |
| String fileName = getFileChooser().getName(file); | |
| setText(fileName); | |
| setFont(list.getFont()); | |
| Icon icon = getFileChooser().getIcon(file); | |
| if (icon != null) { | |
| setIcon(icon); | |
| } else { | |
| if (getFileChooser().getFileSystemView().isTraversable(file)) { | |
| setText(fileName+File.separator); | |
| } | |
| } | |
| putClientProperty("html.disable", getFileChooser().getClientProperty("html.disable")); | |
| return this; | |
| } | |
| } | |
| @SuppressWarnings("deprecation") | |
| void setFileSelected() { | |
| if (getFileChooser().isMultiSelectionEnabled() && !isDirectorySelected()) { | |
| File[] files = getFileChooser().getSelectedFiles(); // Should be selected | |
| Object[] selectedObjects = list.getSelectedValues(); // Are actually selected | |
| listSelectionModel.setValueIsAdjusting(true); | |
| try { | |
| int lead = listSelectionModel.getLeadSelectionIndex(); | |
| int anchor = listSelectionModel.getAnchorSelectionIndex(); | |
| Arrays.sort(files); | |
| Arrays.sort(selectedObjects); | |
| int shouldIndex = 0; | |
| int actuallyIndex = 0; | |
| // Remove files that shouldn't be selected and add files which should be selected | |
| // Note: Assume files are already sorted in compareTo order. | |
| while (shouldIndex < files.length && | |
| actuallyIndex < selectedObjects.length) { | |
| int comparison = files[shouldIndex].compareTo((File)selectedObjects[actuallyIndex]); | |
| if (comparison < 0) { | |
| doSelectFile(files[shouldIndex++]); | |
| } else if (comparison > 0) { | |
| doDeselectFile(selectedObjects[actuallyIndex++]); | |
| } else { | |
| // Do nothing | |
| shouldIndex++; | |
| actuallyIndex++; | |
| } | |
| } | |
| while (shouldIndex < files.length) { | |
| doSelectFile(files[shouldIndex++]); | |
| } | |
| while (actuallyIndex < selectedObjects.length) { | |
| doDeselectFile(selectedObjects[actuallyIndex++]); | |
| } | |
| // restore the anchor and lead | |
| if (listSelectionModel instanceof DefaultListSelectionModel) { | |
| ((DefaultListSelectionModel)listSelectionModel). | |
| moveLeadSelectionIndex(lead); | |
| listSelectionModel.setAnchorSelectionIndex(anchor); | |
| } | |
| } finally { | |
| listSelectionModel.setValueIsAdjusting(false); | |
| } | |
| } else { | |
| JFileChooser chooser = getFileChooser(); | |
| File f; | |
| if (isDirectorySelected()) { | |
| f = getDirectory(); | |
| } else { | |
| f = chooser.getSelectedFile(); | |
| } | |
| int i; | |
| if (f != null && (i = getModel().indexOf(f)) >= 0) { | |
| int viewIndex = getRowSorter().convertRowIndexToView(i); | |
| listSelectionModel.setSelectionInterval(viewIndex, viewIndex); | |
| ensureIndexIsVisible(viewIndex); | |
| } else { | |
| clearSelection(); | |
| } | |
| } | |
| } | |
| private void doSelectFile(File fileToSelect) { | |
| int index = getModel().indexOf(fileToSelect); | |
| // could be missed in the current directory if it changed | |
| if (index >= 0) { | |
| index = getRowSorter().convertRowIndexToView(index); | |
| listSelectionModel.addSelectionInterval(index, index); | |
| } | |
| } | |
| private void doDeselectFile(Object fileToDeselect) { | |
| int index = getRowSorter().convertRowIndexToView( | |
| getModel().indexOf(fileToDeselect)); | |
| listSelectionModel.removeSelectionInterval(index, index); | |
| } | |
| /* The following methods are used by the PropertyChange Listener */ | |
| private void doSelectedFileChanged(PropertyChangeEvent e) { | |
| applyEdit(); | |
| File f = (File) e.getNewValue(); | |
| JFileChooser fc = getFileChooser(); | |
| if (f != null | |
| && ((fc.isFileSelectionEnabled() && !f.isDirectory()) | |
| || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) { | |
| setFileSelected(); | |
| } | |
| } | |
| private void doSelectedFilesChanged(PropertyChangeEvent e) { | |
| applyEdit(); | |
| File[] files = (File[]) e.getNewValue(); | |
| JFileChooser fc = getFileChooser(); | |
| if (files != null | |
| && files.length > 0 | |
| && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) { | |
| setFileSelected(); | |
| } | |
| } | |
| private void doDirectoryChanged(PropertyChangeEvent e) { | |
| getDetailsTableModel().updateColumnInfo(); | |
| JFileChooser fc = getFileChooser(); | |
| FileSystemView fsv = fc.getFileSystemView(); | |
| applyEdit(); | |
| resetEditIndex(); | |
| ensureIndexIsVisible(0); | |
| File currentDirectory = fc.getCurrentDirectory(); | |
| if (currentDirectory != null) { | |
| if (!readOnly) { | |
| getNewFolderAction().setEnabled(canWrite(currentDirectory)); | |
| } | |
| fileChooserUIAccessor.getChangeToParentDirectoryAction().setEnabled(!fsv.isRoot(currentDirectory)); | |
| } | |
| if (list != null) { | |
| list.clearSelection(); | |
| } | |
| } | |
| private void doFilterChanged(PropertyChangeEvent e) { | |
| applyEdit(); | |
| resetEditIndex(); | |
| clearSelection(); | |
| } | |
| private void doFileSelectionModeChanged(PropertyChangeEvent e) { | |
| applyEdit(); | |
| resetEditIndex(); | |
| clearSelection(); | |
| } | |
| private void doMultiSelectionChanged(PropertyChangeEvent e) { | |
| clearSelection(); | |
| if (getFileChooser().isMultiSelectionEnabled()) { | |
| listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); | |
| getFileChooser().setSelectedFile(null); | |
| } else { | |
| listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | |
| getFileChooser().setSelectedFiles(null); | |
| } | |
| } | |
| /* | |
| * Listen for filechooser property changes, such as | |
| * the selected file changing, or the type of the dialog changing. | |
| */ | |
| public void propertyChange(PropertyChangeEvent e) { | |
| if (viewType == -1) { | |
| setViewType(VIEWTYPE_LIST); | |
| } | |
| String s = e.getPropertyName(); | |
| if (s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) { | |
| doSelectedFileChanged(e); | |
| } else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) { | |
| doSelectedFilesChanged(e); | |
| } else if (s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) { | |
| doDirectoryChanged(e); | |
| } else if (s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) { | |
| doFilterChanged(e); | |
| } else if (s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) { | |
| doFileSelectionModeChanged(e); | |
| } else if (s.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) { | |
| doMultiSelectionChanged(e); | |
| } else if (s.equals(JFileChooser.CANCEL_SELECTION)) { | |
| applyEdit(); | |
| } else if (s.equals("busy")) { | |
| setCursor((Boolean)e.getNewValue() ? waitCursor : null); | |
| } else if (s.equals("componentOrientation")) { | |
| ComponentOrientation o = (ComponentOrientation)e.getNewValue(); | |
| JFileChooser cc = (JFileChooser)e.getSource(); | |
| if (o != e.getOldValue()) { | |
| cc.applyComponentOrientation(o); | |
| } | |
| if (detailsTable != null) { | |
| detailsTable.setComponentOrientation(o); | |
| detailsTable.getParent().getParent().setComponentOrientation(o); | |
| } | |
| } | |
| } | |
| private void ensureIndexIsVisible(int i) { | |
| if (i >= 0) { | |
| if (list != null) { | |
| list.ensureIndexIsVisible(i); | |
| } | |
| if (detailsTable != null) { | |
| detailsTable.scrollRectToVisible(detailsTable.getCellRect(i, COLUMN_FILENAME, true)); | |
| } | |
| } | |
| } | |
| public void ensureFileIsVisible(JFileChooser fc, File f) { | |
| int modelIndex = getModel().indexOf(f); | |
| if (modelIndex >= 0) { | |
| ensureIndexIsVisible(getRowSorter().convertRowIndexToView(modelIndex)); | |
| } | |
| } | |
| public void rescanCurrentDirectory() { | |
| getModel().validateFileCache(); | |
| } | |
| public void clearSelection() { | |
| if (listSelectionModel != null) { | |
| listSelectionModel.clearSelection(); | |
| if (listSelectionModel instanceof DefaultListSelectionModel) { | |
| ((DefaultListSelectionModel)listSelectionModel).moveLeadSelectionIndex(-1); | |
| listSelectionModel.setAnchorSelectionIndex(-1); | |
| } | |
| } | |
| } | |
| public JMenu getViewMenu() { | |
| if (viewMenu == null) { | |
| viewMenu = new JMenu(viewMenuLabelText); | |
| ButtonGroup viewButtonGroup = new ButtonGroup(); | |
| for (int i = 0; i < VIEWTYPE_COUNT; i++) { | |
| JRadioButtonMenuItem mi = | |
| new JRadioButtonMenuItem(new ViewTypeAction(i)); | |
| viewButtonGroup.add(mi); | |
| viewMenu.add(mi); | |
| } | |
| updateViewMenu(); | |
| } | |
| return viewMenu; | |
| } | |
| private void updateViewMenu() { | |
| if (viewMenu != null) { | |
| Component[] comps = viewMenu.getMenuComponents(); | |
| for (Component comp : comps) { | |
| if (comp instanceof JRadioButtonMenuItem) { | |
| JRadioButtonMenuItem mi = (JRadioButtonMenuItem) comp; | |
| if (((ViewTypeAction)mi.getAction()).viewType == viewType) { | |
| mi.setSelected(true); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public JPopupMenu getComponentPopupMenu() { | |
| JPopupMenu popupMenu = getFileChooser().getComponentPopupMenu(); | |
| if (popupMenu != null) { | |
| return popupMenu; | |
| } | |
| JMenu viewMenu = getViewMenu(); | |
| if (contextMenu == null) { | |
| contextMenu = new JPopupMenu(); | |
| if (viewMenu != null) { | |
| contextMenu.add(viewMenu); | |
| if (listViewWindowsStyle) { | |
| contextMenu.addSeparator(); | |
| } | |
| } | |
| ActionMap actionMap = getActionMap(); | |
| Action refreshAction = actionMap.get(ACTION_REFRESH); | |
| Action newFolderAction = actionMap.get(ACTION_NEW_FOLDER); | |
| if (refreshAction != null) { | |
| contextMenu.add(refreshAction); | |
| if (listViewWindowsStyle && newFolderAction != null) { | |
| contextMenu.addSeparator(); | |
| } | |
| } | |
| if (newFolderAction != null) { | |
| contextMenu.add(newFolderAction); | |
| } | |
| } | |
| if (viewMenu != null) { | |
| viewMenu.getPopupMenu().setInvoker(viewMenu); | |
| } | |
| contextMenu.applyComponentOrientation(getFileChooser().getComponentOrientation()); | |
| return contextMenu; | |
| } | |
| private Handler handler; | |
| protected Handler getMouseHandler() { | |
| if (handler == null) { | |
| handler = new Handler(); | |
| } | |
| return handler; | |
| } | |
| private class Handler implements MouseListener { | |
| private MouseListener doubleClickListener; | |
| @SuppressWarnings("deprecation") | |
| public void mouseClicked(MouseEvent evt) { | |
| JComponent source = (JComponent)evt.getSource(); | |
| if (!source.isEnabled()) { | |
| return; | |
| } | |
| int index; | |
| if (source instanceof JList) { | |
| index = SwingUtilities2.loc2IndexFileList(list, evt.getPoint()); | |
| } else if (source instanceof JTable) { | |
| JTable table = (JTable)source; | |
| Point p = evt.getPoint(); | |
| index = table.rowAtPoint(p); | |
| boolean pointOutsidePrefSize = | |
| SwingUtilities2.pointOutsidePrefSize( | |
| table, index, table.columnAtPoint(p), p); | |
| if (pointOutsidePrefSize && !fullRowSelection) { | |
| return; | |
| } | |
| // Translate point from table to list | |
| if (index >= 0 && list != null && | |
| listSelectionModel.isSelectedIndex(index)) { | |
| // Make a new event with the list as source, placing the | |
| // click in the corresponding list cell. | |
| Rectangle r = list.getCellBounds(index, index); | |
| MouseEvent newEvent = new MouseEvent(list, evt.getID(), | |
| evt.getWhen(), evt.getModifiers(), | |
| r.x + 1, r.y + r.height/2, | |
| evt.getXOnScreen(), | |
| evt.getYOnScreen(), | |
| evt.getClickCount(), evt.isPopupTrigger(), | |
| evt.getButton()); | |
| MouseEventAccessor meAccessor = AWTAccessor.getMouseEventAccessor(); | |
| meAccessor.setCausedByTouchEvent(newEvent, | |
| meAccessor.isCausedByTouchEvent(evt)); | |
| evt = newEvent; | |
| } | |
| } else { | |
| return; | |
| } | |
| if (index >= 0 && SwingUtilities.isLeftMouseButton(evt)) { | |
| JFileChooser fc = getFileChooser(); | |
| // For single click, we handle editing file name | |
| if (evt.getClickCount() == 1 && source instanceof JList) { | |
| if ((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1) | |
| && index >= 0 && listSelectionModel.isSelectedIndex(index) | |
| && getEditIndex() == index && editFile == null) { | |
| editFileName(index); | |
| } else { | |
| if (index >= 0) { | |
| setEditIndex(index); | |
| } else { | |
| resetEditIndex(); | |
| } | |
| } | |
| } else if (evt.getClickCount() == 2) { | |
| // on double click (open or drill down one directory) be | |
| // sure to clear the edit index | |
| resetEditIndex(); | |
| } | |
| } | |
| // Forward event to Basic | |
| if (getDoubleClickListener() != null) { | |
| getDoubleClickListener().mouseClicked(evt); | |
| } | |
| } | |
| public void mouseEntered(MouseEvent evt) { | |
| JComponent source = (JComponent)evt.getSource(); | |
| if (source instanceof JTable) { | |
| JTable table = (JTable)evt.getSource(); | |
| TransferHandler th1 = getFileChooser().getTransferHandler(); | |
| TransferHandler th2 = table.getTransferHandler(); | |
| if (th1 != th2) { | |
| table.setTransferHandler(th1); | |
| } | |
| boolean dragEnabled = getFileChooser().getDragEnabled(); | |
| if (dragEnabled != table.getDragEnabled()) { | |
| table.setDragEnabled(dragEnabled); | |
| } | |
| } else if (source instanceof JList) { | |
| // Forward event to Basic | |
| if (getDoubleClickListener() != null) { | |
| getDoubleClickListener().mouseEntered(evt); | |
| } | |
| } | |
| } | |
| public void mouseExited(MouseEvent evt) { | |
| if (evt.getSource() instanceof JList) { | |
| // Forward event to Basic | |
| if (getDoubleClickListener() != null) { | |
| getDoubleClickListener().mouseExited(evt); | |
| } | |
| } | |
| } | |
| public void mousePressed(MouseEvent evt) { | |
| if (evt.getSource() instanceof JList) { | |
| // Forward event to Basic | |
| if (getDoubleClickListener() != null) { | |
| getDoubleClickListener().mousePressed(evt); | |
| } | |
| } | |
| } | |
| public void mouseReleased(MouseEvent evt) { | |
| if (evt.getSource() instanceof JList) { | |
| // Forward event to Basic | |
| if (getDoubleClickListener() != null) { | |
| getDoubleClickListener().mouseReleased(evt); | |
| } | |
| } | |
| } | |
| private MouseListener getDoubleClickListener() { | |
| // Lazy creation of Basic's listener | |
| if (doubleClickListener == null && list != null) { | |
| doubleClickListener = | |
| fileChooserUIAccessor.createDoubleClickListener(list); | |
| } | |
| return doubleClickListener; | |
| } | |
| } | |
| /** | |
| * Property to remember whether a directory is currently selected in the UI. | |
| * | |
| * @return <code>true</code> iff a directory is currently selected. | |
| */ | |
| protected boolean isDirectorySelected() { | |
| return fileChooserUIAccessor.isDirectorySelected(); | |
| } | |
| /** | |
| * Property to remember the directory that is currently selected in the UI. | |
| * | |
| * @return the value of the <code>directory</code> property | |
| * @see javax.swing.plaf.basic.BasicFileChooserUI#setDirectory | |
| */ | |
| protected File getDirectory() { | |
| return fileChooserUIAccessor.getDirectory(); | |
| } | |
| private <T> T findChildComponent(Container container, Class<T> cls) { | |
| int n = container.getComponentCount(); | |
| for (int i = 0; i < n; i++) { | |
| Component comp = container.getComponent(i); | |
| if (cls.isInstance(comp)) { | |
| return cls.cast(comp); | |
| } else if (comp instanceof Container) { | |
| T c = findChildComponent((Container)comp, cls); | |
| if (c != null) { | |
| return c; | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| public boolean canWrite(File f) { | |
| // Return false for non FileSystem files or if file doesn't exist. | |
| if (!f.exists()) { | |
| return false; | |
| } | |
| if (f instanceof ShellFolder) { | |
| return f.canWrite(); | |
| } else { | |
| if (usesShellFolder(getFileChooser())) { | |
| try { | |
| return ShellFolder.getShellFolder(f).canWrite(); | |
| } catch (FileNotFoundException ex) { | |
| // File doesn't exist | |
| return false; | |
| } | |
| } else { | |
| // Ordinary file | |
| return f.canWrite(); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns true if specified FileChooser should use ShellFolder | |
| */ | |
| public static boolean usesShellFolder(JFileChooser chooser) { | |
| Boolean prop = (Boolean) chooser.getClientProperty("FileChooser.useShellFolder"); | |
| return prop == null ? chooser.getFileSystemView().equals(FileSystemView.getFileSystemView()) | |
| : prop.booleanValue(); | |
| } | |
| // This interface is used to access methods in the FileChooserUI | |
| // that are not public. | |
| public interface FileChooserUIAccessor { | |
| public JFileChooser getFileChooser(); | |
| public BasicDirectoryModel getModel(); | |
| public JPanel createList(); | |
| public JPanel createDetailsView(); | |
| public boolean isDirectorySelected(); | |
| public File getDirectory(); | |
| public Action getApproveSelectionAction(); | |
| public Action getChangeToParentDirectoryAction(); | |
| public Action getNewFolderAction(); | |
| public MouseListener createDoubleClickListener(JList<?> list); | |
| public ListSelectionListener createListSelectionListener(); | |
| } | |
| } |