/* class JFileChooser * * Copyright (C) 2003 R M Pitman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Modified Jul 14, 2003 by Tadpole Computer, Inc. * Modifications Copyright 2003 by Tadpole Computer, Inc. * * Modifications are hereby licensed to all parties at no charge under * the same terms as the original. * * Fixed bug to allow save dialog to work when files do not exist. * Added setSelectedFile method. Fixed fileSelectionMode to mean * that when FILES_ONLY, entry of a directory name in the textfield now * causes the appropriate setCurrentDirectory() call. */ package charvax.swing; import java.io.File; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; import java.util.TreeSet; import java.util.Vector; import charva.awt.BorderLayout; import charva.awt.Component; import charva.awt.Dialog; import charva.awt.Dimension; import charva.awt.FlowLayout; import charva.awt.Insets; import charva.awt.Point; import charva.awt.Toolkit; import charva.awt.event.ActionEvent; import charva.awt.event.ActionListener; import charva.awt.event.EventListener; import charva.awt.event.KeyEvent; import charva.awt.event.KeyListener; import charvax.swing.border.EmptyBorder; import charvax.swing.border.TitledBorder; import charvax.swing.event.ListSelectionEvent; import charvax.swing.event.ListSelectionListener; import charvax.swing.filechooser.FileFilter; /** * <p>The JFileChooser class displays a dialog from which the user can choose * a file. The dialog is always modal (i.e. the user cannot interact * with any other windows until he closes the dialog).</p> * * <p>The dialog is displayed by calling its showDialog() method, which blocks * until the dialog is closed (by the user pressing the Approve or Cancel * buttons, or by pressing ENTER while the focus is in the "Filename" field). * After the dialog has been closed, the program can find out what * File was selected by calling the getSelectedFile() method.</p> * * The labels of the buttons that are displayed in the JFileChooser * can be customized by changing the following static variables: * <ul> * <li> <code>PARENT_DIRECTORY_LABEL</code> * <li> <code>NEW_DIRECTORY_LABEL</code> * <li> <code>APPROVE_LABEL</code> * <li> <code>CANCEL_LABEL</code> * </ul> * <p>"Accelerator keys" can also be set for the buttons. For example, * to set the F1 key to have the same effect as pressing the CANCEL * button, call the following code before using the JFileChooser:</p> * <pre> * JFileChooser.CANCEL_LABEL = "Cancel (F1)"; * JFileChooser.CANCEL_ACCELERATOR = KeyEvent.VK_F1; * </pre> * Note that after the buttons have been customized, they stay customized * for all future invocations of JFileChooser (until they are re-customized * to some other value). */ public class JFileChooser extends JComponent { /** * Constructs a JFileChooser pointing to the user's home directory. */ public JFileChooser() { this((File) null); } /** * Constructs a JFileChooser pointing to the specified directory. * Passing in a null parameter causes the JFileChooser to point to * the user's home directory. */ public JFileChooser(File currentDirectory_) { setCurrentDirectory(currentDirectory_); } /** * Constructs a JFileChooser with the specified pathname. Passing a value * of <code>null</code> causes the file chooser to point to the user's * home directory. */ public JFileChooser(String currentDirectoryPath_) { if (currentDirectoryPath_ == null) setCurrentDirectory(null); else setCurrentDirectory(new File(currentDirectoryPath_)); } /** Set the current directory. Passing a parameter of <code>null</code> * cause the JFileChooser to point to the user's home directory. */ public void setCurrentDirectory(File dir_) { if (dir_ == null) { dir_ = new File(System.getProperty("user.home")); } if (dir_.isDirectory() == false) { throw new IllegalArgumentException("not a directory"); } _currentDirectory = dir_; _location = dir_.getAbsolutePath(); } /** * Returns the currently displayed directory. */ public File getCurrentDirectory() { return _currentDirectory; } /** * Get the File selected by the user. If the user pressed Cancel, * the return value is null. */ public File getSelectedFile() { if (_cancelWasPressed) return null; return new File(_location); } public void setSelectedFile(File file_) { if (!file_.isAbsolute()) { file_ = new File(_currentDirectory, file_.getPath()); } File parent = file_.getParentFile(); if (!file_.isDirectory() && (parent != null)) { _currentDirectory = parent; _location = file_.getAbsolutePath(); } else if (file_.isDirectory()) { _currentDirectory = file_; _location = file_.getAbsolutePath(); } fireFileChooserEvent(); } /** * Pops up a custom file chooser dialog with a custom approve button. * @param parent_ the parent component of the dialog; can be * <code>null</code>. * @param approveButtonText_ the custom text string to display in the * Approve button. * @return the return state of the file chooser on popdown: * <ul><li>JFileChooser.CANCEL_OPTION * <li>JFileChooser.APPROVE_OPTION * <li>JFileChooser.ERROR_OPTION</ul> */ public int showDialog(Component parent_, String approveButtonText_) { _approveButtonText = approveButtonText_; JDialog chooserDialog = new ChooserDialog(parent_); chooserDialog.setLocationRelativeTo(parent_); chooserDialog.show(); if (_cancelWasPressed) return CANCEL_OPTION; else return APPROVE_OPTION; } /** * Pops up a "Save File" file chooser dialog; this is a convenience * method and is equivalent to showDialog(Component, "Save"). * @return the return state of the file chooser on popdown: * <ul><li>JFileChooser.CANCEL_OPTION * <li>JFileChooser.APPROVE_OPTION * <li>JFileChooser.ERROR_OPTION</ul> */ public int showSaveDialog(Component parent_) { return showDialog(parent_, "Save"); } /** * Pops up a "Open File" file chooser dialog; this is a convenience * method and is equivalent to showDialog(Component, "Open"). * @return the return state of the file chooser on popdown: * <ul> * <li>JFileChooser.CANCEL_OPTION * <li>JFileChooser.APPROVE_OPTION * <li>JFileChooser.ERROR_OPTION * </ul> */ public int showOpenDialog(Component parent_) { return showDialog(parent_, "Open"); } /** * Sets the <code>JFileChooser</code> to allow the user to select * files only directories only, or files and directories. The default * is JFileChooser.FILES_ONLY. */ public void setFileSelectionMode(int mode_) { if (mode_ < FILES_ONLY || mode_ > FILES_AND_DIRECTORIES) throw new IllegalArgumentException("invalid file selection mode"); _fileSelectionMode = mode_; } /** Returns the current file-selection mode. * @return the file-selection mode, one of the following:<p> * <ul> * <li><code>JFileChooser.FILES_ONLY</code> * <li><code>JFileChooser.DIRECTORIES_ONLY</code> * <li><code>JFileChooser.FILES_AND_DIRECTORIES</code> * </ul> */ public int getFileSelectionMode() { return _fileSelectionMode; } public void setDialogTitle(String title_) { _title = title_; } /** * Sets the current file filter. The file filter is used by the * file chooser to filter out files from the user's view. */ public void setFileFilter(FileFilter filter_) { _fileFilter = filter_; } /** * Returns the currently selected file filter. */ public FileFilter getFileFilter() { return _fileFilter; } public void debug(int level_) { System.err.println("JFileChooser origin=" + _origin + " title=" + _title ); } /** Required to implement abstract method of JComponent (never used). */ public Dimension minimumSize() { return null; } /** Required to implement abstract method of JComponent (never used). */ public Dimension getSize() { return null; } /** Required to implement abstract method of JComponent (never used). */ public int getHeight() { return 0; } /** Required to implement abstract method of JComponent (never used). */ public int getWidth() { return 0; } protected void addFileChooserListener(FileChooserListener l) { _filelisteners.addElement(l); } protected void fireFileChooserEvent() { Enumeration<FileChooserListener> e = _filelisteners.elements(); while (e.hasMoreElements()) { FileChooserListener l = (FileChooserListener) e.nextElement(); l.fileChanged(new FileChooserEvent(this)); } } //==================================================================== // INSTANCE VARIABLES protected String _title; protected String _approveButtonText = "Open File"; /** The current directory shown in the dialog. */ protected File _currentDirectory = null; protected JFileChooser.DirList _dirList = this.new DirList(); protected String _location = ""; protected boolean _cancelWasPressed = true; protected int _fileSelectionMode = FILES_ONLY; protected FileFilter _fileFilter = null; protected Vector<FileChooserListener> _filelisteners = new Vector<FileChooserListener>(); protected static final int _COLS = 50; protected static final int _ROWS = 20; public static final int FILES_ONLY = 200; public static final int DIRECTORIES_ONLY = 201; public static final int FILES_AND_DIRECTORIES = 202; public static final int CANCEL_OPTION = 300; public static final int APPROVE_OPTION = 301; public static final int ERROR_OPTION = 302; // Default button labels - can be customized. public static String CANCEL_LABEL = "Cancel"; public static String APPROVE_LABEL = "Approve"; public static String PARENT_DIRECTORY_LABEL = "Parent Directory"; public static String NEW_DIRECTORY_LABEL = "New Directory"; // Button accelerators (disabled by default). public static int CANCEL_ACCELERATOR = -1; public static int APPROVE_ACCELERATOR = -1; public static int PARENT_DIRECTORY_ACCELERATOR = -1; public static int NEW_DIRECTORY_ACCELERATOR = -1; /*==================================================================== * This is a nonstatic inner class used by JFileChooser to display * a popup dialog. */ private class ChooserDialog extends JDialog implements ActionListener, ListSelectionListener, KeyListener, FileChooserListener { ChooserDialog(Component parent_) { setTitle(_title); setSize(_COLS, _ROWS); // Inherit colors from the parent component unless they have // been set already. if (JFileChooser.this.getForeground() == null) setForeground(parent_.getForeground()); else setForeground(JFileChooser.this.getForeground()); if (JFileChooser.this.getBackground() == null) setBackground(parent_.getBackground()); else setBackground(JFileChooser.this.getBackground()); /* Insert the directory list in the west. */ _dirList.setVisibleRowCount(12); _dirList.setColumns(45); _dirList.addListSelectionListener(this); displayCurrentDirectory(); _scrollPane = new JScrollPane(_dirList); _scrollPane.setViewportBorder(new TitledBorder("Files")); add(_scrollPane, BorderLayout.WEST); /* Insert a north panel that contains the Parent and New buttons. */ JPanel toppanel = new JPanel(); toppanel.setBorder(new EmptyBorder(0,1,0,1)); toppanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 1, 0)); toppanel.add(_parentButton); _parentButton.setText(PARENT_DIRECTORY_LABEL); _parentButton.addActionListener(this); toppanel.add(_newButton); _newButton.setText(NEW_DIRECTORY_LABEL); _newButton.addActionListener(this); add(toppanel, BorderLayout.NORTH); /* Insert a panel in the south for the textfield and the * Approve and Cancel buttons. */ JPanel southpanel = new JPanel(); southpanel.setLayout(new BorderLayout()); JPanel topsouth = new JPanel(); topsouth.add(new JLabel("Pathname:")); topsouth.add(_locationField); _locationField.setText(_location); _locationField.setActionCommand("locationField"); _locationField.addActionListener(this); southpanel.add(topsouth, BorderLayout.NORTH); JPanel bottomsouth = new JPanel(); bottomsouth.setLayout(new FlowLayout(FlowLayout.RIGHT, 1, 0)); bottomsouth.setBorder(new EmptyBorder(1,1,0,1)); bottomsouth.add(_approveButton); bottomsouth.add(_cancelButton); _approveButton.setText(_approveButtonText); _cancelButton.setText(CANCEL_LABEL); _approveButton.addActionListener(this); _cancelButton.addActionListener(this); southpanel.add(bottomsouth, BorderLayout.SOUTH); add(southpanel, BorderLayout.SOUTH); pack(); Insets insets = getInsets(); _dirList.setColumns(getWidth() - insets.left - insets.right - 2); addKeyListener(this); addFileChooserListener(this); } /** Implements the ActionListener interface. Handles button-presses, * and the ENTER keystroke in the Pathname field. */ public void actionPerformed(ActionEvent e_) { Object source = e_.getSource(); if (source == _parentButton) { _doParentDirectoryAction(); } else if (source == _newButton) { _doNewDirectoryAction(); } else if (source == _approveButton) { _doApproveAction(); } else if (source == _cancelButton) { _doCancelAction(); } else if (source == _locationField) { _doApproveAction(); } } /** Implements the KeyListener interface */ public void keyPressed(KeyEvent e_) { int key = e_.getKeyCode(); if (key == PARENT_DIRECTORY_ACCELERATOR) { _doParentDirectoryAction(); } else if (key == NEW_DIRECTORY_ACCELERATOR) { _doNewDirectoryAction(); } else if (key == APPROVE_ACCELERATOR) { _doApproveAction(); } else if (key == CANCEL_ACCELERATOR) { _doCancelAction(); } } /** Implements the KeyListener interface */ public void keyTyped(KeyEvent e_) { } /** Implements KeyListener interface; is never called. */ public void keyReleased(KeyEvent e_) { } /** Implements the ListSelectionListener interface. */ public void valueChanged(ListSelectionEvent e_) { String listitem = (String) _dirList.getSelectedValue(); if (listitem == null) { // The selection is empty; so there must have been a // file selected, but it has just been deselected. _locationField.setText(_currentDirectory.getAbsolutePath()); return; } /* Strip the trailing "/" */ if (listitem.endsWith("/")) listitem = listitem.substring(0, listitem.length()-1); File file = new File(_currentDirectory, listitem); if (file.canRead() == false) { String[] msgs = { "File or directory not readable:", file.getAbsolutePath() }; JOptionPane.showMessageDialog( this, msgs, "Error", JOptionPane.ERROR_MESSAGE); return; } else if (file.isDirectory() == false) { if (_fileSelectionMode == DIRECTORIES_ONLY) { String[] msgs = { "Not a directory:", file.getAbsolutePath() }; JOptionPane.showMessageDialog( this, msgs, "Error", JOptionPane.ERROR_MESSAGE); } else { _locationField.setText(file.getAbsolutePath()); } return; } // The selected file is a directory. setCurrentDirectory(file); displayCurrentDirectory(); repaint(); _scrollPane.getViewport().setViewPosition(new Point(0,0)); _scrollPane.repaint(); /* If the newly selected directory is a root directory, * don't allow the Parent button to be pressed. */ if (_isRoot(_currentDirectory) == false) _parentButton.setEnabled(true); } /** Implements the FileChooserListener interface. */ public void fileChanged(FileChooserEvent e) { displayCurrentDirectory(); repaint(); } private void _doNewDirectoryAction() { JFileChooser.NewDirDialog dlg = new NewDirDialog(this, _currentDirectory); dlg.setLocation(getLocation().addOffset(2, 2)); dlg.show(); File newdir = dlg.getDirectory(); if (newdir != null) setCurrentDirectory(newdir); displayCurrentDirectory(); repaint(); } private void _doParentDirectoryAction() { if (_isRoot(_currentDirectory)) { // We are already in a root directory. Display the // filesystem roots in the listbox. The list of // root directories is system-dependent; on Windows it // would be A:, B:, C: etc. On Unix it would be "/". File[] roots = File.listRoots(); for (int i=0; i<roots.length; i++) { DefaultListModel listModel = (DefaultListModel) _dirList.getModel(); listModel.addElement(roots[i].getAbsolutePath()); } _location = ""; } else { File parent = _currentDirectory.getParentFile(); if (_isRoot(parent)) { _parentButton.setEnabled(false); _dirList.requestFocus(); } setCurrentDirectory(parent); displayCurrentDirectory(); repaint(); } } private void _doApproveAction() { File file = new File(_locationField.getText()); String errmsg = null; if (_fileSelectionMode == DIRECTORIES_ONLY && file.isDirectory() == false) { errmsg = "Entry is not a directory: "; } else if (_fileSelectionMode == FILES_ONLY && file.isDirectory()) { setCurrentDirectory(file); displayCurrentDirectory(); repaint(); return; } if (errmsg != null) { String[] msgs = { errmsg, _locationField.getText() }; JOptionPane.showMessageDialog( this, msgs, "Error", JOptionPane.ERROR_MESSAGE); return; } _cancelWasPressed = false; _location = _locationField.getText(); hide(); } private void _doCancelAction() { _cancelWasPressed = true; hide(); } /** Returns true if the specified file is a root directory. */ private boolean _isRoot(File dir_) { String dirname = dir_.getAbsolutePath(); File[] roots = File.listRoots(); for (int i=0; i<roots.length; i++) { if (roots[i].getAbsolutePath().equals(dirname)) return true; } return false; } /** * Causes the JFileChooser to scan its file list for the current * directory, using the currently selected file filter if applicable. * Note that this method does not cause the file chooser to be redrawn. */ private void displayCurrentDirectory() { /* Clear the list of Files in the current dir */ _dirList.clear(); DefaultListModel listModel = (DefaultListModel) _dirList.getModel(); /* Add all the current directory's children into the list. */ File[] files = _currentDirectory.listFiles(); /* Define and instantiate an anonymous class that implements * the Comparator interface. This will be used by the TreeSet * to keep the filenames in lexicographical order. */ Comparator<String> fileSorter = new Comparator<String>() { public int compare(String file1, String file2) { return file1.compareTo(file2); } }; TreeSet<String> dirs = new TreeSet<String>(fileSorter); int numEntries = 0; for (int i=0; i<files.length; i++) { if (files[i].isDirectory()) { dirs.add(files[i].getName() + "/"); } else if ((_fileSelectionMode != DIRECTORIES_ONLY) && (_fileFilter == null || _fileFilter.accept(files[i]))) { /* This is a regular file, and either there is no * file filter or the file is accepted by the * filter. */ dirs.add(files[i].getName()); } numEntries++; } /* Copy the filenames from the TreeSet to the JList widget */ Iterator<String> iter = dirs.iterator(); while (iter.hasNext()) { listModel.addElement(iter.next()); } _locationField.setText(_location); } private JScrollPane _scrollPane; protected JButton _cancelButton = new JButton("Cancel"); protected JButton _approveButton = new JButton("Open"); protected JButton _parentButton = new JButton("Parent Directory"); protected JButton _newButton = new JButton("New Directory"); private JTextField _locationField = new JTextField(35); } /*==================================================================== * This is a non-static inner class used by the JFileChooser * to implement a sorted list of directory names. The user can * find a directory quickly by entering the first few characters * of the directory name. */ private class DirList extends JList { DirList() { super(); setVisibleRowCount(10); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } /** Clears the list of files displayed by this JList. * Does not generate any ListSelectionEvents. (?) */ void clear() { // Clear the selection model without notifying any // ListSelectionListeners. int min = getSelectionModel().getMinSelectionIndex(); if (min != -1) { int max = getSelectionModel().getMaxSelectionIndex(); getSelectionModel().removeIndexInterval(min, max); } // Clear the contents of the data model. ((DefaultListModel) getModel()).clear(); _currentRow = 0; _matchbuf.setLength(0); } /** Overrides corresponding method in JList, and allows the * user to find a directory quickly by typing the first few letters * of the filename. */ public void processKeyEvent(KeyEvent evt_) { int key = evt_.getKeyCode(); ListModel listmodel = super.getModel(); if (listmodel.getSize() > 0 && (key == KeyEvent.VK_BACK_SPACE || (key > ' ' && key < 255))) { if (key == KeyEvent.VK_BACK_SPACE) { if (_matchbuf.length() > 0) // truncate _matchbuf.setLength(_matchbuf.length() - 1); else Toolkit.getDefaultToolkit().beep(); } else _matchbuf.append((char) key); /* Scan through the items in the list until we get * to an item that is lexicographically greater than the * pattern we have typed. */ String matchstring = _matchbuf.toString(); int i; for (i=0; i<listmodel.getSize(); i++) { if (matchstring.compareTo(listmodel.getElementAt(i).toString()) <= 0) { break; } } if (i == listmodel.getSize()) i--; // the loop completed. String item = (String) listmodel.getElementAt(i); if ( ! item.startsWith(matchstring)) Toolkit.getDefaultToolkit().beep(); _currentRow = i; super.ensureIndexIsVisible(i); } super.processKeyEvent(evt_); } // INSTANCE VARIABLE private StringBuffer _matchbuf = new StringBuffer(); } /*==================================================================== * This is a non-static inner class used by the JFileChooser * to get the name of the new directory to create. */ private class NewDirDialog extends JDialog implements ActionListener { NewDirDialog(Dialog owner_, File parent_) { super(owner_); setTitle("Enter the new directory name"); _parentFile = parent_; setSize(60, 10); JPanel midpan = new JPanel(); midpan.setBorder(new EmptyBorder(2,2,2,2)); midpan.add(new JLabel("Directory name:")); _dirnameField = new JTextField(35); _dirnameField.setActionCommand("dirname"); _dirnameField.addActionListener(this); midpan.add(_dirnameField); add(midpan, BorderLayout.CENTER); _okButton = new JButton("OK"); _okButton.addActionListener(this); _cancelButton = new JButton("Cancel"); _cancelButton.addActionListener(this); JPanel southpan = new JPanel(); southpan.setLayout(new FlowLayout(FlowLayout.RIGHT, 1, 1)); southpan.add(_okButton); southpan.add(_cancelButton); add(southpan, BorderLayout.SOUTH); pack(); } public void actionPerformed(ActionEvent e_) { String cmd = e_.getActionCommand(); if (cmd.equals("OK") || cmd.equals("dirname")) { if (_parentFile.canWrite() == false) { String[] msgs = {"Permission denied"}; JOptionPane.showMessageDialog( this, msgs, "Error", JOptionPane.ERROR_MESSAGE); return; } File newdir = new File(_parentFile, _dirnameField.getText()); boolean ok = newdir.mkdir(); if (ok == false) { String[] msgs = {"Invalid directory"}; JOptionPane.showMessageDialog( this, msgs, "Error", JOptionPane.ERROR_MESSAGE); } else { _directory = newdir; hide(); } } else if (cmd.equals("Cancel")) { _directory = null; hide(); } } File getDirectory() { return _directory; } private File _parentFile; private JButton _okButton; private JButton _cancelButton; private JTextField _dirnameField; private File _directory = null; } // end of inner class NewDirDialog private interface FileChooserListener extends EventListener { public void fileChanged(FileChooserEvent e); } private class FileChooserEvent extends java.util.EventObject { private static final long serialVersionUID = 1L; public FileChooserEvent(Object source_) { super(source_); } } }