1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing;
26
27import javax.swing.event.*;
28import javax.swing.filechooser.*;
29import javax.swing.filechooser.FileFilter;
30import javax.swing.plaf.FileChooserUI;
31
32import javax.accessibility.*;
33
34import java.io.*;
35
36import java.util.Vector;
37import java.awt.AWTEvent;
38import java.awt.Component;
39import java.awt.Container;
40import java.awt.BorderLayout;
41import java.awt.Window;
42import java.awt.Dialog;
43import java.awt.Frame;
44import java.awt.GraphicsEnvironment;
45import java.awt.HeadlessException;
46import java.awt.EventQueue;
47import java.awt.Toolkit;
48import java.awt.event.*;
49import java.beans.JavaBean;
50import java.beans.BeanProperty;
51import java.beans.PropertyChangeListener;
52import java.beans.PropertyChangeEvent;
53import java.lang.ref.WeakReference;
54
55/**
56 * <code>JFileChooser</code> provides a simple mechanism for the user to
57 * choose a file.
58 * For information about using <code>JFileChooser</code>, see
59 * <a
60 href="http://docs.oracle.com/javase/tutorial/uiswing/components/filechooser.html">How to Use File Choosers</a>,
61 * a section in <em>The Java Tutorial</em>.
62 *
63 * <p>
64 *
65 * The following code pops up a file chooser for the user's home directory that
66 * sees only .jpg and .gif images:
67 * <pre>
68 *    JFileChooser chooser = new JFileChooser();
69 *    FileNameExtensionFilter filter = new FileNameExtensionFilter(
70 *        "JPG &amp; GIF Images", "jpg", "gif");
71 *    chooser.setFileFilter(filter);
72 *    int returnVal = chooser.showOpenDialog(parent);
73 *    if(returnVal == JFileChooser.APPROVE_OPTION) {
74 *       System.out.println("You chose to open this file: " +
75 *            chooser.getSelectedFile().getName());
76 *    }
77 * </pre>
78 * <p>
79 * <strong>Warning:</strong> Swing is not thread safe. For more
80 * information see <a
81 * href="package-summary.html#threading">Swing's Threading
82 * Policy</a>.
83 *
84 * @author Jeff Dinkins
85 * @since 1.2
86 */
87@JavaBean(defaultProperty = "UI", description = "A component which allows for the interactive selection of a file.")
88@SwingContainer(false)
89@SuppressWarnings("serial") // Superclass is not serializable across versions
90public class JFileChooser extends JComponent implements Accessible {
91
92    /**
93     * @see #getUIClassID
94     * @see #readObject
95     */
96    private static final String uiClassID = "FileChooserUI";
97
98    // ************************
99    // ***** Dialog Types *****
100    // ************************
101
102    /**
103     * Type value indicating that the <code>JFileChooser</code> supports an
104     * "Open" file operation.
105     */
106    public static final int OPEN_DIALOG = 0;
107
108    /**
109     * Type value indicating that the <code>JFileChooser</code> supports a
110     * "Save" file operation.
111     */
112    public static final int SAVE_DIALOG = 1;
113
114    /**
115     * Type value indicating that the <code>JFileChooser</code> supports a
116     * developer-specified file operation.
117     */
118    public static final int CUSTOM_DIALOG = 2;
119
120
121    // ********************************
122    // ***** Dialog Return Values *****
123    // ********************************
124
125    /**
126     * Return value if cancel is chosen.
127     */
128    public static final int CANCEL_OPTION = 1;
129
130    /**
131     * Return value if approve (yes, ok) is chosen.
132     */
133    public static final int APPROVE_OPTION = 0;
134
135    /**
136     * Return value if an error occurred.
137     */
138    public static final int ERROR_OPTION = -1;
139
140
141    // **********************************
142    // ***** JFileChooser properties *****
143    // **********************************
144
145
146    /** Instruction to display only files. */
147    public static final int FILES_ONLY = 0;
148
149    /** Instruction to display only directories. */
150    public static final int DIRECTORIES_ONLY = 1;
151
152    /** Instruction to display both files and directories. */
153    public static final int FILES_AND_DIRECTORIES = 2;
154
155    /** Instruction to cancel the current selection. */
156    public static final String CANCEL_SELECTION = "CancelSelection";
157
158    /**
159     * Instruction to approve the current selection
160     * (same as pressing yes or ok).
161     */
162    public static final String APPROVE_SELECTION = "ApproveSelection";
163
164    /** Identifies change in the text on the approve (yes, ok) button. */
165    public static final String APPROVE_BUTTON_TEXT_CHANGED_PROPERTY = "ApproveButtonTextChangedProperty";
166
167    /**
168     * Identifies change in the tooltip text for the approve (yes, ok)
169     * button.
170     */
171    public static final String APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY = "ApproveButtonToolTipTextChangedProperty";
172
173    /** Identifies change in the mnemonic for the approve (yes, ok) button. */
174    public static final String APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY = "ApproveButtonMnemonicChangedProperty";
175
176    /** Instruction to display the control buttons. */
177    public static final String CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY = "ControlButtonsAreShownChangedProperty";
178
179    /** Identifies user's directory change. */
180    public static final String DIRECTORY_CHANGED_PROPERTY = "directoryChanged";
181
182    /** Identifies change in user's single-file selection. */
183    public static final String SELECTED_FILE_CHANGED_PROPERTY = "SelectedFileChangedProperty";
184
185    /** Identifies change in user's multiple-file selection. */
186    public static final String SELECTED_FILES_CHANGED_PROPERTY = "SelectedFilesChangedProperty";
187
188    /** Enables multiple-file selections. */
189    public static final String MULTI_SELECTION_ENABLED_CHANGED_PROPERTY = "MultiSelectionEnabledChangedProperty";
190
191    /**
192     * Says that a different object is being used to find available drives
193     * on the system.
194     */
195    public static final String FILE_SYSTEM_VIEW_CHANGED_PROPERTY = "FileSystemViewChanged";
196
197    /**
198     * Says that a different object is being used to retrieve file
199     * information.
200     */
201    public static final String FILE_VIEW_CHANGED_PROPERTY = "fileViewChanged";
202
203    /** Identifies a change in the display-hidden-files property. */
204    public static final String FILE_HIDING_CHANGED_PROPERTY = "FileHidingChanged";
205
206    /** User changed the kind of files to display. */
207    public static final String FILE_FILTER_CHANGED_PROPERTY = "fileFilterChanged";
208
209    /**
210     * Identifies a change in the kind of selection (single,
211     * multiple, etc.).
212     */
213    public static final String FILE_SELECTION_MODE_CHANGED_PROPERTY = "fileSelectionChanged";
214
215    /**
216     * Says that a different accessory component is in use
217     * (for example, to preview files).
218     */
219    public static final String ACCESSORY_CHANGED_PROPERTY = "AccessoryChangedProperty";
220
221    /**
222     * Identifies whether a the AcceptAllFileFilter is used or not.
223     */
224    public static final String ACCEPT_ALL_FILE_FILTER_USED_CHANGED_PROPERTY = "acceptAllFileFilterUsedChanged";
225
226    /** Identifies a change in the dialog title. */
227    public static final String DIALOG_TITLE_CHANGED_PROPERTY = "DialogTitleChangedProperty";
228
229    /**
230     * Identifies a change in the type of files displayed (files only,
231     * directories only, or both files and directories).
232     */
233    public static final String DIALOG_TYPE_CHANGED_PROPERTY = "DialogTypeChangedProperty";
234
235    /**
236     * Identifies a change in the list of predefined file filters
237     * the user can choose from.
238     */
239    public static final String CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY = "ChoosableFileFilterChangedProperty";
240
241    // ******************************
242    // ***** instance variables *****
243    // ******************************
244
245    private String dialogTitle = null;
246    private String approveButtonText = null;
247    private String approveButtonToolTipText = null;
248    private int approveButtonMnemonic = 0;
249
250    private Vector<FileFilter> filters = new Vector<FileFilter>(5);
251    private JDialog dialog = null;
252    private int dialogType = OPEN_DIALOG;
253    private int returnValue = ERROR_OPTION;
254    private JComponent accessory = null;
255
256    private FileView fileView = null;
257
258    private boolean controlsShown = true;
259
260    private boolean useFileHiding = true;
261    private static final String SHOW_HIDDEN_PROP = "awt.file.showHiddenFiles";
262
263    // Listens to changes in the native setting for showing hidden files.
264    // The Listener is removed and the native setting is ignored if
265    // setFileHidingEnabled() is ever called.
266    private transient PropertyChangeListener showFilesListener = null;
267
268    private int fileSelectionMode = FILES_ONLY;
269
270    private boolean multiSelectionEnabled = false;
271
272    private boolean useAcceptAllFileFilter = true;
273
274    private boolean dragEnabled = false;
275
276    private FileFilter fileFilter = null;
277
278    private FileSystemView fileSystemView = null;
279
280    private File currentDirectory = null;
281    private File selectedFile = null;
282    private File[] selectedFiles;
283
284    // *************************************
285    // ***** JFileChooser Constructors *****
286    // *************************************
287
288    /**
289     * Constructs a <code>JFileChooser</code> pointing to the user's
290     * default directory. This default depends on the operating system.
291     * It is typically the "My Documents" folder on Windows, and the
292     * user's home directory on Unix.
293     */
294    public JFileChooser() {
295        this((File) null, (FileSystemView) null);
296    }
297
298    /**
299     * Constructs a <code>JFileChooser</code> using the given path.
300     * Passing in a <code>null</code>
301     * string causes the file chooser to point to the user's default directory.
302     * This default depends on the operating system. It is
303     * typically the "My Documents" folder on Windows, and the user's
304     * home directory on Unix.
305     *
306     * @param currentDirectoryPath  a <code>String</code> giving the path
307     *                          to a file or directory
308     */
309    public JFileChooser(String currentDirectoryPath) {
310        this(currentDirectoryPath, (FileSystemView) null);
311    }
312
313    /**
314     * Constructs a <code>JFileChooser</code> using the given <code>File</code>
315     * as the path. Passing in a <code>null</code> file
316     * causes the file chooser to point to the user's default directory.
317     * This default depends on the operating system. It is
318     * typically the "My Documents" folder on Windows, and the user's
319     * home directory on Unix.
320     *
321     * @param currentDirectory  a <code>File</code> object specifying
322     *                          the path to a file or directory
323     */
324    public JFileChooser(File currentDirectory) {
325        this(currentDirectory, (FileSystemView) null);
326    }
327
328    /**
329     * Constructs a <code>JFileChooser</code> using the given
330     * <code>FileSystemView</code>.
331     *
332     * @param fsv a {@code FileSystemView}
333     */
334    public JFileChooser(FileSystemView fsv) {
335        this((File) null, fsv);
336    }
337
338
339    /**
340     * Constructs a <code>JFileChooser</code> using the given current directory
341     * and <code>FileSystemView</code>.
342     *
343     * @param currentDirectory a {@code File} object specifying the path to a
344     *                         file or directory
345     * @param fsv a {@code FileSystemView}
346     */
347    public JFileChooser(File currentDirectory, FileSystemView fsv) {
348        setup(fsv);
349        setCurrentDirectory(currentDirectory);
350    }
351
352    /**
353     * Constructs a <code>JFileChooser</code> using the given current directory
354     * path and <code>FileSystemView</code>.
355     *
356     * @param currentDirectoryPath a {@code String} specifying the path to a file
357     *                             or directory
358     * @param fsv a {@code FileSystemView}
359     */
360    public JFileChooser(String currentDirectoryPath, FileSystemView fsv) {
361        setup(fsv);
362        if(currentDirectoryPath == null) {
363            setCurrentDirectory(null);
364        } else {
365            setCurrentDirectory(fileSystemView.createFileObject(currentDirectoryPath));
366        }
367    }
368
369    /**
370     * Performs common constructor initialization and setup.
371     *
372     * @param view the {@code FileSystemView} used for setup
373     */
374    protected void setup(FileSystemView view) {
375        installShowFilesListener();
376        installHierarchyListener();
377
378        if(view == null) {
379            view = FileSystemView.getFileSystemView();
380        }
381        setFileSystemView(view);
382        updateUI();
383        if(isAcceptAllFileFilterUsed()) {
384            setFileFilter(getAcceptAllFileFilter());
385        }
386        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
387    }
388
389    private void installHierarchyListener() {
390        addHierarchyListener(new FCHierarchyListener());
391    }
392
393    private void installShowFilesListener() {
394        // Track native setting for showing hidden files
395        Toolkit tk = Toolkit.getDefaultToolkit();
396        Object showHiddenProperty = tk.getDesktopProperty(SHOW_HIDDEN_PROP);
397        if (showHiddenProperty instanceof Boolean) {
398            useFileHiding = !((Boolean)showHiddenProperty).booleanValue();
399            showFilesListener = new WeakPCL(this);
400            tk.addPropertyChangeListener(SHOW_HIDDEN_PROP, showFilesListener);
401        }
402    }
403
404    /**
405     * Sets the <code>dragEnabled</code> property,
406     * which must be <code>true</code> to enable
407     * automatic drag handling (the first part of drag and drop)
408     * on this component.
409     * The <code>transferHandler</code> property needs to be set
410     * to a non-<code>null</code> value for the drag to do
411     * anything.  The default value of the <code>dragEnabled</code>
412     * property
413     * is <code>false</code>.
414     *
415     * <p>
416     *
417     * When automatic drag handling is enabled,
418     * most look and feels begin a drag-and-drop operation
419     * whenever the user presses the mouse button over an item
420     * and then moves the mouse a few pixels.
421     * Setting this property to <code>true</code>
422     * can therefore have a subtle effect on
423     * how selections behave.
424     *
425     * <p>
426     *
427     * Some look and feels might not support automatic drag and drop;
428     * they will ignore this property.  You can work around such
429     * look and feels by modifying the component
430     * to directly call the <code>exportAsDrag</code> method of a
431     * <code>TransferHandler</code>.
432     *
433     * @param b the value to set the <code>dragEnabled</code> property to
434     * @exception HeadlessException if
435     *            <code>b</code> is <code>true</code> and
436     *            <code>GraphicsEnvironment.isHeadless()</code>
437     *            returns <code>true</code>
438     * @see java.awt.GraphicsEnvironment#isHeadless
439     * @see #getDragEnabled
440     * @see #setTransferHandler
441     * @see TransferHandler
442     * @since 1.4
443     */
444    @BeanProperty(bound = false, description
445            = "determines whether automatic drag handling is enabled")
446    public void setDragEnabled(boolean b) {
447        checkDragEnabled(b);
448        dragEnabled = b;
449    }
450
451    private static void checkDragEnabled(boolean b) {
452        if (b && GraphicsEnvironment.isHeadless()) {
453            throw new HeadlessException();
454        }
455    }
456
457    /**
458     * Gets the value of the <code>dragEnabled</code> property.
459     *
460     * @return  the value of the <code>dragEnabled</code> property
461     * @see #setDragEnabled
462     * @since 1.4
463     */
464    public boolean getDragEnabled() {
465        return dragEnabled;
466    }
467
468    // *****************************
469    // ****** File Operations ******
470    // *****************************
471
472    /**
473     * Returns the selected file. This can be set either by the
474     * programmer via <code>setSelectedFile</code> or by a user action, such as
475     * either typing the filename into the UI or selecting the
476     * file from a list in the UI.
477     *
478     * @see #setSelectedFile
479     * @return the selected file
480     */
481    public File getSelectedFile() {
482        return selectedFile;
483    }
484
485    /**
486     * Sets the selected file. If the file's parent directory is
487     * not the current directory, changes the current directory
488     * to be the file's parent directory.
489     *
490     * @see #getSelectedFile
491     *
492     * @param file the selected file
493     */
494    @BeanProperty(preferred = true)
495    public void setSelectedFile(File file) {
496        File oldValue = selectedFile;
497        selectedFile = file;
498        if(selectedFile != null) {
499            if (file.isAbsolute() && !getFileSystemView().isParent(getCurrentDirectory(), selectedFile)) {
500                setCurrentDirectory(selectedFile.getParentFile());
501            }
502            if (!isMultiSelectionEnabled() || selectedFiles == null || selectedFiles.length == 1) {
503                ensureFileIsVisible(selectedFile);
504            }
505        }
506        firePropertyChange(SELECTED_FILE_CHANGED_PROPERTY, oldValue, selectedFile);
507    }
508
509    /**
510     * Returns a list of selected files if the file chooser is
511     * set to allow multiple selection.
512     *
513     * @return an array of selected {@code File}s
514     */
515    public File[] getSelectedFiles() {
516        if(selectedFiles == null) {
517            return new File[0];
518        } else {
519            return selectedFiles.clone();
520        }
521    }
522
523    /**
524     * Sets the list of selected files if the file chooser is
525     * set to allow multiple selection.
526     *
527     * @param selectedFiles an array {@code File}s to be selected
528     */
529    @BeanProperty(description
530            = "The list of selected files if the chooser is in multiple selection mode.")
531    public void setSelectedFiles(File[] selectedFiles) {
532        File[] oldValue = this.selectedFiles;
533        if (selectedFiles == null || selectedFiles.length == 0) {
534            selectedFiles = null;
535            this.selectedFiles = null;
536            setSelectedFile(null);
537        } else {
538            this.selectedFiles = selectedFiles.clone();
539            setSelectedFile(this.selectedFiles[0]);
540        }
541        firePropertyChange(SELECTED_FILES_CHANGED_PROPERTY, oldValue, selectedFiles);
542    }
543
544    /**
545     * Returns the current directory.
546     *
547     * @return the current directory
548     * @see #setCurrentDirectory
549     */
550    public File getCurrentDirectory() {
551        return currentDirectory;
552    }
553
554    /**
555     * Sets the current directory. Passing in <code>null</code> sets the
556     * file chooser to point to the user's default directory.
557     * This default depends on the operating system. It is
558     * typically the "My Documents" folder on Windows, and the user's
559     * home directory on Unix.
560     *
561     * If the file passed in as <code>currentDirectory</code> is not a
562     * directory, the parent of the file will be used as the currentDirectory.
563     * If the parent is not traversable, then it will walk up the parent tree
564     * until it finds a traversable directory, or hits the root of the
565     * file system.
566     *
567     * @param dir the current directory to point to
568     * @see #getCurrentDirectory
569     */
570    @BeanProperty(preferred = true, description
571            = "The directory that the JFileChooser is showing files of.")
572    public void setCurrentDirectory(File dir) {
573        File oldValue = currentDirectory;
574
575        if (dir != null && !dir.exists()) {
576            dir = currentDirectory;
577        }
578        if (dir == null) {
579            dir = getFileSystemView().getDefaultDirectory();
580        }
581        if (currentDirectory != null) {
582            /* Verify the toString of object */
583            if (this.currentDirectory.equals(dir)) {
584                return;
585            }
586        }
587
588        File prev = null;
589        while (!isTraversable(dir) && prev != dir) {
590            prev = dir;
591            dir = getFileSystemView().getParentDirectory(dir);
592        }
593        currentDirectory = dir;
594
595        firePropertyChange(DIRECTORY_CHANGED_PROPERTY, oldValue, currentDirectory);
596    }
597
598    /**
599     * Changes the directory to be set to the parent of the
600     * current directory.
601     *
602     * @see #getCurrentDirectory
603     */
604    public void changeToParentDirectory() {
605        selectedFile = null;
606        File oldValue = getCurrentDirectory();
607        setCurrentDirectory(getFileSystemView().getParentDirectory(oldValue));
608    }
609
610    /**
611     * Tells the UI to rescan its files list from the current directory.
612     */
613    public void rescanCurrentDirectory() {
614        getUI().rescanCurrentDirectory(this);
615    }
616
617    /**
618     * Makes sure that the specified file is viewable, and
619     * not hidden.
620     *
621     * @param f  a File object
622     */
623    public void ensureFileIsVisible(File f) {
624        getUI().ensureFileIsVisible(this, f);
625    }
626
627    // **************************************
628    // ***** JFileChooser Dialog methods *****
629    // **************************************
630
631    /**
632     * Pops up an "Open File" file chooser dialog. Note that the
633     * text that appears in the approve button is determined by
634     * the L&amp;F.
635     *
636     * @param    parent  the parent component of the dialog,
637     *                  can be <code>null</code>;
638     *                  see <code>showDialog</code> for details
639     * @return   the return state of the file chooser on popdown:
640     * <ul>
641     * <li>JFileChooser.CANCEL_OPTION
642     * <li>JFileChooser.APPROVE_OPTION
643     * <li>JFileChooser.ERROR_OPTION if an error occurs or the
644     *                  dialog is dismissed
645     * </ul>
646     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
647     * returns true.
648     * @see java.awt.GraphicsEnvironment#isHeadless
649     * @see #showDialog
650     */
651    public int showOpenDialog(Component parent) throws HeadlessException {
652        setDialogType(OPEN_DIALOG);
653        return showDialog(parent, null);
654    }
655
656    /**
657     * Pops up a "Save File" file chooser dialog. Note that the
658     * text that appears in the approve button is determined by
659     * the L&amp;F.
660     *
661     * @param    parent  the parent component of the dialog,
662     *                  can be <code>null</code>;
663     *                  see <code>showDialog</code> for details
664     * @return   the return state of the file chooser on popdown:
665     * <ul>
666     * <li>JFileChooser.CANCEL_OPTION
667     * <li>JFileChooser.APPROVE_OPTION
668     * <li>JFileChooser.ERROR_OPTION if an error occurs or the
669     *                  dialog is dismissed
670     * </ul>
671     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
672     * returns true.
673     * @see java.awt.GraphicsEnvironment#isHeadless
674     * @see #showDialog
675     */
676    public int showSaveDialog(Component parent) throws HeadlessException {
677        setDialogType(SAVE_DIALOG);
678        return showDialog(parent, null);
679    }
680
681    /**
682     * Pops a custom file chooser dialog with a custom approve button.
683     * For example, the following code
684     * pops up a file chooser with a "Run Application" button
685     * (instead of the normal "Save" or "Open" button):
686     * <pre>
687     * filechooser.showDialog(parentFrame, "Run Application");
688     * </pre>
689     *
690     * Alternatively, the following code does the same thing:
691     * <pre>
692     *    JFileChooser chooser = new JFileChooser(null);
693     *    chooser.setApproveButtonText("Run Application");
694     *    chooser.showDialog(parentFrame, null);
695     * </pre>
696     *
697     * <!--PENDING(jeff) - the following method should be added to the api:
698     *      showDialog(Component parent);-->
699     * <!--PENDING(kwalrath) - should specify modality and what
700     *      "depends" means.-->
701     *
702     * <p>
703     *
704     * The <code>parent</code> argument determines two things:
705     * the frame on which the open dialog depends and
706     * the component whose position the look and feel
707     * should consider when placing the dialog.  If the parent
708     * is a <code>Frame</code> object (such as a <code>JFrame</code>)
709     * then the dialog depends on the frame and
710     * the look and feel positions the dialog
711     * relative to the frame (for example, centered over the frame).
712     * If the parent is a component, then the dialog
713     * depends on the frame containing the component,
714     * and is positioned relative to the component
715     * (for example, centered over the component).
716     * If the parent is <code>null</code>, then the dialog depends on
717     * no visible window, and it's placed in a
718     * look-and-feel-dependent position
719     * such as the center of the screen.
720     *
721     * @param   parent  the parent component of the dialog;
722     *                  can be <code>null</code>
723     * @param   approveButtonText the text of the <code>ApproveButton</code>
724     * @return  the return state of the file chooser on popdown:
725     * <ul>
726     * <li>JFileChooser.CANCEL_OPTION
727     * <li>JFileChooser.APPROVE_OPTION
728     * <li>JFileChooser.ERROR_OPTION if an error occurs or the
729     *                  dialog is dismissed
730     * </ul>
731     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
732     * returns true.
733     * @see java.awt.GraphicsEnvironment#isHeadless
734     */
735    @SuppressWarnings("deprecation")
736    public int showDialog(Component parent, String approveButtonText)
737        throws HeadlessException {
738        if (dialog != null) {
739            // Prevent to show second instance of dialog if the previous one still exists
740            return JFileChooser.ERROR_OPTION;
741        }
742
743        if(approveButtonText != null) {
744            setApproveButtonText(approveButtonText);
745            setDialogType(CUSTOM_DIALOG);
746        }
747        dialog = createDialog(parent);
748        dialog.addWindowListener(new WindowAdapter() {
749            public void windowClosing(WindowEvent e) {
750                returnValue = CANCEL_OPTION;
751            }
752        });
753        returnValue = ERROR_OPTION;
754        rescanCurrentDirectory();
755
756        dialog.show();
757        firePropertyChange("JFileChooserDialogIsClosingProperty", dialog, null);
758
759        // Remove all components from dialog. The MetalFileChooserUI.installUI() method (and other LAFs)
760        // registers AWT listener for dialogs and produces memory leaks. It happens when
761        // installUI invoked after the showDialog method.
762        dialog.getContentPane().removeAll();
763        dialog.dispose();
764        dialog = null;
765        return returnValue;
766    }
767
768    /**
769     * Creates and returns a new <code>JDialog</code> wrapping
770     * <code>this</code> centered on the <code>parent</code>
771     * in the <code>parent</code>'s frame.
772     * This method can be overriden to further manipulate the dialog,
773     * to disable resizing, set the location, etc. Example:
774     * <pre>
775     *     class MyFileChooser extends JFileChooser {
776     *         protected JDialog createDialog(Component parent) throws HeadlessException {
777     *             JDialog dialog = super.createDialog(parent);
778     *             dialog.setLocation(300, 200);
779     *             dialog.setResizable(false);
780     *             return dialog;
781     *         }
782     *     }
783     * </pre>
784     *
785     * @param   parent  the parent component of the dialog;
786     *                  can be <code>null</code>
787     * @return a new <code>JDialog</code> containing this instance
788     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
789     * returns true.
790     * @see java.awt.GraphicsEnvironment#isHeadless
791     * @since 1.4
792     */
793    protected JDialog createDialog(Component parent) throws HeadlessException {
794        FileChooserUI ui = getUI();
795        String title = ui.getDialogTitle(this);
796        putClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY,
797                          title);
798
799        JDialog dialog;
800        Window window = JOptionPane.getWindowForComponent(parent);
801        if (window instanceof Frame) {
802            dialog = new JDialog((Frame)window, title, true);
803        } else {
804            dialog = new JDialog((Dialog)window, title, true);
805        }
806        dialog.setComponentOrientation(this.getComponentOrientation());
807
808        Container contentPane = dialog.getContentPane();
809        contentPane.setLayout(new BorderLayout());
810        contentPane.add(this, BorderLayout.CENTER);
811
812        if (JDialog.isDefaultLookAndFeelDecorated()) {
813            boolean supportsWindowDecorations =
814            UIManager.getLookAndFeel().getSupportsWindowDecorations();
815            if (supportsWindowDecorations) {
816                dialog.getRootPane().setWindowDecorationStyle(JRootPane.FILE_CHOOSER_DIALOG);
817            }
818        }
819        dialog.pack();
820        dialog.setLocationRelativeTo(parent);
821
822        return dialog;
823    }
824
825    // **************************
826    // ***** Dialog Options *****
827    // **************************
828
829    /**
830     * Returns the value of the <code>controlButtonsAreShown</code>
831     * property.
832     *
833     * @return   the value of the <code>controlButtonsAreShown</code>
834     *     property
835     *
836     * @see #setControlButtonsAreShown
837     * @since 1.3
838     */
839    public boolean getControlButtonsAreShown() {
840        return controlsShown;
841    }
842
843
844    /**
845     * Sets the property
846     * that indicates whether the <i>approve</i> and <i>cancel</i>
847     * buttons are shown in the file chooser.  This property
848     * is <code>true</code> by default.  Look and feels
849     * that always show these buttons will ignore the value
850     * of this property.
851     * This method fires a property-changed event,
852     * using the string value of
853     * <code>CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY</code>
854     * as the name of the property.
855     *
856     * @param b <code>false</code> if control buttons should not be
857     *    shown; otherwise, <code>true</code>
858     *
859     * @see #getControlButtonsAreShown
860     * @see #CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY
861     * @since 1.3
862     */
863    @BeanProperty(preferred = true, description
864            = "Sets whether the approve &amp; cancel buttons are shown.")
865    public void setControlButtonsAreShown(boolean b) {
866        if(controlsShown == b) {
867            return;
868        }
869        boolean oldValue = controlsShown;
870        controlsShown = b;
871        firePropertyChange(CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY, oldValue, controlsShown);
872    }
873
874    /**
875     * Returns the type of this dialog.  The default is
876     * <code>JFileChooser.OPEN_DIALOG</code>.
877     *
878     * @return   the type of dialog to be displayed:
879     * <ul>
880     * <li>JFileChooser.OPEN_DIALOG
881     * <li>JFileChooser.SAVE_DIALOG
882     * <li>JFileChooser.CUSTOM_DIALOG
883     * </ul>
884     *
885     * @see #setDialogType
886     */
887    public int getDialogType() {
888        return dialogType;
889    }
890
891    /**
892     * Sets the type of this dialog. Use <code>OPEN_DIALOG</code> when you
893     * want to bring up a file chooser that the user can use to open a file.
894     * Likewise, use <code>SAVE_DIALOG</code> for letting the user choose
895     * a file for saving.
896     * Use <code>CUSTOM_DIALOG</code> when you want to use the file
897     * chooser in a context other than "Open" or "Save".
898     * For instance, you might want to bring up a file chooser that allows
899     * the user to choose a file to execute. Note that you normally would not
900     * need to set the <code>JFileChooser</code> to use
901     * <code>CUSTOM_DIALOG</code>
902     * since a call to <code>setApproveButtonText</code> does this for you.
903     * The default dialog type is <code>JFileChooser.OPEN_DIALOG</code>.
904     *
905     * @param dialogType the type of dialog to be displayed:
906     * <ul>
907     * <li>JFileChooser.OPEN_DIALOG
908     * <li>JFileChooser.SAVE_DIALOG
909     * <li>JFileChooser.CUSTOM_DIALOG
910     * </ul>
911     *
912     * @exception IllegalArgumentException if <code>dialogType</code> is
913     *                          not legal
914     *
915     * @see #getDialogType
916     * @see #setApproveButtonText
917     */
918    // PENDING(jeff) - fire button text change property
919    @BeanProperty(preferred = true, enumerationValues = {
920            "JFileChooser.OPEN_DIALOG",
921            "JFileChooser.SAVE_DIALOG",
922            "JFileChooser.CUSTOM_DIALOG"}, description
923            = "The type (open, save, custom) of the JFileChooser.")
924    public void setDialogType(int dialogType) {
925        if(this.dialogType == dialogType) {
926            return;
927        }
928        checkDialogType(dialogType);
929        int oldValue = this.dialogType;
930        this.dialogType = dialogType;
931        if(dialogType == OPEN_DIALOG || dialogType == SAVE_DIALOG) {
932            setApproveButtonText(null);
933        }
934        firePropertyChange(DIALOG_TYPE_CHANGED_PROPERTY, oldValue, dialogType);
935    }
936
937    private static void checkDialogType(int dialogType) {
938        if (!(dialogType == OPEN_DIALOG || dialogType == SAVE_DIALOG
939                || dialogType == CUSTOM_DIALOG)) {
940            throw new IllegalArgumentException(
941                    "Incorrect Dialog Type: " + dialogType);
942        }
943    }
944
945    /**
946     * Sets the string that goes in the <code>JFileChooser</code> window's
947     * title bar.
948     *
949     * @param dialogTitle the new <code>String</code> for the title bar
950     *
951     * @see #getDialogTitle
952     *
953     */
954    @BeanProperty(preferred = true, description
955            = "The title of the JFileChooser dialog window.")
956    public void setDialogTitle(String dialogTitle) {
957        String oldValue = this.dialogTitle;
958        this.dialogTitle = dialogTitle;
959        if(dialog != null) {
960            dialog.setTitle(dialogTitle);
961        }
962        firePropertyChange(DIALOG_TITLE_CHANGED_PROPERTY, oldValue, dialogTitle);
963    }
964
965    /**
966     * Gets the string that goes in the <code>JFileChooser</code>'s titlebar.
967     *
968     * @return the string from the {@code JFileChooser} window's title bar
969     * @see #setDialogTitle
970     */
971    public String getDialogTitle() {
972        return dialogTitle;
973    }
974
975    // ************************************
976    // ***** JFileChooser View Options *****
977    // ************************************
978
979
980
981    /**
982     * Sets the tooltip text used in the <code>ApproveButton</code>.
983     * If <code>null</code>, the UI object will determine the button's text.
984     *
985     * @param toolTipText the tooltip text for the approve button
986     * @see #setApproveButtonText
987     * @see #setDialogType
988     * @see #showDialog
989     */
990    @BeanProperty(preferred = true, description
991            = "The tooltip text for the ApproveButton.")
992    public void setApproveButtonToolTipText(String toolTipText) {
993        if(approveButtonToolTipText == toolTipText) {
994            return;
995        }
996        String oldValue = approveButtonToolTipText;
997        approveButtonToolTipText = toolTipText;
998        firePropertyChange(APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY, oldValue, approveButtonToolTipText);
999    }
1000
1001
1002    /**
1003     * Returns the tooltip text used in the <code>ApproveButton</code>.
1004     * If <code>null</code>, the UI object will determine the button's text.
1005     *
1006     * @return the tooltip text used for the approve button
1007     *
1008     * @see #setApproveButtonText
1009     * @see #setDialogType
1010     * @see #showDialog
1011     */
1012    public String getApproveButtonToolTipText() {
1013        return approveButtonToolTipText;
1014    }
1015
1016    /**
1017     * Returns the approve button's mnemonic.
1018     * @return an integer value for the mnemonic key
1019     *
1020     * @see #setApproveButtonMnemonic
1021     */
1022    public int getApproveButtonMnemonic() {
1023        return approveButtonMnemonic;
1024    }
1025
1026    /**
1027     * Sets the approve button's mnemonic using a numeric keycode.
1028     *
1029     * @param mnemonic  an integer value for the mnemonic key
1030     *
1031     * @see #getApproveButtonMnemonic
1032     */
1033    @BeanProperty(preferred = true, description
1034            = "The mnemonic key accelerator for the ApproveButton.")
1035    public void setApproveButtonMnemonic(int mnemonic) {
1036        if(approveButtonMnemonic == mnemonic) {
1037           return;
1038        }
1039        int oldValue = approveButtonMnemonic;
1040        approveButtonMnemonic = mnemonic;
1041        firePropertyChange(APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY, oldValue, approveButtonMnemonic);
1042    }
1043
1044    /**
1045     * Sets the approve button's mnemonic using a character.
1046     * @param mnemonic  a character value for the mnemonic key
1047     *
1048     * @see #getApproveButtonMnemonic
1049     */
1050    public void setApproveButtonMnemonic(char mnemonic) {
1051        int vk = (int) mnemonic;
1052        if(vk >= 'a' && vk <='z') {
1053            vk -= ('a' - 'A');
1054        }
1055        setApproveButtonMnemonic(vk);
1056    }
1057
1058
1059    /**
1060     * Sets the text used in the <code>ApproveButton</code> in the
1061     * <code>FileChooserUI</code>.
1062     *
1063     * @param approveButtonText the text used in the <code>ApproveButton</code>
1064     *
1065     * @see #getApproveButtonText
1066     * @see #setDialogType
1067     * @see #showDialog
1068     */
1069    // PENDING(jeff) - have ui set this on dialog type change
1070    @BeanProperty(preferred = true, description
1071            = "The text that goes in the ApproveButton.")
1072    public void setApproveButtonText(String approveButtonText) {
1073        if(this.approveButtonText == approveButtonText) {
1074            return;
1075        }
1076        String oldValue = this.approveButtonText;
1077        this.approveButtonText = approveButtonText;
1078        firePropertyChange(APPROVE_BUTTON_TEXT_CHANGED_PROPERTY, oldValue, approveButtonText);
1079    }
1080
1081    /**
1082     * Returns the text used in the <code>ApproveButton</code> in the
1083     * <code>FileChooserUI</code>.
1084     * If <code>null</code>, the UI object will determine the button's text.
1085     *
1086     * Typically, this would be "Open" or "Save".
1087     *
1088     * @return the text used in the <code>ApproveButton</code>
1089     *
1090     * @see #setApproveButtonText
1091     * @see #setDialogType
1092     * @see #showDialog
1093     */
1094    public String getApproveButtonText() {
1095        return approveButtonText;
1096    }
1097
1098    /**
1099     * Gets the list of user choosable file filters.
1100     *
1101     * @return a <code>FileFilter</code> array containing all the choosable
1102     *         file filters
1103     *
1104     * @see #addChoosableFileFilter
1105     * @see #removeChoosableFileFilter
1106     * @see #resetChoosableFileFilters
1107     */
1108    @BeanProperty(bound = false)
1109    public FileFilter[] getChoosableFileFilters() {
1110        FileFilter[] filterArray = new FileFilter[filters.size()];
1111        filters.copyInto(filterArray);
1112        return filterArray;
1113    }
1114
1115    /**
1116     * Adds a filter to the list of user choosable file filters.
1117     * For information on setting the file selection mode, see
1118     * {@link #setFileSelectionMode setFileSelectionMode}.
1119     *
1120     * @param filter the <code>FileFilter</code> to add to the choosable file
1121     *               filter list
1122     *
1123     * @see #getChoosableFileFilters
1124     * @see #removeChoosableFileFilter
1125     * @see #resetChoosableFileFilters
1126     * @see #setFileSelectionMode
1127     */
1128    @BeanProperty(preferred = true, description
1129            = "Adds a filter to the list of user choosable file filters.")
1130    public void addChoosableFileFilter(FileFilter filter) {
1131        if(filter != null && !filters.contains(filter)) {
1132            FileFilter[] oldValue = getChoosableFileFilters();
1133            filters.addElement(filter);
1134            firePropertyChange(CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY, oldValue, getChoosableFileFilters());
1135            if (fileFilter == null && filters.size() == 1) {
1136                setFileFilter(filter);
1137            }
1138        }
1139    }
1140
1141    /**
1142     * Removes a filter from the list of user choosable file filters. Returns
1143     * true if the file filter was removed.
1144     *
1145     * @param f the file filter to be removed
1146     * @return true if the file filter was removed, false otherwise
1147     * @see #addChoosableFileFilter
1148     * @see #getChoosableFileFilters
1149     * @see #resetChoosableFileFilters
1150     */
1151    public boolean removeChoosableFileFilter(FileFilter f) {
1152        int index = filters.indexOf(f);
1153        if (index >= 0) {
1154            if(getFileFilter() == f) {
1155                FileFilter aaff = getAcceptAllFileFilter();
1156                if (isAcceptAllFileFilterUsed() && (aaff != f)) {
1157                    // choose default filter if it is used
1158                    setFileFilter(aaff);
1159                }
1160                else if (index > 0) {
1161                    // choose the first filter, because it is not removed
1162                    setFileFilter(filters.get(0));
1163                }
1164                else if (filters.size() > 1) {
1165                    // choose the second filter, because the first one is removed
1166                    setFileFilter(filters.get(1));
1167                }
1168                else {
1169                    // no more filters
1170                    setFileFilter(null);
1171                }
1172            }
1173            FileFilter[] oldValue = getChoosableFileFilters();
1174            filters.removeElement(f);
1175            firePropertyChange(CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY, oldValue, getChoosableFileFilters());
1176            return true;
1177        } else {
1178            return false;
1179        }
1180    }
1181
1182    /**
1183     * Resets the choosable file filter list to its starting state. Normally,
1184     * this removes all added file filters while leaving the
1185     * <code>AcceptAll</code> file filter.
1186     *
1187     * @see #addChoosableFileFilter
1188     * @see #getChoosableFileFilters
1189     * @see #removeChoosableFileFilter
1190     */
1191    public void resetChoosableFileFilters() {
1192        FileFilter[] oldValue = getChoosableFileFilters();
1193        setFileFilter(null);
1194        filters.removeAllElements();
1195        if(isAcceptAllFileFilterUsed()) {
1196           addChoosableFileFilter(getAcceptAllFileFilter());
1197        }
1198        firePropertyChange(CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY, oldValue, getChoosableFileFilters());
1199    }
1200
1201    /**
1202     * Returns the <code>AcceptAll</code> file filter.
1203     * For example, on Microsoft Windows this would be All Files (*.*).
1204     *
1205     * @return the {@code AcceptAll} file filter
1206     */
1207    @BeanProperty(bound = false)
1208    public FileFilter getAcceptAllFileFilter() {
1209        FileFilter filter = null;
1210        if(getUI() != null) {
1211            filter = getUI().getAcceptAllFileFilter(this);
1212        }
1213        return filter;
1214    }
1215
1216   /**
1217    * Returns whether the <code>AcceptAll FileFilter</code> is used.
1218    * @return true if the <code>AcceptAll FileFilter</code> is used
1219    * @see #setAcceptAllFileFilterUsed
1220    * @since 1.3
1221    */
1222    public boolean isAcceptAllFileFilterUsed() {
1223        return useAcceptAllFileFilter;
1224    }
1225
1226   /**
1227    * Determines whether the <code>AcceptAll FileFilter</code> is used
1228    * as an available choice in the choosable filter list.
1229    * If false, the <code>AcceptAll</code> file filter is removed from
1230    * the list of available file filters.
1231    * If true, the <code>AcceptAll</code> file filter will become the
1232    * actively used file filter.
1233    *
1234    * @param b a {@code boolean} which determines whether the {@code AcceptAll}
1235    *          file filter is an available choice in the choosable filter list
1236    *
1237    * @see #isAcceptAllFileFilterUsed
1238    * @see #getAcceptAllFileFilter
1239    * @see #setFileFilter
1240    * @since 1.3
1241    */
1242    @BeanProperty(preferred = true, description
1243            = "Sets whether the AcceptAll FileFilter is used as an available choice in the choosable filter list.")
1244    public void setAcceptAllFileFilterUsed(boolean b) {
1245        boolean oldValue = useAcceptAllFileFilter;
1246        useAcceptAllFileFilter = b;
1247        if(!b) {
1248            removeChoosableFileFilter(getAcceptAllFileFilter());
1249        } else {
1250            removeChoosableFileFilter(getAcceptAllFileFilter());
1251            addChoosableFileFilter(getAcceptAllFileFilter());
1252        }
1253        firePropertyChange(ACCEPT_ALL_FILE_FILTER_USED_CHANGED_PROPERTY, oldValue, useAcceptAllFileFilter);
1254    }
1255
1256    /**
1257     * Returns the accessory component.
1258     *
1259     * @return this JFileChooser's accessory component, or null
1260     * @see #setAccessory
1261     */
1262    public JComponent getAccessory() {
1263        return accessory;
1264    }
1265
1266    /**
1267     * Sets the accessory component. An accessory is often used to show a
1268     * preview image of the selected file; however, it can be used for anything
1269     * that the programmer wishes, such as extra custom file chooser controls.
1270     *
1271     * <p>
1272     * Note: if there was a previous accessory, you should unregister
1273     * any listeners that the accessory might have registered with the
1274     * file chooser.
1275     *
1276     * @param newAccessory the accessory component to be set
1277     */
1278    @BeanProperty(preferred = true, description
1279            = "Sets the accessory component on the JFileChooser.")
1280    public void setAccessory(JComponent newAccessory) {
1281        JComponent oldValue = accessory;
1282        accessory = newAccessory;
1283        firePropertyChange(ACCESSORY_CHANGED_PROPERTY, oldValue, accessory);
1284    }
1285
1286    /**
1287     * Sets the <code>JFileChooser</code> to allow the user to just
1288     * select files, just select
1289     * directories, or select both files and directories.  The default is
1290     * <code>JFilesChooser.FILES_ONLY</code>.
1291     *
1292     * @param mode the type of files to be displayed:
1293     * <ul>
1294     * <li>JFileChooser.FILES_ONLY
1295     * <li>JFileChooser.DIRECTORIES_ONLY
1296     * <li>JFileChooser.FILES_AND_DIRECTORIES
1297     * </ul>
1298     *
1299     * @exception IllegalArgumentException  if <code>mode</code> is an
1300     *                          illegal file selection mode
1301     *
1302     * @see #getFileSelectionMode
1303     */
1304    @BeanProperty(preferred = true, enumerationValues = {
1305            "JFileChooser.FILES_ONLY",
1306            "JFileChooser.DIRECTORIES_ONLY",
1307            "JFileChooser.FILES_AND_DIRECTORIES"}, description
1308            = "Sets the types of files that the JFileChooser can choose.")
1309    public void setFileSelectionMode(int mode) {
1310        if(fileSelectionMode == mode) {
1311            return;
1312        }
1313
1314        checkFileSelectionMode(mode);
1315           int oldValue = fileSelectionMode;
1316           fileSelectionMode = mode;
1317           firePropertyChange(FILE_SELECTION_MODE_CHANGED_PROPERTY, oldValue, fileSelectionMode);
1318    }
1319
1320    private static void checkFileSelectionMode(int mode) {
1321        if ((mode != FILES_ONLY) && (mode != DIRECTORIES_ONLY)
1322                && (mode != FILES_AND_DIRECTORIES)) {
1323            throw new IllegalArgumentException(
1324                    "Incorrect Mode for file selection: " + mode);
1325        }
1326    }
1327
1328    /**
1329     * Returns the current file-selection mode.  The default is
1330     * <code>JFilesChooser.FILES_ONLY</code>.
1331     *
1332     * @return the type of files to be displayed, one of the following:
1333     * <ul>
1334     * <li>JFileChooser.FILES_ONLY
1335     * <li>JFileChooser.DIRECTORIES_ONLY
1336     * <li>JFileChooser.FILES_AND_DIRECTORIES
1337     * </ul>
1338     * @see #setFileSelectionMode
1339     */
1340    public int getFileSelectionMode() {
1341        return fileSelectionMode;
1342    }
1343
1344    /**
1345     * Convenience call that determines if files are selectable based on the
1346     * current file selection mode.
1347     *
1348     * @return true if files are selectable, false otherwise
1349     * @see #setFileSelectionMode
1350     * @see #getFileSelectionMode
1351     */
1352    @BeanProperty(bound = false)
1353    public boolean isFileSelectionEnabled() {
1354        return ((fileSelectionMode == FILES_ONLY) || (fileSelectionMode == FILES_AND_DIRECTORIES));
1355    }
1356
1357    /**
1358     * Convenience call that determines if directories are selectable based
1359     * on the current file selection mode.
1360     *
1361     * @return true if directories are selectable, false otherwise
1362     * @see #setFileSelectionMode
1363     * @see #getFileSelectionMode
1364     */
1365    @BeanProperty(bound = false)
1366    public boolean isDirectorySelectionEnabled() {
1367        return ((fileSelectionMode == DIRECTORIES_ONLY) || (fileSelectionMode == FILES_AND_DIRECTORIES));
1368    }
1369
1370    /**
1371     * Sets the file chooser to allow multiple file selections.
1372     *
1373     * @param b true if multiple files may be selected
1374     *
1375     * @see #isMultiSelectionEnabled
1376     */
1377    @BeanProperty(description
1378            = "Sets multiple file selection mode.")
1379    public void setMultiSelectionEnabled(boolean b) {
1380        if(multiSelectionEnabled == b) {
1381            return;
1382        }
1383        boolean oldValue = multiSelectionEnabled;
1384        multiSelectionEnabled = b;
1385        firePropertyChange(MULTI_SELECTION_ENABLED_CHANGED_PROPERTY, oldValue, multiSelectionEnabled);
1386    }
1387
1388    /**
1389     * Returns true if multiple files can be selected.
1390     * @return true if multiple files can be selected
1391     * @see #setMultiSelectionEnabled
1392     */
1393    public boolean isMultiSelectionEnabled() {
1394        return multiSelectionEnabled;
1395    }
1396
1397
1398    /**
1399     * Returns true if hidden files are not shown in the file chooser;
1400     * otherwise, returns false.
1401     *
1402     * @return the status of the file hiding property
1403     * @see #setFileHidingEnabled
1404     */
1405    public boolean isFileHidingEnabled() {
1406        return useFileHiding;
1407    }
1408
1409    /**
1410     * Sets file hiding on or off. If true, hidden files are not shown
1411     * in the file chooser. The job of determining which files are
1412     * shown is done by the <code>FileView</code>.
1413     *
1414     * @param b the boolean value that determines whether file hiding is
1415     *          turned on
1416     * @see #isFileHidingEnabled
1417     */
1418    @BeanProperty(preferred = true, description
1419            = "Sets file hiding on or off.")
1420    public void setFileHidingEnabled(boolean b) {
1421        // Dump showFilesListener since we'll ignore it from now on
1422        if (showFilesListener != null) {
1423            Toolkit.getDefaultToolkit().removePropertyChangeListener(SHOW_HIDDEN_PROP, showFilesListener);
1424            showFilesListener = null;
1425        }
1426        boolean oldValue = useFileHiding;
1427        useFileHiding = b;
1428        firePropertyChange(FILE_HIDING_CHANGED_PROPERTY, oldValue, useFileHiding);
1429    }
1430
1431    /**
1432     * Sets the current file filter. The file filter is used by the
1433     * file chooser to filter out files from the user's view.
1434     *
1435     * @param filter the new current file filter to use
1436     * @see #getFileFilter
1437     */
1438    @BeanProperty(preferred = true, description
1439            = "Sets the File Filter used to filter out files of type.")
1440    public void setFileFilter(FileFilter filter) {
1441        FileFilter oldValue = fileFilter;
1442        fileFilter = filter;
1443        if (filter != null) {
1444            if (isMultiSelectionEnabled() && selectedFiles != null && selectedFiles.length > 0) {
1445                Vector<File> fList = new Vector<File>();
1446                boolean failed = false;
1447                for (File file : selectedFiles) {
1448                    if (filter.accept(file)) {
1449                        fList.add(file);
1450                    } else {
1451                        failed = true;
1452                    }
1453                }
1454                if (failed) {
1455                    setSelectedFiles((fList.size() == 0) ? null : fList.toArray(new File[fList.size()]));
1456                }
1457            } else if (selectedFile != null && !filter.accept(selectedFile)) {
1458                setSelectedFile(null);
1459            }
1460        }
1461        firePropertyChange(FILE_FILTER_CHANGED_PROPERTY, oldValue, fileFilter);
1462    }
1463
1464
1465    /**
1466     * Returns the currently selected file filter.
1467     *
1468     * @return the current file filter
1469     * @see #setFileFilter
1470     * @see #addChoosableFileFilter
1471     */
1472    public FileFilter getFileFilter() {
1473        return fileFilter;
1474    }
1475
1476    /**
1477     * Sets the file view to be used to retrieve UI information, such as
1478     * the icon that represents a file or the type description of a file.
1479     *
1480     * @param fileView a {@code FileView} to be used to retrieve UI information
1481     *
1482     * @see #getFileView
1483     */
1484    @BeanProperty(preferred = true, description
1485            = "Sets the File View used to get file type information.")
1486    public void setFileView(FileView fileView) {
1487        FileView oldValue = this.fileView;
1488        this.fileView = fileView;
1489        firePropertyChange(FILE_VIEW_CHANGED_PROPERTY, oldValue, fileView);
1490    }
1491
1492    /**
1493     * Returns the current file view.
1494     *
1495     * @return the current file view
1496     * @see #setFileView
1497     */
1498    public FileView getFileView() {
1499        return fileView;
1500    }
1501
1502    // ******************************
1503    // *****FileView delegation *****
1504    // ******************************
1505
1506    // NOTE: all of the following methods attempt to delegate
1507    // first to the client set fileView, and if <code>null</code> is returned
1508    // (or there is now client defined fileView) then calls the
1509    // UI's default fileView.
1510
1511    /**
1512     * Returns the filename.
1513     * @param f the <code>File</code>
1514     * @return the <code>String</code> containing the filename for
1515     *          <code>f</code>
1516     * @see FileView#getName
1517     */
1518    public String getName(File f) {
1519        String filename = null;
1520        if(f != null) {
1521            if(getFileView() != null) {
1522                filename = getFileView().getName(f);
1523            }
1524
1525            FileView uiFileView = getUI().getFileView(this);
1526
1527            if(filename == null && uiFileView != null) {
1528                filename = uiFileView.getName(f);
1529            }
1530        }
1531        return filename;
1532    }
1533
1534    /**
1535     * Returns the file description.
1536     * @param f the <code>File</code>
1537     * @return the <code>String</code> containing the file description for
1538     *          <code>f</code>
1539     * @see FileView#getDescription
1540     */
1541    public String getDescription(File f) {
1542        String description = null;
1543        if(f != null) {
1544            if(getFileView() != null) {
1545                description = getFileView().getDescription(f);
1546            }
1547
1548            FileView uiFileView = getUI().getFileView(this);
1549
1550            if(description == null && uiFileView != null) {
1551                description = uiFileView.getDescription(f);
1552            }
1553        }
1554        return description;
1555    }
1556
1557    /**
1558     * Returns the file type.
1559     * @param f the <code>File</code>
1560     * @return the <code>String</code> containing the file type description for
1561     *          <code>f</code>
1562     * @see FileView#getTypeDescription
1563     */
1564    public String getTypeDescription(File f) {
1565        String typeDescription = null;
1566        if(f != null) {
1567            if(getFileView() != null) {
1568                typeDescription = getFileView().getTypeDescription(f);
1569            }
1570
1571            FileView uiFileView = getUI().getFileView(this);
1572
1573            if(typeDescription == null && uiFileView != null) {
1574                typeDescription = uiFileView.getTypeDescription(f);
1575            }
1576        }
1577        return typeDescription;
1578    }
1579
1580    /**
1581     * Returns the icon for this file or type of file, depending
1582     * on the system.
1583     * @param f the <code>File</code>
1584     * @return the <code>Icon</code> for this file, or type of file
1585     * @see FileView#getIcon
1586     */
1587    public Icon getIcon(File f) {
1588        Icon icon = null;
1589        if (f != null) {
1590            if(getFileView() != null) {
1591                icon = getFileView().getIcon(f);
1592            }
1593
1594            FileView uiFileView = getUI().getFileView(this);
1595
1596            if(icon == null && uiFileView != null) {
1597                icon = uiFileView.getIcon(f);
1598            }
1599        }
1600        return icon;
1601    }
1602
1603    /**
1604     * Returns true if the file (directory) can be visited.
1605     * Returns false if the directory cannot be traversed.
1606     * @param f the <code>File</code>
1607     * @return true if the file/directory can be traversed, otherwise false
1608     * @see FileView#isTraversable
1609     */
1610    public boolean isTraversable(File f) {
1611        Boolean traversable = null;
1612        if (f != null) {
1613            if (getFileView() != null) {
1614                traversable = getFileView().isTraversable(f);
1615            }
1616
1617            FileView uiFileView = getUI().getFileView(this);
1618
1619            if (traversable == null && uiFileView != null) {
1620                traversable = uiFileView.isTraversable(f);
1621            }
1622            if (traversable == null) {
1623                traversable = getFileSystemView().isTraversable(f);
1624            }
1625        }
1626        return (traversable != null && traversable.booleanValue());
1627    }
1628
1629    /**
1630     * Returns true if the file should be displayed.
1631     * @param f the <code>File</code>
1632     * @return true if the file should be displayed, otherwise false
1633     * @see FileFilter#accept
1634     */
1635    public boolean accept(File f) {
1636        boolean shown = true;
1637        if(f != null && fileFilter != null) {
1638            shown = fileFilter.accept(f);
1639        }
1640        return shown;
1641    }
1642
1643    /**
1644     * Sets the file system view that the <code>JFileChooser</code> uses for
1645     * accessing and creating file system resources, such as finding
1646     * the floppy drive and getting a list of root drives.
1647     * @param fsv  the new <code>FileSystemView</code>
1648     *
1649     * @see FileSystemView
1650     */
1651    @BeanProperty(expert = true, description
1652            = "Sets the FileSytemView used to get filesystem information.")
1653    public void setFileSystemView(FileSystemView fsv) {
1654        FileSystemView oldValue = fileSystemView;
1655        fileSystemView = fsv;
1656        firePropertyChange(FILE_SYSTEM_VIEW_CHANGED_PROPERTY, oldValue, fileSystemView);
1657    }
1658
1659    /**
1660     * Returns the file system view.
1661     * @return the <code>FileSystemView</code> object
1662     * @see #setFileSystemView
1663     */
1664    public FileSystemView getFileSystemView() {
1665        return fileSystemView;
1666    }
1667
1668    // **************************
1669    // ***** Event Handling *****
1670    // **************************
1671
1672    /**
1673     * Called by the UI when the user hits the Approve button
1674     * (labeled "Open" or "Save", by default). This can also be
1675     * called by the programmer.
1676     * This method causes an action event to fire
1677     * with the command string equal to
1678     * <code>APPROVE_SELECTION</code>.
1679     *
1680     * @see #APPROVE_SELECTION
1681     */
1682    public void approveSelection() {
1683        returnValue = APPROVE_OPTION;
1684        if(dialog != null) {
1685            dialog.setVisible(false);
1686        }
1687        fireActionPerformed(APPROVE_SELECTION);
1688    }
1689
1690    /**
1691     * Called by the UI when the user chooses the Cancel button.
1692     * This can also be called by the programmer.
1693     * This method causes an action event to fire
1694     * with the command string equal to
1695     * <code>CANCEL_SELECTION</code>.
1696     *
1697     * @see #CANCEL_SELECTION
1698     */
1699    public void cancelSelection() {
1700        returnValue = CANCEL_OPTION;
1701        if(dialog != null) {
1702            dialog.setVisible(false);
1703        }
1704        fireActionPerformed(CANCEL_SELECTION);
1705    }
1706
1707    /**
1708     * Adds an <code>ActionListener</code> to the file chooser.
1709     *
1710     * @param l  the listener to be added
1711     *
1712     * @see #approveSelection
1713     * @see #cancelSelection
1714     */
1715    public void addActionListener(ActionListener l) {
1716        listenerList.add(ActionListener.class, l);
1717    }
1718
1719    /**
1720     * Removes an <code>ActionListener</code> from the file chooser.
1721     *
1722     * @param l  the listener to be removed
1723     *
1724     * @see #addActionListener
1725     */
1726    public void removeActionListener(ActionListener l) {
1727        listenerList.remove(ActionListener.class, l);
1728    }
1729
1730    /**
1731     * Returns an array of all the action listeners
1732     * registered on this file chooser.
1733     *
1734     * @return all of this file chooser's <code>ActionListener</code>s
1735     *         or an empty
1736     *         array if no action listeners are currently registered
1737     *
1738     * @see #addActionListener
1739     * @see #removeActionListener
1740     *
1741     * @since 1.4
1742     */
1743    @BeanProperty(bound = false)
1744    public ActionListener[] getActionListeners() {
1745        return listenerList.getListeners(ActionListener.class);
1746    }
1747
1748    /**
1749     * Notifies all listeners that have registered interest for
1750     * notification on this event type. The event instance
1751     * is lazily created using the <code>command</code> parameter.
1752     *
1753     * @param command a string that may specify a command associated with
1754     *                the event
1755     * @see EventListenerList
1756     */
1757    @SuppressWarnings("deprecation")
1758    protected void fireActionPerformed(String command) {
1759        // Guaranteed to return a non-null array
1760        Object[] listeners = listenerList.getListenerList();
1761        long mostRecentEventTime = EventQueue.getMostRecentEventTime();
1762        int modifiers = 0;
1763        AWTEvent currentEvent = EventQueue.getCurrentEvent();
1764        if (currentEvent instanceof InputEvent) {
1765            modifiers = ((InputEvent)currentEvent).getModifiers();
1766        } else if (currentEvent instanceof ActionEvent) {
1767            modifiers = ((ActionEvent)currentEvent).getModifiers();
1768        }
1769        ActionEvent e = null;
1770        // Process the listeners last to first, notifying
1771        // those that are interested in this event
1772        for (int i = listeners.length-2; i>=0; i-=2) {
1773            if (listeners[i]==ActionListener.class) {
1774                // Lazily create the event:
1775                if (e == null) {
1776                    e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
1777                                        command, mostRecentEventTime,
1778                                        modifiers);
1779                }
1780                ((ActionListener)listeners[i+1]).actionPerformed(e);
1781            }
1782        }
1783    }
1784
1785    private static class WeakPCL implements PropertyChangeListener {
1786        WeakReference<JFileChooser> jfcRef;
1787
1788        public WeakPCL(JFileChooser jfc) {
1789            jfcRef = new WeakReference<JFileChooser>(jfc);
1790        }
1791        public void propertyChange(PropertyChangeEvent ev) {
1792            assert ev.getPropertyName().equals(SHOW_HIDDEN_PROP);
1793            JFileChooser jfc = jfcRef.get();
1794            if (jfc == null) {
1795                // Our JFileChooser is no longer around, so we no longer need to
1796                // listen for PropertyChangeEvents.
1797                Toolkit.getDefaultToolkit().removePropertyChangeListener(SHOW_HIDDEN_PROP, this);
1798            }
1799            else {
1800                boolean oldValue = jfc.useFileHiding;
1801                jfc.useFileHiding = !((Boolean)ev.getNewValue()).booleanValue();
1802                jfc.firePropertyChange(FILE_HIDING_CHANGED_PROPERTY, oldValue, jfc.useFileHiding);
1803            }
1804        }
1805    }
1806
1807    // *********************************
1808    // ***** Pluggable L&F methods *****
1809    // *********************************
1810
1811    /**
1812     * Resets the UI property to a value from the current look and feel.
1813     *
1814     * @see JComponent#updateUI
1815     */
1816    public void updateUI() {
1817        if (isAcceptAllFileFilterUsed()) {
1818            removeChoosableFileFilter(getAcceptAllFileFilter());
1819        }
1820        FileChooserUI ui = ((FileChooserUI)UIManager.getUI(this));
1821        if (fileSystemView == null) {
1822            // We were probably deserialized
1823            setFileSystemView(FileSystemView.getFileSystemView());
1824        }
1825        setUI(ui);
1826
1827        if(isAcceptAllFileFilterUsed()) {
1828            addChoosableFileFilter(getAcceptAllFileFilter());
1829        }
1830    }
1831
1832    /**
1833     * Returns a string that specifies the name of the L&amp;F class
1834     * that renders this component.
1835     *
1836     * @return the string "FileChooserUI"
1837     * @see JComponent#getUIClassID
1838     * @see UIDefaults#getUI
1839     */
1840    @BeanProperty(bound = false, expert = true, description
1841            = "A string that specifies the name of the L&amp;F class.")
1842    public String getUIClassID() {
1843        return uiClassID;
1844    }
1845
1846    /**
1847     * Gets the UI object which implements the L&amp;F for this component.
1848     *
1849     * @return the FileChooserUI object that implements the FileChooserUI L&amp;F
1850     */
1851    @BeanProperty(bound = false)
1852    public FileChooserUI getUI() {
1853        return (FileChooserUI) ui;
1854    }
1855
1856    /**
1857     * See <code>readObject</code> and <code>writeObject</code> in
1858     * <code>JComponent</code> for more
1859     * information about serialization in Swing.
1860     */
1861    private void readObject(java.io.ObjectInputStream in)
1862            throws IOException, ClassNotFoundException {
1863        ObjectInputStream.GetField f = in.readFields();
1864
1865        dialogTitle = (String) f.get("dialogTitle", null);
1866        approveButtonText = (String) f.get("approveButtonText", null);
1867        approveButtonToolTipText =
1868                (String) f.get("approveButtonToolTipText", null);
1869        approveButtonMnemonic = f.get("approveButtonMnemonic", 0);
1870        @SuppressWarnings("unchecked")
1871        Vector<FileFilter> newFilters = (Vector<FileFilter>) f.get("filters", null);
1872        if (newFilters == null) {
1873            throw new InvalidObjectException("Null filters");
1874        }
1875        filters = newFilters;
1876        dialog = (JDialog) f.get("dialog", null);
1877        int newDialogType = f.get("dialogType", OPEN_DIALOG);
1878        checkDialogType(newDialogType);
1879        dialogType = newDialogType;
1880        returnValue = f.get("returnValue", 0);
1881        accessory = (JComponent) f.get("accessory", null);
1882        fileView = (FileView) f.get("fileView", null);
1883        controlsShown = f.get("controlsShown", false);
1884        useFileHiding = f.get("useFileHiding", false);
1885        int newFileSelectionMode = f.get("fileSelectionMode", FILES_ONLY);
1886        checkFileSelectionMode(newFileSelectionMode);
1887        fileSelectionMode = newFileSelectionMode;
1888        multiSelectionEnabled = f.get("multiSelectionEnabled", false);
1889        useAcceptAllFileFilter = f.get("useAcceptAllFileFilter", false);
1890        boolean newDragEnabled = f.get("dragEnabled", false);
1891        checkDragEnabled(newDragEnabled);
1892        dragEnabled = newDragEnabled;
1893        fileFilter = (FileFilter) f.get("fileFilter", null);
1894        fileSystemView = (FileSystemView) f.get("fileSystemView", null);
1895        currentDirectory = (File) f.get("currentDirectory", null);
1896        selectedFile = (File) f.get("selectedFile", null);
1897        selectedFiles = (File[]) f.get("selectedFiles", null);
1898        accessibleContext = (AccessibleContext) f.get("accessibleContext", null);
1899
1900        installShowFilesListener();
1901    }
1902
1903    /**
1904     * See <code>readObject</code> and <code>writeObject</code> in
1905     * <code>JComponent</code> for more
1906     * information about serialization in Swing.
1907     */
1908    private void writeObject(ObjectOutputStream s) throws IOException {
1909        FileSystemView fsv = null;
1910
1911        if (isAcceptAllFileFilterUsed()) {
1912            //The AcceptAllFileFilter is UI specific, it will be reset by
1913            //updateUI() after deserialization
1914            removeChoosableFileFilter(getAcceptAllFileFilter());
1915        }
1916        if (fileSystemView.equals(FileSystemView.getFileSystemView())) {
1917            //The default FileSystemView is platform specific, it will be
1918            //reset by updateUI() after deserialization
1919            fsv = fileSystemView;
1920            fileSystemView = null;
1921        }
1922        s.defaultWriteObject();
1923        if (fsv != null) {
1924            fileSystemView = fsv;
1925        }
1926        if (isAcceptAllFileFilterUsed()) {
1927            addChoosableFileFilter(getAcceptAllFileFilter());
1928        }
1929        if (getUIClassID().equals(uiClassID)) {
1930            byte count = JComponent.getWriteObjCounter(this);
1931            JComponent.setWriteObjCounter(this, --count);
1932            if (count == 0 && ui != null) {
1933                ui.installUI(this);
1934            }
1935        }
1936    }
1937
1938
1939    /**
1940     * Returns a string representation of this <code>JFileChooser</code>.
1941     * This method
1942     * is intended to be used only for debugging purposes, and the
1943     * content and format of the returned string may vary between
1944     * implementations. The returned string may be empty but may not
1945     * be <code>null</code>.
1946     *
1947     * @return  a string representation of this <code>JFileChooser</code>
1948     */
1949    protected String paramString() {
1950        String approveButtonTextString = (approveButtonText != null ?
1951                                          approveButtonText: "");
1952        String dialogTitleString = (dialogTitle != null ?
1953                                    dialogTitle: "");
1954        String dialogTypeString;
1955        if (dialogType == OPEN_DIALOG) {
1956            dialogTypeString = "OPEN_DIALOG";
1957        } else if (dialogType == SAVE_DIALOG) {
1958            dialogTypeString = "SAVE_DIALOG";
1959        } else if (dialogType == CUSTOM_DIALOG) {
1960            dialogTypeString = "CUSTOM_DIALOG";
1961        } else dialogTypeString = "";
1962        String returnValueString;
1963        if (returnValue == CANCEL_OPTION) {
1964            returnValueString = "CANCEL_OPTION";
1965        } else if (returnValue == APPROVE_OPTION) {
1966            returnValueString = "APPROVE_OPTION";
1967        } else if (returnValue == ERROR_OPTION) {
1968            returnValueString = "ERROR_OPTION";
1969        } else returnValueString = "";
1970        String useFileHidingString = (useFileHiding ?
1971                                    "true" : "false");
1972        String fileSelectionModeString;
1973        if (fileSelectionMode == FILES_ONLY) {
1974            fileSelectionModeString = "FILES_ONLY";
1975        } else if (fileSelectionMode == DIRECTORIES_ONLY) {
1976            fileSelectionModeString = "DIRECTORIES_ONLY";
1977        } else if (fileSelectionMode == FILES_AND_DIRECTORIES) {
1978            fileSelectionModeString = "FILES_AND_DIRECTORIES";
1979        } else fileSelectionModeString = "";
1980        String currentDirectoryString = (currentDirectory != null ?
1981                                         currentDirectory.toString() : "");
1982        String selectedFileString = (selectedFile != null ?
1983                                     selectedFile.toString() : "");
1984
1985        return super.paramString() +
1986        ",approveButtonText=" + approveButtonTextString +
1987        ",currentDirectory=" + currentDirectoryString +
1988        ",dialogTitle=" + dialogTitleString +
1989        ",dialogType=" + dialogTypeString +
1990        ",fileSelectionMode=" + fileSelectionModeString +
1991        ",returnValue=" + returnValueString +
1992        ",selectedFile=" + selectedFileString +
1993        ",useFileHiding=" + useFileHidingString;
1994    }
1995
1996/////////////////
1997// Accessibility support
1998////////////////
1999
2000    /**
2001     * {@code AccessibleContext} associated with this {@code JFileChooser}
2002     */
2003    protected AccessibleContext accessibleContext = null;
2004
2005    /**
2006     * Gets the AccessibleContext associated with this JFileChooser.
2007     * For file choosers, the AccessibleContext takes the form of an
2008     * AccessibleJFileChooser.
2009     * A new AccessibleJFileChooser instance is created if necessary.
2010     *
2011     * @return an AccessibleJFileChooser that serves as the
2012     *         AccessibleContext of this JFileChooser
2013     */
2014    @BeanProperty(bound = false)
2015    public AccessibleContext getAccessibleContext() {
2016        if (accessibleContext == null) {
2017            accessibleContext = new AccessibleJFileChooser();
2018        }
2019        return accessibleContext;
2020    }
2021
2022    /**
2023     * This class implements accessibility support for the
2024     * <code>JFileChooser</code> class.  It provides an implementation of the
2025     * Java Accessibility API appropriate to file chooser user-interface
2026     * elements.
2027     */
2028    @SuppressWarnings("serial") // Superclass is not serializable across versions
2029    protected class AccessibleJFileChooser extends AccessibleJComponent {
2030
2031        /**
2032         * Gets the role of this object.
2033         *
2034         * @return an instance of AccessibleRole describing the role of the
2035         * object
2036         * @see AccessibleRole
2037         */
2038        public AccessibleRole getAccessibleRole() {
2039            return AccessibleRole.FILE_CHOOSER;
2040        }
2041
2042    } // inner class AccessibleJFileChooser
2043
2044    private class FCHierarchyListener implements HierarchyListener,
2045            Serializable {
2046        @Override
2047        public void hierarchyChanged(HierarchyEvent e) {
2048            if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED)
2049                    == HierarchyEvent.PARENT_CHANGED) {
2050                JFileChooser fc = JFileChooser.this;
2051                JRootPane rootPane = SwingUtilities.getRootPane(fc);
2052                if (rootPane != null) {
2053                    rootPane.setDefaultButton(fc.getUI().getDefaultButton(fc));
2054                }
2055            }
2056        }
2057    }
2058}
2059