001/*******************************************************************************
002
003        File:           FolderDialog.java
004        Author:         Steve Roy <steve@sillybit.com>
005                                
006        Part of MRJ Adapter, a unified API for easy integration of Mac OS specific
007        functionality within your cross-platform Java application.
008        
009        This library is open source and can be modified and/or distributed under
010        the terms of the Artistic License.
011        <http://mrjadapter.dev.java.net/license.html>
012        
013        Change History:
014        03/06/03        Created this file - Steve
015        03/25/03        Moved to the net.roydesign.ui package, modified to use the
016                                apple.awt.fileDialogForDirectories property with MRJ 4, added
017                                the getInitialMode() method, removed getFolder() because
018                                it's redundant with getDirectory(), removed the filename filter
019                                which was irrelevant - Steve
020        12/16/03    Fixed getDirectory() to check if super.getFile() is null before
021                                trying to build a path with it - Steve
022    01/01/23    Copied out of defunct MRJAdapter into my private library and replaced
023                references to "MRJAdapter.mrjVersion" with MacOSUtilities.isMacOS().
024    03/22/25    Fixed an error with setVisible() throwing a NullPointerException on Windows.
025
026*******************************************************************************/
027
028package jahuwaldt.swing;
029
030import java.awt.FileDialog;
031import java.awt.Frame;
032import java.io.File;
033import static java.util.Objects.isNull;
034import static java.util.Objects.nonNull;
035import java.util.Properties;
036
037/**
038 * A folder dialog is a modal file dialog to specially select a folder on
039 * disk. This class takes advantage of a little know trick in Apple's VMs to
040 * show a real folder dialog, with a Choose button and all. However, there is
041 * no such thing on other platforms, where this class employs the usual
042 * kludge which is to show a Save dialog. If you would rather use the Swing
043 * JFileChooser, go right ahead.
044 * 
045 * Based on: MRJ Adapter 1.2, Modified by Joseph A. Huwaldt
046 * @version March 22, 2025
047 */
048public class FolderDialog extends FileDialog {
049
050    /**
051     * Whether the <code>setMode()</code> method should check calls or not.
052     */
053    private boolean modeCheckingEnabled = false;
054
055    /**
056     * Construct a folder dialog with the given parent frame.
057     *
058     * @param parent the parent frame
059     */
060    public FolderDialog(Frame parent) {
061        this(parent, "");
062    }
063
064    /**
065     * Construct a folder dialog with the given parent frame and title.
066     *
067     * @param parent the parent frame
068     * @param title  the title of the dialog
069     */
070    @SuppressWarnings("OverridableMethodCallInConstructor")
071    public FolderDialog(Frame parent, String title) {
072        super(parent, title, getInitialMode());
073        if (!MacOSUtilities.isMacOS())
074            setFile("-");
075        modeCheckingEnabled = true;
076    }
077
078    /**
079     * Get the file of this file dialog, which in the case of this class, is always an
080     * empty string ("") unless the user has canceled where the return value will be
081     * <code>null</code>.
082     *
083     * @return an empty string if a directory was selected, or <code>null</code>
084     */
085    @Override
086    public String getFile() {
087        return nonNull(super.getFile()) ? "" : null;
088    }
089
090    /**
091     * Get the directory of this file dialog.
092     *
093     * @return the directory of the dialog, or null
094     */
095    @Override
096    public String getDirectory() {
097        String path = super.getDirectory();
098        if (isNull(path))
099            return null;
100        if (MacOSUtilities.isMacOS() && nonNull(super.getFile()))
101            return new File(path, super.getFile()).getPath();
102        return path;
103    }
104
105    /**
106     * Set the mode of the dialog. This method is overriden because it doesn't make sense
107     * in the context of an application dialog to allow selection of the mode. It will
108     * throw an error if you try to call it.
109     *
110     * @param mode the mode
111     */
112    @Override
113    public void setMode(int mode) {
114        if (modeCheckingEnabled)
115            throw new Error("can't set mode");
116        super.setMode(mode);
117    }
118
119    /**
120     * Shows or hides this Dialog depending on the value of parameter 'visible'. Since the
121     * dialog is modal, this method will not return until either the user dismisses the
122     * dialog or you make it invisible yourself via <code>setVisible(false)</code> or
123     * <code>dispose()</code>.
124     */
125    @Override
126    public void setVisible(boolean visible) {
127        // Set the system property required by MacOS.
128        String prop = "apple.awt.fileDialogForDirectories";
129        Object oldValue = null;
130        Properties props = System.getProperties();
131                if (MacOSUtilities.isMacOS()) {                 
132            oldValue = props.get(prop);
133            props.put(prop, "true");
134        }
135        
136        // Do the usual thing
137        super.setVisible(visible);
138
139        // Reset the system property.
140        if (MacOSUtilities.isMacOS()) {
141            if (isNull(oldValue))
142                props.remove(prop);
143            else
144                props.put(prop, oldValue);
145        }
146    }
147
148    /**
149     * Perform the preparatory setup for the folder dialog and return the value to use for
150     * the mode of the dialog. This method is called by the constructor.
151     *
152     * @return the mode value to use
153     */
154    private static int getInitialMode() {
155        if (MacOSUtilities.isMacOS())
156            return LOAD;
157        return SAVE;
158    }
159}