0001: /*
0002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025: package sun.swing;
0026:
0027: import java.awt.*;
0028: import java.awt.event.*;
0029: import java.beans.PropertyChangeEvent;
0030: import java.beans.PropertyChangeListener;
0031: import java.io.*;
0032: import java.security.AccessControlException;
0033: import java.text.DateFormat;
0034: import java.text.MessageFormat;
0035: import java.util.*;
0036: import java.util.List;
0037:
0038: import javax.swing.*;
0039: import javax.swing.border.*;
0040: import javax.swing.event.*;
0041: import javax.swing.filechooser.*;
0042: import javax.swing.plaf.basic.*;
0043: import javax.swing.table.*;
0044: import javax.swing.text.*;
0045:
0046: import sun.awt.shell.*;
0047:
0048: /**
0049: * <b>WARNING:</b> This class is an implementation detail and is only
0050: * public so that it can be used by two packages. You should NOT consider
0051: * this public API.
0052: * <p>
0053: * This component is intended to be used in a subclass of
0054: * javax.swing.plaf.basic.BasicFileChooserUI. It realies heavily on the
0055: * implementation of BasicFileChooserUI, and is intended to be API compatible
0056: * with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI.
0057: *
0058: * @version 1.41, 05/09/07
0059: * @author Leif Samuelsson
0060: */
0061: public class FilePane extends JPanel implements PropertyChangeListener {
0062: // Constants for actions. These are used for the actions' ACTION_COMMAND_KEY
0063: // and as keys in the action maps for FilePane and the corresponding UI classes
0064:
0065: public final static String ACTION_APPROVE_SELECTION = "approveSelection";
0066: public final static String ACTION_CANCEL = "cancelSelection";
0067: public final static String ACTION_EDIT_FILE_NAME = "editFileName";
0068: public final static String ACTION_REFRESH = "refresh";
0069: public final static String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up";
0070: public final static String ACTION_NEW_FOLDER = "New Folder";
0071: public final static String ACTION_VIEW_LIST = "viewTypeList";
0072: public final static String ACTION_VIEW_DETAILS = "viewTypeDetails";
0073:
0074: private Action[] actions;
0075:
0076: // "enums" for setViewType()
0077: public static final int VIEWTYPE_LIST = 0;
0078: public static final int VIEWTYPE_DETAILS = 1;
0079: private static final int VIEWTYPE_COUNT = 2;
0080:
0081: private int viewType = -1;
0082: private JPanel[] viewPanels = new JPanel[VIEWTYPE_COUNT];
0083: private JPanel currentViewPanel;
0084: private String[] viewTypeActionNames;
0085:
0086: private JPopupMenu contextMenu;
0087: private JMenu viewMenu;
0088:
0089: private String viewMenuLabelText;
0090: private String refreshActionLabelText;
0091: private String newFolderActionLabelText;
0092:
0093: private String kiloByteString;
0094: private String megaByteString;
0095: private String gigaByteString;
0096:
0097: private static final Cursor waitCursor = Cursor
0098: .getPredefinedCursor(Cursor.WAIT_CURSOR);
0099:
0100: private final KeyListener detailsKeyListener = new KeyAdapter() {
0101: private final long timeFactor;
0102:
0103: private final StringBuilder typedString = new StringBuilder();
0104:
0105: private long lastTime = 1000L;
0106:
0107: {
0108: Long l = (Long) UIManager.get("Table.timeFactor");
0109: timeFactor = (l != null) ? l : 1000L;
0110: }
0111:
0112: /**
0113: * Moves the keyboard focus to the first element whose prefix matches
0114: * the sequence of alphanumeric keys pressed by the user with delay
0115: * less than value of <code>timeFactor</code>. Subsequent same key
0116: * presses move the keyboard focus to the next object that starts with
0117: * the same letter until another key is pressed, then it is treated
0118: * as the prefix with appropriate number of the same letters followed
0119: * by first typed another letter.
0120: */
0121: public void keyTyped(KeyEvent e) {
0122: BasicDirectoryModel model = getModel();
0123: int rowCount = model.getSize();
0124:
0125: if (detailsTable == null || rowCount == 0 || e.isAltDown()
0126: || e.isControlDown() || e.isMetaDown()) {
0127: return;
0128: }
0129:
0130: InputMap inputMap = detailsTable
0131: .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0132: KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
0133:
0134: if (inputMap != null && inputMap.get(key) != null) {
0135: return;
0136: }
0137:
0138: int startIndex = detailsTable.getSelectionModel()
0139: .getLeadSelectionIndex();
0140:
0141: if (startIndex < 0) {
0142: startIndex = 0;
0143: }
0144:
0145: if (startIndex >= rowCount) {
0146: startIndex = rowCount - 1;
0147: }
0148:
0149: char c = e.getKeyChar();
0150:
0151: long time = e.getWhen();
0152:
0153: if (time - lastTime < timeFactor) {
0154: if (typedString.length() == 1
0155: && typedString.charAt(0) == c) {
0156: // Subsequent same key presses move the keyboard focus to the next
0157: // object that starts with the same letter.
0158: startIndex++;
0159: } else {
0160: typedString.append(c);
0161: }
0162: } else {
0163: startIndex++;
0164:
0165: typedString.setLength(0);
0166: typedString.append(c);
0167: }
0168:
0169: lastTime = time;
0170:
0171: if (startIndex >= rowCount) {
0172: startIndex = 0;
0173: }
0174:
0175: // Find next file
0176: int index = getNextMatch(startIndex, rowCount - 1);
0177:
0178: if (index < 0 && startIndex > 0) { // wrap
0179: index = getNextMatch(0, startIndex - 1);
0180: }
0181:
0182: if (index >= 0) {
0183: detailsTable.getSelectionModel().setSelectionInterval(
0184: index, index);
0185:
0186: Rectangle cellRect = detailsTable
0187: .getCellRect(
0188: index,
0189: detailsTable
0190: .convertColumnIndexToView(COLUMN_FILENAME),
0191: false);
0192: detailsTable.scrollRectToVisible(cellRect);
0193: }
0194: }
0195:
0196: private int getNextMatch(int startIndex, int finishIndex) {
0197: BasicDirectoryModel model = getModel();
0198: JFileChooser fileChooser = getFileChooser();
0199: DetailsTableRowSorter rowSorter = getRowSorter();
0200:
0201: String prefix = typedString.toString().toLowerCase();
0202:
0203: // Search element
0204: for (int index = startIndex; index <= finishIndex; index++) {
0205: File file = (File) model.getElementAt(rowSorter
0206: .convertRowIndexToModel(index));
0207:
0208: String fileName = fileChooser.getName(file)
0209: .toLowerCase();
0210:
0211: if (fileName.startsWith(prefix)) {
0212: return index;
0213: }
0214: }
0215:
0216: return -1;
0217: }
0218: };
0219:
0220: private FocusListener editorFocusListener = new FocusAdapter() {
0221: public void focusLost(FocusEvent e) {
0222: if (!e.isTemporary()) {
0223: applyEdit();
0224: }
0225: }
0226: };
0227:
0228: private static FocusListener repaintListener = new FocusListener() {
0229: public void focusGained(FocusEvent fe) {
0230: repaintSelection(fe.getSource());
0231: }
0232:
0233: public void focusLost(FocusEvent fe) {
0234: repaintSelection(fe.getSource());
0235: }
0236:
0237: private void repaintSelection(Object source) {
0238: if (source instanceof JList) {
0239: repaintListSelection((JList) source);
0240: } else if (source instanceof JTable) {
0241: repaintTableSelection((JTable) source);
0242: }
0243: }
0244:
0245: private void repaintListSelection(JList list) {
0246: int[] indices = list.getSelectedIndices();
0247: for (int i : indices) {
0248: Rectangle bounds = list.getCellBounds(i, i);
0249: list.repaint(bounds);
0250: }
0251: }
0252:
0253: private void repaintTableSelection(JTable table) {
0254: int minRow = table.getSelectionModel()
0255: .getMinSelectionIndex();
0256: int maxRow = table.getSelectionModel()
0257: .getMaxSelectionIndex();
0258: if (minRow == -1 || maxRow == -1) {
0259: return;
0260: }
0261:
0262: int col0 = table.convertColumnIndexToView(COLUMN_FILENAME);
0263:
0264: Rectangle first = table.getCellRect(minRow, col0, false);
0265: Rectangle last = table.getCellRect(maxRow, col0, false);
0266: Rectangle dirty = first.union(last);
0267: table.repaint(dirty);
0268: }
0269: };
0270:
0271: private boolean smallIconsView = false;
0272: private Border listViewBorder;
0273: private Color listViewBackground;
0274: private boolean listViewWindowsStyle;
0275: private boolean readOnly;
0276:
0277: private ListSelectionModel listSelectionModel;
0278: private JList list;
0279: private JTable detailsTable;
0280:
0281: private static final int COLUMN_FILENAME = 0;
0282:
0283: // Provides a way to recognize a newly created folder, so it can
0284: // be selected when it appears in the model.
0285: private File newFolderFile;
0286:
0287: // Used for accessing methods in the corresponding UI class
0288: private FileChooserUIAccessor fileChooserUIAccessor;
0289: private DetailsTableModel detailsTableModel;
0290: private DetailsTableRowSorter rowSorter;
0291:
0292: public FilePane(FileChooserUIAccessor fileChooserUIAccessor) {
0293: super (new BorderLayout());
0294:
0295: this .fileChooserUIAccessor = fileChooserUIAccessor;
0296:
0297: installDefaults();
0298: createActionMap();
0299: }
0300:
0301: public void uninstallUI() {
0302: if (getModel() != null) {
0303: getModel().removePropertyChangeListener(this );
0304: }
0305: }
0306:
0307: protected JFileChooser getFileChooser() {
0308: return fileChooserUIAccessor.getFileChooser();
0309: }
0310:
0311: protected BasicDirectoryModel getModel() {
0312: return fileChooserUIAccessor.getModel();
0313: }
0314:
0315: public int getViewType() {
0316: return viewType;
0317: }
0318:
0319: public void setViewType(int viewType) {
0320: int oldValue = this .viewType;
0321: if (viewType == oldValue) {
0322: return;
0323: }
0324: this .viewType = viewType;
0325:
0326: switch (viewType) {
0327: case VIEWTYPE_LIST:
0328: if (viewPanels[viewType] == null) {
0329: JPanel p = fileChooserUIAccessor.createList();
0330: if (p == null) {
0331: p = createList();
0332: }
0333: setViewPanel(viewType, p);
0334: }
0335: list.setLayoutOrientation(JList.VERTICAL_WRAP);
0336: break;
0337:
0338: case VIEWTYPE_DETAILS:
0339: if (viewPanels[viewType] == null) {
0340: JPanel p = fileChooserUIAccessor.createDetailsView();
0341: if (p == null) {
0342: p = createDetailsView();
0343: }
0344: setViewPanel(viewType, p);
0345: }
0346: break;
0347: }
0348: JPanel oldViewPanel = currentViewPanel;
0349: currentViewPanel = viewPanels[viewType];
0350: if (currentViewPanel != oldViewPanel) {
0351: if (oldViewPanel != null) {
0352: remove(oldViewPanel);
0353: }
0354: add(currentViewPanel, BorderLayout.CENTER);
0355: revalidate();
0356: repaint();
0357: }
0358: updateViewMenu();
0359: firePropertyChange("viewType", oldValue, viewType);
0360: }
0361:
0362: class ViewTypeAction extends AbstractAction {
0363: private int viewType;
0364:
0365: ViewTypeAction(int viewType) {
0366: super (viewTypeActionNames[viewType]);
0367: this .viewType = viewType;
0368:
0369: String cmd;
0370: switch (viewType) {
0371: case VIEWTYPE_LIST:
0372: cmd = ACTION_VIEW_LIST;
0373: break;
0374: case VIEWTYPE_DETAILS:
0375: cmd = ACTION_VIEW_DETAILS;
0376: break;
0377: default:
0378: cmd = (String) getValue(Action.NAME);
0379: }
0380: putValue(Action.ACTION_COMMAND_KEY, cmd);
0381: }
0382:
0383: public void actionPerformed(ActionEvent e) {
0384: setViewType(viewType);
0385: }
0386: }
0387:
0388: public Action getViewTypeAction(int viewType) {
0389: return new ViewTypeAction(viewType);
0390: }
0391:
0392: private static void recursivelySetInheritsPopupMenu(
0393: Container container, boolean b) {
0394: if (container instanceof JComponent) {
0395: ((JComponent) container).setInheritsPopupMenu(b);
0396: }
0397: int n = container.getComponentCount();
0398: for (int i = 0; i < n; i++) {
0399: recursivelySetInheritsPopupMenu((Container) container
0400: .getComponent(i), b);
0401: }
0402: }
0403:
0404: public void setViewPanel(int viewType, JPanel viewPanel) {
0405: viewPanels[viewType] = viewPanel;
0406: recursivelySetInheritsPopupMenu(viewPanel, true);
0407:
0408: switch (viewType) {
0409: case VIEWTYPE_LIST:
0410: list = (JList) findChildComponent(viewPanels[viewType],
0411: JList.class);
0412: if (listSelectionModel == null) {
0413: listSelectionModel = list.getSelectionModel();
0414: if (detailsTable != null) {
0415: detailsTable.setSelectionModel(listSelectionModel);
0416: }
0417: } else {
0418: list.setSelectionModel(listSelectionModel);
0419: }
0420: break;
0421:
0422: case VIEWTYPE_DETAILS:
0423: detailsTable = (JTable) findChildComponent(
0424: viewPanels[viewType], JTable.class);
0425: detailsTable.setRowHeight(Math.max(detailsTable.getFont()
0426: .getSize() + 4, 16 + 1));
0427: if (listSelectionModel != null) {
0428: detailsTable.setSelectionModel(listSelectionModel);
0429: }
0430: break;
0431: }
0432: if (this .viewType == viewType) {
0433: if (currentViewPanel != null) {
0434: remove(currentViewPanel);
0435: }
0436: currentViewPanel = viewPanel;
0437: add(currentViewPanel, BorderLayout.CENTER);
0438: revalidate();
0439: repaint();
0440: }
0441: }
0442:
0443: protected void installDefaults() {
0444: Locale l = getFileChooser().getLocale();
0445:
0446: listViewBorder = UIManager
0447: .getBorder("FileChooser.listViewBorder");
0448: listViewBackground = UIManager
0449: .getColor("FileChooser.listViewBackground");
0450: listViewWindowsStyle = UIManager
0451: .getBoolean("FileChooser.listViewWindowsStyle");
0452: readOnly = UIManager.getBoolean("FileChooser.readOnly");
0453:
0454: // TODO: On windows, get the following localized strings from the OS
0455:
0456: viewMenuLabelText = UIManager.getString(
0457: "FileChooser.viewMenuLabelText", l);
0458: refreshActionLabelText = UIManager.getString(
0459: "FileChooser.refreshActionLabelText", l);
0460: newFolderActionLabelText = UIManager.getString(
0461: "FileChooser.newFolderActionLabelText", l);
0462:
0463: viewTypeActionNames = new String[VIEWTYPE_COUNT];
0464: viewTypeActionNames[VIEWTYPE_LIST] = UIManager.getString(
0465: "FileChooser.listViewActionLabelText", l);
0466: viewTypeActionNames[VIEWTYPE_DETAILS] = UIManager.getString(
0467: "FileChooser.detailsViewActionLabelText", l);
0468:
0469: kiloByteString = UIManager.getString(
0470: "FileChooser.fileSizeKiloBytes", l);
0471: megaByteString = UIManager.getString(
0472: "FileChooser.fileSizeMegaBytes", l);
0473: gigaByteString = UIManager.getString(
0474: "FileChooser.fileSizeGigaBytes", l);
0475: }
0476:
0477: /**
0478: * Fetches the command list for the FilePane. These commands
0479: * are useful for binding to events, such as in a keymap.
0480: *
0481: * @return the command list
0482: */
0483: public Action[] getActions() {
0484: if (actions == null) {
0485: class FilePaneAction extends AbstractAction {
0486: FilePaneAction(String name) {
0487: this (name, name);
0488: }
0489:
0490: FilePaneAction(String name, String cmd) {
0491: super (name);
0492: putValue(Action.ACTION_COMMAND_KEY, cmd);
0493: }
0494:
0495: public void actionPerformed(ActionEvent e) {
0496: String cmd = (String) getValue(Action.ACTION_COMMAND_KEY);
0497:
0498: if (cmd == ACTION_CANCEL) {
0499: if (editFile != null) {
0500: cancelEdit();
0501: } else {
0502: getFileChooser().cancelSelection();
0503: }
0504: } else if (cmd == ACTION_EDIT_FILE_NAME) {
0505: JFileChooser fc = getFileChooser();
0506: int index = listSelectionModel
0507: .getMinSelectionIndex();
0508: if (index >= 0
0509: && editFile == null
0510: && (!fc.isMultiSelectionEnabled() || fc
0511: .getSelectedFiles().length <= 1)) {
0512:
0513: editFileName(index);
0514: }
0515: } else if (cmd == ACTION_REFRESH) {
0516: getFileChooser().rescanCurrentDirectory();
0517: }
0518: }
0519:
0520: public boolean isEnabled() {
0521: String cmd = (String) getValue(Action.ACTION_COMMAND_KEY);
0522: if (cmd == ACTION_CANCEL) {
0523: return getFileChooser().isEnabled();
0524: } else if (cmd == ACTION_EDIT_FILE_NAME) {
0525: return !readOnly
0526: && getFileChooser().isEnabled();
0527: } else {
0528: return true;
0529: }
0530: }
0531: }
0532:
0533: ArrayList<Action> actionList = new ArrayList<Action>(8);
0534: Action action;
0535:
0536: actionList.add(new FilePaneAction(ACTION_CANCEL));
0537: actionList.add(new FilePaneAction(ACTION_EDIT_FILE_NAME));
0538: actionList.add(new FilePaneAction(refreshActionLabelText,
0539: ACTION_REFRESH));
0540:
0541: action = fileChooserUIAccessor.getApproveSelectionAction();
0542: if (action != null) {
0543: actionList.add(action);
0544: }
0545: action = fileChooserUIAccessor
0546: .getChangeToParentDirectoryAction();
0547: if (action != null) {
0548: actionList.add(action);
0549: }
0550: action = getNewFolderAction();
0551: if (action != null) {
0552: actionList.add(action);
0553: }
0554: action = getViewTypeAction(VIEWTYPE_LIST);
0555: if (action != null) {
0556: actionList.add(action);
0557: }
0558: action = getViewTypeAction(VIEWTYPE_DETAILS);
0559: if (action != null) {
0560: actionList.add(action);
0561: }
0562: actions = actionList.toArray(new Action[actionList.size()]);
0563: }
0564:
0565: return actions;
0566: }
0567:
0568: protected void createActionMap() {
0569: addActionsToMap(super .getActionMap(), getActions());
0570: }
0571:
0572: public static void addActionsToMap(ActionMap map, Action[] actions) {
0573: if (map != null && actions != null) {
0574: for (int i = 0; i < actions.length; i++) {
0575: Action a = actions[i];
0576: String cmd = (String) a
0577: .getValue(Action.ACTION_COMMAND_KEY);
0578: if (cmd == null) {
0579: cmd = (String) a.getValue(Action.NAME);
0580: }
0581: map.put(cmd, a);
0582: }
0583: }
0584: }
0585:
0586: private void updateListRowCount(JList list) {
0587: if (smallIconsView) {
0588: list.setVisibleRowCount(getModel().getSize() / 3);
0589: } else {
0590: list.setVisibleRowCount(-1);
0591: }
0592: }
0593:
0594: public JPanel createList() {
0595: JPanel p = new JPanel(new BorderLayout());
0596: final JFileChooser fileChooser = getFileChooser();
0597: final JList list = new JList() {
0598: public int getNextMatch(String prefix, int startIndex,
0599: Position.Bias bias) {
0600: ListModel model = getModel();
0601: int max = model.getSize();
0602: if (prefix == null || startIndex < 0
0603: || startIndex >= max) {
0604: throw new IllegalArgumentException();
0605: }
0606: // start search from the next element before/after the selected element
0607: boolean backwards = (bias == Position.Bias.Backward);
0608: for (int i = startIndex; backwards ? i >= 0 : i < max; i += (backwards ? -1
0609: : 1)) {
0610: String filename = fileChooser.getName((File) model
0611: .getElementAt(i));
0612: if (filename.regionMatches(true, 0, prefix, 0,
0613: prefix.length())) {
0614: return i;
0615: }
0616: }
0617: return -1;
0618: }
0619: };
0620: list.setCellRenderer(new FileRenderer());
0621: list.setLayoutOrientation(JList.VERTICAL_WRAP);
0622:
0623: // 4835633 : tell BasicListUI that this is a file list
0624: list.putClientProperty("List.isFileList", Boolean.TRUE);
0625:
0626: if (listViewWindowsStyle) {
0627: list.addFocusListener(repaintListener);
0628: }
0629:
0630: updateListRowCount(list);
0631:
0632: getModel().addListDataListener(new ListDataListener() {
0633: public void intervalAdded(ListDataEvent e) {
0634: updateListRowCount(list);
0635: }
0636:
0637: public void intervalRemoved(ListDataEvent e) {
0638: updateListRowCount(list);
0639: }
0640:
0641: public void contentsChanged(ListDataEvent e) {
0642: if (isShowing()) {
0643: clearSelection();
0644: }
0645: updateListRowCount(list);
0646: }
0647: });
0648:
0649: getModel().addPropertyChangeListener(this );
0650:
0651: if (fileChooser.isMultiSelectionEnabled()) {
0652: list
0653: .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
0654: } else {
0655: list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
0656: }
0657: list.setModel(new SortableListModel());
0658:
0659: list.addListSelectionListener(createListSelectionListener());
0660: list.addMouseListener(getMouseHandler());
0661:
0662: JScrollPane scrollpane = new JScrollPane(list);
0663: if (listViewBackground != null) {
0664: list.setBackground(listViewBackground);
0665: }
0666: if (listViewBorder != null) {
0667: scrollpane.setBorder(listViewBorder);
0668: }
0669: p.add(scrollpane, BorderLayout.CENTER);
0670: return p;
0671: }
0672:
0673: /**
0674: * This model allows for sorting JList
0675: */
0676: private class SortableListModel extends AbstractListModel implements
0677: TableModelListener, RowSorterListener {
0678:
0679: public SortableListModel() {
0680: getDetailsTableModel().addTableModelListener(this );
0681: getRowSorter().addRowSorterListener(this );
0682: }
0683:
0684: public int getSize() {
0685: return getModel().getSize();
0686: }
0687:
0688: public Object getElementAt(int index) {
0689: // JList doesn't support RowSorter so far, so we put it into the list model
0690: return getModel().getElementAt(
0691: getRowSorter().convertRowIndexToModel(index));
0692: }
0693:
0694: public void tableChanged(TableModelEvent e) {
0695: fireContentsChanged(this , 0, getSize());
0696: }
0697:
0698: public void sorterChanged(RowSorterEvent e) {
0699: fireContentsChanged(this , 0, getSize());
0700: }
0701: }
0702:
0703: private DetailsTableModel getDetailsTableModel() {
0704: if (detailsTableModel == null) {
0705: detailsTableModel = new DetailsTableModel(getFileChooser());
0706: }
0707: return detailsTableModel;
0708: }
0709:
0710: class DetailsTableModel extends AbstractTableModel implements
0711: ListDataListener {
0712: JFileChooser chooser;
0713: BasicDirectoryModel directoryModel;
0714:
0715: ShellFolderColumnInfo[] columns;
0716: int[] columnMap;
0717:
0718: DetailsTableModel(JFileChooser fc) {
0719: this .chooser = fc;
0720: directoryModel = getModel();
0721: directoryModel.addListDataListener(this );
0722:
0723: updateColumnInfo();
0724: }
0725:
0726: void updateColumnInfo() {
0727: File dir = chooser.getCurrentDirectory();
0728: if (dir != null && fileChooserUIAccessor.usesShellFolder()) {
0729: try {
0730: dir = ShellFolder.getShellFolder(dir);
0731: } catch (FileNotFoundException e) {
0732: // Leave dir without changing
0733: }
0734: }
0735:
0736: ShellFolderColumnInfo[] allColumns = ShellFolder
0737: .getFolderColumns(dir);
0738:
0739: ArrayList<ShellFolderColumnInfo> visibleColumns = new ArrayList<ShellFolderColumnInfo>();
0740: columnMap = new int[allColumns.length];
0741: for (int i = 0; i < allColumns.length; i++) {
0742: ShellFolderColumnInfo column = allColumns[i];
0743: if (column.isVisible()) {
0744: columnMap[visibleColumns.size()] = i;
0745: visibleColumns.add(column);
0746: }
0747: }
0748:
0749: columns = new ShellFolderColumnInfo[visibleColumns.size()];
0750: visibleColumns.toArray(columns);
0751: columnMap = Arrays.copyOf(columnMap, columns.length);
0752:
0753: List<RowSorter.SortKey> sortKeys = (rowSorter == null) ? null
0754: : rowSorter.getSortKeys();
0755: fireTableStructureChanged();
0756: restoreSortKeys(sortKeys);
0757: }
0758:
0759: private void restoreSortKeys(List<RowSorter.SortKey> sortKeys) {
0760: if (sortKeys != null) {
0761: // check if preserved sortKeys are valid for this folder
0762: for (int i = 0; i < sortKeys.size(); i++) {
0763: RowSorter.SortKey sortKey = sortKeys.get(i);
0764: if (sortKey.getColumn() >= columns.length) {
0765: sortKeys = null;
0766: break;
0767: }
0768: }
0769: if (sortKeys != null) {
0770: rowSorter.setSortKeys(sortKeys);
0771: }
0772: }
0773: }
0774:
0775: public int getRowCount() {
0776: return directoryModel.getSize();
0777: }
0778:
0779: public int getColumnCount() {
0780: return columns.length;
0781: }
0782:
0783: public Object getValueAt(int row, int col) {
0784: // Note: It is very important to avoid getting info on drives, as
0785: // this will trigger "No disk in A:" and similar dialogs.
0786: //
0787: // Use (f.exists() && !chooser.getFileSystemView().isFileSystemRoot(f)) to
0788: // determine if it is safe to call methods directly on f.
0789: return getFileColumnValue((File) directoryModel
0790: .getElementAt(row), col);
0791: }
0792:
0793: private Object getFileColumnValue(File f, int col) {
0794: return (col == COLUMN_FILENAME) ? f // always return the file itself for the 1st column
0795: : ShellFolder.getFolderColumnValue(f,
0796: columnMap[col]);
0797: }
0798:
0799: public void setValueAt(Object value, int row, int col) {
0800: if (col == COLUMN_FILENAME) {
0801: JFileChooser chooser = getFileChooser();
0802: File f = (File) getValueAt(row, col);
0803: if (f != null) {
0804: String oldDisplayName = chooser.getName(f);
0805: String oldFileName = f.getName();
0806: String newDisplayName = ((String) value).trim();
0807: String newFileName;
0808:
0809: if (!newDisplayName.equals(oldDisplayName)) {
0810: newFileName = newDisplayName;
0811: //Check if extension is hidden from user
0812: int i1 = oldFileName.length();
0813: int i2 = oldDisplayName.length();
0814: if (i1 > i2 && oldFileName.charAt(i2) == '.') {
0815: newFileName = newDisplayName
0816: + oldFileName.substring(i2);
0817: }
0818:
0819: // rename
0820: FileSystemView fsv = chooser
0821: .getFileSystemView();
0822: File f2 = fsv.createFileObject(f
0823: .getParentFile(), newFileName);
0824: if (!f2.exists()
0825: && FilePane.this .getModel().renameFile(
0826: f, f2)) {
0827: if (fsv.isParent(chooser
0828: .getCurrentDirectory(), f2)) {
0829: if (chooser.isMultiSelectionEnabled()) {
0830: chooser
0831: .setSelectedFiles(new File[] { f2 });
0832: } else {
0833: chooser.setSelectedFile(f2);
0834: }
0835: } else {
0836: //Could be because of delay in updating Desktop folder
0837: //chooser.setSelectedFile(null);
0838: }
0839: } else {
0840: // PENDING(jeff) - show a dialog indicating failure
0841: }
0842: }
0843: }
0844: }
0845: }
0846:
0847: public boolean isCellEditable(int row, int column) {
0848: File currentDirectory = getFileChooser()
0849: .getCurrentDirectory();
0850: return (!readOnly && column == COLUMN_FILENAME && canWrite(currentDirectory));
0851: }
0852:
0853: public void contentsChanged(ListDataEvent e) {
0854: // Update the selection after the model has been updated
0855: new DelayedSelectionUpdater();
0856: fireTableDataChanged();
0857: }
0858:
0859: public void intervalAdded(ListDataEvent e) {
0860: int i0 = e.getIndex0();
0861: int i1 = e.getIndex1();
0862: if (i0 == i1) {
0863: File file = (File) getModel().getElementAt(i0);
0864: if (file.equals(newFolderFile)) {
0865: new DelayedSelectionUpdater(file);
0866: newFolderFile = null;
0867: }
0868: }
0869:
0870: fireTableRowsInserted(e.getIndex0(), e.getIndex1());
0871: }
0872:
0873: public void intervalRemoved(ListDataEvent e) {
0874: fireTableRowsDeleted(e.getIndex0(), e.getIndex1());
0875: }
0876:
0877: public ShellFolderColumnInfo[] getColumns() {
0878: return columns;
0879: }
0880: }
0881:
0882: private void updateDetailsColumnModel(JTable table) {
0883: if (table != null) {
0884: ShellFolderColumnInfo[] columns = detailsTableModel
0885: .getColumns();
0886:
0887: TableColumnModel columnModel = new DefaultTableColumnModel();
0888: for (int i = 0; i < columns.length; i++) {
0889: ShellFolderColumnInfo dataItem = columns[i];
0890: TableColumn column = new TableColumn(i);
0891:
0892: String title = dataItem.getTitle();
0893: if (title != null && title.startsWith("FileChooser.")
0894: && title.endsWith("HeaderText")) {
0895: // the column must have a string resource that we try to get
0896: String uiTitle = UIManager.getString(title, table
0897: .getLocale());
0898: if (uiTitle != null) {
0899: title = uiTitle;
0900: }
0901: }
0902: column.setHeaderValue(title);
0903:
0904: Integer width = dataItem.getWidth();
0905: if (width != null) {
0906: column.setPreferredWidth(width);
0907: // otherwise we let JTable to decide the actual width
0908: }
0909:
0910: columnModel.addColumn(column);
0911: }
0912:
0913: // Install cell editor for editing file name
0914: if (!readOnly
0915: && columnModel.getColumnCount() > COLUMN_FILENAME) {
0916: columnModel.getColumn(COLUMN_FILENAME).setCellEditor(
0917: getDetailsTableCellEditor());
0918: }
0919:
0920: table.setColumnModel(columnModel);
0921: }
0922: }
0923:
0924: private DetailsTableRowSorter getRowSorter() {
0925: if (rowSorter == null) {
0926: rowSorter = new DetailsTableRowSorter();
0927: }
0928: return rowSorter;
0929: }
0930:
0931: private class DetailsTableRowSorter extends TableRowSorter {
0932: public DetailsTableRowSorter() {
0933: setModelWrapper(new SorterModelWrapper());
0934: }
0935:
0936: public void updateComparators(ShellFolderColumnInfo[] columns) {
0937: for (int i = 0; i < columns.length; i++) {
0938: Comparator c = columns[i].getComparator();
0939: if (c != null) {
0940: c = new DirectoriesFirstComparatorWrapper(i, c);
0941: }
0942: setComparator(i, c);
0943: }
0944: }
0945:
0946: public void modelStructureChanged() {
0947: super .modelStructureChanged();
0948: updateComparators(detailsTableModel.getColumns());
0949: }
0950:
0951: private class SorterModelWrapper extends ModelWrapper {
0952: public Object getModel() {
0953: return getDetailsTableModel();
0954: }
0955:
0956: public int getColumnCount() {
0957: return getDetailsTableModel().getColumnCount();
0958: }
0959:
0960: public int getRowCount() {
0961: return getDetailsTableModel().getRowCount();
0962: }
0963:
0964: public Object getValueAt(int row, int column) {
0965: return FilePane.this .getModel().getElementAt(row);
0966: }
0967:
0968: public Object getIdentifier(int row) {
0969: return row;
0970: }
0971: }
0972: }
0973:
0974: /**
0975: * This class sorts directories before files, comparing directory to
0976: * directory and file to file using the wrapped comparator.
0977: */
0978: private class DirectoriesFirstComparatorWrapper implements
0979: Comparator<File> {
0980: private Comparator comparator;
0981: private int column;
0982:
0983: public DirectoriesFirstComparatorWrapper(int column,
0984: Comparator comparator) {
0985: this .column = column;
0986: this .comparator = comparator;
0987: }
0988:
0989: public int compare(File f1, File f2) {
0990: if (f1 != null && f2 != null) {
0991: boolean traversable1 = getFileChooser().isTraversable(
0992: f1);
0993: boolean traversable2 = getFileChooser().isTraversable(
0994: f2);
0995: // directories go first
0996: if (traversable1 && !traversable2) {
0997: return -1;
0998: }
0999: if (!traversable1 && traversable2) {
1000: return 1;
1001: }
1002: }
1003: if (detailsTableModel.getColumns()[column]
1004: .isCompareByColumn()) {
1005: return comparator.compare(getDetailsTableModel()
1006: .getFileColumnValue(f1, column),
1007: getDetailsTableModel().getFileColumnValue(f2,
1008: column));
1009: }
1010: // For this column we need to pass the file itself (not a
1011: // column value) to the comparator
1012: return comparator.compare(f1, f2);
1013: }
1014: }
1015:
1016: private DetailsTableCellEditor tableCellEditor;
1017:
1018: private DetailsTableCellEditor getDetailsTableCellEditor() {
1019: if (tableCellEditor == null) {
1020: tableCellEditor = new DetailsTableCellEditor(
1021: new JTextField());
1022: }
1023: return tableCellEditor;
1024: }
1025:
1026: private class DetailsTableCellEditor extends DefaultCellEditor {
1027: private final JTextField tf;
1028:
1029: public DetailsTableCellEditor(JTextField tf) {
1030: super (tf);
1031: this .tf = tf;
1032: tf.addFocusListener(editorFocusListener);
1033: }
1034:
1035: public Component getTableCellEditorComponent(JTable table,
1036: Object value, boolean isSelected, int row, int column) {
1037: Component comp = super .getTableCellEditorComponent(table,
1038: value, isSelected, row, column);
1039: if (value instanceof File) {
1040: tf.setText(getFileChooser().getName((File) value));
1041: tf.selectAll();
1042: }
1043: return comp;
1044: }
1045: }
1046:
1047: class DetailsTableCellRenderer extends DefaultTableCellRenderer {
1048: JFileChooser chooser;
1049: DateFormat df;
1050:
1051: DetailsTableCellRenderer(JFileChooser chooser) {
1052: this .chooser = chooser;
1053: df = DateFormat.getDateTimeInstance(DateFormat.SHORT,
1054: DateFormat.SHORT, chooser.getLocale());
1055: }
1056:
1057: public void setBounds(int x, int y, int width, int height) {
1058: if (getHorizontalAlignment() == SwingConstants.LEADING) {
1059: // Restrict width to actual text
1060: width = Math.min(width,
1061: this .getPreferredSize().width + 4);
1062: } else {
1063: x -= 4;
1064: }
1065: super .setBounds(x, y, width, height);
1066: }
1067:
1068: public Insets getInsets(Insets i) {
1069: // Provide some space between columns
1070: i = super .getInsets(i);
1071: i.left += 4;
1072: i.right += 4;
1073: return i;
1074: }
1075:
1076: public Component getTableCellRendererComponent(JTable table,
1077: Object value, boolean isSelected, boolean hasFocus,
1078: int row, int column) {
1079:
1080: if (table.convertColumnIndexToModel(column) != COLUMN_FILENAME
1081: || (listViewWindowsStyle && !table.isFocusOwner())) {
1082:
1083: isSelected = false;
1084: }
1085:
1086: super .getTableCellRendererComponent(table, value,
1087: isSelected, hasFocus, row, column);
1088:
1089: setIcon(null);
1090:
1091: int modelColumn = table.convertColumnIndexToModel(column);
1092: ShellFolderColumnInfo columnInfo = detailsTableModel
1093: .getColumns()[modelColumn];
1094:
1095: Integer alignment = columnInfo.getAlignment();
1096: if (alignment == null) {
1097: alignment = (value instanceof Number) ? SwingConstants.RIGHT
1098: : SwingConstants.LEADING;
1099: }
1100:
1101: setHorizontalAlignment(alignment);
1102:
1103: // formatting cell text
1104: // TODO: it's rather a temporary trick, to be revised
1105: String text;
1106:
1107: if (value == null) {
1108: text = "";
1109:
1110: } else if (value instanceof File) {
1111: File file = (File) value;
1112: text = chooser.getName(file);
1113: Icon icon = chooser.getIcon(file);
1114: setIcon(icon);
1115:
1116: } else if (value instanceof Long) {
1117: long len = ((Long) value) / 1024L;
1118: if (listViewWindowsStyle) {
1119: text = MessageFormat
1120: .format(kiloByteString, len + 1);
1121: } else if (len < 1024L) {
1122: text = MessageFormat.format(kiloByteString,
1123: (len == 0L) ? 1L : len);
1124: } else {
1125: len /= 1024L;
1126: if (len < 1024L) {
1127: text = MessageFormat
1128: .format(megaByteString, len);
1129: } else {
1130: len /= 1024L;
1131: text = MessageFormat
1132: .format(gigaByteString, len);
1133: }
1134: }
1135:
1136: } else if (value instanceof Date) {
1137: text = df.format((Date) value);
1138:
1139: } else {
1140: text = value.toString();
1141: }
1142:
1143: setText(text);
1144:
1145: return this ;
1146: }
1147: }
1148:
1149: public JPanel createDetailsView() {
1150: final JFileChooser chooser = getFileChooser();
1151:
1152: JPanel p = new JPanel(new BorderLayout());
1153:
1154: final JTable detailsTable = new JTable(getDetailsTableModel()) {
1155: // Handle Escape key events here
1156: protected boolean processKeyBinding(KeyStroke ks,
1157: KeyEvent e, int condition, boolean pressed) {
1158: if (e.getKeyCode() == KeyEvent.VK_ESCAPE
1159: && getCellEditor() == null) {
1160: // We are not editing, forward to filechooser.
1161: chooser.dispatchEvent(e);
1162: return true;
1163: }
1164: return super .processKeyBinding(ks, e, condition,
1165: pressed);
1166: }
1167:
1168: public void tableChanged(TableModelEvent e) {
1169: super .tableChanged(e);
1170:
1171: if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
1172: // update header with possibly changed column set
1173: updateDetailsColumnModel(this );
1174: }
1175: }
1176: };
1177:
1178: detailsTable.setRowSorter(getRowSorter());
1179: detailsTable.setAutoCreateColumnsFromModel(false);
1180: detailsTable.setComponentOrientation(chooser
1181: .getComponentOrientation());
1182: detailsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1183: detailsTable.setShowGrid(false);
1184: detailsTable.putClientProperty("JTable.autoStartsEdit",
1185: Boolean.FALSE);
1186: detailsTable.addKeyListener(detailsKeyListener);
1187:
1188: Font font = list.getFont();
1189: detailsTable.setFont(font);
1190: detailsTable.setIntercellSpacing(new Dimension(0, 0));
1191:
1192: TableCellRenderer headerRenderer = new AlignableTableHeaderRenderer(
1193: detailsTable.getTableHeader().getDefaultRenderer());
1194: detailsTable.getTableHeader()
1195: .setDefaultRenderer(headerRenderer);
1196: TableCellRenderer cellRenderer = new DetailsTableCellRenderer(
1197: chooser);
1198: detailsTable.setDefaultRenderer(Object.class, cellRenderer);
1199:
1200: // So that drag can be started on a mouse press
1201: detailsTable.getColumnModel().getSelectionModel()
1202: .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1203:
1204: detailsTable.addMouseListener(getMouseHandler());
1205: // No need to addListSelectionListener because selections are forwarded
1206: // to our JList.
1207:
1208: // 4835633 : tell BasicTableUI that this is a file list
1209: detailsTable
1210: .putClientProperty("Table.isFileList", Boolean.TRUE);
1211:
1212: if (listViewWindowsStyle) {
1213: detailsTable.addFocusListener(repaintListener);
1214: }
1215:
1216: // TAB/SHIFT-TAB should transfer focus and ENTER should select an item.
1217: // We don't want them to navigate within the table
1218: ActionMap am = SwingUtilities.getUIActionMap(detailsTable);
1219: am.remove("selectNextRowCell");
1220: am.remove("selectPreviousRowCell");
1221: am.remove("selectNextColumnCell");
1222: am.remove("selectPreviousColumnCell");
1223: detailsTable.setFocusTraversalKeys(
1224: KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
1225: detailsTable.setFocusTraversalKeys(
1226: KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
1227:
1228: JScrollPane scrollpane = new JScrollPane(detailsTable);
1229: scrollpane.setComponentOrientation(chooser
1230: .getComponentOrientation());
1231: LookAndFeel.installColors(scrollpane.getViewport(),
1232: "Table.background", "Table.foreground");
1233:
1234: // Adjust width of first column so the table fills the viewport when
1235: // first displayed (temporary listener).
1236: scrollpane.addComponentListener(new ComponentAdapter() {
1237: public void componentResized(ComponentEvent e) {
1238: JScrollPane sp = (JScrollPane) e.getComponent();
1239: fixNameColumnWidth(sp.getViewport().getSize().width);
1240: sp.removeComponentListener(this );
1241: }
1242: });
1243:
1244: // 4835633.
1245: // If the mouse is pressed in the area below the Details view table, the
1246: // event is not dispatched to the Table MouseListener but to the
1247: // scrollpane. Listen for that here so we can clear the selection.
1248: scrollpane.addMouseListener(new MouseAdapter() {
1249: public void mousePressed(MouseEvent e) {
1250: JScrollPane jsp = ((JScrollPane) e.getComponent());
1251: JTable table = (JTable) jsp.getViewport().getView();
1252:
1253: if (!e.isShiftDown()
1254: || table.getSelectionModel().getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
1255: clearSelection();
1256: TableCellEditor tce = table.getCellEditor();
1257: if (tce != null) {
1258: tce.stopCellEditing();
1259: }
1260: }
1261: }
1262: });
1263:
1264: detailsTable.setForeground(list.getForeground());
1265: detailsTable.setBackground(list.getBackground());
1266:
1267: if (listViewBorder != null) {
1268: scrollpane.setBorder(listViewBorder);
1269: }
1270: p.add(scrollpane, BorderLayout.CENTER);
1271:
1272: detailsTableModel.fireTableStructureChanged();
1273:
1274: return p;
1275: } // createDetailsView
1276:
1277: private class AlignableTableHeaderRenderer implements
1278: TableCellRenderer {
1279: TableCellRenderer wrappedRenderer;
1280:
1281: public AlignableTableHeaderRenderer(
1282: TableCellRenderer wrappedRenderer) {
1283: this .wrappedRenderer = wrappedRenderer;
1284: }
1285:
1286: public Component getTableCellRendererComponent(JTable table,
1287: Object value, boolean isSelected, boolean hasFocus,
1288: int row, int column) {
1289:
1290: Component c = wrappedRenderer
1291: .getTableCellRendererComponent(table, value,
1292: isSelected, hasFocus, row, column);
1293:
1294: int modelColumn = table.convertColumnIndexToModel(column);
1295: ShellFolderColumnInfo columnInfo = detailsTableModel
1296: .getColumns()[modelColumn];
1297:
1298: Integer alignment = columnInfo.getAlignment();
1299: if (alignment == null) {
1300: alignment = SwingConstants.CENTER;
1301: }
1302: if (c instanceof JLabel) {
1303: ((JLabel) c).setHorizontalAlignment(alignment);
1304: }
1305:
1306: return c;
1307: }
1308: }
1309:
1310: private void fixNameColumnWidth(int viewWidth) {
1311: TableColumn nameCol = detailsTable.getColumnModel().getColumn(
1312: COLUMN_FILENAME);
1313: int tableWidth = detailsTable.getPreferredSize().width;
1314:
1315: if (tableWidth < viewWidth) {
1316: nameCol.setPreferredWidth(nameCol.getPreferredWidth()
1317: + viewWidth - tableWidth);
1318: }
1319: }
1320:
1321: private class DelayedSelectionUpdater implements Runnable {
1322: File editFile;
1323:
1324: DelayedSelectionUpdater() {
1325: this (null);
1326: }
1327:
1328: DelayedSelectionUpdater(File editFile) {
1329: this .editFile = editFile;
1330: if (isShowing()) {
1331: SwingUtilities.invokeLater(this );
1332: }
1333: }
1334:
1335: public void run() {
1336: setFileSelected();
1337: if (editFile != null) {
1338: editFileName(getRowSorter().convertRowIndexToView(
1339: getModel().indexOf(editFile)));
1340: editFile = null;
1341: }
1342: }
1343: }
1344:
1345: /**
1346: * Creates a selection listener for the list of files and directories.
1347: *
1348: * @return a <code>ListSelectionListener</code>
1349: */
1350: public ListSelectionListener createListSelectionListener() {
1351: return fileChooserUIAccessor.createListSelectionListener();
1352: }
1353:
1354: int lastIndex = -1;
1355: File editFile = null;
1356: int editX = 20;
1357:
1358: private int getEditIndex() {
1359: return lastIndex;
1360: }
1361:
1362: private void setEditIndex(int i) {
1363: lastIndex = i;
1364: }
1365:
1366: private void resetEditIndex() {
1367: lastIndex = -1;
1368: }
1369:
1370: private void cancelEdit() {
1371: if (editFile != null) {
1372: editFile = null;
1373: list.remove(editCell);
1374: repaint();
1375: } else if (detailsTable != null && detailsTable.isEditing()) {
1376: detailsTable.getCellEditor().cancelCellEditing();
1377: }
1378: }
1379:
1380: JTextField editCell = null;
1381:
1382: /**
1383: * @param index visual index of the file to be edited
1384: */
1385: private void editFileName(int index) {
1386: File currentDirectory = getFileChooser().getCurrentDirectory();
1387: if (readOnly || !canWrite(currentDirectory)) {
1388: return;
1389: }
1390:
1391: ensureIndexIsVisible(index);
1392: switch (viewType) {
1393: case VIEWTYPE_LIST:
1394: editFile = (File) getModel().getElementAt(
1395: getRowSorter().convertRowIndexToModel(index));
1396: Rectangle r = list.getCellBounds(index, index);
1397: if (editCell == null) {
1398: editCell = new JTextField();
1399: editCell.addActionListener(new EditActionListener());
1400: editCell.addFocusListener(editorFocusListener);
1401: editCell.setNextFocusableComponent(list);
1402: }
1403: list.add(editCell);
1404: editCell.setText(getFileChooser().getName(editFile));
1405: ComponentOrientation orientation = list
1406: .getComponentOrientation();
1407: editCell.setComponentOrientation(orientation);
1408: if (orientation.isLeftToRight()) {
1409: editCell.setBounds(editX + r.x, r.y, r.width - editX,
1410: r.height);
1411: } else {
1412: editCell.setBounds(r.x, r.y, r.width - editX, r.height);
1413: }
1414: editCell.requestFocus();
1415: editCell.selectAll();
1416: break;
1417:
1418: case VIEWTYPE_DETAILS:
1419: detailsTable.editCellAt(index, COLUMN_FILENAME);
1420: break;
1421: }
1422: }
1423:
1424: class EditActionListener implements ActionListener {
1425: public void actionPerformed(ActionEvent e) {
1426: applyEdit();
1427: }
1428: }
1429:
1430: private void applyEdit() {
1431: if (editFile != null && editFile.exists()) {
1432: JFileChooser chooser = getFileChooser();
1433: String oldDisplayName = chooser.getName(editFile);
1434: String oldFileName = editFile.getName();
1435: String newDisplayName = editCell.getText().trim();
1436: String newFileName;
1437:
1438: if (!newDisplayName.equals(oldDisplayName)) {
1439: newFileName = newDisplayName;
1440: //Check if extension is hidden from user
1441: int i1 = oldFileName.length();
1442: int i2 = oldDisplayName.length();
1443: if (i1 > i2 && oldFileName.charAt(i2) == '.') {
1444: newFileName = newDisplayName
1445: + oldFileName.substring(i2);
1446: }
1447:
1448: // rename
1449: FileSystemView fsv = chooser.getFileSystemView();
1450: File f2 = fsv.createFileObject(
1451: editFile.getParentFile(), newFileName);
1452: if (!f2.exists() && getModel().renameFile(editFile, f2)) {
1453: if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
1454: if (chooser.isMultiSelectionEnabled()) {
1455: chooser.setSelectedFiles(new File[] { f2 });
1456: } else {
1457: chooser.setSelectedFile(f2);
1458: }
1459: } else {
1460: //Could be because of delay in updating Desktop folder
1461: //chooser.setSelectedFile(null);
1462: }
1463: } else {
1464: // PENDING(jeff) - show a dialog indicating failure
1465: }
1466: }
1467: }
1468: if (detailsTable != null && detailsTable.isEditing()) {
1469: detailsTable.getCellEditor().stopCellEditing();
1470: }
1471: cancelEdit();
1472: }
1473:
1474: protected Action newFolderAction;
1475:
1476: public Action getNewFolderAction() {
1477: if (!readOnly && newFolderAction == null) {
1478: newFolderAction = new AbstractAction(
1479: newFolderActionLabelText) {
1480: private Action basicNewFolderAction;
1481:
1482: // Initializer
1483: {
1484: putValue(Action.ACTION_COMMAND_KEY,
1485: FilePane.ACTION_NEW_FOLDER);
1486:
1487: File currentDirectory = getFileChooser()
1488: .getCurrentDirectory();
1489: if (currentDirectory != null) {
1490: setEnabled(canWrite(currentDirectory));
1491: }
1492: }
1493:
1494: public void actionPerformed(ActionEvent ev) {
1495: if (basicNewFolderAction == null) {
1496: basicNewFolderAction = fileChooserUIAccessor
1497: .getNewFolderAction();
1498: }
1499: JFileChooser fc = getFileChooser();
1500: File oldFile = fc.getSelectedFile();
1501: basicNewFolderAction.actionPerformed(ev);
1502: File newFile = fc.getSelectedFile();
1503: if (newFile != null && !newFile.equals(oldFile)
1504: && newFile.isDirectory()) {
1505: newFolderFile = newFile;
1506: }
1507: }
1508: };
1509: }
1510: return newFolderAction;
1511: }
1512:
1513: protected class FileRenderer extends DefaultListCellRenderer {
1514:
1515: public Component getListCellRendererComponent(JList list,
1516: Object value, int index, boolean isSelected,
1517: boolean cellHasFocus) {
1518:
1519: if (listViewWindowsStyle && !list.isFocusOwner()) {
1520: isSelected = false;
1521: }
1522:
1523: super .getListCellRendererComponent(list, value, index,
1524: isSelected, cellHasFocus);
1525: File file = (File) value;
1526: String fileName = getFileChooser().getName(file);
1527: setText(fileName);
1528: setFont(list.getFont());
1529:
1530: Icon icon = getFileChooser().getIcon(file);
1531: if (icon != null) {
1532: setIcon(icon);
1533:
1534: if (isSelected) {
1535: // PENDING - grab padding (4) below from defaults table.
1536: editX = icon.getIconWidth() + 4;
1537: }
1538: } else {
1539: if (getFileChooser().getFileSystemView().isTraversable(
1540: file)) {
1541: setText(fileName + File.separator);
1542: }
1543: }
1544:
1545: return this ;
1546: }
1547: }
1548:
1549: void setFileSelected() {
1550: if (getFileChooser().isMultiSelectionEnabled()
1551: && !isDirectorySelected()) {
1552: File[] files = getFileChooser().getSelectedFiles(); // Should be selected
1553: Object[] selectedObjects = list.getSelectedValues(); // Are actually selected
1554:
1555: listSelectionModel.setValueIsAdjusting(true);
1556: try {
1557: int lead = listSelectionModel.getLeadSelectionIndex();
1558: int anchor = listSelectionModel
1559: .getAnchorSelectionIndex();
1560:
1561: Arrays.sort(files);
1562: Arrays.sort(selectedObjects);
1563:
1564: int shouldIndex = 0;
1565: int actuallyIndex = 0;
1566:
1567: // Remove files that shouldn't be selected and add files which should be selected
1568: // Note: Assume files are already sorted in compareTo order.
1569: while (shouldIndex < files.length
1570: && actuallyIndex < selectedObjects.length) {
1571: int comparison = files[shouldIndex]
1572: .compareTo((File) selectedObjects[actuallyIndex]);
1573: if (comparison < 0) {
1574: doSelectFile(files[shouldIndex++]);
1575: } else if (comparison > 0) {
1576: doDeselectFile(selectedObjects[actuallyIndex++]);
1577: } else {
1578: // Do nothing
1579: shouldIndex++;
1580: actuallyIndex++;
1581: }
1582:
1583: }
1584:
1585: while (shouldIndex < files.length) {
1586: doSelectFile(files[shouldIndex++]);
1587: }
1588:
1589: while (actuallyIndex < selectedObjects.length) {
1590: doDeselectFile(selectedObjects[actuallyIndex++]);
1591: }
1592:
1593: // restore the anchor and lead
1594: if (listSelectionModel instanceof DefaultListSelectionModel) {
1595: ((DefaultListSelectionModel) listSelectionModel)
1596: .moveLeadSelectionIndex(lead);
1597: listSelectionModel.setAnchorSelectionIndex(anchor);
1598: }
1599: } finally {
1600: listSelectionModel.setValueIsAdjusting(false);
1601: }
1602: } else {
1603: JFileChooser chooser = getFileChooser();
1604: File f;
1605: if (isDirectorySelected()) {
1606: f = getDirectory();
1607: } else {
1608: f = chooser.getSelectedFile();
1609: }
1610: int i;
1611: if (f != null && (i = getModel().indexOf(f)) >= 0) {
1612: int viewIndex = getRowSorter().convertRowIndexToView(i);
1613: listSelectionModel.setSelectionInterval(viewIndex,
1614: viewIndex);
1615: ensureIndexIsVisible(viewIndex);
1616: } else {
1617: clearSelection();
1618: }
1619: }
1620: }
1621:
1622: private void doSelectFile(File fileToSelect) {
1623: int index = getModel().indexOf(fileToSelect);
1624: // could be missed in the current directory if it changed
1625: if (index >= 0) {
1626: index = getRowSorter().convertRowIndexToView(index);
1627: listSelectionModel.addSelectionInterval(index, index);
1628: }
1629: }
1630:
1631: private void doDeselectFile(Object fileToDeselect) {
1632: int index = getRowSorter().convertRowIndexToView(
1633: getModel().indexOf(fileToDeselect));
1634: listSelectionModel.removeSelectionInterval(index, index);
1635: }
1636:
1637: /* The following methods are used by the PropertyChange Listener */
1638:
1639: private void doSelectedFileChanged(PropertyChangeEvent e) {
1640: applyEdit();
1641: File f = (File) e.getNewValue();
1642: JFileChooser fc = getFileChooser();
1643: if (f != null
1644: && ((fc.isFileSelectionEnabled() && !f.isDirectory()) || (f
1645: .isDirectory() && fc
1646: .isDirectorySelectionEnabled()))) {
1647:
1648: setFileSelected();
1649: }
1650: }
1651:
1652: private void doSelectedFilesChanged(PropertyChangeEvent e) {
1653: applyEdit();
1654: File[] files = (File[]) e.getNewValue();
1655: JFileChooser fc = getFileChooser();
1656: if (files != null
1657: && files.length > 0
1658: && (files.length > 1
1659: || fc.isDirectorySelectionEnabled() || !files[0]
1660: .isDirectory())) {
1661: setFileSelected();
1662: }
1663: }
1664:
1665: private void doDirectoryChanged(PropertyChangeEvent e) {
1666: getDetailsTableModel().updateColumnInfo();
1667:
1668: JFileChooser fc = getFileChooser();
1669: FileSystemView fsv = fc.getFileSystemView();
1670:
1671: applyEdit();
1672: resetEditIndex();
1673: ensureIndexIsVisible(0);
1674: File currentDirectory = fc.getCurrentDirectory();
1675: if (currentDirectory != null) {
1676: if (!readOnly) {
1677: getNewFolderAction().setEnabled(
1678: canWrite(currentDirectory));
1679: }
1680: fileChooserUIAccessor.getChangeToParentDirectoryAction()
1681: .setEnabled(!fsv.isRoot(currentDirectory));
1682: }
1683: }
1684:
1685: private void doFilterChanged(PropertyChangeEvent e) {
1686: applyEdit();
1687: resetEditIndex();
1688: clearSelection();
1689: }
1690:
1691: private void doFileSelectionModeChanged(PropertyChangeEvent e) {
1692: applyEdit();
1693: resetEditIndex();
1694: clearSelection();
1695: }
1696:
1697: private void doMultiSelectionChanged(PropertyChangeEvent e) {
1698: if (getFileChooser().isMultiSelectionEnabled()) {
1699: listSelectionModel
1700: .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
1701: } else {
1702: listSelectionModel
1703: .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1704: clearSelection();
1705: getFileChooser().setSelectedFiles(null);
1706: }
1707: }
1708:
1709: /*
1710: * Listen for filechooser property changes, such as
1711: * the selected file changing, or the type of the dialog changing.
1712: */
1713: public void propertyChange(PropertyChangeEvent e) {
1714: if (viewType == -1) {
1715: setViewType(VIEWTYPE_LIST);
1716: }
1717:
1718: String s = e.getPropertyName();
1719: if (s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
1720: doSelectedFileChanged(e);
1721: } else if (s
1722: .equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
1723: doSelectedFilesChanged(e);
1724: } else if (s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
1725: doDirectoryChanged(e);
1726: } else if (s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
1727: doFilterChanged(e);
1728: } else if (s
1729: .equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
1730: doFileSelectionModeChanged(e);
1731: } else if (s
1732: .equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
1733: doMultiSelectionChanged(e);
1734: } else if (s.equals(JFileChooser.CANCEL_SELECTION)) {
1735: applyEdit();
1736: } else if (s.equals("busy")) {
1737: setCursor((Boolean) e.getNewValue() ? waitCursor : null);
1738: } else if (s.equals("componentOrientation")) {
1739: ComponentOrientation o = (ComponentOrientation) e
1740: .getNewValue();
1741: JFileChooser cc = (JFileChooser) e.getSource();
1742: if (o != e.getOldValue()) {
1743: cc.applyComponentOrientation(o);
1744: }
1745: if (detailsTable != null) {
1746: detailsTable.setComponentOrientation(o);
1747: detailsTable.getParent().getParent()
1748: .setComponentOrientation(o);
1749: }
1750: }
1751: }
1752:
1753: private void ensureIndexIsVisible(int i) {
1754: if (i >= 0) {
1755: if (list != null) {
1756: list.ensureIndexIsVisible(i);
1757: }
1758: if (detailsTable != null) {
1759: detailsTable.scrollRectToVisible(detailsTable
1760: .getCellRect(i, COLUMN_FILENAME, true));
1761: }
1762: }
1763: }
1764:
1765: public void ensureFileIsVisible(JFileChooser fc, File f) {
1766: int modelIndex = getModel().indexOf(f);
1767: if (modelIndex >= 0) {
1768: ensureIndexIsVisible(getRowSorter().convertRowIndexToView(
1769: modelIndex));
1770: }
1771: }
1772:
1773: public void rescanCurrentDirectory() {
1774: getModel().validateFileCache();
1775: }
1776:
1777: public void clearSelection() {
1778: if (listSelectionModel != null) {
1779: listSelectionModel.clearSelection();
1780: if (listSelectionModel instanceof DefaultListSelectionModel) {
1781: ((DefaultListSelectionModel) listSelectionModel)
1782: .moveLeadSelectionIndex(0);
1783: listSelectionModel.setAnchorSelectionIndex(0);
1784: }
1785: }
1786: }
1787:
1788: public JMenu getViewMenu() {
1789: if (viewMenu == null) {
1790: viewMenu = new JMenu(viewMenuLabelText);
1791: ButtonGroup viewButtonGroup = new ButtonGroup();
1792:
1793: for (int i = 0; i < VIEWTYPE_COUNT; i++) {
1794: JRadioButtonMenuItem mi = new JRadioButtonMenuItem(
1795: new ViewTypeAction(i));
1796: viewButtonGroup.add(mi);
1797: viewMenu.add(mi);
1798: }
1799: updateViewMenu();
1800: }
1801: return viewMenu;
1802: }
1803:
1804: private void updateViewMenu() {
1805: if (viewMenu != null) {
1806: Component[] comps = viewMenu.getMenuComponents();
1807: for (int i = 0; i < comps.length; i++) {
1808: if (comps[i] instanceof JRadioButtonMenuItem) {
1809: JRadioButtonMenuItem mi = (JRadioButtonMenuItem) comps[i];
1810: if (((ViewTypeAction) mi.getAction()).viewType == viewType) {
1811: mi.setSelected(true);
1812: }
1813: }
1814: }
1815: }
1816: }
1817:
1818: public JPopupMenu getComponentPopupMenu() {
1819: JPopupMenu popupMenu = getFileChooser().getComponentPopupMenu();
1820: if (popupMenu != null) {
1821: return popupMenu;
1822: }
1823:
1824: JMenu viewMenu = getViewMenu();
1825: if (contextMenu == null) {
1826: contextMenu = new JPopupMenu();
1827: if (viewMenu != null) {
1828: contextMenu.add(viewMenu);
1829: if (listViewWindowsStyle) {
1830: contextMenu.addSeparator();
1831: }
1832: }
1833: ActionMap actionMap = getActionMap();
1834: Action refreshAction = actionMap.get(ACTION_REFRESH);
1835: Action newFolderAction = actionMap.get(ACTION_NEW_FOLDER);
1836: if (refreshAction != null) {
1837: contextMenu.add(refreshAction);
1838: if (listViewWindowsStyle && newFolderAction != null) {
1839: contextMenu.addSeparator();
1840: }
1841: }
1842: if (newFolderAction != null) {
1843: contextMenu.add(newFolderAction);
1844: }
1845: }
1846: if (viewMenu != null) {
1847: viewMenu.getPopupMenu().setInvoker(viewMenu);
1848: }
1849: return contextMenu;
1850: }
1851:
1852: private Handler handler;
1853:
1854: protected Handler getMouseHandler() {
1855: if (handler == null) {
1856: handler = new Handler();
1857: }
1858: return handler;
1859: }
1860:
1861: private class Handler implements MouseListener {
1862: private MouseListener doubleClickListener;
1863:
1864: public void mouseClicked(MouseEvent evt) {
1865: JComponent source = (JComponent) evt.getSource();
1866:
1867: int index;
1868: if (source instanceof JList) {
1869: index = SwingUtilities2.loc2IndexFileList(list, evt
1870: .getPoint());
1871: } else if (source instanceof JTable) {
1872: JTable table = (JTable) source;
1873: Point p = evt.getPoint();
1874: index = table.rowAtPoint(p);
1875:
1876: if (SwingUtilities2.pointOutsidePrefSize(table, index,
1877: table.columnAtPoint(p), p)) {
1878:
1879: return;
1880: }
1881:
1882: // Translate point from table to list
1883: if (index >= 0 && list != null
1884: && listSelectionModel.isSelectedIndex(index)) {
1885:
1886: // Make a new event with the list as source, placing the
1887: // click in the corresponding list cell.
1888: Rectangle r = list.getCellBounds(index, index);
1889: evt = new MouseEvent(list, evt.getID(), evt
1890: .getWhen(), evt.getModifiers(), r.x + 1,
1891: r.y + r.height / 2, evt.getXOnScreen(), evt
1892: .getYOnScreen(), evt
1893: .getClickCount(), evt
1894: .isPopupTrigger(), evt.getButton());
1895: }
1896: } else {
1897: return;
1898: }
1899:
1900: if (index >= 0 && SwingUtilities.isLeftMouseButton(evt)) {
1901: JFileChooser fc = getFileChooser();
1902:
1903: // For single click, we handle editing file name
1904: if (evt.getClickCount() == 1 && source instanceof JList) {
1905: if ((!fc.isMultiSelectionEnabled() || fc
1906: .getSelectedFiles().length <= 1)
1907: && index >= 0
1908: && listSelectionModel
1909: .isSelectedIndex(index)
1910: && getEditIndex() == index
1911: && editFile == null) {
1912:
1913: editFileName(index);
1914: } else {
1915: if (index >= 0) {
1916: setEditIndex(index);
1917: } else {
1918: resetEditIndex();
1919: }
1920: }
1921: } else if (evt.getClickCount() == 2) {
1922: // on double click (open or drill down one directory) be
1923: // sure to clear the edit index
1924: resetEditIndex();
1925: }
1926: }
1927:
1928: // Forward event to Basic
1929: if (getDoubleClickListener() != null) {
1930: getDoubleClickListener().mouseClicked(evt);
1931: }
1932: }
1933:
1934: public void mouseEntered(MouseEvent evt) {
1935: JComponent source = (JComponent) evt.getSource();
1936: if (source instanceof JTable) {
1937: JTable table = (JTable) evt.getSource();
1938:
1939: TransferHandler th1 = getFileChooser()
1940: .getTransferHandler();
1941: TransferHandler th2 = table.getTransferHandler();
1942: if (th1 != th2) {
1943: table.setTransferHandler(th1);
1944: }
1945:
1946: boolean dragEnabled = getFileChooser().getDragEnabled();
1947: if (dragEnabled != table.getDragEnabled()) {
1948: table.setDragEnabled(dragEnabled);
1949: }
1950: } else if (source instanceof JList) {
1951: // Forward event to Basic
1952: if (getDoubleClickListener() != null) {
1953: getDoubleClickListener().mouseEntered(evt);
1954: }
1955: }
1956: }
1957:
1958: public void mouseExited(MouseEvent evt) {
1959: if (evt.getSource() instanceof JList) {
1960: // Forward event to Basic
1961: if (getDoubleClickListener() != null) {
1962: getDoubleClickListener().mouseExited(evt);
1963: }
1964: }
1965: }
1966:
1967: public void mousePressed(MouseEvent evt) {
1968: if (evt.getSource() instanceof JList) {
1969: // Forward event to Basic
1970: if (getDoubleClickListener() != null) {
1971: getDoubleClickListener().mousePressed(evt);
1972: }
1973: }
1974: }
1975:
1976: public void mouseReleased(MouseEvent evt) {
1977: if (evt.getSource() instanceof JList) {
1978: // Forward event to Basic
1979: if (getDoubleClickListener() != null) {
1980: getDoubleClickListener().mouseReleased(evt);
1981: }
1982: }
1983: }
1984:
1985: private MouseListener getDoubleClickListener() {
1986: // Lazy creation of Basic's listener
1987: if (doubleClickListener == null && list != null) {
1988: doubleClickListener = fileChooserUIAccessor
1989: .createDoubleClickListener(list);
1990: }
1991: return doubleClickListener;
1992: }
1993: }
1994:
1995: /**
1996: * Property to remember whether a directory is currently selected in the UI.
1997: *
1998: * @return <code>true</code> iff a directory is currently selected.
1999: */
2000: protected boolean isDirectorySelected() {
2001: return fileChooserUIAccessor.isDirectorySelected();
2002: }
2003:
2004: /**
2005: * Property to remember the directory that is currently selected in the UI.
2006: *
2007: * @return the value of the <code>directory</code> property
2008: * @see javax.swing.plaf.basic.BasicFileChooserUI#setDirectory
2009: */
2010: protected File getDirectory() {
2011: return fileChooserUIAccessor.getDirectory();
2012: }
2013:
2014: private Component findChildComponent(Container container, Class cls) {
2015: int n = container.getComponentCount();
2016: for (int i = 0; i < n; i++) {
2017: Component comp = container.getComponent(i);
2018: if (cls.isInstance(comp)) {
2019: return comp;
2020: } else if (comp instanceof Container) {
2021: Component c = findChildComponent((Container) comp, cls);
2022: if (c != null) {
2023: return c;
2024: }
2025: }
2026: }
2027: return null;
2028: }
2029:
2030: public static boolean canWrite(File f) {
2031: boolean writeable = false;
2032: if (f != null) {
2033: try {
2034: writeable = f.canWrite();
2035: } catch (AccessControlException ex) {
2036: writeable = false;
2037: }
2038: }
2039: return writeable;
2040: }
2041:
2042: // This interface is used to access methods in the FileChooserUI
2043: // that are not public.
2044: public interface FileChooserUIAccessor {
2045: public JFileChooser getFileChooser();
2046:
2047: public BasicDirectoryModel getModel();
2048:
2049: public JPanel createList();
2050:
2051: public JPanel createDetailsView();
2052:
2053: public boolean isDirectorySelected();
2054:
2055: public File getDirectory();
2056:
2057: public Action getApproveSelectionAction();
2058:
2059: public Action getChangeToParentDirectoryAction();
2060:
2061: public Action getNewFolderAction();
2062:
2063: public MouseListener createDoubleClickListener(JList list);
2064:
2065: public ListSelectionListener createListSelectionListener();
2066:
2067: public boolean usesShellFolder();
2068: }
2069: }
|