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