/* * Copyright 2003-2008 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.swing; import java.awt.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; import java.text.DateFormat; import java.text.MessageFormat; import java.util.*; import java.util.List; import java.util.concurrent.Callable; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import javax.swing.filechooser.*; import javax.swing.plaf.basic.*; import javax.swing.table.*; import javax.swing.text.*; import sun.awt.shell.*; /** * WARNING: 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. *

* This component is intended to be used in a subclass of * javax.swing.plaf.basic.BasicFileChooserUI. It realies heavily on the * implementation of BasicFileChooserUI, and is intended to be API compatible * with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI. * * @author Leif Samuelsson */ 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 final static String ACTION_APPROVE_SELECTION = "approveSelection"; public final static String ACTION_CANCEL = "cancelSelection"; public final static String ACTION_EDIT_FILE_NAME = "editFileName"; public final static String ACTION_REFRESH = "refresh"; public final static String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up"; public final static String ACTION_NEW_FOLDER = "New Folder"; public final static String ACTION_VIEW_LIST = "viewTypeList"; public final static 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 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 timeFactor. 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 = (JList) 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 = (JTable) 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); } 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"); 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) { 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 actionList = new ArrayList(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 actions; } 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 list = new JList() { 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); } p.add(scrollpane, BorderLayout.CENTER); return p; } /** * This model allows for sorting JList */ private class SortableListModel extends AbstractListModel 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; } class DetailsTableModel extends AbstractTableModel implements ListDataListener { JFileChooser chooser; 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 visibleColumns = new ArrayList(); 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 sortKeys = (rowSorter == null) ? null : rowSorter.getSortKeys(); fireTableStructureChanged(); restoreSortKeys(sortKeys); } private void restoreSortKeys(List 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) { 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(); File f2 = fsv.createFileObject(f.getParentFile(), newFileName); if (f2.exists()) { JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText, oldFileName), renameErrorTitleText, JOptionPane.ERROR_MESSAGE); } else { if (FilePane.this.getModel().renameFile(f, 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); } } } } } } 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)getModel().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 { public DetailsTableRowSorter() { setModelWrapper(new SorterModelWrapper()); } 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.getInvoker().invoke(new Callable() { public Void call() throws Exception { DetailsTableRowSorter.super.sort(); return null; } }); } public void modelStructureChanged() { super.modelStructureChanged(); updateComparators(detailsTableModel.getColumns()); } private class SorterModelWrapper extends ModelWrapper { 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 { private Comparator comparator; private int column; public DirectoriesFirstComparatorWrapper(int column, Comparator comparator) { this.column = column; this.comparator = 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; } 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; } } class DetailsTableCellRenderer extends DefaultTableCellRenderer { JFileChooser chooser; DateFormat df; DetailsTableCellRenderer(JFileChooser chooser) { this.chooser = chooser; df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, chooser.getLocale()); } 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 Long) { long len = ((Long) value) / 1024L; if (listViewWindowsStyle) { text = MessageFormat.format(kiloByteString, len + 1); } else if (len < 1024L) { text = MessageFormat.format(kiloByteString, (len == 0L) ? 1L : len); } else { len /= 1024L; if (len < 1024L) { text = MessageFormat.format(megaByteString, len); } else { len /= 1024L; text = MessageFormat.format(gigaByteString, len); } } } else if (value instanceof Date) { text = df.format((Date)value); } else { text = value.toString(); } setText(text); return this; } } 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); } // TAB/SHIFT-TAB should transfer focus and ENTER should select an item. // We don't want them to navigate within the table ActionMap am = SwingUtilities.getUIActionMap(detailsTable); am.remove("selectNextRowCell"); am.remove("selectPreviousRowCell"); am.remove("selectNextColumnCell"); am.remove("selectPreviousColumnCell"); 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(); 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 ListSelectionListener */ 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 */ 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; } 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); } } return this; } } 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) { if (getFileChooser().isMultiSelectionEnabled()) { listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); clearSelection(); 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(0); listSelectionModel.setAnchorSelectionIndex(0); } } } 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); } return contextMenu; } private Handler handler; protected Handler getMouseHandler() { if (handler == null) { handler = new Handler(); } return handler; } private class Handler implements MouseListener { private MouseListener doubleClickListener; public void mouseClicked(MouseEvent evt) { JComponent source = (JComponent)evt.getSource(); 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); evt = 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()); } } 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 true 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 directory property * @see javax.swing.plaf.basic.BasicFileChooserUI#setDirectory */ protected File getDirectory() { return fileChooserUIAccessor.getDirectory(); } private Component findChildComponent(Container container, Class cls) { int n = container.getComponentCount(); for (int i = 0; i < n; i++) { Component comp = container.getComponent(i); if (cls.isInstance(comp)) { return comp; } else if (comp instanceof Container) { Component 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 ((ShellFolder) f).isFileSystem(); } else { if (usesShellFolder(getFileChooser())) { try { return ShellFolder.getShellFolder(f).isFileSystem(); } catch (FileNotFoundException ex) { // File doesn't exist return false; } } else { // Ordinary file return true; } } } /** * 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(); } }