0001: /*=============================================================================
0002: * Copyright Texas Instruments 2001, 2002. All Rights Reserved.
0003: *
0004: * This program is free software; you can redistribute it and/or modify
0005: * it under the terms of the GNU General Public License as published by
0006: * the Free Software Foundation; either version 2 of the License, or
0007: * (at your option) any later version.
0008: *
0009: * This program is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0012: * GNU General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU General Public License
0015: * along with this program; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: */
0018:
0019: package ti.chimera;
0020:
0021: import ti.chimera.fs.*;
0022: import ti.chimera.registry.*;
0023: import ti.chimera.service.FileBrowser;
0024: import ti.swing.treetable.*;
0025:
0026: import ti.exceptions.ProgrammingErrorException;
0027: import oscript.fs.AbstractFile;
0028: import oscript.fs.AbstractFileSystem;
0029:
0030: import java.awt.*;
0031: import java.awt.event.*;
0032: import java.util.*;
0033:
0034: import javax.swing.*;
0035: import javax.swing.event.*;
0036: import javax.swing.filechooser.*;
0037: import javax.swing.tree.*;
0038:
0039: /**
0040: * Like <code>JFileChooser</code>, but displays itself within the provided
0041: * <code>Dialog</code>, rather than a <code>JDialog</code>. This is done
0042: * because we abstract windows away, so that they can either be, for example
0043: * <code>JInternalFrame</code> or <code>JDialog</code>. (Having an
0044: * interface that <code>JInternalFrame</code>, <code>JWindow</code>,
0045: * <code>JDialog</code>, etc., all implement would have been a pretty smart
0046: * way of doing things, but I guess I'm expecting too much.)
0047: * <p>
0048: * It works something like this:
0049: * <pre>
0050: * FileChooser chooser = new FileChooser( main.getWindowManager().getDialog("Open File") );
0051: *
0052: * chooser.setFileFilter( ... );
0053: *
0054: * if( chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION )
0055: * {
0056: * AbstractFile file = chooser.getSelectedFile();
0057: * ...
0058: * }
0059: * </pre>
0060: *
0061: * @author Rob Clark
0062: * @version 0.1
0063: */
0064: public class FileChooser extends JComponent {
0065: private Dialog dialog;
0066:
0067: /* Note: dialog return values are the same as for JFileChooser, so that
0068: * things still function properly if the user accidentally compares
0069: * the return value to JFileChooser.{CANCEL,APPROVE,ERROR}_OPTION
0070: */
0071:
0072: /**
0073: * Return value if cancel is chosen.
0074: */
0075: public static final int CANCEL_OPTION = JFileChooser.CANCEL_OPTION;
0076:
0077: /**
0078: * Return value if approve (yes, ok) is chosen.
0079: */
0080: public static final int APPROVE_OPTION = JFileChooser.APPROVE_OPTION;
0081:
0082: /**
0083: * Return value if an error occured.
0084: */
0085: public static final int ERROR_OPTION = JFileChooser.ERROR_OPTION;
0086:
0087: /**
0088: * The dialog's return value
0089: */
0090: private int retVal = ERROR_OPTION;
0091:
0092: /* Note: dialog types are same as for JFileChooser, see above
0093: */
0094:
0095: /**
0096: * The dialog is to be an "Open File" type dialog.
0097: */
0098: public static final int OPEN_DIALOG = JFileChooser.OPEN_DIALOG;
0099:
0100: /**
0101: * The dialog is to be a "Save File" type dialog.
0102: */
0103: public static final int SAVE_DIALOG = JFileChooser.SAVE_DIALOG;
0104:
0105: /**
0106: * The dialog type.
0107: */
0108: private int dialogType = OPEN_DIALOG;
0109:
0110: /**
0111: * Maps dialog-type to dialog title.
0112: */
0113: private static final Hashtable dialogTitleTable = new Hashtable();
0114:
0115: /**
0116: * Maps dialog-type to approve button text.
0117: */
0118: private static final Hashtable approveTextTable = new Hashtable();
0119:
0120: static {
0121: dialogTitleTable.put(new Integer(SAVE_DIALOG), "Save As...");
0122: dialogTitleTable.put(new Integer(OPEN_DIALOG), "Open...");
0123:
0124: approveTextTable.put(new Integer(SAVE_DIALOG), "Save");
0125: approveTextTable.put(new Integer(OPEN_DIALOG), "Open");
0126: }
0127:
0128: /**
0129: * This is a bit of a hack, but we need to get at the
0130: * main object in order to access the registry. This
0131: * is set by the Main class.
0132: */
0133: private static Main main;
0134:
0135: static void setMain(Main main) {
0136: FileChooser.main = main;
0137: }
0138:
0139: /**
0140: * List of "favorites" paths.
0141: */
0142: private LinkedList favoritesList;
0143:
0144: /**
0145: * Favorites subscriber:
0146: */
0147: private NodeSubscriber favoritesNodeSubscriber = new NodeSubscriber() {
0148: public void publish(Node node, Object val) {
0149: favoritesList = (LinkedList) val;
0150: refresh();
0151: }
0152: };
0153:
0154: /**
0155: * Set of choosable AbstractFileFilter-s
0156: */
0157: private Vector choosableFilters = new Vector();
0158: private AbstractFileFilter currentFilter = ALL_FILES_FILTER;
0159:
0160: /**
0161: * Class Constructor.
0162: *
0163: * @param dialog the dialog to contain the FileChooser
0164: */
0165: public FileChooser(Dialog dialog) {
0166: this (dialog, null);
0167: }
0168:
0169: /**
0170: * Class Constructor.
0171: *
0172: * @param dialog the dialog to contain the FileChooser
0173: * @param file the currently selected file
0174: */
0175: public FileChooser(Dialog dialog, AbstractFile file) {
0176: super ();
0177:
0178: staticInit();
0179:
0180: this .dialog = dialog;
0181:
0182: setLayout(new BorderLayout());
0183:
0184: add(getTopPane(), BorderLayout.NORTH);
0185: add(getMiddlePane(), BorderLayout.CENTER);
0186: add(getBottomPane(), BorderLayout.SOUTH);
0187:
0188: main.getRegistry().subscribeToValue("/FileChooser/favorites",
0189: new TypeNodeContract(LinkedList.class),
0190: favoritesNodeSubscriber);
0191:
0192: setFileFilter(null);
0193:
0194: treeTable.setIntercellSpacing(new Dimension(2, 2));
0195:
0196: if (file != null)
0197: setSelectedFile(file);
0198: else
0199: setSelectedFile(getCwd());
0200:
0201: refresh();
0202: }
0203:
0204: private static final void staticInit() {
0205: Registry r = main.getRegistry();
0206: synchronized (r) {
0207: if (!r.exists("/FileChooser/favorites")) {
0208: try {
0209: r.link(new PersistentNode(new LinkedList(),
0210: new TypeNodeContract(LinkedList.class),
0211: "list of favorite paths"),
0212: "/FileChooser/favorites");
0213: } catch (RegistryException e) {
0214: throw new ProgrammingErrorException(e);
0215: }
0216: }
0217: }
0218: }
0219:
0220: private static final AbstractFile getCwd() {
0221: return resolve(AbstractFileSystem.getCwd());
0222: }
0223:
0224: private static final AbstractFile resolve(String path) {
0225: try {
0226: return AbstractFileSystem.resolve(path);
0227: } catch (java.io.IOException e) {
0228: throw new ProgrammingErrorException(e);
0229: }
0230: }
0231:
0232: /*
0233: * TOP PANE
0234: */
0235:
0236: private JComboBox favoritesComboBox;
0237:
0238: private final Component getTopPane() {
0239: JPanel panel = new JPanel();
0240:
0241: panel.add(new JLabel("Favorites:"));
0242: panel.add(favoritesComboBox = new JComboBox());
0243:
0244: favoritesComboBox.setRenderer(new DefaultListCellRenderer() {
0245: public Component getListCellRendererComponent(JList list,
0246: Object val, int idx, boolean selected,
0247: boolean hasFocus) {
0248: super .getListCellRendererComponent(list, val, idx,
0249: selected, hasFocus);
0250:
0251: if (val != null) {
0252: String str = (String) val;
0253:
0254: // show max width of 25 chars:
0255: if (str.length() > 25)
0256: str = "..." + str.substring(str.length() - 22);
0257:
0258: setText(str);
0259: }
0260:
0261: return this ;
0262: }
0263: });
0264:
0265: favoritesComboBox.addActionListener(new ActionListener() {
0266:
0267: public void actionPerformed(ActionEvent evt) {
0268: if (!updatedFavoritesComboBox) {
0269: String path = (String) (favoritesComboBox
0270: .getSelectedItem());
0271: if ((path != null)
0272: && (treeTable.getTreeTableModel() != null))
0273: setSelectedFile(resolve(path));
0274: }
0275: updatedFavoritesComboBox = false;
0276: }
0277:
0278: });
0279:
0280: panel.add(new JButton(new ButtonAction(null, PLUS24_ICON,
0281: "Add to favorites") {
0282:
0283: public void actionPerformed(ActionEvent evt) {
0284: AbstractFile[] files = getSelectedFiles(true);
0285: // remove first, in case they are already in the list
0286: for (int i = 0; i < files.length; i++)
0287: favoritesList.remove(files[i].getPath());
0288: // then re-add at front of list
0289: for (int i = 0; i < files.length; i++)
0290: favoritesList.addFirst(files[i].getPath());
0291: try {
0292: main.getRegistry()
0293: .resolve("/FileChooser/favorites")
0294: .setValue(favoritesList);
0295: } catch (RegistryException e) {
0296: throw new ProgrammingErrorException(e);
0297: }
0298: }
0299:
0300: }));
0301:
0302: panel.add(new JButton(new ButtonAction(null, MINUS24_ICON,
0303: "Remove from favorites") {
0304:
0305: public void actionPerformed(ActionEvent evt) {
0306: AbstractFile[] files = getSelectedFiles(true);
0307: for (int i = 0; i < files.length; i++)
0308: favoritesList.remove(files[i].getPath());
0309: try {
0310: main.getRegistry()
0311: .resolve("/FileChooser/favorites")
0312: .setValue(favoritesList);
0313: } catch (RegistryException e) {
0314: throw new ProgrammingErrorException(e);
0315: }
0316: }
0317:
0318: }));
0319:
0320: panel.add(new JButton(new ButtonAction(null, HOME24_ICON,
0321: "Go to home directory") {
0322:
0323: public void actionPerformed(ActionEvent evt) {
0324: setSelectedFile(getCwd());
0325: }
0326:
0327: }));
0328:
0329: return panel;
0330: }
0331:
0332: /**
0333: * this flag is set when we programatically update the combo box,
0334: * so it's ActionListener knows not setSelectedFile()
0335: */
0336: private boolean updatedFavoritesComboBox = false;
0337:
0338: private final void refreshTop() {
0339: Object currentVal = favoritesComboBox.getSelectedItem();
0340:
0341: updatedFavoritesComboBox = true;
0342: favoritesComboBox.removeAllItems();
0343:
0344: if (favoritesList != null) {
0345: for (Iterator itr = favoritesList.iterator(); itr.hasNext();) {
0346: String path = (String) (itr.next());
0347: updatedFavoritesComboBox = true;
0348: try {
0349: if (AbstractFileSystem.resolve(path).exists())
0350: favoritesComboBox.addItem(path);
0351: } catch (java.io.IOException e) {
0352: throw new ProgrammingErrorException(e);
0353: }
0354: }
0355: }
0356:
0357: if (currentVal != null) {
0358: updatedFavoritesComboBox = true;
0359: favoritesComboBox.setSelectedItem(currentVal);
0360: }
0361: }
0362:
0363: /*
0364: * MIDDLE PANE
0365: */
0366:
0367: private TreeTable treeTable;
0368:
0369: private final Component getMiddlePane() {
0370: treeTable = new TreeTable();
0371:
0372: treeTable.addMouseListener(new MouseAdapter() {
0373:
0374: public void mouseClicked(MouseEvent evt) {
0375: fileNameComboBox
0376: .setSelectedItem(selectedPathsToString());
0377: refreshButtons();
0378:
0379: if (evt.getClickCount() > 1) {
0380: AbstractFile[] selectedFiles = getSelectedFiles();
0381: for (int i = 0; i < selectedFiles.length; i++)
0382: if (selectedFiles[i].isDirectory())
0383: return;
0384: doApproveButtonPress();
0385: }
0386: }
0387:
0388: });
0389:
0390: PopupTrigger popupTrigger = new PopupTrigger(
0391: new PopupTrigger.PopupListener() {
0392:
0393: public void showPopup(MouseEvent evt) {
0394: TreePath path = treeTable.getTree()
0395: .getPathForLocation(evt.getX(),
0396: evt.getY());
0397: if (path != null) {
0398: AbstractFile file = ((FileSystemTreeModel.FileSystemNode) (path
0399: .getLastPathComponent())).getFile();
0400:
0401: FileBrowser fb = (FileBrowser) (main
0402: .getRegistry()
0403: .getService("file browser"));
0404: JPopupMenu popup = null;
0405:
0406: if (fb != null)
0407: popup = fb.getPopupMenu(file);
0408:
0409: if (popup != null)
0410: popup.show(treeTable, evt.getX(), evt
0411: .getY());
0412: }
0413: }
0414:
0415: });
0416:
0417: treeTable.addMouseListener(popupTrigger);
0418: treeTable.addMouseMotionListener(popupTrigger);
0419:
0420: return new JScrollPane(treeTable);
0421: }
0422:
0423: private final void refreshMiddle() {
0424: // no-op
0425: }
0426:
0427: /*
0428: * BOTTOM PANE
0429: */
0430:
0431: private JComboBox fileNameComboBox;
0432: private JButton approveButton;
0433: private JButton cancelButton;
0434: private JComboBox fileFilterComboBox;
0435:
0436: private final Component getBottomPane() {
0437: JPanel panel = new JPanel(new BorderLayout());
0438:
0439: panel.add(fileNameComboBox = new JComboBox(),
0440: BorderLayout.NORTH);
0441: fileNameComboBox.setEditable(true);
0442: JPanel subpanel = new JPanel(new GridLayout(2, 2));
0443:
0444: subpanel.add(new JLabel("File Type:"));
0445:
0446: subpanel.add(fileFilterComboBox = new JComboBox());
0447: fileFilterComboBox.addActionListener(new ActionListener() {
0448:
0449: public void actionPerformed(ActionEvent evt) {
0450: AbstractFileFilter filter = (AbstractFileFilter) (fileFilterComboBox
0451: .getSelectedItem());
0452: if (filter != null)
0453: setFileFilter(filter);
0454: }
0455:
0456: });
0457:
0458: subpanel.add(approveButton = new JButton(new ButtonAction("",
0459: OK24_ICON, null) {
0460:
0461: public void actionPerformed(ActionEvent evt) {
0462: doApproveButtonPress();
0463: }
0464:
0465: }));
0466:
0467: subpanel.add(cancelButton = new JButton(new ButtonAction(
0468: "Cancel", CANCEL24_ICON, null) {
0469:
0470: public void actionPerformed(ActionEvent evt) {
0471: doCancelButtonPress();
0472: }
0473:
0474: }));
0475:
0476: panel.add(subpanel, BorderLayout.SOUTH);
0477:
0478: return panel;
0479: }
0480:
0481: private final void doApproveButtonPress() {
0482: AbstractFile[] selectedFiles = getSelectedFiles();
0483: if (selectedFiles.length > 0) {
0484: retVal = APPROVE_OPTION;
0485: if (dialog != null)
0486: dialog.setVisible(false);
0487: }
0488: }
0489:
0490: private final void doCancelButtonPress() {
0491: retVal = CANCEL_OPTION;
0492: if (dialog != null)
0493: dialog.setVisible(false);
0494: }
0495:
0496: private final void refreshBottom() {
0497: {
0498: FileBrowser fb = (FileBrowser) (main.getRegistry()
0499: .getService("file browser"));
0500: String[] recentItems = (fb != null) ? fb.getRecentItems()
0501: : new String[0];
0502: if (currentFilter != null) {
0503: LinkedList l = new LinkedList();
0504: for (int i = 0; i < recentItems.length; i++)
0505: if (currentFilter.accept(resolve(recentItems[i])))
0506: l.add(recentItems[i]);
0507: recentItems = (String[]) (l
0508: .toArray(new String[l.size()]));
0509: }
0510:
0511: fileNameComboBox.setModel(new DefaultComboBoxModel(
0512: recentItems));
0513: }
0514:
0515: approveButton.setText(getApproveButtonText());
0516: refreshButtons();
0517:
0518: // reset filters pull-down:
0519: boolean currentFilterInList = false;
0520: fileFilterComboBox.removeAllItems();
0521: for (int i = 0; i < choosableFilters.size(); i++) {
0522: AbstractFileFilter filter = (AbstractFileFilter) (choosableFilters
0523: .elementAt(i));
0524: fileFilterComboBox.addItem(filter);
0525: if (filter.equals(currentFilter)) {
0526: fileFilterComboBox.setSelectedItem(filter);
0527: currentFilterInList = true;
0528: }
0529: }
0530: if (!currentFilterInList) {
0531: fileFilterComboBox.addItem(currentFilter);
0532: fileFilterComboBox.setSelectedItem(currentFilter);
0533: }
0534: }
0535:
0536: private final void refreshButtons() {
0537: approveButton.setEnabled(getSelectedFiles(false).length > 0);
0538: }
0539:
0540: /**
0541: * Called when state changes, ie.
0542: * + approve button text
0543: * + list of favorites
0544: */
0545: private final void refresh() {
0546: refreshTop();
0547: refreshMiddle();
0548: refreshBottom();
0549: }
0550:
0551: /**
0552: * since I can't figure out how to get notification events as the user types
0553: * in the fileNameComboBox, this timer is used to watch for changes
0554: */
0555: private javax.swing.Timer fileNameComboBoxTimer = new javax.swing.Timer(
0556: 100, new ActionListener() {
0557: private String lastPath = null;
0558:
0559: public void actionPerformed(ActionEvent evt) {
0560: String path = (String) (fileNameComboBox
0561: .getEditor().getItem());
0562: if ((path != null) && !path.equals(lastPath)) {
0563: setSelectedFiles(stringToFiles(path));
0564: lastPath = path;
0565: }
0566:
0567: }
0568: });
0569:
0570: private boolean realized = false;
0571:
0572: public void addNotify() {
0573: realized = true;
0574: super .addNotify();
0575: setSelectedFiles(getSelectedFiles());
0576: fileNameComboBoxTimer.start();
0577: }
0578:
0579: public void removeNotify() {
0580: super .removeNotify();
0581: fileNameComboBoxTimer.stop();
0582: }
0583:
0584: /**
0585: * Get the title of this dialog.
0586: */
0587: public String getDialogTitle() {
0588: return (String) (dialogTitleTable.get(new Integer(dialogType)));
0589: }
0590:
0591: /**
0592: * Get the approve button text.
0593: */
0594:
0595: public String getApproveButtonText() {
0596: return (String) (approveTextTable.get(new Integer(dialogType)));
0597: }
0598:
0599: /**
0600: * Set the file selected by this file chooser.
0601: *
0602: * @param file the file to select
0603: */
0604: public void setSelectedFile(AbstractFile file) {
0605: setSelectedFiles(new AbstractFile[] { file });
0606: }
0607:
0608: /**
0609: * Set the files select by this file chooser.
0610: *
0611: * @param files the files to select
0612: */
0613: public void setSelectedFiles(AbstractFile[] files) {
0614: if (realized) {
0615: TreePath[] treePaths = new TreePath[files.length];
0616:
0617: for (int i = 0; i < files.length; i++) {
0618: // find nearest ancestor which exists:
0619: AbstractFile file = files[i];
0620: while (!file.exists())
0621: file = resolve(AbstractFileSystem.dirname(file
0622: .getPath()));
0623:
0624: treePaths[i] = ((FileSystemTreeModel) (treeTable
0625: .getTreeTableModel())).getTreePath(file);
0626: }
0627:
0628: treeTable.setSelectionPaths(treePaths);
0629:
0630: String selectedPaths = filesToString(files);
0631: if (!selectedPaths.equals(fileNameComboBox.getEditor()
0632: .getItem()))
0633: fileNameComboBox.setSelectedItem(selectedPaths);
0634:
0635: refreshButtons();
0636: }
0637: }
0638:
0639: /**
0640: * Get the selected file. If there are more than one selected files, this
0641: * returns the first selected file.
0642: *
0643: * @return the selected file or <code>null</code> if none
0644: */
0645: public AbstractFile getSelectedFile() {
0646: AbstractFile[] selectedFiles = getSelectedFiles();
0647: if (selectedFiles.length > 0)
0648: return selectedFiles[0];
0649: else
0650: return null;
0651: }
0652:
0653: /**
0654: * Get the set of selected files.
0655: *
0656: * @return an array of selected files, of length 0 or more
0657: */
0658: public AbstractFile[] getSelectedFiles() {
0659: return getSelectedFiles(false);
0660: }
0661:
0662: /**
0663: * Get the set of selected files.
0664: *
0665: * @param all if <code>true</code>, return all selected files, not
0666: * just those that are accepted by the current filter
0667: * @return an array of selected files, of length 0 or more
0668: */
0669: private AbstractFile[] getSelectedFiles(boolean all) {
0670: /* XXX this is kind of a hack... maybe a cleaner way would be track if
0671: * the text-field has been updated more recently than the tree, and if
0672: * so refresh the tree from the text field:
0673: */
0674: try {
0675: LinkedList selectedFileList = new LinkedList();
0676: Object selected = fileNameComboBox.getEditor().getItem();
0677:
0678: if (selected != null) {
0679: StringTokenizer tokenizer = new StringTokenizer(
0680: selected.toString(), ";");
0681:
0682: while (tokenizer.hasMoreTokens()) {
0683: String filePath = tokenizer.nextToken();
0684: AbstractFile file = AbstractFileSystem
0685: .resolve(filePath);
0686:
0687: if (all || currentFilter.accept(file))
0688: selectedFileList.add(file);
0689: }
0690: }
0691:
0692: return (AbstractFile[]) (selectedFileList
0693: .toArray(new AbstractFile[selectedFileList.size()]));
0694: } catch (java.io.IOException e) {
0695: // if we fix the hack of resolving files based on the paths in the
0696: // text area, then we won't have this problem:
0697: // Tweek says:
0698: throw new ProgrammingErrorException(
0699: "Tweek says: \"Arrg! I can't stand this kind of pressure! No sweet Jesus please!\"");
0700: }
0701: }
0702:
0703: /**
0704: * This methods iterates through all the user selection in the
0705: * tree list and builds a string of file paths separated by ";"
0706: *
0707: * @return string of file paths seperated by ";"
0708: * Ex: "/c:/File1;/c:/File2;/c:/FileX"
0709: */
0710: private String selectedPathsToString() {
0711: TreePath[] treePaths = treeTable.getSelectionPaths();
0712: if (treePaths != null) {
0713: AbstractFile[] files = new AbstractFile[treePaths.length];
0714: for (int i = 0; i < files.length; i++) {
0715: Object[] objs = treePaths[i].getPath();
0716: files[i] = ((FileSystemTreeModel.FileSystemNode) (objs[objs.length - 1]))
0717: .getFile();
0718: }
0719: return filesToString(files);
0720: }
0721: return "";
0722: }
0723:
0724: private static String filesToString(AbstractFile[] files) {
0725: String selectionStr = "";
0726:
0727: for (int i = 0; i < files.length; i++) {
0728: String path = files[i].getPath();
0729:
0730: if (selectionStr == "") // == is ok, because initial value is a literal
0731: selectionStr = path;
0732: else
0733: selectionStr += ";" + path;
0734: }
0735:
0736: return selectionStr;
0737: }
0738:
0739: private static AbstractFile[] stringToFiles(String str) {
0740: LinkedList fileList = new LinkedList();
0741:
0742: for (StringTokenizer stok = new StringTokenizer(str, ";"); stok
0743: .hasMoreTokens();)
0744: fileList.add(resolve(stok.nextToken()));
0745:
0746: return (AbstractFile[]) (fileList
0747: .toArray(new AbstractFile[fileList.size()]));
0748: }
0749:
0750: /**
0751: * Set the file filter. The file filter will determine what types
0752: * of files are visible.
0753: */
0754: public void setFileFilter(AbstractFileFilter filter) {
0755: if (filter != currentFilter) {
0756: if (filter == null) {
0757: if (choosableFilters.size() > 0)
0758: filter = (AbstractFileFilter) (choosableFilters
0759: .elementAt(0));
0760: else
0761: filter = ALL_FILES_FILTER;
0762: }
0763:
0764: currentFilter = filter;
0765:
0766: // save the selected files prior to replacing the tree model:
0767: AbstractFile[] selectedFiles = getSelectedFiles(true);
0768:
0769: // we setModel here, because what is visible depends on what filter is
0770: // installed. We set the tree-selection-listener here, because it seems
0771: // that needs to be set again whenever the model is changed.
0772: treeTable.setModel(new FileSystemTreeTableModel("/",
0773: new FileSystemTreeModel.FileSystemTreeFilter() {
0774: // we want to let the user of the FileChooser
0775: // specify a filter that doesn't select dirs
0776: // but we still need the user to see them:
0777: public boolean accept(AbstractFile file) {
0778: String name;
0779: return currentFilter.accept(file)
0780: || (file.isDirectory()
0781: && ((name = file.getName())
0782: .length() > 0) && (name
0783: .charAt(0) != '.'));
0784: }
0785: }));
0786:
0787: treeTable
0788: .addTreeSelectionListener(new TreeSelectionListener() {
0789:
0790: public void valueChanged(TreeSelectionEvent evt) {
0791: TreePath[] paths = treeTable
0792: .getSelectionPaths();
0793: if (paths != null) {
0794: for (int i = 0; i < paths.length; i++) {
0795: Object[] objs = paths[i].getPath();
0796: String path = ((FileSystemTreeModel.FileSystemNode) (objs[objs.length - 1]))
0797: .getFile().getPath();
0798: if ((favoritesList != null)
0799: && favoritesList
0800: .contains(path)) {
0801: updatedFavoritesComboBox = true;
0802: favoritesComboBox
0803: .setSelectedItem(path);
0804: }
0805: }
0806: }
0807:
0808: /* XXX use a MouseListener (listening for mouse clicks) rather than a
0809: * TreeSelectionListener to deal with the case where someone tries to
0810: * programatically set a non-existent file (using setSelectedFile(s))
0811: * which ends up generating a tree event... since the file doesn't
0812: * exist, it won't be in the result of selectedPathsToString(). By
0813: * using a MouseListener, we only update the text field (which is
0814: * more or less the master when it comes to getSelectedFile(s)) as
0815: * a result of the user clicking in the tree
0816: */
0817: //fileNameComboBox.setSelectedItem( selectedPathsToString() );
0818: //refreshButtons();
0819: }
0820:
0821: });
0822:
0823: // this needs to happen after setting the model, or TreeTable will throw
0824: // a NullPointerException:
0825: treeTable
0826: .getTree()
0827: .setCellRenderer(
0828: new FileSystemTreeModel.FileSystemTreeCellRenderer());
0829: treeTable
0830: .getTree()
0831: .addTreeExpansionListener(
0832: new FileSystemTreeModel.FileSystemTreeExpansionListener());
0833:
0834: // and restore the selected files after replacing the tree model:
0835: setSelectedFiles(selectedFiles);
0836:
0837: refresh();
0838:
0839: treeTable.getColumnModel().getColumn(0).setPreferredWidth(
0840: 380);
0841: treeTable.getColumnModel().getColumn(1).setPreferredWidth(
0842: 60);
0843: treeTable.getColumnModel().getColumn(2).setPreferredWidth(
0844: 120);
0845: }
0846: }
0847:
0848: /**
0849: * Get the current file filter.
0850: */
0851: public AbstractFileFilter getFileFilter() {
0852: return currentFilter;
0853: }
0854:
0855: /**
0856: * Add a file filter to the list of choosable file filters.
0857: */
0858: public void addChoosableFileFilter(AbstractFileFilter filter) {
0859: if (filter != null) {
0860: choosableFilters.addElement(filter);
0861: setFileFilter(filter);
0862: refresh();
0863: }
0864: }
0865:
0866: /**
0867: * Remove a file filter from the list of choosable file filters.
0868: */
0869: public boolean removeChoosableFileFilter(AbstractFileFilter filter) {
0870: if (choosableFilters.contains(filter)) {
0871: choosableFilters.removeElement(filter);
0872: if (getFileFilter() == filter)
0873: setFileFilter(null);
0874: refresh();
0875: return true;
0876: }
0877:
0878: return false;
0879: }
0880:
0881: /**
0882: * The file-filter interface. Unfortunately because the JFileChooser's
0883: * FileFilter is tied to java.io.File, we can't reuse it.
0884: */
0885: public abstract static class AbstractFileFilter implements
0886: FileSystemTreeModel.FileSystemTreeFilter {
0887: /**
0888: * A description of this filter, for example "JPEG Files (*.jpg, *.jpeg)".
0889: */
0890: public abstract String getDescription();
0891:
0892: public String toString() {
0893: return getDescription();
0894: }
0895: }
0896:
0897: public static final AbstractFileFilter ALL_FILES_FILTER = new AbstractFileFilter() {
0898:
0899: public boolean accept(AbstractFile file) {
0900: return !file.getName().startsWith(".");
0901: }
0902:
0903: public String getDescription() {
0904: return "All Files (*.*)";
0905: }
0906:
0907: };
0908:
0909: /**
0910: * Show a "Save" file chooser dialog.
0911: *
0912: * @param parent the parent of this dialog, can be <code>null</code>
0913: * @return <code>CANCEL_OPTION</code>, <code>APPROVE_OPTION</code>, or
0914: * <code>ERROR_OPTION</code> (if an arror occurs or dialog is dismissed)
0915: */
0916: public int showSaveDialog(Component parent) {
0917: setDialogType(SAVE_DIALOG);
0918: return showDialog(parent, null);
0919: }
0920:
0921: /**
0922: * Show an "Open" file chooser dialog.
0923: *
0924: * @param parent the parent of this dialog, can be <code>null</code>
0925: * @return <code>CANCEL_OPTION</code>, <code>APPROVE_OPTION</code>, or
0926: * <code>ERROR_OPTION</code> (if an arror occurs or dialog is dismissed)
0927: */
0928: public int showOpenDialog(Component parent) {
0929: setDialogType(OPEN_DIALOG);
0930: return showDialog(parent, null);
0931: }
0932:
0933: /**
0934: * Show the dialog.
0935: *
0936: * @param parent the parent component of the dialog
0937: * @param approveButtonText text to show on the approve button. By
0938: * default, ie. if <code>null</code>, the approve button text is
0939: * determined by the dialog type
0940: * @return CANCEL_OPTION, APPROVE_OPTION, or ERROR_OPTION
0941: */
0942: public int showDialog(Component parent, String approveButtonText) {
0943: if (approveButtonText != null)
0944: System.err.println("XXX ignored approveButtonText");
0945:
0946: dialog.getContentPane().setLayout(new BorderLayout());
0947: dialog.getContentPane().add(this , BorderLayout.CENTER);
0948:
0949: dialog.pack();
0950: dialog.center();
0951:
0952: retVal = ERROR_OPTION;
0953:
0954: dialog.setVisible(true);
0955: dialog.showModal();
0956: dialog.dispose();
0957:
0958: main.getRegistry()
0959: .unsubscribeFromValue(favoritesNodeSubscriber);
0960:
0961: return retVal;
0962: }
0963:
0964: /**
0965: * Set the dialog type, ie. <code>OPEN_DIALOG</code> or <code>SAVE_DIALOG</code>
0966: */
0967: public void setDialogType(int dialogType) {
0968: if ((dialogType != OPEN_DIALOG) && (dialogType != SAVE_DIALOG))
0969: throw new IllegalArgumentException("bad monkey, no banana");
0970: this .dialogType = dialogType;
0971: refresh();
0972: }
0973:
0974: public Dimension getPreferredSize() {
0975: return new Dimension(550, 400);
0976: }
0977:
0978: public Dimension getMinimumSize() {
0979: return getPreferredSize();
0980: }
0981:
0982: public Dimension getMaximumSize() {
0983: return getPreferredSize();
0984: }
0985:
0986: private abstract class ButtonAction extends AbstractAction {
0987: ButtonAction(String name, Icon icon, String tip) {
0988: super ();
0989:
0990: if (name != null)
0991: putValue(NAME, name);
0992:
0993: if (icon != null)
0994: putValue(SMALL_ICON, icon);
0995:
0996: if (tip != null)
0997: putValue(SHORT_DESCRIPTION, tip);
0998: }
0999: }
1000:
1001: private static final Icon PLUS24_ICON = getIcon("img/Plus24.gif");
1002: private static final Icon MINUS24_ICON = getIcon("img/Minus24.gif");
1003: private static final Icon HOME24_ICON = getIcon("img/Home24.gif");
1004: private static final Icon OK24_ICON = getIcon("img/Ok24.gif");
1005: private static final Icon CANCEL24_ICON = getIcon("img/Cancel24.gif");
1006:
1007: private static Icon getIcon(String path) {
1008: return new javax.swing.ImageIcon(FileChooser.class
1009: .getClassLoader().getResource(path));
1010: }
1011: }
1012:
1013: /*
1014: * Local Variables:
1015: * tab-width: 2
1016: * indent-tabs-mode: nil
1017: * mode: java
1018: * c-indentation-style: java
1019: * c-basic-offset: 2
1020: * eval: (c-set-offset 'substatement-open '0)
1021: * eval: (c-set-offset 'case-label '+)
1022: * eval: (c-set-offset 'inclass '+)
1023: * eval: (c-set-offset 'inline-open '0)
1024: * End:
1025: */
|