| 1 | // Jomic - a viewer for comic book archives. |
| 2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
| 3 | // |
| 4 | // This program is free software: you can redistribute it and/or modify |
| 5 | // it under the terms of the GNU General Public License as published by |
| 6 | // the Free Software Foundation, either version 3 of the License, or |
| 7 | // (at your option) any later version. |
| 8 | // |
| 9 | // This program is distributed in the hope that it will be useful, |
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | // GNU General Public License for more details. |
| 13 | // |
| 14 | // You should have received a copy of the GNU General Public License |
| 15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | package net.sf.jomic.tools; |
| 17 | |
| 18 | import java.awt.Toolkit; |
| 19 | import java.awt.event.KeyEvent; |
| 20 | import java.io.File; |
| 21 | import java.io.FileWriter; |
| 22 | import java.io.IOException; |
| 23 | import java.io.Writer; |
| 24 | import java.lang.reflect.Method; |
| 25 | import java.net.URL; |
| 26 | import java.net.URLClassLoader; |
| 27 | import java.util.Locale; |
| 28 | |
| 29 | import javax.swing.JMenuItem; |
| 30 | import javax.swing.JOptionPane; |
| 31 | import javax.swing.KeyStroke; |
| 32 | import javax.swing.UIManager; |
| 33 | |
| 34 | import net.roydesign.mac.MRJAdapter; |
| 35 | import net.sf.jomic.common.PropertyConstants; |
| 36 | |
| 37 | import org.apache.commons.logging.Log; |
| 38 | import org.apache.commons.logging.LogFactory; |
| 39 | |
| 40 | import com.centerkey.utils.BareBonesBrowserLaunch; |
| 41 | |
| 42 | /** |
| 43 | * Utility methods to simplify dealing system dependent issues. |
| 44 | * |
| 45 | * @author Thomas Aglassinger |
| 46 | */ |
| 47 | public final class SystemTools |
| 48 | { |
| 49 | private static SystemTools instance; |
| 50 | private ConsoleTools consoleTools; |
| 51 | private boolean isMacOSX; |
| 52 | private boolean isWindows; |
| 53 | private LocaleTools localeTools; |
| 54 | private Log logger; |
| 55 | |
| 56 | private SystemTools() { |
| 57 | String osName = System.getProperty("os.name").toLowerCase(); |
| 58 | |
| 59 | if (Boolean.getBoolean(PropertyConstants.SYSTEM_PROPERTY_PREFIX + PropertyConstants.IGNORE_OSX)) { |
| 60 | isMacOSX = false; |
| 61 | } else { |
| 62 | isMacOSX = osName.startsWith("mac os x"); |
| 63 | } |
| 64 | isWindows = osName.startsWith("windows"); |
| 65 | consoleTools = ConsoleTools.instance(); |
| 66 | localeTools = LocaleTools.instance(); |
| 67 | logger = LogFactory.getLog(SystemTools.class); |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * <p> |
| 72 | * |
| 73 | * Set the accelerator for the "Help" menu item.</p> <p> |
| 74 | * |
| 75 | * <b>Known bug</b> : This does not work with Mac OS X. The Help key would be Command-?, but |
| 76 | * there is no no KeyEvent for "?". There are some hacks, but they don't work for |
| 77 | * internaltional keyboards (like using Shift-/ to get a "?"). For example, on the German |
| 78 | * keyboard the "?" is Shift-ß. |
| 79 | */ |
| 80 | public void setHelpAccelerato(JMenuItem helpMenuItem) { |
| 81 | assert helpMenuItem != null; |
| 82 | KeyStroke helpKeyStroke; |
| 83 | |
| 84 | if (isMacOSX()) { |
| 85 | // FIXME: the code below should assign Command-? to Help > Help, but does not |
| 86 | helpKeyStroke = KeyStroke.getKeyStroke('?', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); |
| 87 | } else { |
| 88 | helpKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0); |
| 89 | } |
| 90 | helpMenuItem.setAccelerator(helpKeyStroke); |
| 91 | } |
| 92 | |
| 93 | public String getJavaInfo() { |
| 94 | return System.getProperty("java.version"); |
| 95 | } |
| 96 | |
| 97 | public String getLocaleInfo() { |
| 98 | Locale locale = localeTools.getLocale(); |
| 99 | |
| 100 | return locale.getDisplayName() + " (" + locale.toString() + ")"; |
| 101 | } |
| 102 | |
| 103 | public String getPlatformInfo() { |
| 104 | return System.getProperty("os.name") |
| 105 | + "-" |
| 106 | + System.getProperty("os.version") |
| 107 | + "-" |
| 108 | + System.getProperty("os.arch"); |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Is Aqua the current look and feel? |
| 113 | */ |
| 114 | public boolean isAqua() { |
| 115 | boolean result = false; |
| 116 | |
| 117 | if (isMacOSX()) { |
| 118 | String systemLook = UIManager.getSystemLookAndFeelClassName(); |
| 119 | String currentLook = UIManager.getLookAndFeel().getClass().getName(); |
| 120 | |
| 121 | result = systemLook.equals(currentLook); |
| 122 | } |
| 123 | return result; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Do we run under Mac OS X? <p> |
| 128 | * |
| 129 | * For implementation details, see <a |
| 130 | * href="http://developer.apple.com/technotes/tn2002/tn2110.html"> TN2110</a> |
| 131 | */ |
| 132 | public boolean isMacOSX() { |
| 133 | return isMacOSX; |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Do we run under some Windows derivate? |
| 138 | */ |
| 139 | public boolean isWindows() { |
| 140 | return isWindows; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Get accessor to unique instance. |
| 145 | */ |
| 146 | public static synchronized SystemTools instance() { |
| 147 | if (instance == null) { |
| 148 | instance = new SystemTools(); |
| 149 | } |
| 150 | return instance; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Open <code>url</code> in web browser. |
| 155 | */ |
| 156 | public void openURL(String url) { |
| 157 | assert url != null; |
| 158 | try { |
| 159 | if (isMacOSX()) { |
| 160 | // HACK: Ideally we would check for MRJAdapter.VERSION >= 1.0.7. |
| 161 | // Considering that a current MRJAdapter is included in the Mac OS X distribution |
| 162 | // and comparing numeric String variables is messy, such a check is omitted. |
| 163 | MRJAdapter.openURL(url); |
| 164 | } else { |
| 165 | BareBonesBrowserLaunch.openURL(url); |
| 166 | } |
| 167 | } catch (Exception error) { |
| 168 | String message = localeTools.getMessage("warnings.cannotOpenWebPage", url); |
| 169 | String title = localeTools.getMessage("dialogs.warning.title", url); |
| 170 | |
| 171 | logger.warn(message, error); |
| 172 | JOptionPane.showMessageDialog( |
| 173 | null, message, title, JOptionPane.WARNING_MESSAGE); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Reveals a file or directory in the Systems file browser. For Mac OS, this is the Finder. For |
| 179 | * Windows, this is the Explorer. |
| 180 | */ |
| 181 | public void reveal(File path) |
| 182 | throws IOException, InterruptedException { |
| 183 | if (isMacOSX()) { |
| 184 | revealOsx(path); |
| 185 | } else if (isWindows()) { |
| 186 | // TODO: Really reveal the file, not only the directory where it is located |
| 187 | File dir = path.getParentFile(); |
| 188 | |
| 189 | consoleTools.run(new String[]{"cmd", "/C", "\"start .\""}, dir, 0); |
| 190 | } else { |
| 191 | // TODO: fix portability |
| 192 | String platform = System.getProperty("os.name"); |
| 193 | String message = localeTools.getMessage("errors.revealMustBeImplemented", platform); |
| 194 | |
| 195 | throw new IllegalStateException(message); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Run script containing <code>scriptText</code>. The script is stored in a temporary file |
| 201 | * having a name with the specified <code>prefix</code> and <code>suffix</code> (without the |
| 202 | * dot). |
| 203 | */ |
| 204 | public void runScript(String prefix, String suffix, String runner, String scriptText) |
| 205 | throws IOException, InterruptedException { |
| 206 | File scriptFile = File.createTempFile(prefix, "." + suffix); |
| 207 | |
| 208 | scriptFile.deleteOnExit(); |
| 209 | |
| 210 | Writer out = new FileWriter(scriptFile); |
| 211 | |
| 212 | if (logger.isDebugEnabled()) { |
| 213 | logger.debug("run script: " + scriptFile + ":\n" + scriptText); |
| 214 | } |
| 215 | |
| 216 | out.write(scriptText); |
| 217 | out.close(); |
| 218 | |
| 219 | String[] arguments; |
| 220 | |
| 221 | if (runner == null) { |
| 222 | arguments = new String[]{scriptFile.getAbsolutePath()}; |
| 223 | |
| 224 | } else { |
| 225 | arguments = new String[]{runner, scriptFile.getAbsolutePath()}; |
| 226 | } |
| 227 | |
| 228 | consoleTools.run(arguments, null, 0); |
| 229 | if (!scriptFile.delete()) { |
| 230 | logger.warn("cannot delete temporary script file: " + scriptFile); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Reveal <code>path</code> in Finder. |
| 236 | */ |
| 237 | private void revealOsx(File path) |
| 238 | throws IOException { |
| 239 | Class nsWorkspace; |
| 240 | Exception cause = null; |
| 241 | boolean success = false; |
| 242 | |
| 243 | try { |
| 244 | // Source of this code snipplet: |
| 245 | // http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=73&t=000010 |
| 246 | File nsWorkspaceClassFile = new File("/System/Library/Java/com/apple/cocoa/application/NSWorkspace.class"); |
| 247 | |
| 248 | if (nsWorkspaceClassFile.exists()) { |
| 249 | // Mac OS X has NSWorkspace, but it is not in the classpath |
| 250 | File systemLibraryJavaFolder = new File("/System/Library/Java"); |
| 251 | ClassLoader classLoader = new URLClassLoader(new URL[]{systemLibraryJavaFolder.toURI().toURL()}); |
| 252 | |
| 253 | nsWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace", true, |
| 254 | classLoader); |
| 255 | } else { |
| 256 | nsWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace"); |
| 257 | } |
| 258 | |
| 259 | Method sharedWorkspace = nsWorkspace.getMethod("sharedWorkspace", new Class[]{}); |
| 260 | Object workspace = sharedWorkspace.invoke(null, new Object[]{}); |
| 261 | Method selectFile = nsWorkspace.getMethod("selectFile", new Class[]{String.class, |
| 262 | String.class}); |
| 263 | |
| 264 | success = ((Boolean) selectFile.invoke(workspace, new Object[]{ |
| 265 | path.getAbsolutePath(), path.getAbsolutePath()})).booleanValue(); |
| 266 | } catch (Exception error) { |
| 267 | cause = error; |
| 268 | } |
| 269 | if (!success || (cause != null)) { |
| 270 | String message = localeTools.getMessage("errors.cannotRevealFileInFinder", path.getAbsolutePath()); |
| 271 | IOException error = new IOExceptionWithCause(message, cause); |
| 272 | |
| 273 | throw error; |
| 274 | } |
| 275 | } |
| 276 | } |