001/**
002 * Please feel free to use any fragment of the code in this file that you need in your own
003 * work. As far as I am concerned, it's in the public domain. No permission is necessary
004 * or required. Credit is always appreciated if you use a large chunk or base a
005 * significant product on one of my examples, but that's not required either.
006 * 
007 * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
008 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
009 * PURPOSE.
010 * 
011 * --- Joseph A. Huwaldt
012 */
013package jahuwaldt.swing;
014
015import java.io.*;
016import java.util.Locale;
017import static java.util.Objects.isNull;
018import static java.util.Objects.nonNull;
019import javax.swing.JFrame;
020import javax.swing.JRootPane;
021
022
023
024/**
025 * A set of utilities that are used by my programs when running under MacOS only. These
026 * static methods provide Mac specific functionality but all the methods in it can be
027 * called safely from any platform. On non-Mac systems, the worst that will happen when
028 * calling these methods is nothing and sometimes appropriate non-Mac behavior is
029 * provided.
030 * 
031 * <p> Modified by: Joseph A. Huwaldt </p>
032 * 
033 * @author Joseph A. Huwaldt Date: September 3, 2000
034 * @version December 3, 2023
035 */
036public class MacOSUtilities {
037
038        private static final boolean IS_MACOS;
039
040    static {
041        String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
042        IS_MACOS = (OS.contains("mac") || OS.contains("darwin"));
043    }
044    
045        /**
046        *  Returns true if this program is running in any MacOS 8/9/X environment,
047        *  false is returned otherwise.
048        **/
049        public static boolean isMacOS() {
050                return IS_MACOS;
051        }
052
053        /**
054        *  Returns the a File reference to the specified resource in the MacOS X application
055        *  bundle's Resource directory.  This method takes advantage of the application
056        *  bundle's used by MacOS X to hide resources from the user.
057        *  If not running on a MacOS X system, then the resource is searched for in three
058        *  locations.  1st the directory that the application is running in.  2nd the
059        *  default application name supplied is combined with
060        *  ".app/Contents/Resources/" + resourceName and searched for in the current directory.
061        *  This is how a MacOS X application bundle would appear on a non-Mac system.
062        *  Third, the default application name is combined with ".app/Contents/Resources/" + resourceName
063        *  and searched for in the install directory (if it can be determined).
064        *
065        *  @param  resourceName   The name of the resource file.
066        *  @param  defaultAppName The default application name to use if not running on MacOS.
067        *  @throws FileNotFoundException if the specified resource file could not be found.
068        **/
069        public static File getResource(String resourceName, String defaultAppName) throws FileNotFoundException {
070
071        //      First look for the resource in the application directory.
072        File file = new File(resourceName);
073
074        if (!file.exists()) {
075
076            //  Try looking in a MacOS X style application bundle in current (working) directory.
077            String sep = System.getProperty("file.separator");
078            StringBuilder path = new StringBuilder(defaultAppName);
079            path.append(".app");
080            path.append(sep);
081            path.append("Contents");
082            path.append(sep);
083            path.append("Resources");
084            path.append(sep);
085            path.append(resourceName);
086            file = new File(path.toString());
087
088            //  Try looking for MacOS X style application bundle in install directory (if known).
089            if (!file.exists()) {
090                String installPath = System.getProperty("lax.root.install.dir");
091                if (installPath == null || installPath.equals(""))
092                    installPath = System.getProperty("user.dir");
093
094                installPath = installPath.replace(File.separatorChar, '/');
095                if (!installPath.endsWith("/"))
096                    path.insert(0, "/");
097                path.insert(0, installPath);
098                file = new File(path.toString());
099
100                //      Couldn't find the resource file anywhere.
101                if (!file.exists())
102                    throw new FileNotFoundException(path.toString());
103            }
104
105        }
106                
107                return file;
108        }
109        
110        /**
111        *  Returns the a File reference to the specified resource in the specified
112        *  sub-directory of the application bundle's Resource directory.  This
113        *  method takes advantage of the application bundle's used by MacOS X to
114        *  hide resources from the user.
115        *  If not running on a MacOS X system, then the resource is searched for in three
116        *  locations.  1st the directory that the application is running in.  2nd the
117        *  default application name supplied is combined with
118        *  ".app/Contents/Resources/" + resourceName and searched for in the current directory.
119        *  This is how a MacOS X application bundle would appear on a non-Mac system.
120        *  Third, the default application name is combined with ".app/Contents/Resources/" + resourceName
121        *  and searched for in the install directory (if it can be determined).
122        *
123        *  @param  resourceName   The name of the resource file.
124        *  @param  subDirName     The name of the sub-directory under Resources.
125        *  @param  defaultAppName The default application name to use if not running on MacOS.
126        *  @throws FileNotFoundException if the specified resource file could not be found.
127        **/
128        public static File getResource(String resourceName, String subDirName, String defaultAppName)
129                                                                                                                        throws FileNotFoundException {
130
131        //      First look for the resource in the application directory.
132        String sep = System.getProperty("file.separator");
133        StringBuffer path = new StringBuffer(subDirName);
134        path.append(sep);
135        path.append(resourceName);
136        File file = new File(path.toString());
137
138        if (!file.exists()) {
139
140            //  Try looking in a MacOS X style application bundle in current (working) directory.
141            path = new StringBuffer(defaultAppName);
142            path.append(".app");
143            path.append(sep);
144            path.append("Contents");
145            path.append(sep);
146            path.append("Resources");
147            path.append(sep);
148            path.append(subDirName);
149            path.append(sep);
150            path.append(resourceName);
151            file = new File(path.toString());
152
153            //  Try looking for MacOS X style application bundle in install directory (if known).
154            if (!file.exists()) {
155                String installPath = System.getProperty("lax.root.install.dir");
156                if (installPath == null || installPath.equals(""))
157                    installPath = System.getProperty("user.dir");
158
159                installPath = installPath.replace(File.separatorChar, '/');
160                if (!installPath.endsWith("/"))
161                    path.insert(0, "/");
162                path.insert(0, installPath);
163                file = new File(path.toString());
164
165                //      Couldn't find the resource file anywhere.
166                if (!file.exists())
167                    throw new FileNotFoundException(path.toString());
168            }
169        }
170                
171                return file;
172        }
173        
174        
175    /**
176     * Sets the "Window.documentModified" client property on the frame's root pane to the
177     * value indicated. On a Mac, the modified-document mark is standardized as a black
178     * dot in the center of the red close button on the title bar. The proxy icon, if
179     * present, is also dimmed if <code>modified</code> is set to true.
180     *
181     * @param window The JFrame to have the modified mark set on.
182     * @param modified Set to true to indicate that the document represented by frame is
183     * modified. Set to false to indicate that it has not been modified since it was last
184     * created, read in, or saved.
185     */
186    public static void setModifiedMark(JFrame window, boolean modified) {
187        JRootPane root = window.getRootPane();
188        root.putClientProperty("Window.documentModified", modified);
189    }
190    
191    /**
192     * Return the state of the "Window.documentModified" client property for the specified
193     * frame.  On a Mac, the modified-document mark is standardized as a black
194     * dot in the center of the red close button on the title bar.  It is used to indicate
195     * that a document has been modified.
196     * 
197     * @param window  The frame to have the document modified client property read from.
198     */
199    public static Boolean isMarkedModified(JFrame window) {
200        JRootPane root = window.getRootPane();
201        Object prop = root.getClientProperty("Window.documentModified");
202        if (nonNull(prop) && prop instanceof Boolean) {
203            return (Boolean)prop;
204        }
205        return Boolean.FALSE;
206    }
207
208    /**
209     * Set's the title bar proxy icon. On a Mac, document windows have a proxy icon to the
210     * left of the title text. Users can click and drag the icon to other windows or
211     * right-click/command-click to get a path to the document.
212     *
213     * @param window The JFrame to have a proxy icon added to the title bar.
214     * @param file The file who's icon should be added to the title bar. The Mac
215     * automatically finds the correct icon for the file or folder.
216     */
217    public static void setProxyIcon(JFrame window, File file) {
218        JRootPane root = window.getRootPane();
219        root.putClientProperty("Window.documentFile", file);
220    }
221    
222    /**
223     * Return the contents of the "Window.documentFile" client property (which can
224     * return null).  On a Mac, document windows have a proxy icon to the
225     * left of the title text. Users can click and drag the icon to other windows or
226     * right-click/command-click to get a path to the document.  The file who's icon
227     * is used as the proxy icon, is retrieved from a frame with this method.
228     * 
229     * @param window The JFrame to have the proxy icon file retrieved.
230     * @return The file who's icon is used as the proxy icon or null if there is none.
231     */
232    public static File getProxyIcon(JFrame window) {
233        JRootPane root = window.getRootPane();
234        return (File)root.getClientProperty("Window.documentFile");
235    }
236}