/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Tiny Look and Feel * * * * (C) Copyright 2003 - 2007 Hans Bickel * * * * For licensing information and credits, please refer to the * * comment in file de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * This is an almost unchanged version of MetalFileChooserUI. * * * @(#)MetalFileChooserUI.java 1.45 02/04/11 * * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package de.muntjak.tinylookandfeel; import java.awt.BorderLayout; import java.awt.Component; import java.awt.ComponentOrientation; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; 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.KeyEvent; 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.io.IOException; import java.text.DateFormat; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.EventObject; import java.util.Iterator; import java.util.Locale; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.ComboBoxModel; import javax.swing.DefaultCellEditor; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.KeyStroke; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileSystemView; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.TableHeaderUI; import javax.swing.plaf.basic.BasicDirectoryModel; import javax.swing.plaf.basic.BasicFileChooserUI; import javax.swing.plaf.metal.MetalFileChooserUI; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.text.Position; import sun.awt.shell.ShellFolder; import de.muntjak.tinylookandfeel.borders.TinyToolButtonBorder; import de.muntjak.tinylookandfeel.table.SortableTableData; import de.muntjak.tinylookandfeel.table.TinyTableHeaderRenderer; /** * TinyFileChooserUI is a customized MetalFileChooserUI. * @author Hans Bickel * @version 1.3.7 * */ public class TinyFileChooserUI extends BasicFileChooserUI { /** Key for a button's isFileChooserButton client property */ public static final String IS_FILE_CHOOSER_BUTTON_KEY = "JFileChooser.isFileChooserButton"; // This one is evaluated at ensureFileIsVisible() and // will only be false while DetailsTableModel restores // the table selection private boolean doScrolling = true; private JPanel centerPanel; private TinyDirectoryModel directoryModel = null; private JLabel lookInLabel; private JComboBox directoryComboBox; private DirectoryComboBoxModel directoryComboBoxModel; private Action directoryComboBoxAction = new DirectoryComboBoxAction(); private FilterComboBoxModel filterComboBoxModel; private JTextField fileNameTextField; private JToggleButton listViewButton; private JToggleButton detailsViewButton; private JPanel listViewPanel; private JPanel detailsViewPanel; private JPanel currentViewPanel; private FocusListener editorFocusListener = new FocusAdapter() { public void focusLost(FocusEvent e) { if(!e.isTemporary()) { applyEdit(); } } }; private boolean useShellFolder; private ListSelectionModel listSelectionModel; private JList list; private JTable detailsTable; private DetailsTableModel detailsTableModel; private JButton approveButton; private JButton cancelButton; private JPanel buttonPanel; private JPanel bottomPanel; private JComboBox filterComboBox; private static final Dimension hstrut1 = new Dimension(1, 1); private static final Dimension hstrut4 = new Dimension(4, 1); private static final Dimension hstrut11 = new Dimension(11, 1); private static final Dimension vstrut5 = new Dimension(1, 5); private static final Insets shrinkwrap = new Insets(2, 2, 2, 2); // Preferred and Minimum sizes for the dialog box private static int PREF_WIDTH = 500; private static int PREF_HEIGHT = 326; private static Dimension PREF_SIZE = new Dimension(PREF_WIDTH, PREF_HEIGHT); private static int MIN_WIDTH = 500; private static int MIN_HEIGHT = 326; private static Dimension MIN_SIZE = new Dimension(MIN_WIDTH, MIN_HEIGHT); private static int LIST_PREF_WIDTH = 405; private static int LIST_PREF_HEIGHT = 183; private static Dimension LIST_PREF_SIZE = new Dimension(LIST_PREF_WIDTH, LIST_PREF_HEIGHT); private static final int COLUMN_FILENAME = 0; private static final int COLUMN_FILESIZE = 1; private static final int COLUMN_FILETYPE = 2; private static final int COLUMN_FILEDATE = 3; //private static final int COLUMN_FILEATTR = 4; private static final int COLUMN_COLCOUNT = 4; // name, size, type, last modified, attributes private int[] COLUMN_WIDTHS = {150, 75, 130, 100}; // Labels, mnemonics, and tooltips (oh my!) private int lookInLabelMnemonic = 0; private String lookInLabelText = null; private String saveInLabelText = null; private int fileNameLabelMnemonic = 0; private String fileNameLabelText = null; private int filesOfTypeLabelMnemonic = 0; private String filesOfTypeLabelText = null; private String upFolderToolTipText = null; private String upFolderAccessibleName = null; private String homeFolderToolTipText = null; private String homeFolderAccessibleName = null; private String newFolderToolTipText = null; private String newFolderAccessibleName = null; private String listViewButtonToolTipText = null; private String listViewButtonAccessibleName = null; private String detailsViewButtonToolTipText = null; private String detailsViewButtonAccessibleName = null; private String fileNameHeaderText = null; private String fileSizeHeaderText = null; private String fileTypeHeaderText = null; private String fileDateHeaderText = null; private String fileAttrHeaderText = null; // // ComponentUI Interface Implementation methods // public static ComponentUI createUI(JComponent c) { return new TinyFileChooserUI((JFileChooser) c); } public TinyFileChooserUI(JFileChooser filechooser) { super(filechooser); } public void uninstallComponents(JFileChooser fc) { fc.removeAll(); bottomPanel = null; buttonPanel = null; } public void installComponents(JFileChooser fc) { FileSystemView fsv = fc.getFileSystemView(); fc.setBorder(new EmptyBorder(12, 12, 11, 11)); fc.setLayout(new BorderLayout(0, 11)); // ********************************* // // **** Construct the top panel **** // // ********************************* // // Directory manipulation buttons JPanel topPanel = new JPanel(new BorderLayout(11, 0)); JPanel topButtonPanel = new JPanel(); topButtonPanel.setLayout(new BoxLayout(topButtonPanel, BoxLayout.X_AXIS)); topPanel.add(topButtonPanel, BorderLayout.EAST); // Add the top panel to the fileChooser fc.add(topPanel, BorderLayout.NORTH); // ComboBox Label lookInLabel = new JLabel(lookInLabelText); lookInLabel.setDisplayedMnemonic(lookInLabelMnemonic); topPanel.add(lookInLabel, BorderLayout.BEFORE_LINE_BEGINS); // CurrentDir ComboBox directoryComboBox = new JComboBox(); directoryComboBox.putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight"); lookInLabel.setLabelFor(directoryComboBox); directoryComboBoxModel = createDirectoryComboBoxModel(fc); directoryComboBox.setModel(directoryComboBoxModel); directoryComboBox.addActionListener(directoryComboBoxAction); directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc)); directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT); directoryComboBox.setAlignmentY(JComponent.TOP_ALIGNMENT); directoryComboBox.setMaximumRowCount(8); topPanel.add(directoryComboBox, BorderLayout.CENTER); Border toolButtonBorder = new TinyToolButtonBorder(); // Up Button Insets margin = new Insets(2, 2, 2, 2); JButton b = new JButton(upFolderIcon); b.putClientProperty(IS_FILE_CHOOSER_BUTTON_KEY, Boolean.TRUE); b.setOpaque(false); b.setText(null); b.setIcon(upFolderIcon); b.setToolTipText(upFolderToolTipText); b.getAccessibleContext().setAccessibleName(upFolderAccessibleName); b.setAlignmentX(JComponent.LEFT_ALIGNMENT); b.setAlignmentY(JComponent.CENTER_ALIGNMENT); b.setMargin(margin); b.addActionListener(getChangeToParentDirectoryAction()); b.setBorder(toolButtonBorder); topButtonPanel.add(b); // Home Button File homeDir = fsv.getHomeDirectory(); String toolTipText = homeFolderToolTipText; if(fsv.isRoot(homeDir)) { toolTipText = getFileView(fc).getName(homeDir); // Probably "Desktop". } b = new JButton(homeFolderIcon); b.putClientProperty(IS_FILE_CHOOSER_BUTTON_KEY, Boolean.TRUE); b.setToolTipText(toolTipText); b.getAccessibleContext().setAccessibleName(homeFolderAccessibleName); b.setAlignmentX(JComponent.LEFT_ALIGNMENT); b.setAlignmentY(JComponent.CENTER_ALIGNMENT); b.setMargin(margin); b.addActionListener(getGoHomeAction()); b.setBorder(toolButtonBorder); topButtonPanel.add(b); // New Directory Button if(!UIManager.getBoolean("FileChooser.readOnly")) { b = new JButton(newFolderIcon); b.putClientProperty(IS_FILE_CHOOSER_BUTTON_KEY, Boolean.TRUE); b.setText(null); b.setToolTipText(newFolderToolTipText); b.getAccessibleContext().setAccessibleName(newFolderAccessibleName); b.setAlignmentX(JComponent.LEFT_ALIGNMENT); b.setAlignmentY(JComponent.CENTER_ALIGNMENT); b.setMargin(margin); b.addActionListener(getNewFolderAction()); b.setBorder(toolButtonBorder); topButtonPanel.add(b); } topButtonPanel.add(Box.createRigidArea(hstrut1)); // View button group ButtonGroup viewButtonGroup = new ButtonGroup(); class ViewButtonListener implements ActionListener { JFileChooser fc; ViewButtonListener(JFileChooser fc) { this.fc = fc; } public void actionPerformed(ActionEvent e) { JToggleButton b = (JToggleButton) e.getSource(); JPanel oldViewPanel = currentViewPanel; if(b == detailsViewButton) { if(detailsViewPanel == null) { detailsViewPanel = createDetailsView(fc); detailsViewPanel.setPreferredSize(LIST_PREF_SIZE); } currentViewPanel = detailsViewPanel; } else { currentViewPanel = listViewPanel; } if(currentViewPanel != oldViewPanel) { centerPanel.remove(oldViewPanel); centerPanel.add(currentViewPanel, BorderLayout.CENTER); centerPanel.revalidate(); centerPanel.repaint(); } } } ViewButtonListener viewButtonListener = new ViewButtonListener(fc); // List Button listViewButton = new JToggleButton(listViewIcon); listViewButton.putClientProperty(IS_FILE_CHOOSER_BUTTON_KEY, Boolean.TRUE); listViewButton.setToolTipText(listViewButtonToolTipText); listViewButton.getAccessibleContext().setAccessibleName(listViewButtonAccessibleName); listViewButton.setSelected(true); listViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT); listViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT); listViewButton.setMargin(/*shrinkwrap*/ new Insets(4, 2, 5, 2)); listViewButton.addActionListener(viewButtonListener); listViewButton.setBorder(toolButtonBorder); topButtonPanel.add(listViewButton); topButtonPanel.add(Box.createRigidArea(hstrut1)); viewButtonGroup.add(listViewButton); // Details Button detailsViewButton = new JToggleButton(detailsViewIcon); detailsViewButton.putClientProperty(IS_FILE_CHOOSER_BUTTON_KEY, Boolean.TRUE); detailsViewButton.setToolTipText(detailsViewButtonToolTipText); detailsViewButton.getAccessibleContext().setAccessibleName(detailsViewButtonAccessibleName); detailsViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT); detailsViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT); detailsViewButton.setMargin(/*shrinkwrap*/ new Insets(4, 2, 5, 2)); detailsViewButton.addActionListener(viewButtonListener); detailsViewButton.setBorder(toolButtonBorder); topButtonPanel.add(detailsViewButton); viewButtonGroup.add(detailsViewButton); // Use ShellFolder class to populate combobox only if // FileSystemView.getRoots() returns one folder and that is // the same as the first item in the ShellFolder combobox list. { useShellFolder = false; File[] roots = fsv.getRoots(); if(roots != null && roots.length == 1) { File[] cbFolders = (File[]) ShellFolder.get("fileChooserComboBoxFolders"); if(cbFolders != null && cbFolders.length > 0 && roots[0] == cbFolders[0]) { useShellFolder = true; } } } // ************************************** // // ******* Add the directory pane ******* // // ************************************** // centerPanel = new JPanel(new BorderLayout()); listViewPanel = createList(fc); listSelectionModel = list.getSelectionModel(); listViewPanel.setPreferredSize(LIST_PREF_SIZE); centerPanel.add(listViewPanel, BorderLayout.CENTER); currentViewPanel = listViewPanel; centerPanel.add(getAccessoryPanel(), BorderLayout.AFTER_LINE_ENDS); JComponent accessory = fc.getAccessory(); if(accessory != null) { getAccessoryPanel().add(accessory); } fc.add(centerPanel, BorderLayout.CENTER); // ********************************** // // **** Construct the bottom panel ** // // ********************************** // JPanel bottomPanel = getBottomPanel(); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS)); fc.add(bottomPanel, BorderLayout.SOUTH); // FileName label and textfield JPanel fileNamePanel = new JPanel(); fileNamePanel.setLayout(new BoxLayout(fileNamePanel, BoxLayout.LINE_AXIS)); bottomPanel.add(fileNamePanel); bottomPanel.add(Box.createRigidArea(vstrut5)); AlignedLabel fileNameLabel = new AlignedLabel(fileNameLabelText); fileNameLabel.setDisplayedMnemonic(fileNameLabelMnemonic); fileNamePanel.add(fileNameLabel); fileNameTextField = new JTextField() { public Dimension getMaximumSize() { return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height); } }; fileNamePanel.add(fileNameTextField); fileNameLabel.setLabelFor(fileNameTextField); fileNameTextField.addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent e) { if(!getFileChooser().isMultiSelectionEnabled()) { listSelectionModel.clearSelection(); } } }); if(fc.isMultiSelectionEnabled()) { setFileName(fileNameString(fc.getSelectedFiles())); } else { setFileName(fileNameString(fc.getSelectedFile())); } // Filetype label and combobox JPanel filesOfTypePanel = new JPanel(); filesOfTypePanel.setLayout(new BoxLayout(filesOfTypePanel, BoxLayout.LINE_AXIS)); bottomPanel.add(filesOfTypePanel); AlignedLabel filesOfTypeLabel = new AlignedLabel(filesOfTypeLabelText); filesOfTypeLabel.setDisplayedMnemonic(filesOfTypeLabelMnemonic); filesOfTypePanel.add(filesOfTypeLabel); filterComboBoxModel = createFilterComboBoxModel(); fc.addPropertyChangeListener(filterComboBoxModel); filterComboBox = new JComboBox(filterComboBoxModel); filesOfTypeLabel.setLabelFor(filterComboBox); filterComboBox.setRenderer(createFilterComboBoxRenderer()); filesOfTypePanel.add(filterComboBox); // buttons getButtonPanel().setLayout(new ButtonAreaLayout()); approveButton = new JButton(getApproveButtonText(fc)); // Note: Metal does not use mnemonics for approve and cancel approveButton.addActionListener(getApproveSelectionAction()); approveButton.setToolTipText(getApproveButtonToolTipText(fc)); getButtonPanel().add(approveButton); cancelButton = new JButton(cancelButtonText); cancelButton.setToolTipText(cancelButtonToolTipText); cancelButton.addActionListener(getCancelSelectionAction()); getButtonPanel().add(cancelButton); if(fc.getControlButtonsAreShown()) { addControlButtons(); } groupLabels(new AlignedLabel[] { fileNameLabel, filesOfTypeLabel }); } protected JPanel getButtonPanel() { if(buttonPanel == null) { buttonPanel = new JPanel(); } return buttonPanel; } protected JPanel getBottomPanel() { if(bottomPanel == null) { bottomPanel = new JPanel(); } return bottomPanel; } protected void installStrings(JFileChooser fc) { super.installStrings(fc); Locale l = fc.getLocale(); lookInLabelMnemonic = UIManager.getInt("FileChooser.lookInLabelMnemonic"); lookInLabelText = UIManager.getString("FileChooser.lookInLabelText", l); saveInLabelText = UIManager.getString("FileChooser.saveInLabelText", l); fileNameLabelMnemonic = UIManager.getInt("FileChooser.fileNameLabelMnemonic"); fileNameLabelText = UIManager.getString("FileChooser.fileNameLabelText", l); filesOfTypeLabelMnemonic = UIManager.getInt("FileChooser.filesOfTypeLabelMnemonic"); filesOfTypeLabelText = UIManager.getString("FileChooser.filesOfTypeLabelText", l); upFolderToolTipText = UIManager.getString("FileChooser.upFolderToolTipText", l); upFolderAccessibleName = UIManager.getString("FileChooser.upFolderAccessibleName", l); homeFolderToolTipText = UIManager.getString("FileChooser.homeFolderToolTipText", l); homeFolderAccessibleName = UIManager.getString("FileChooser.homeFolderAccessibleName", l); newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText", l); newFolderAccessibleName = UIManager.getString("FileChooser.newFolderAccessibleName", l); listViewButtonToolTipText = UIManager.getString("FileChooser.listViewButtonToolTipText", l); listViewButtonAccessibleName = UIManager.getString("FileChooser.listViewButtonAccessibleName", l); detailsViewButtonToolTipText = UIManager.getString("FileChooser.detailsViewButtonToolTipText", l); detailsViewButtonAccessibleName = UIManager.getString("FileChooser.detailsViewButtonAccessibleName", l); fileNameHeaderText = UIManager.getString("FileChooser.fileNameHeaderText", l); fileSizeHeaderText = UIManager.getString("FileChooser.fileSizeHeaderText", l); fileTypeHeaderText = UIManager.getString("FileChooser.fileTypeHeaderText", l); fileDateHeaderText = UIManager.getString("FileChooser.fileDateHeaderText", l); fileAttrHeaderText = UIManager.getString("FileChooser.fileAttrHeaderText", l); } protected void installListeners(JFileChooser fc) { super.installListeners(fc); ActionMap actionMap = getActionMap(); SwingUtilities.replaceUIActionMap(fc, actionMap); } protected ActionMap getActionMap() { return createActionMap(); } protected ActionMap createActionMap() { AbstractAction escAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { if(editFile != null) { cancelEdit(); } else { getFileChooser().cancelSelection(); } } public boolean isEnabled() { return getFileChooser().isEnabled(); } }; ActionMap map = new ActionMapUIResource(); map.put("approveSelection", getApproveSelectionAction()); map.put("cancelSelection", escAction); map.put("Go Up", getChangeToParentDirectoryAction()); return map; } protected JPanel createList(JFileChooser fc) { JPanel p = new JPanel(new BorderLayout()); final JFileChooser fileChooser = fc; 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); list.setVisibleRowCount(-1); if(fc.isMultiSelectionEnabled()) { list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } list.setModel(getModel()); list.addListSelectionListener(createListSelectionListener(fc)); list.addMouseListener(createDoubleClickListener(fc, list)); list.addMouseListener(createSingleClickListener(fc, list)); getModel().addListDataListener(new ListDataListener() { public void contentsChanged(ListDataEvent e) { // Update the selection after JList has been updated new DelayedSelectionUpdater(); } public void intervalAdded(ListDataEvent e) { new DelayedSelectionUpdater(); } public void intervalRemoved(ListDataEvent e) { } }); JScrollPane scrollpane = new JScrollPane(list); p.add(scrollpane, BorderLayout.CENTER); return p; } class DetailsTableModel extends AbstractTableModel implements ListDataListener, SortableTableData { String[] columnNames = { " " + fileNameHeaderText, fileSizeHeaderText, " " + fileTypeHeaderText, " " + fileDateHeaderText, " " + fileAttrHeaderText }; JFileChooser chooser; ListModel listModel; // Comparators are lazily created Comparator fileAttributeComparator; Comparator fileDateComparator; Comparator fileNameComparator; Comparator fileSizeComparator; Comparator fileTypeComparator; int sortingDirection; int[] sortColumns; int[] sortDirections; DetailsTableModel(JFileChooser fc) { this.chooser = fc; listModel = getModel(); listModel.addListDataListener(this); } public int getRowCount() { return listModel.getSize(); } public int getColumnCount() { return COLUMN_COLCOUNT; } public String getColumnName(int column) { return columnNames[column]; } public Class getColumnClass(int column) { switch (column) { case COLUMN_FILENAME : return File.class; case COLUMN_FILEDATE : return Date.class; case COLUMN_FILESIZE : return Long.class; case COLUMN_FILETYPE : //case COLUMN_FILEATTR : return String.class; default : return super.getColumnClass(column); } } 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. File f = (File) listModel.getElementAt(row); switch (col) { case COLUMN_FILENAME : return f; case COLUMN_FILESIZE : if(!f.exists() || f.isDirectory()) { return null; } return new Long(f.length()); case COLUMN_FILETYPE : if(!f.exists()) { return null; } return chooser.getFileSystemView().getSystemTypeDescription(f); case COLUMN_FILEDATE : if(!f.exists() || chooser.getFileSystemView().isFileSystemRoot(f)) { return null; } long time = f.lastModified(); return (time == 0L) ? null : new Date(time); // case COLUMN_FILEATTR : // if(!f.exists() || chooser.getFileSystemView().isFileSystemRoot(f)) { // return null; // } // // String attributes = ""; // // if(!f.canWrite()) { // attributes += "R"; // } // // if(f.isHidden()) { // attributes += "H"; // } // // return attributes; } return null; } 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() && TinyFileChooserUI.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 { // PENDING(jeff) - show a dialog indicating failure } } } } } public boolean isCellEditable(int row, int column) { return (column == COLUMN_FILENAME && !UIManager.getBoolean("FileChooser.readOnly")); } public void contentsChanged(ListDataEvent e) { DetailsTableModel model = (DetailsTableModel)detailsTable.getModel(); model.sortColumns(detailsTable); } public void intervalAdded(ListDataEvent e) { DetailsTableModel model = (DetailsTableModel)detailsTable.getModel(); model.sortColumns(detailsTable); } public void intervalRemoved(ListDataEvent e) { DetailsTableModel model = (DetailsTableModel)detailsTable.getModel(); model.sortColumns(detailsTable); } public void invalidateCachedFiles() { } // SortableTableData implementation public boolean isColumnSortable(int column) { return Comparable.class.isAssignableFrom(getColumnClass(column)); } public boolean supportsMultiColumnSort() { return false; } public void sortColumns(final int[] columns, final int[] sortingDirections, JTable table) { sortColumns = new int[columns.length]; sortDirections = new int[sortingDirections.length]; System.arraycopy(columns, 0, sortColumns, 0, columns.length); System.arraycopy(sortingDirections, 0, sortDirections, 0, sortingDirections.length); sortColumns(table); } private void sortColumns(JTable table) { // long t = System.currentTimeMillis(); Vector files = ((TinyDirectoryModel)listModel).getFileCache(); //store column selection int[] sc = table.getSelectedColumns(); int[] sr = new int[table.getSelectedRowCount()]; // store file list, each file knows if it is selected or not Vector sortFiles = new Vector(files.size()); int end = files.size(); // System.out.println("\nNumber of files: " + end); for(int i = 0; i < end; i++) { sortFiles.add(new SelectedFile( (File)files.get(i), table.isRowSelected(i))); } // System.out.println("Create sortFiles: " + (System.currentTimeMillis() - t)); if(sortColumns.length == 0) { // sort file names ascending sortingDirection = SORT_ASCENDING; if(fileNameComparator == null) { fileNameComparator = createFileNameComparator(); } Collections.sort(sortFiles, fileNameComparator); } else { // perform single-column sort sortingDirection = (sortDirections[0] == SORT_ASCENDING ? 1: -1); switch(sortColumns[0]) { case COLUMN_FILENAME: if(fileNameComparator == null) { fileNameComparator = createFileNameComparator(); } Collections.sort(sortFiles, fileNameComparator); break; case COLUMN_FILESIZE: if(fileSizeComparator == null) { fileSizeComparator = createFileSizeComparator(); } Collections.sort(sortFiles, fileSizeComparator); break; case COLUMN_FILETYPE: if(fileTypeComparator == null) { fileTypeComparator = createFileTypeComparator(); } Collections.sort(sortFiles, fileTypeComparator); break; case COLUMN_FILEDATE: if(fileDateComparator == null) { fileDateComparator = createFileDateComparator(); } Collections.sort(sortFiles, fileDateComparator); break; // case COLUMN_FILEATTR: // if(fileAttributeComparator == null) { // fileAttributeComparator = createFileAttributeComparator(); // } // // Collections.sort(sortFiles, fileAttributeComparator); // break; } } // System.out.println("sort: " + (System.currentTimeMillis() - t)); // store sorted files and restore selection files.clear(); int row = 0; int index = 0; Iterator ii = sortFiles.iterator(); while(ii.hasNext()) { SelectedFile sf = (SelectedFile)ii.next(); files.add(sf.file); if(sf.selected) { sr[index++] = row; } row ++; } // System.out.println("restore files: " + (System.currentTimeMillis() - t)); fireTableDataChanged(); // We don't want the table's scroll pane to scroll // as we restore the selection doScrolling = false; for(int i = 0; i < sr.length; i++) { table.addRowSelectionInterval(sr[i], sr[i]); } for(int i = 0; i < sc.length; i++) { table.addColumnSelectionInterval(sc[i], sc[i]); } doScrolling = true; // System.out.println("restore selection: " + (System.currentTimeMillis() - t)); } // A container for a File and its selected state private class SelectedFile { File file; boolean selected; String name; //String attribute; Long size; Long date; String type; Boolean isDirectory; Boolean exists; Boolean isFileSystemRoot; SelectedFile(File file, boolean selected) { this.file = file; this.selected = selected; } // public String getAttribute() { // if(attribute == null) { // attribute = ""; // if(exists() && !isFileSystemRoot()) { // if(!file.canWrite()) { // attribute += "R"; // } // // if(file.isHidden()) { // attribute += "H"; // } // } // } // // return attribute; // } public Long getDate() { if(date == null) { if(exists() && !isFileSystemRoot()) { date = new Long(file.lastModified()); } else { date = new Long(0); } } return date; } public String getName() { if(name == null) { name = chooser.getName(file); if(name == null) name = ""; else name = name.toLowerCase(); } return name; } public Long getSize() { if(size == null) { if(exists() && !isDirectory()) { size = new Long(file.length()); } else { size = new Long(0L); } } return size; } public String getType() { if(type == null) { if(exists()) { type = chooser.getFileSystemView().getSystemTypeDescription(file); } else { type = ""; } } return type; } public boolean isDirectory() { if(isDirectory == null) { isDirectory = new Boolean(exists() && file.isDirectory()); } return isDirectory.booleanValue(); } public boolean exists() { if(exists == null) { exists = new Boolean(file.exists()); } return exists.booleanValue(); } public boolean isFileSystemRoot() { if(isFileSystemRoot == null) { isFileSystemRoot = new Boolean( chooser.getFileSystemView().isFileSystemRoot(file)); } return isFileSystemRoot.booleanValue(); } } Comparator createFileNameComparator() { return new Comparator() { public int compare(Object o1, Object o2) { SelectedFile f1 = (SelectedFile)o1; SelectedFile f2 = (SelectedFile)o2; if(f1.isDirectory() == f2.isDirectory()) { return f1.getName().compareTo(f2.getName()) * sortingDirection; } else if(f1.isDirectory()) { return -1 * sortingDirection; } else { return 1 * sortingDirection; } } }; } Comparator createFileSizeComparator() { return new Comparator() { public int compare(Object o1, Object o2) { Long s1 = ((SelectedFile)o1).getSize(); Long s2 = ((SelectedFile)o2).getSize(); return s1.compareTo(s2) * sortingDirection; } }; } Comparator createFileTypeComparator() { return new Comparator() { public int compare(Object o1, Object o2) { SelectedFile f1 = (SelectedFile)o1; SelectedFile f2 = (SelectedFile)o2; if(f1.isDirectory() == f2.isDirectory()) { return f1.getType().compareTo(f2.getType()) * sortingDirection; } else if(f1.isDirectory()) { return -1 * sortingDirection; } else { return 1 * sortingDirection; } } }; } Comparator createFileDateComparator() { return new Comparator() { public int compare(Object o1, Object o2) { Long d1 = ((SelectedFile)o1).getDate(); Long d2 = ((SelectedFile)o2).getDate(); return d1.compareTo(d2) * sortingDirection; } }; } // Comparator createFileAttributeComparator() { // return new Comparator() { // public int compare(Object o1, Object o2) { // String a1 = ((SelectedFile)o1).getAttribute(); // String a2 = ((SelectedFile)o2).getAttribute(); // // return a1.compareTo(a2) * sortingDirection; // } // }; // } } class DetailsTableCellRenderer extends DefaultTableCellRenderer { JFileChooser chooser; DateFormat df; DetailsTableCellRenderer(JFileChooser chooser) { this.chooser = chooser; df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, chooser.getLocale()); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { int modelColumn = table.getColumnModel().getColumn(column).getModelIndex(); if(modelColumn == COLUMN_FILESIZE /*|| modelColumn == COLUMN_FILEATTR*/) { super.setHorizontalAlignment(SwingConstants.TRAILING); } else { super.setHorizontalAlignment(SwingConstants.LEADING); } Component c = super.getTableCellRendererComponent(table, value, isSelected & (modelColumn == 0), false, row, column); int w1 = table.getColumnModel().getColumn(column).getWidth(); int w2 = c.getPreferredSize().width; if(w2 > w1) { super.setToolTipText(getText()); } else { super.setToolTipText(null); } // paint selection only for file name column and // don't paint focus return c; } public void setValue(Object value) { setIcon(null); if(value == null) { setText(""); } else if(value instanceof File) { File file = (File) value; String fileName = chooser.getName(file); setText(fileName); Icon icon = chooser.getIcon(file); setIcon(icon); } else if(value instanceof Date) { setText((value == null) ? "" : df.format((Date) value)); } else if(value instanceof Long) { long len = ((Long)value).longValue() / 1024L; if(len < 1024L) { setText(((len == 0L) ? 1L : len) + " KB"); } else { len /= 1024L; if(len < 1024L) { setText(len + " MB"); } else { len /= 1024L; setText(len + " GB"); } } } else { super.setValue(value); } } } protected JPanel createDetailsView(JFileChooser fc) { final JFileChooser chooser = fc; JPanel p = new JPanel(new BorderLayout()); detailsTableModel = new DetailsTableModel(chooser); detailsTable = new JTable(detailsTableModel) { // 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); } }; detailsTable.setComponentOrientation(chooser.getComponentOrientation()); detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); detailsTable.setShowGrid(false); detailsTable.setSelectionModel(listSelectionModel); detailsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); Font font = detailsTable.getFont(); // Minimum row height will be 18 px. (in MetalFileChooserUI is 22 px.) detailsTable.setRowHeight(Math.max(font.getSize(), 15) + 3); TableColumnModel columnModel = detailsTable.getColumnModel(); TableColumn[] columns = new TableColumn[COLUMN_COLCOUNT]; boolean isWin = System.getProperty("os.name").startsWith("Windows"); for (int i = 0; i < COLUMN_COLCOUNT; i++) { columns[i] = columnModel.getColumn(i); columns[i].setPreferredWidth(COLUMN_WIDTHS[i]); } if(!isWin) { columnModel.removeColumn(columns[COLUMN_FILETYPE]); //columnModel.removeColumn(columns[COLUMN_FILEATTR]); } TableHeaderUI headerUI = detailsTable.getTableHeader().getUI(); if(headerUI instanceof TinyTableHeaderUI) { // sort first column (= directory/file name) ((TinyTableHeaderUI)headerUI).sortColumns( new int[] {0}, new int[] {SortableTableData.SORT_ASCENDING}, detailsTable); // set horizontal alignments of table header renderers if(isWin) { ((TinyTableHeaderUI)headerUI).setHorizontalAlignments( new int[] {SwingConstants.LEADING, SwingConstants.TRAILING, SwingConstants.LEADING, SwingConstants.LEADING} ); } else { ((TinyTableHeaderUI)headerUI).setHorizontalAlignments( new int[] {SwingConstants.LEADING, SwingConstants.TRAILING, SwingConstants.LEADING} ); } } TableCellRenderer cellRenderer = new DetailsTableCellRenderer(chooser); detailsTable.setDefaultRenderer(File.class, cellRenderer); detailsTable.setDefaultRenderer(Date.class, cellRenderer); detailsTable.setDefaultRenderer(Long.class, cellRenderer); detailsTable.setDefaultRenderer(String.class, cellRenderer); detailsTable.setDefaultRenderer(Object.class, cellRenderer); // Install cell editor for editing file name final JTextField tf = new JTextField(); tf.addFocusListener(editorFocusListener); columns[COLUMN_FILENAME].setCellEditor(new DefaultCellEditor(tf) { public boolean isCellEditable(EventObject e) { if(e instanceof MouseEvent) { MouseEvent me = (MouseEvent) e; int index = detailsTable.rowAtPoint(me.getPoint()); return (me.getClickCount() == 1 && detailsTable.isRowSelected(index)); } return super.isCellEditable(e); } 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(chooser.getName((File) value)); tf.requestFocus(); tf.selectAll(); } return comp; } }); JList fakeList = new JList(detailsTableModel.listModel) { JTable table = detailsTable; public int locationToIndex(Point location) { return table.rowAtPoint(location); } public Rectangle getCellBounds(int index0, int index1) { Rectangle r0 = table.getCellRect(index0, COLUMN_FILENAME, false); Rectangle r1 = table.getCellRect(index1, COLUMN_FILENAME, false); return r0.union(r1); } public Object getSelectedValue() { return table.getValueAt(table.getSelectedRow(), COLUMN_FILENAME); } public Component add(Component comp) { if(comp instanceof JTextField) { return table.add(comp); } else { return super.add(comp); } } public void repaint() { if(table != null) table.repaint(); } public TransferHandler getTransferHandler() { if(table != null) { return table.getTransferHandler(); } else { return super.getTransferHandler(); } } public void setTransferHandler(TransferHandler newHandler) { if(table != null) { table.setTransferHandler(newHandler); } else { super.setTransferHandler(newHandler); } } public boolean getDragEnabled() { if(table != null) { return table.getDragEnabled(); } else { return super.getDragEnabled(); } } public void setDragEnabled(boolean b) { if(table != null) { table.setDragEnabled(b); } else { super.setDragEnabled(b); } } }; fakeList.setSelectionModel(listSelectionModel); detailsTable.addMouseListener(createDoubleClickListener(chooser, fakeList)); //detailsTable.addMouseListener(createSingleClickListener(chooser, fakeList)); 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); } }); p.add(scrollpane, BorderLayout.CENTER); return p; } 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 { DelayedSelectionUpdater() { SwingUtilities.invokeLater(this); } public void run() { setFileSelected(); } } /** * Creates a selection listener for the list of files and directories. * * @param fc a <code>JFileChooser</code> * @return a <code>ListSelectionListener</code> */ public ListSelectionListener createListSelectionListener(JFileChooser fc) { return new SelectionListener() { public void valueChanged(ListSelectionEvent e) { if(!e.getValueIsAdjusting()) { JFileChooser chooser = getFileChooser(); FileSystemView fsv = chooser.getFileSystemView(); JList list = (JList) e.getSource(); if(chooser.isMultiSelectionEnabled()) { File[] files = null; Object[] objects = list.getSelectedValues(); if(objects != null) { if(objects.length == 1 && ((File) objects[0]).isDirectory() && chooser.isTraversable(((File) objects[0])) && (chooser.getFileSelectionMode() == chooser.FILES_ONLY || !fsv.isFileSystem(((File) objects[0])))) { setDirectorySelected(true); setDirectory(((File) objects[0])); } else { files = new File[objects.length]; int j = 0; for (int i = 0; i < objects.length; i++) { File f = (File) objects[i]; if((chooser.isFileSelectionEnabled() && f.isFile()) || (chooser.isDirectorySelectionEnabled() && fsv.isFileSystem(f) && f.isDirectory())) { files[j++] = f; } } if(j == 0) { files = null; } else if(j < objects.length) { File[] tmpFiles = new File[j]; System.arraycopy(files, 0, tmpFiles, 0, j); files = tmpFiles; } setDirectorySelected(false); } } chooser.setSelectedFiles(files); } else { File file = (File) list.getSelectedValue(); if(file != null && file.isDirectory() && chooser.isTraversable(file) && (chooser.getFileSelectionMode() == chooser.FILES_ONLY || !fsv.isFileSystem(file))) { setDirectorySelected(true); setDirectory(file); chooser.setSelectedFile(null); } else { setDirectorySelected(false); if(file != null) { chooser.setSelectedFile(file); } } } } } }; } private MouseListener createSingleClickListener(JFileChooser fc, JList list) { return new SingleClickListener(list); } int lastIndex = -1; File editFile = null; int editX = 20; 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); centerPanel.repaint(); } else if(detailsTable != null && detailsTable.isEditing()) { detailsTable.getCellEditor().cancelCellEditing(); } } JTextField editCell = null; private void editFileName(int index) { if(UIManager.getBoolean("FileChooser.readOnly")) return; ensureIndexIsVisible(index); if(listViewPanel.isVisible()) { editFile = (File) getModel().getElementAt(index); Rectangle r = list.getCellBounds(index, index); if(editCell == null) { editCell = new JTextField(); editCell.addActionListener(new EditActionListener()); editCell.addFocusListener(editorFocusListener); editCell.setNextFocusableComponent(list); } list.add(editCell); editCell.setText(getFileChooser().getName(editFile)); if(list.getComponentOrientation().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(); } else if(detailsViewPanel.isVisible()) { detailsTable.editCellAt(index, COLUMN_FILENAME); } } protected class SingleClickListener extends MouseAdapter { JList list; public SingleClickListener(JList list) { this.list = list; } public void mouseClicked(MouseEvent e) { if(SwingUtilities.isLeftMouseButton(e)) { if(e.getClickCount() == 1) { JFileChooser fc = getFileChooser(); int index = list.locationToIndex(e.getPoint()); if((!fc.isMultiSelectionEnabled() || fc.getSelectedFiles().length <= 1) && index >= 0 && list.isSelectedIndex(index) && getEditIndex() == index && editFile == null) { editFileName(index); } else { if(index >= 0) { setEditIndex(index); } else { resetEditIndex(); } } } else { // on double click (open or drill down one directory) be // sure to clear the edit index resetEditIndex(); } } } } 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() && 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 { // PENDING(jeff) - show a dialog indicating failure } } } if(detailsTable != null && detailsTable.isEditing()) { detailsTable.getCellEditor().stopCellEditing(); } cancelEdit(); } protected class FileRenderer extends DefaultListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); File file = (File) value; String fileName = getFileChooser().getName(file); setText(fileName); Icon icon = getFileChooser().getIcon(file); setIcon(icon); if(isSelected) { // PENDING(jeff) - grab padding (4) below from defaults table. editX = icon.getIconWidth() + 4; } return this; } } public void uninstallUI(JComponent c) { // Remove listeners c.removePropertyChangeListener(filterComboBoxModel); cancelButton.removeActionListener(getCancelSelectionAction()); approveButton.removeActionListener(getApproveSelectionAction()); fileNameTextField.removeActionListener(getApproveSelectionAction()); super.uninstallUI(c); } /** * Returns the preferred size of the specified * <code>JFileChooser</code>. * The preferred size is at least as large, * in both height and width, * as the preferred size recommended * by the file chooser's layout manager. * * @param mainColor a <code>JFileChooser</code> * @return a <code>Dimension</code> specifying the preferred * width and height of the file chooser */ public Dimension getPreferredSize(JComponent c) { int prefWidth = PREF_SIZE.width; Dimension d = c.getLayout().preferredLayoutSize(c); if(d != null) { return new Dimension(d.width < prefWidth ? prefWidth : d.width, d.height < PREF_SIZE.height ? PREF_SIZE.height : d.height); } else { return new Dimension(prefWidth, PREF_SIZE.height); } } /** * Returns the minimum size of the <code>JFileChooser</code>. * * @param mainColor a <code>JFileChooser</code> * @return a <code>Dimension</code> specifying the minimum * width and height of the file chooser */ public Dimension getMinimumSize(JComponent c) { return MIN_SIZE; } /** * Returns the maximum size of the <code>JFileChooser</code>. * * @param mainColor a <code>JFileChooser</code> * @return a <code>Dimension</code> specifying the maximum * width and height of the file chooser */ public Dimension getMaximumSize(JComponent c) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } void setFileSelected() { if(getFileChooser().isMultiSelectionEnabled() && !isDirectorySelected()) { File[] files = getFileChooser().getSelectedFiles(); // Should be selected Object[] selectedObjects = list.getSelectedValues(); // Are actually selected // Remove files that shouldn't be selected for (int j = 0; j < selectedObjects.length; j++) { boolean found = false; for (int i = 0; i < files.length; i++) { if(files[i].equals(selectedObjects[j])) { found = true; break; } } if(!found) { int index = getModel().indexOf(selectedObjects[j]); if(index >= 0) { listSelectionModel.removeSelectionInterval(index, index); } } } // Add files that should be selected for (int i = 0; i < files.length; i++) { boolean found = false; for (int j = 0; j < selectedObjects.length; j++) { if(files[i].equals(selectedObjects[j])) { found = true; break; } } if(!found) { int index = getModel().indexOf(files[i]); if(index >= 0) { listSelectionModel.addSelectionInterval(index, index); } } } } else { JFileChooser chooser = getFileChooser(); File f = null; if(isDirectorySelected()) { f = getDirectory(); } else { f = chooser.getSelectedFile(); } int i; if(f != null && (i = getModel().indexOf(f)) >= 0) { listSelectionModel.setSelectionInterval(i, i); ensureIndexIsVisible(i); } else { listSelectionModel.clearSelection(); } } } private String fileNameString(File file) { if(file == null) { return null; } else { JFileChooser fc = getFileChooser(); if(fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) { return file.getPath(); } else { return file.getName(); } } } private String fileNameString(File[] files) { StringBuffer buf = new StringBuffer(); for (int i = 0; files != null && i < files.length; i++) { if(i > 0) { buf.append(" "); } if(files.length > 1) { buf.append("\""); } buf.append(fileNameString(files[i])); if(files.length > 1) { buf.append("\""); } } return buf.toString(); } /* 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()))) { setFileName(fileNameString(f)); 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())) { setFileName(fileNameString(files)); setFileSelected(); } } private void doDirectoryChanged(PropertyChangeEvent e) { JFileChooser fc = getFileChooser(); FileSystemView fsv = fc.getFileSystemView(); applyEdit(); resetEditIndex(); clearIconCache(); listSelectionModel.clearSelection(); ensureIndexIsVisible(0); File currentDirectory = fc.getCurrentDirectory(); if(currentDirectory != null) { directoryComboBoxModel.addItem(currentDirectory); getNewFolderAction().setEnabled(currentDirectory.canWrite()); getChangeToParentDirectoryAction().setEnabled(!fsv.isRoot(currentDirectory)); if(fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) { if(fsv.isFileSystem(currentDirectory)) { setFileName(currentDirectory.getPath()); } else { setFileName(null); } } } } private void doFilterChanged(PropertyChangeEvent e) { applyEdit(); resetEditIndex(); clearIconCache(); listSelectionModel.clearSelection(); } private void doFileSelectionModeChanged(PropertyChangeEvent e) { applyEdit(); resetEditIndex(); clearIconCache(); listSelectionModel.clearSelection(); JFileChooser fc = getFileChooser(); File currentDirectory = fc.getCurrentDirectory(); if(currentDirectory != null && fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled() && fc.getFileSystemView().isFileSystem(currentDirectory)) { setFileName(currentDirectory.getPath()); } else { setFileName(null); } } private void doMultiSelectionChanged(PropertyChangeEvent e) { if(getFileChooser().isMultiSelectionEnabled()) { listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); listSelectionModel.clearSelection(); getFileChooser().setSelectedFiles(null); } } private void doAccessoryChanged(PropertyChangeEvent e) { if(getAccessoryPanel() != null) { if(e.getOldValue() != null) { getAccessoryPanel().remove((JComponent) e.getOldValue()); } JComponent accessory = (JComponent) e.getNewValue(); if(accessory != null) { getAccessoryPanel().add(accessory, BorderLayout.CENTER); } } } private void doApproveButtonTextChanged(PropertyChangeEvent e) { JFileChooser chooser = getFileChooser(); approveButton.setText(getApproveButtonText(chooser)); approveButton.setToolTipText(getApproveButtonToolTipText(chooser)); } private void doDialogTypeChanged(PropertyChangeEvent e) { JFileChooser chooser = getFileChooser(); approveButton.setText(getApproveButtonText(chooser)); approveButton.setToolTipText(getApproveButtonToolTipText(chooser)); if(chooser.getDialogType() == JFileChooser.SAVE_DIALOG) { lookInLabel.setText(saveInLabelText); } else { lookInLabel.setText(lookInLabelText); } } private void doApproveButtonMnemonicChanged(PropertyChangeEvent e) { // Note: Metal does not use mnemonics for approve and cancel } private void doControlButtonsChanged(PropertyChangeEvent e) { if(getFileChooser().getControlButtonsAreShown()) { addControlButtons(); } else { removeControlButtons(); } } /* * Listen for filechooser property changes, such as * the selected file changing, or the type of the dialog changing. */ public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { 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.ACCESSORY_CHANGED_PROPERTY)) { doAccessoryChanged(e); } else if( s.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) || s.equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY)) { doApproveButtonTextChanged(e); } else if(s.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)) { doDialogTypeChanged(e); } else if(s.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) { doApproveButtonMnemonicChanged(e); } else if(s.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) { doControlButtonsChanged(e); } else if(s.equals(JFileChooser.CANCEL_SELECTION)) { applyEdit(); } else if(s.equals("componentOrientation")) { ComponentOrientation o = (ComponentOrientation) e.getNewValue(); JFileChooser cc = (JFileChooser) e.getSource(); if(o != (ComponentOrientation) e.getOldValue()) { cc.applyComponentOrientation(o); } if(detailsTable != null) { detailsTable.setComponentOrientation(o); detailsTable.getParent().getParent().setComponentOrientation(o); } } else if(s.equals("ancestor")) { if(e.getOldValue() == null && e.getNewValue() != null) { // Ancestor was added, set initial focus fileNameTextField.selectAll(); fileNameTextField.requestFocus(); } } } }; } protected void createModel() { if(directoryModel != null) { directoryModel.invalidateFileCache(); } directoryModel = new TinyDirectoryModel(getFileChooser()); } public BasicDirectoryModel getModel() { return directoryModel; } protected void removeControlButtons() { getBottomPanel().remove(getButtonPanel()); } protected void addControlButtons() { getBottomPanel().add(getButtonPanel()); } private void ensureIndexIsVisible(int i) { if(i >= 0) { list.ensureIndexIsVisible(i); if(detailsTable != null) { // This is the Metal code ... // detailsTable.scrollRectToVisible(detailsTable.getCellRect(i, COLUMN_FILENAME, true)); // ... we only want to make sure that the row is visible // (but not scroll to the file name column) Rectangle r1 = detailsTable.getCellRect(i, COLUMN_FILENAME, true); Rectangle r2 = detailsTable.getCellRect(i, detailsTable.getColumnCount() - 1, true); r1.x = (r1.x + r2.x + r2.width) / 2; r1.width = 1; detailsTable.scrollRectToVisible(r1); } } } public void ensureFileIsVisible(JFileChooser fc, File f) { if(!doScrolling) return; ensureIndexIsVisible(getModel().indexOf(f)); } public void rescanCurrentDirectory(JFileChooser fc) { getModel().validateFileCache(); } public String getFileName() { if(fileNameTextField != null) { return fileNameTextField.getText(); } else { return null; } } public void setFileName(String filename) { if(fileNameTextField != null) { fileNameTextField.setText(filename); } } /** * Property to remember whether a directory is currently selected in the UI. * This is normally called by the UI on a selection event. * * @param directorySelected if a directory is currently selected. * @since 1.4 */ protected void setDirectorySelected(boolean directorySelected) { super.setDirectorySelected(directorySelected); JFileChooser chooser = getFileChooser(); if(directorySelected) { approveButton.setText(directoryOpenButtonText); approveButton.setToolTipText(directoryOpenButtonToolTipText); } else { approveButton.setText(getApproveButtonText(chooser)); approveButton.setToolTipText(getApproveButtonToolTipText(chooser)); } } public String getDirectoryName() { // PENDING(jeff) - get the name from the directory combobox return null; } public void setDirectoryName(String dirname) { // PENDING(jeff) - set the name in the directory combobox } protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(JFileChooser fc) { return new DirectoryComboBoxRenderer(); } // // Renderer for DirectoryComboBox // class DirectoryComboBoxRenderer extends DefaultListCellRenderer { IndentIcon ii = new IndentIcon(); public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if(value == null) { setText(""); return this; } File directory = (File) value; setText(getFileChooser().getName(directory)); Icon icon = getFileChooser().getIcon(directory); ii.icon = icon; ii.depth = directoryComboBoxModel.getDepth(index); setIcon(ii); return this; } } final static int space = 10; class IndentIcon implements Icon { Icon icon = null; int depth = 0; public void paintIcon(Component c, Graphics g, int x, int y) { if(c.getComponentOrientation().isLeftToRight()) { icon.paintIcon(c, g, x + depth * space, y); } else { icon.paintIcon(c, g, x, y); } } public int getIconWidth() { return icon.getIconWidth() + depth * space; } public int getIconHeight() { return icon.getIconHeight(); } } // // DataModel for DirectoryComboxbox // protected DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) { return new DirectoryComboBoxModel(); } /** * Data model for a type-face selection combo-box. */ protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel { Vector directories = new Vector(); int[] depths = null; File selectedDirectory = null; JFileChooser chooser = getFileChooser(); FileSystemView fsv = chooser.getFileSystemView(); public DirectoryComboBoxModel() { // Add the current directory to the model, and make it the // selectedDirectory File dir = getFileChooser().getCurrentDirectory(); if(dir != null) { addItem(dir); } } /** * Adds the directory to the model and sets it to be selected, * additionally clears out the previous selected directory and * the paths leading up to it, if any. */ private void addItem(File directory) { if(directory == null) { return; } directories.clear(); File[] baseFolders; if(useShellFolder) { baseFolders = (File[]) ShellFolder.get("fileChooserComboBoxFolders"); } else { baseFolders = fsv.getRoots(); } directories.addAll(Arrays.asList(baseFolders)); // Get the canonical (full) path. This has the side // benefit of removing extraneous chars from the path, // for example /foo/bar/ becomes /foo/bar File canonical = null; try { canonical = directory.getCanonicalFile(); } catch (IOException e) { // Maybe drive is not ready. Can't abort here. canonical = directory; } // create File instances of each directory leading up to the top try { File sf = ShellFolder.getShellFolder(canonical); File f = sf; Vector path = new Vector(10); do { path.addElement(f); } while ((f = f.getParentFile()) != null); int pathCount = path.size(); // Insert chain at appropriate place in vector for (int i = 0; i < pathCount; i++) { f = (File) path.get(i); if(directories.contains(f)) { int topIndex = directories.indexOf(f); for (int j = i - 1; j >= 0; j--) { directories.insertElementAt(path.get(j), topIndex + i - j); } break; } } calculateDepths(); setSelectedItem(sf); } catch (FileNotFoundException ex) { calculateDepths(); } } private void calculateDepths() { depths = new int[directories.size()]; for (int i = 0; i < depths.length; i++) { File dir = (File) directories.get(i); File parent = dir.getParentFile(); depths[i] = 0; if(parent != null) { for (int j = i - 1; j >= 0; j--) { if(parent.equals((File) directories.get(j))) { depths[i] = depths[j] + 1; break; } } } } } public int getDepth(int i) { return (depths != null && i >= 0 && i < depths.length) ? depths[i] : 0; } public void setSelectedItem(Object selectedDirectory) { this.selectedDirectory = (File) selectedDirectory; fireContentsChanged(this, -1, -1); } public Object getSelectedItem() { return selectedDirectory; } public int getSize() { return directories.size(); } public Object getElementAt(int index) { return directories.elementAt(index); } } // // Renderer for Types ComboBox // protected FilterComboBoxRenderer createFilterComboBoxRenderer() { return new FilterComboBoxRenderer(); } /** * Render different type sizes and styles. */ public class FilterComboBoxRenderer extends DefaultListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if(value != null && value instanceof FileFilter) { setText(((FileFilter) value).getDescription()); } return this; } } // // DataModel for Types Comboxbox // protected FilterComboBoxModel createFilterComboBoxModel() { return new FilterComboBoxModel(); } /** * Data model for a type-face selection combo-box. */ protected class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener { protected FileFilter[] filters; protected FilterComboBoxModel() { super(); filters = getFileChooser().getChoosableFileFilters(); } public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) { filters = (FileFilter[]) e.getNewValue(); fireContentsChanged(this, -1, -1); } else if(prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) { fireContentsChanged(this, -1, -1); } } public void setSelectedItem(Object filter) { if(filter != null) { getFileChooser().setFileFilter((FileFilter) filter); setFileName(null); fireContentsChanged(this, -1, -1); } } public Object getSelectedItem() { // Ensure that the current filter is in the list. // NOTE: we shouldnt' have to do this, since JFileChooser adds // the filter to the choosable filters list when the filter // is set. Lets be paranoid just in case someone overrides // setFileFilter in JFileChooser. FileFilter currentFilter = getFileChooser().getFileFilter(); boolean found = false; if(currentFilter != null) { for (int i = 0; i < filters.length; i++) { if(filters[i] == currentFilter) { found = true; } } if(found == false) { getFileChooser().addChoosableFileFilter(currentFilter); } } return getFileChooser().getFileFilter(); } public int getSize() { if(filters != null) { return filters.length; } else { return 0; } } public Object getElementAt(int index) { if(index > getSize() - 1) { // This shouldn't happen. Try to recover gracefully. return getFileChooser().getFileFilter(); } if(filters != null) { return filters[index]; } else { return null; } } } public void valueChanged(ListSelectionEvent e) { JFileChooser fc = getFileChooser(); File f = fc.getSelectedFile(); if(!e.getValueIsAdjusting() && f != null && !getFileChooser().isTraversable(f)) { setFileName(fileNameString(f)); } } /** * Acts when DirectoryComboBox has changed the selected item. */ protected class DirectoryComboBoxAction extends AbstractAction { protected DirectoryComboBoxAction() { super("DirectoryComboBoxAction"); } public void actionPerformed(ActionEvent e) { File f = (File) directoryComboBox.getSelectedItem(); getFileChooser().setCurrentDirectory(f); } } protected JButton getApproveButton(JFileChooser fc) { return approveButton; } /** * <code>ButtonAreaLayout</code> behaves in a similar manner to * <code>FlowLayout</code>. It lays out all components from left to * right, flushed right. The widths of all components will be set * to the largest preferred size width. */ private static class ButtonAreaLayout implements LayoutManager { private int hGap = 5; private int topMargin = 17; public void addLayoutComponent(String string, Component comp) { } public void layoutContainer(Container container) { Component[] children = container.getComponents(); if(children != null && children.length > 0) { int numChildren = children.length; Dimension[] sizes = new Dimension[numChildren]; Insets insets = container.getInsets(); int yLocation = insets.top + topMargin; int maxWidth = 0; for (int counter = 0; counter < numChildren; counter++) { sizes[counter] = children[counter].getPreferredSize(); maxWidth = Math.max(maxWidth, sizes[counter].width); } int xLocation, xOffset; if(container.getComponentOrientation().isLeftToRight()) { xLocation = container.getSize().width - insets.left - maxWidth; xOffset = hGap + maxWidth; } else { xLocation = insets.left; xOffset = - (hGap + maxWidth); } for (int counter = numChildren - 1; counter >= 0; counter--) { children[counter].setBounds(xLocation, yLocation, maxWidth, sizes[counter].height); xLocation -= xOffset; } } } public Dimension minimumLayoutSize(Container c) { if(c != null) { Component[] children = c.getComponents(); if(children != null && children.length > 0) { int numChildren = children.length; int height = 0; Insets cInsets = c.getInsets(); int extraHeight = topMargin + cInsets.top + cInsets.bottom; int extraWidth = cInsets.left + cInsets.right; int maxWidth = 0; for (int counter = 0; counter < numChildren; counter++) { Dimension aSize = children[counter].getPreferredSize(); height = Math.max(height, aSize.height); maxWidth = Math.max(maxWidth, aSize.width); } return new Dimension(extraWidth + numChildren * maxWidth + (numChildren - 1) * hGap, extraHeight + height); } } return new Dimension(0, 0); } public Dimension preferredLayoutSize(Container c) { return minimumLayoutSize(c); } public void removeLayoutComponent(Component c) { } } private static void groupLabels(AlignedLabel[] group) { for (int i = 0; i < group.length; i++) { group[i].group = group; } } private class AlignedLabel extends JLabel { private AlignedLabel[] group; private int maxWidth = 0; AlignedLabel(String text) { super(text); setAlignmentX(JComponent.LEFT_ALIGNMENT); } public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); // Align the width with all other labels in group. return new Dimension(getMaxWidth() + 11, d.height); } private int getMaxWidth() { if(maxWidth == 0 && group != null) { int max = 0; for (int i = 0; i < group.length; i++) { max = Math.max(group[i].getSuperPreferredWidth(), max); } for (int i = 0; i < group.length; i++) { group[i].maxWidth = max; } } return maxWidth; } private int getSuperPreferredWidth() { return super.getPreferredSize().width; } } }