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}