001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Component;
005import java.io.File;
006import java.util.Collection;
007import java.util.Collections;
008
009import javax.swing.JFileChooser;
010import javax.swing.filechooser.FileFilter;
011
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.actions.DiskAccessAction;
014import org.openstreetmap.josm.actions.ExtensionFileFilter;
015import org.openstreetmap.josm.actions.SaveActionBase;
016
017/**
018 * A chained utility class used to create and open {@link JFileChooser} dialogs.<br/>
019 * Use only this class if you need to control specifically your JFileChooser dialog.<br/>
020 * <p>
021 * A simpler usage is to call the {@link DiskAccessAction#createAndOpenFileChooser} methods.
022 * 
023 * @since 5438
024 */
025public class JFileChooserManager {
026    private final boolean open;
027    private final String lastDirProperty;
028    private final String curDir;
029    
030    private JFileChooser fc;
031
032    /**
033     * Creates a new {@code JFileChooserManager}.
034     * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
035     * @see #createFileChooser
036     */
037    public JFileChooserManager(boolean open) {
038        this(open, null);
039    }
040    
041    /**
042     * Creates a new {@code JFileChooserManager}.
043     * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
044     * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the JFileChooser. 
045     *                        Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path.
046     * @see #createFileChooser
047     */
048    public JFileChooserManager(boolean open, String lastDirProperty) {
049        this(open, lastDirProperty, null);
050    }
051
052    /**
053     * Creates a new {@code JFileChooserManager}.
054     * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
055     * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the JFileChooser. 
056     *                        Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path.
057     * @param defaultDir The default directory used to initialize the JFileChooser if the {@code lastDirProperty} property value is missing. 
058     * @see #createFileChooser
059     */
060    public JFileChooserManager(boolean open, String lastDirProperty, String defaultDir) {
061        this.open = open;
062        this.lastDirProperty = lastDirProperty == null || lastDirProperty.isEmpty() ? "lastDirectory" : lastDirProperty;
063        this.curDir = Main.pref.get(this.lastDirProperty).isEmpty() ? 
064                (defaultDir == null || defaultDir.isEmpty() ? "." : defaultDir) 
065                : Main.pref.get(this.lastDirProperty);
066    }
067
068    /**
069     * Replies the {@code JFileChooser} that has been previously created.
070     * @return The {@code JFileChooser} that has been previously created, or {@code null} if it has not been created yet. 
071     * @see #createFileChooser
072     */
073    public final JFileChooser getFileChooser() {
074        return fc;
075    }
076
077    /**
078     * Replies the initial directory used to construct the {@code JFileChooser}.
079     * @return The initial directory used to construct the {@code JFileChooser}.
080     */
081    public final String getInitialDirectory() {
082        return curDir;
083    }
084
085    /**
086     * Creates a new {@link JFileChooser} with default settings. All files will be accepted.
087     * @return this
088     */
089    public final JFileChooserManager createFileChooser() {
090        return doCreateFileChooser(false, null, null, null, null, JFileChooser.FILES_ONLY, false);
091    }
092    
093    /**
094     * Creates a new {@link JFileChooser} with given settings for a single {@code FileFilter}.
095     * 
096     * @param multiple If true, makes the dialog allow multiple file selections
097     * @param title The string that goes in the dialog window's title bar
098     * @param filter The only file filter that will be proposed by the dialog
099     * @param selectionMode The selection mode that allows the user to:<br/>
100     *                      <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 
101     *                      <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 
102     *                      <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li>
103     * @return this
104     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
105     */
106    public final JFileChooserManager createFileChooser(boolean multiple, String title, FileFilter filter, int selectionMode) {
107        doCreateFileChooser(multiple, title, Collections.singleton(filter), filter, null, selectionMode, false);
108        getFileChooser().setAcceptAllFileFilterUsed(false);
109        return this;
110    }
111    
112    /**
113     * Creates a new {@link JFileChooser} with given settings for a collection of {@code FileFilter}s.
114     * 
115     * @param multiple If true, makes the dialog allow multiple file selections
116     * @param title The string that goes in the dialog window's title bar
117     * @param filters The file filters that will be proposed by the dialog
118     * @param defaultFilter The file filter that will be selected by default
119     * @param selectionMode The selection mode that allows the user to:<br/>
120     *                      <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 
121     *                      <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 
122     *                      <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li>
123     * @return this
124     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, Collection, FileFilter, int, String)
125     */
126    public final JFileChooserManager createFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters, FileFilter defaultFilter, int selectionMode) {
127        return doCreateFileChooser(multiple, title, filters, defaultFilter, null, selectionMode, false);
128    }
129
130    /**
131     * Creates a new {@link JFileChooser} with given settings for a file extension.
132     * 
133     * @param multiple If true, makes the dialog allow multiple file selections
134     * @param title The string that goes in the dialog window's title bar
135     * @param extension The file extension that will be selected as the default file filter
136     * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox. 
137     *                 If false, only the file filters that include {@code extension} will be proposed
138     * @param selectionMode The selection mode that allows the user to:<br/>
139     *                      <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 
140     *                      <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 
141     *                      <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li>
142     * @return this
143     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
144     */
145    public final JFileChooserManager createFileChooser(boolean multiple, String title, String extension, boolean allTypes, int selectionMode) {
146        return doCreateFileChooser(multiple, title, null, null, extension, selectionMode, allTypes);
147    }
148
149    private final JFileChooserManager doCreateFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters, FileFilter defaultFilter, String extension, int selectionMode, boolean allTypes) {
150        fc = new JFileChooser(new File(curDir));
151        if (title != null) {
152            fc.setDialogTitle(title);
153        }
154
155        fc.setFileSelectionMode(selectionMode);
156        fc.setMultiSelectionEnabled(multiple);
157        fc.setAcceptAllFileFilterUsed(false);
158        
159        if (filters != null) {
160            for (FileFilter filter : filters) {
161                fc.addChoosableFileFilter(filter);
162            }
163            if (defaultFilter != null) {
164                fc.setFileFilter(defaultFilter);
165            }
166        } else if (open) {
167            ExtensionFileFilter.applyChoosableImportFileFilters(fc, extension, allTypes);
168        } else {
169            ExtensionFileFilter.applyChoosableExportFileFilters(fc, extension, allTypes);
170        }
171        return this;
172    }
173
174    /**
175     * Opens the {@code JFileChooser} that has been created. Nothing happens if it has not been created yet.
176     * @return the {@code JFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 
177     */
178    public final JFileChooser openFileChooser() {
179        return openFileChooser(null);
180    }
181
182    /**
183     * Opens the {@code JFileChooser} that has been created and waits for the user to choose a file/directory, or cancel the dialog.<br/> 
184     * Nothing happens if the dialog has not been created yet.<br/>
185     * When the user choses a file or directory, the {@code lastDirProperty} is updated to the chosen directory path.
186     * 
187     * @param parent The Component used as the parent of the JFileChooser. If null, uses {@code Main.parent}.
188     * @return the {@code JFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 
189     */
190    public JFileChooser openFileChooser(Component parent) {
191        if (fc != null) {
192            if (parent == null) {
193                parent = Main.parent;
194            }
195            
196            int answer = open ? fc.showOpenDialog(parent) : fc.showSaveDialog(parent);
197            if (answer != JFileChooser.APPROVE_OPTION) {
198                return null;
199            }
200
201            if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) {
202                Main.pref.put(lastDirProperty, fc.getCurrentDirectory().getAbsolutePath());
203            }
204
205            if (!open) {
206                File file = fc.getSelectedFile();
207                if (!SaveActionBase.confirmOverwrite(file)) {
208                    return null;
209                }
210            }
211        }
212        return fc;
213    }
214}