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 | } |