1 /* 
2  * Copyright 2005 Paul Hinds
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.tp23.gui.widget;
17
18import java.beans.PropertyChangeEvent;
19import java.beans.PropertyChangeListener;
20import java.io.File;
21
22import javax.swing.JFileChooser;
23import javax.swing.filechooser.FileFilter;
24import javax.swing.filechooser.FileSystemView;
25/**
26 * A file chooser that makes a beter default if the default directory is not available.
27* Basically the default looks for the suggested directory and if it does not find it moves back
28* up the directories.  If still nothing is found (for example it may get to My Computer
29* which you can not add file to) the home directory is selected, as in the default file chooser.
30*
31* To use this class call new DefaultingDirectoryChooser(boolean) and immediately call
32* setDefaultDirectory(File) to determine the default. If you don't it behaves like the default JFileChooser
33* Also if you call setCurrentDirectory(File) it will behave like JFileChooser
34*
35* This chooser has two modes CREATE_MODE and EXISTING_MODE.   <br/><br/>
36*
37* In existing mode it is assumed the chooser is being used to identify an existing directory
38* the default directory should exist if not the directory shown will be the next existing parent.<br/><br/>
39*
40* In create mode it is
41* assumed that the last directory in the default directory is to be created and may not
42* exist yet. Therefor the default directory displayed is the parent (with suitable defaulting)
43* and the text box will contain the name of the last directory.
44* e.g. if default is C:\Program Files\MyApp  C:\Program Files will be displayed and
45* MyApp will be shown in the text box (even if it does not exist yet)  If C:\Program Files does not exist
46* C:\ will be shown as with existing mode.
47* Choosing select can lead to the creation of the file.  It will not be automatically
48* created that is the responsibility of the client code.<br/>
49*
50* This class uses FileSystemView and will only display folders that return true to fsv.isFileSystem()
51*
52* This class also uses a bit of a hack by in CREATE_MODE it sets the selectables to files and directories
53* although only shows the directories this way a non-existing Directory can be chosen.  This is the only way I can
54* get it to work on my Java version but I would not be supprised if it did not work on
55* other versions of the Java API.  It also means that setFileSelectionMode() should
56* never be called by clients as it will break this hack.
57 * @author Paul Hinds
58 * @version 1.0
59 */
60public class DefaultingDirectoryChooser extends JFileChooser{
61
62    public static final boolean CREATE_MODE = true;
63    public static final boolean EXISTING_MODE = false;
64
65    /**
66     * Determines that the default includes a directory name that is requried
67     * e.g. C:\Program Files\MyApp where even if Program Files does not exist
68     * MyApp should be part of the default even if it does not exist.
69     */
70    private boolean createMode = false;
71    private File selectedFile = null;
72    private String desiredName = null;
73    
74    public DefaultingDirectoryChooser(boolean createMode) {
75        this.createMode=createMode;
76        if(createMode){
77            setFileSelectionMode(FILES_AND_DIRECTORIES);
78            removeChoosableFileFilter(getAcceptAllFileFilter());
79            addChoosableFileFilter(new FileFilter(){
80                public boolean accept(File f) {
81                    if(f.exists() && f.isDirectory())return true;
82                    if(!f.exists() && getFileSystemView().getParentDirectory(f).isDirectory())return true;
83                    return false;
84                }
85                public String getDescription() {
86                    return "Directories";
87                }
88            });
89            
90            addPropertyChangeListener(JFileChooser.DIRECTORY_CHANGED_PROPERTY, new PropertyChangeListener(){
91                public void propertyChange(PropertyChangeEvent evt) {
92                    File directory = (File)evt.getNewValue();
93                    DefaultingDirectoryChooser.this.setCurrentDirectory(new File(directory, "newfolder"));
94                    setSelectedFile(new File(directory, desiredName));
95                }               
96            });
97        }
98        else {
99            setFileSelectionMode(DIRECTORIES_ONLY);
00        }
01    }
02
03
04    /**
05     * Sets the default directory.
06     * @param dir the current directory to point to
07     * @todo Implement this javax.swing.JFileChooser method
08     */
09    public void setDefaultDirectory(File dir) {
10        if(dir==null)return;// called by the default consructor
11        if(createMode){
12            if(desiredName == null)desiredName = dir.getName();
13            File selected = determineCreateDir(dir);
14            super.setCurrentDirectory(selected.getParentFile());
15            setSelectedFile(selected);
16        }else{
17            super.setCurrentDirectory(determineExistingDir(dir));
18        }
19    }
20
21
22
23    private File determineExistingDir(File defaultDir){
24        if(defaultDir.exists())return defaultDir;
25        FileSystemView fsv = this.getFileSystemView();
26        File validParent = getFSVParent(fsv,defaultDir);
27        if(validParent!=null) {
28            return validParent;
29        }
30        else {
31            return fsv.getHomeDirectory();
32        }
33    }
34    /**
35     * Select a base path for the default even if it does not exist.  The order of preference is as follows
36     * if the directory exists use it
37     * if the parent does not exist try finding the next parent
38     * if the next parent is a root directory e.g. / or C:\ use the users home directory
39     * @param defaultDir File
40     * @return File
41     */
42    private File determineCreateDir(File defaultDir){
43        if(defaultDir.exists())return defaultDir;
44        FileSystemView fsv = this.getFileSystemView();
45        String dirName = defaultDir.getName();
46        File validParent = getFSVParent(fsv,defaultDir);
47        if(validParent!=null){
48            return new File(validParent, dirName);
49        }
50        else {
51            return new File(fsv.getHomeDirectory(), dirName);
52        }
53    }
54    /**
55     * Step back up trying to find a parent if none if found return null.
56     * null can be found if the path starts e:\ and e: does not exist
57     * @param fsv FileSystemView
58     * @param dir File
59     * @return File
60     */
61    private File getFSVParent(FileSystemView fsv,File dir){
62        File parent = dir.getParentFile();
63        if(fsv.isRoot(parent) && !parent.exists()){
64            return null;
65        }
66        if(!parent.exists() || !fsv.isFileSystem(parent)){
67            parent = getFSVParent(fsv,parent);
68        }
69        return parent;
70    }
71
72    public static void main(String[] args) {
73        DefaultingDirectoryChooser chooser = new DefaultingDirectoryChooser(CREATE_MODE);
74        chooser.setDefaultDirectory(new File("/usr/local/dibble/MyApp"));
75
76        chooser.showDialog(null,"Select");
77        System.out.println("Selected:"+chooser.getSelectedFile().getAbsolutePath());
78        System.exit(0);
79    }
80}
81