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.nonNull; 018import javax.swing.JFrame; 019import javax.swing.JRootPane; 020 021/** 022 * A set of utilities that are used by my programs when running under MacOS only. These 023 * static methods provide Mac specific functionality but all the methods in it can be 024 * called safely from any platform. On non-Mac systems, the worst that will happen when 025 * calling these methods is nothing and sometimes appropriate non-Mac behavior is 026 * provided. 027 * 028 * <p> Modified by: Joseph A. Huwaldt </p> 029 * 030 * @author Joseph A. Huwaldt Date: September 3, 2000 031 * @version February 23, 2025 032 */ 033public class MacOSUtilities { 034 035 private static final boolean IS_MACOS; 036 037 static { 038 String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); 039 IS_MACOS = (OS.contains("mac") || OS.contains("darwin")); 040 } 041 042 /** 043 * Returns true if this program is running in any MacOS 8/9/X environment, false is 044 * returned otherwise. 045 * 046 * @return <code>true</code> if this program is running in any MacOS environment. 047 */ 048 public static boolean isMacOS() { 049 return IS_MACOS; 050 } 051 052 /** 053 * Returns the a File reference to the specified resource in the MacOS application 054 * bundle's Resource directory. This method takes advantage of the application 055 * bundle's used by MacOS to hide resources from the user. If not running on a MacOS 056 * system, then the resource is searched for in three locations. 1st the directory 057 * that the application is running in. 2nd the default application name supplied is 058 * combined with ".app/Contents/Resources/" + resourceName and searched for in the 059 * current directory. This is how a MacOS application bundle would appear on a non-Mac 060 * system. Third, the default application name is combined with 061 * ".app/Contents/Resources/" + resourceName and searched for in the install directory 062 * (if it can be determined). 063 * 064 * @param resourceName The name of the resource file. 065 * @param defaultAppName The default application name to use if not running on MacOS. 066 * @return A File reference to the specified resource in the MacOS X application 067 * bundle's Resource directory. 068 * @throws FileNotFoundException if the specified resource file could not be found. 069 */ 070 public static File getResource(String resourceName, String defaultAppName) throws FileNotFoundException { 071 072 // First look for the resource in the application directory. 073 File file = new File(resourceName); 074 075 if (!file.exists()) { 076 077 // Try looking in a MacOS style application bundle in current (working) directory. 078 String sep = System.getProperty("file.separator"); 079 StringBuilder path = new StringBuilder(defaultAppName); 080 path.append(".app"); 081 path.append(sep); 082 path.append("Contents"); 083 path.append(sep); 084 path.append("Resources"); 085 path.append(sep); 086 path.append(resourceName); 087 file = new File(path.toString()); 088 089 // Try looking for MacOS style application bundle in install directory (if known). 090 if (!file.exists()) { 091 String installPath = System.getProperty("lax.root.install.dir"); 092 if (installPath == null || installPath.equals("")) 093 installPath = System.getProperty("user.dir"); 094 095 installPath = installPath.replace(File.separatorChar, '/'); 096 if (!installPath.endsWith("/")) 097 path.insert(0, "/"); 098 path.insert(0, installPath); 099 file = new File(path.toString()); 100 101 // Couldn't find the resource file anywhere. 102 if (!file.exists()) 103 throw new FileNotFoundException(path.toString()); 104 } 105 106 } 107 108 return file; 109 } 110 111 /** 112 * Returns the a File reference to the specified resource in the specified 113 * sub-directory of the application bundle's Resource directory. This method takes 114 * advantage of the application bundle's used by MacOS to hide resources from the 115 * user. If not running on a MacOS system, then the resource is searched for in three 116 * locations. 1st the directory that the application is running in. 2nd the default 117 * application name supplied is combined with ".app/Contents/Resources/" + 118 * resourceName and searched for in the current directory. This is how a MacOS 119 * application bundle would appear on a non-Mac system. Third, the default application 120 * name is combined with ".app/Contents/Resources/" + resourceName and searched for in 121 * 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 * @return A File reference to the specified resource in the MacOS X application 127 * bundle's Resource directory. 128 * @throws FileNotFoundException if the specified resource file could not be found. 129 */ 130 public static File getResource(String resourceName, String subDirName, String defaultAppName) 131 throws FileNotFoundException { 132 133 // First look for the resource in the application directory. 134 String sep = System.getProperty("file.separator"); 135 StringBuffer path = new StringBuffer(subDirName); 136 path.append(sep); 137 path.append(resourceName); 138 File file = new File(path.toString()); 139 140 if (!file.exists()) { 141 142 // Try looking in a MacOS style application bundle in current (working) directory. 143 path = new StringBuffer(defaultAppName); 144 path.append(".app"); 145 path.append(sep); 146 path.append("Contents"); 147 path.append(sep); 148 path.append("Resources"); 149 path.append(sep); 150 path.append(subDirName); 151 path.append(sep); 152 path.append(resourceName); 153 file = new File(path.toString()); 154 155 // Try looking for MacOS style application bundle in install directory (if known). 156 if (!file.exists()) { 157 String installPath = System.getProperty("lax.root.install.dir"); 158 if (installPath == null || installPath.equals("")) 159 installPath = System.getProperty("user.dir"); 160 161 installPath = installPath.replace(File.separatorChar, '/'); 162 if (!installPath.endsWith("/")) 163 path.insert(0, "/"); 164 path.insert(0, installPath); 165 file = new File(path.toString()); 166 167 // Couldn't find the resource file anywhere. 168 if (!file.exists()) 169 throw new FileNotFoundException(path.toString()); 170 } 171 } 172 173 return file; 174 } 175 176 /** 177 * Return a reference to the MacOS application support folder. This method throws a 178 * <code>FileNotFoundException</code> on other platforms, which is the same as what 179 * this method does on MacOS when the folder can't be found. 180 * 181 * @param appName The name of this application. 182 * @param create <code>true</code> if the folder should be created if it doesn't 183 * exist. 184 * @return the application support folder object, or null 185 * @exception FileNotFoundException when the folder can't be found 186 */ 187 public static File appSupportFolder(String appName, boolean create) throws FileNotFoundException { 188 if (!isMacOS()) 189 throw new FileNotFoundException(); 190 191 // Get the user's home directory path. 192 File home = new File(System.getProperty("user.home")); 193 194 // Construct the path to the Application Support folder for this application. 195 File appSpt = new File(home, "Library/Application Support"); 196 if (!appSpt.exists() || !appSpt.canWrite()) 197 throw new FileNotFoundException("Can't find \"Library/Application Support\" folder."); 198 199 // Construct the application support folder for this application. 200 File folder = new File(appSpt, appName); 201 202 // If the folder doesn't exist, create it. 203 if (create && !folder.exists()) 204 folder.mkdir(); 205 206 return folder; 207 } 208 209 /** 210 * Sets the "Window.documentModified" client property on the frame's root pane to the 211 * value indicated. On a Mac, the modified-document mark is standardized as a black 212 * dot in the center of the red close button on the title bar. The proxy icon, if 213 * present, is also dimmed if <code>modified</code> is set to true. 214 * 215 * @param window The JFrame to have the modified mark set on. 216 * @param modified Set to true to indicate that the document represented by frame is 217 * modified. Set to false to indicate that it has not been modified 218 * since it was last created, read in, or saved. 219 */ 220 public static void setModifiedMark(JFrame window, boolean modified) { 221 JRootPane root = window.getRootPane(); 222 root.putClientProperty("Window.documentModified", modified); 223 } 224 225 /** 226 * Return the state of the "Window.documentModified" client property for the specified 227 * frame. On a Mac, the modified-document mark is standardized as a black dot in the 228 * center of the red close button on the title bar. It is used to indicate that a 229 * document has been modified. 230 * 231 * @param window The frame to have the document modified client property read from. 232 * @return The state of the "Window.documentModified" client property for the 233 * specified frame. 234 */ 235 public static boolean isMarkedModified(JFrame window) { 236 JRootPane root = window.getRootPane(); 237 Object prop = root.getClientProperty("Window.documentModified"); 238 if (nonNull(prop) && prop instanceof Boolean) { 239 return (boolean) prop; 240 } 241 return Boolean.FALSE; 242 } 243 244 /** 245 * Set's the title bar proxy icon. On a Mac, document windows have a proxy icon to the 246 * left of the title text. Users can click and drag the icon to other windows or 247 * right-click/command-click to get a path to the document. 248 * 249 * @param window The JFrame to have a proxy icon added to the title bar. 250 * @param file The file who's icon should be added to the title bar. The Mac 251 * automatically finds the correct icon for the file or folder. 252 */ 253 public static void setProxyIcon(JFrame window, File file) { 254 JRootPane root = window.getRootPane(); 255 root.putClientProperty("Window.documentFile", file); 256 } 257 258 /** 259 * Return the contents of the "Window.documentFile" client property (which can return 260 * null). On a Mac, document windows have a proxy icon to the left of the title text. 261 * Users can click and drag the icon to other windows or right-click/command-click to 262 * get a path to the document. The file who's icon is used as the proxy icon, is 263 * retrieved from a frame with this method. 264 * 265 * @param window The JFrame to have the proxy icon file retrieved. 266 * @return The file who's icon is used as the proxy icon or null if there is none. 267 */ 268 public static File getProxyIcon(JFrame window) { 269 JRootPane root = window.getRootPane(); 270 return (File) root.getClientProperty("Window.documentFile"); 271 } 272}