You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Apple.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. /*
  2. * Copyright (c) 2006-2011 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.ui_swing;
  23. import com.dmdirc.ServerManager;
  24. import com.dmdirc.actions.ActionManager;
  25. import com.dmdirc.actions.CoreActionType;
  26. import com.dmdirc.actions.interfaces.ActionType;
  27. import com.dmdirc.addons.ui_swing.components.menubar.MenuBar;
  28. import com.dmdirc.commandparser.commands.global.NewServer;
  29. import com.dmdirc.config.IdentityManager;
  30. import com.dmdirc.interfaces.ActionListener;
  31. import com.dmdirc.logger.ErrorLevel;
  32. import com.dmdirc.logger.Logger;
  33. import java.awt.Toolkit;
  34. import java.awt.event.ActionEvent;
  35. import java.lang.reflect.Field;
  36. import java.lang.reflect.InvocationHandler;
  37. import java.lang.reflect.InvocationTargetException;
  38. import java.lang.reflect.Method;
  39. import java.lang.reflect.Proxy;
  40. import java.net.URI;
  41. import java.net.URISyntaxException;
  42. import java.util.ArrayList;
  43. import java.util.List;
  44. import javax.swing.JMenu;
  45. import javax.swing.JMenuItem;
  46. import javax.swing.UIManager;
  47. /**
  48. * Integrate DMDirc with OS X better.
  49. */
  50. public final class Apple implements InvocationHandler, ActionListener {
  51. /**
  52. * Dummy interface for ApplicationEvent from the Apple UI on non-Apple
  53. * platforms.
  54. * http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/ApplicationEvent.html
  55. */
  56. public interface ApplicationEvent {
  57. /**
  58. * Provides the filename associated with a particular AppleEvent.
  59. *
  60. * @return The filename associated with a particular AppleEvent.
  61. */
  62. String getFilename();
  63. /**
  64. * Whether or not this event is handled.
  65. *
  66. * @return True if the event is handled, false otherwise
  67. */
  68. boolean isHandled();
  69. /**
  70. * Sets the handled state of this event.
  71. *
  72. * @param handled The new 'handled' state for this event.
  73. */
  74. void setHandled(boolean handled);
  75. /**
  76. * Retrieves the source of this event.
  77. *
  78. * @return This event's source
  79. */
  80. Object getSource();
  81. /**
  82. * Get a string representation of this object.
  83. *
  84. * @return A string representation of this object.
  85. */
  86. @Override
  87. String toString();
  88. }
  89. /** The singleton instance of Apple. */
  90. private static Apple me;
  91. /** The "Application" object used to do stuff on OS X. */
  92. private static Object application;
  93. /** The "NSApplication" object used to do cocoa stuff on OS X. */
  94. private static Object nsApplication;
  95. /** Are we listening? */
  96. private boolean isListener = false;
  97. /** The MenuBar for the application. */
  98. private MenuBar menuBar = null;
  99. /** Has the CLIENT_OPENED action been called? */
  100. private boolean clientOpened = false;
  101. /** Store any addresses that are opened before CLIENT_OPENED. */
  102. private final List<URI> addresses = new ArrayList<URI>();
  103. /**
  104. * Get the "Apple" instance.
  105. *
  106. * @return Apple instance.
  107. */
  108. public static Apple getApple() {
  109. synchronized (Apple.class) {
  110. if (me == null) {
  111. me = new Apple();
  112. }
  113. return me;
  114. }
  115. }
  116. /**
  117. * Create the Apple class.
  118. * <p>This attempts to:</p>
  119. *
  120. * <ul>
  121. * <li>load the JNI library</li>
  122. * <li>register the callback</li>
  123. * <li>register a CLIENT_OPENED listener</li>
  124. * </ul>
  125. */
  126. private Apple() {
  127. if (isApple()) {
  128. try {
  129. System.loadLibrary("DMDirc-Apple"); //NOPMD
  130. registerOpenURLCallback();
  131. ActionManager.addListener(this, CoreActionType.CLIENT_OPENED);
  132. } catch (UnsatisfiedLinkError ule) {
  133. Logger.userError(ErrorLevel.MEDIUM,
  134. "Unable to load JNI library.", ule);
  135. }
  136. }
  137. }
  138. /**
  139. * Get the "Application" object.
  140. *
  141. * @return Object that on OSX will be an "Application"
  142. */
  143. public static Object getApplication() {
  144. synchronized (Apple.class) {
  145. if (isApple() && application == null) {
  146. try {
  147. final Class<?> app = Class.forName(
  148. "com.apple.eawt.Application");
  149. final Method method = app.getMethod("getApplication",
  150. new Class[0]);
  151. application = method.invoke(null, new Object[0]);
  152. } catch (ClassNotFoundException ex) {
  153. application = null;
  154. } catch (NoSuchMethodException ex) {
  155. application = null;
  156. } catch (SecurityException ex) {
  157. application = null;
  158. } catch (IllegalAccessException ex) {
  159. application = null;
  160. } catch (IllegalArgumentException ex) {
  161. application = null;
  162. } catch (InvocationTargetException ex) {
  163. application = null;
  164. }
  165. }
  166. return application;
  167. }
  168. }
  169. /**
  170. * Get the "NSApplication" object.
  171. *
  172. * @return Object that on OSX will be an "NSApplication"
  173. */
  174. public static Object getNSApplication() {
  175. synchronized (Apple.class) {
  176. if (isApple() && nsApplication == null) {
  177. try {
  178. final Class<?> app = Class.forName(
  179. "com.apple.cocoa.application.NSApplication");
  180. final Method method = app.getMethod("sharedApplication",
  181. new Class[0]);
  182. nsApplication = method.invoke(null, new Object[0]);
  183. } catch (ClassNotFoundException ex) {
  184. nsApplication = null;
  185. } catch (NoSuchMethodException ex) {
  186. nsApplication = null;
  187. } catch (IllegalAccessException ex) {
  188. nsApplication = null;
  189. } catch (InvocationTargetException ex) {
  190. nsApplication = null;
  191. }
  192. }
  193. return nsApplication;
  194. }
  195. }
  196. /**
  197. * Are we on OS X?
  198. *
  199. * @return true if we are running on OS X
  200. */
  201. public static boolean isApple() {
  202. return (System.getProperty("mrj.version") != null);
  203. }
  204. /**
  205. * Are we using the OS X look and feel?
  206. *
  207. * @return true if we are using the OS X look and feel
  208. */
  209. public static boolean isAppleUI() {
  210. return isApple() && UIManager.getLookAndFeel().getClass().getName().
  211. equals("apple.laf.AquaLookAndFeel");
  212. }
  213. /**
  214. * Set some OS X only UI settings.
  215. */
  216. public void setUISettings() {
  217. if (!isApple()) {
  218. return;
  219. }
  220. // Set some Apple OS X related stuff from http://tinyurl.com/6xwuld
  221. final String aaText = IdentityManager.getGlobalConfig().getOptionBool(
  222. "ui", "antialias") ? "on" : "off";
  223. System.setProperty("apple.awt.antialiasing", aaText);
  224. System.setProperty("apple.awt.textantialiasing", aaText);
  225. System.setProperty("apple.awt.showGrowBox", "true");
  226. System.setProperty("com.apple.mrj.application.apple.menu.about.name",
  227. "DMDirc");
  228. System.setProperty("apple.laf.useScreenMenuBar", "true");
  229. System.setProperty("com.apple.mrj.application.growbox.intrudes",
  230. "false");
  231. System.setProperty("com.apple.mrj.application.live-resize", "true");
  232. }
  233. /**
  234. * Request user attention (Bounce the dock).
  235. *
  236. * @param isCritical If this is false, the dock icon only bounces once,
  237. * otherwise it will bounce until clicked on.
  238. */
  239. public void requestUserAttention(final boolean isCritical) {
  240. if (!isApple()) {
  241. return;
  242. }
  243. try {
  244. final Field type = isCritical ? getNSApplication().getClass().
  245. getField("UserAttentionRequestCritical") : getNSApplication().
  246. getClass().getField("Informational");
  247. final Method method = getNSApplication().getClass().getMethod(
  248. "requestUserAttention", new Class[]{Integer.TYPE});
  249. method.invoke(getNSApplication(), new Object[]{type.get(null)});
  250. } catch (NoSuchFieldException ex) {
  251. Logger.userError(ErrorLevel.LOW, "Unable to find OS X classes");
  252. } catch (NoSuchMethodException ex) {
  253. Logger.userError(ErrorLevel.LOW, "Unable to find OS X classes");
  254. } catch (IllegalAccessException ex) {
  255. Logger.userError(ErrorLevel.LOW, "Unable to find OS X classes");
  256. } catch (InvocationTargetException ex) {
  257. Logger.userError(ErrorLevel.LOW, "Unable to find OS X classes");
  258. }
  259. }
  260. /**
  261. * Set this up as a listener for the Apple Events.
  262. *
  263. * @return True if the listener was added, else false.
  264. */
  265. public boolean setListener() {
  266. if (!isApple() || isListener) {
  267. return false;
  268. }
  269. try {
  270. final Class listenerClass = Class.forName(
  271. "com.apple.eawt.ApplicationListener");
  272. final Object listener = Proxy.newProxyInstance(getClass().
  273. getClassLoader(), new Class[]{listenerClass}, this);
  274. Method method = getApplication().getClass().getMethod(
  275. "addApplicationListener", new Class[]{listenerClass});
  276. method.invoke(getApplication(), listener);
  277. isListener = true;
  278. method = getApplication().getClass().getMethod(
  279. "setEnabledPreferencesMenu", new Class[]{Boolean.TYPE});
  280. method.invoke(getApplication(), new Object[]{Boolean.TRUE});
  281. method =
  282. getApplication().getClass().getMethod("setEnabledAboutMenu",
  283. new Class[]{Boolean.TYPE});
  284. method.invoke(getApplication(), new Object[]{Boolean.TRUE});
  285. return true;
  286. } catch (ClassNotFoundException ex) {
  287. return false;
  288. } catch (NoSuchMethodException ex) {
  289. return false;
  290. } catch (IllegalAccessException ex) {
  291. return false;
  292. } catch (InvocationTargetException ex) {
  293. return false;
  294. }
  295. }
  296. /**
  297. * {@inheritDoc}
  298. *
  299. * @throws Throwable Throws stuff on errors
  300. */
  301. @Override
  302. public Object invoke(final Object proxy, final Method method,
  303. final Object[] args) throws Throwable {
  304. if (!isApple()) {
  305. return null;
  306. }
  307. try {
  308. final ApplicationEvent event = (ApplicationEvent) Proxy.
  309. newProxyInstance(getClass().getClassLoader(), new Class[]{
  310. ApplicationEvent.class}, new InvocationHandler() {
  311. /** {@inheritDoc} */
  312. @Override
  313. public Object invoke(final Object p, final Method m,
  314. final Object[] a) throws Throwable {
  315. return args[0].getClass().getMethod(m.getName(), m.
  316. getParameterTypes()).invoke(args[0], a);
  317. }
  318. });
  319. final Method thisMethod = this.getClass().getMethod(
  320. method.getName(), new Class[]{ApplicationEvent.class});
  321. return thisMethod.invoke(this, event);
  322. } catch (NoSuchMethodException e) {
  323. if (method.getName().equals("equals") && args.length == 1) {
  324. return Boolean.valueOf(proxy == args[0]);
  325. }
  326. }
  327. return null;
  328. }
  329. /**
  330. * Set the MenuBar.
  331. * This will unset all menu mnemonics aswell if on the OSX ui.
  332. *
  333. * @param newMenuBar MenuBar to use to send events to,
  334. */
  335. public void setMenuBar(final MenuBar newMenuBar) {
  336. this.menuBar = newMenuBar;
  337. if (!isAppleUI()) {
  338. return;
  339. }
  340. for (int i = 0; i < menuBar.getMenuCount(); i++) {
  341. final JMenu menu = menuBar.getMenu(i);
  342. if (menu == null) {
  343. continue;
  344. }
  345. menu.setMnemonic(0);
  346. for (int j = 0; j < menu.getItemCount(); j++) {
  347. final JMenuItem menuItem = menu.getItem(j);
  348. if (menuItem != null) {
  349. menuItem.setMnemonic(0);
  350. }
  351. }
  352. }
  353. }
  354. /**
  355. * Handle an event using the menuBar.
  356. *
  357. * @param name The name of the event according to the menubar
  358. * @param event The ApplicationEvent we are handingle
  359. */
  360. public void handleMenuBarEvent(final String name,
  361. final ApplicationEvent event) {
  362. if (!isApple() || menuBar == null) {
  363. return;
  364. }
  365. event.setHandled(true);
  366. final ActionEvent actionEvent = new ActionEvent(this,
  367. ActionEvent.ACTION_PERFORMED, name);
  368. Toolkit.getDefaultToolkit().getSystemEventQueue()
  369. .postEvent(actionEvent);
  370. }
  371. /**
  372. * This is called when Quit is selected from the Application menu.
  373. *
  374. * @param event an ApplicationEvent object
  375. */
  376. public void handleQuit(final ApplicationEvent event) {
  377. handleMenuBarEvent("Exit", event);
  378. }
  379. /**
  380. * This is called when About is selected from the Application menu.
  381. *
  382. * @param event an ApplicationEvent object
  383. */
  384. public void handleAbout(final ApplicationEvent event) {
  385. handleMenuBarEvent("About", event);
  386. }
  387. /**
  388. * This is called when Preferences is selected from the Application menu.
  389. *
  390. * @param event an ApplicationEvent object
  391. */
  392. public void handlePreferences(final ApplicationEvent event) {
  393. handleMenuBarEvent("Preferences", event);
  394. }
  395. /**
  396. * This is called when the Application is opened.
  397. *
  398. * @param event an ApplicationEvent object
  399. */
  400. public void handleOpenApplication(final ApplicationEvent event) {
  401. //We don't currently support this
  402. }
  403. /**
  404. * This is called when the application is asked to open a file.
  405. *
  406. * @param event an ApplicationEvent object
  407. */
  408. public void handleOpenFile(final ApplicationEvent event) {
  409. //We don't currently support this
  410. }
  411. /**
  412. * This is called when asked to print.
  413. *
  414. * @param event an ApplicationEvent object
  415. */
  416. public void handlePrintFile(final ApplicationEvent event) {
  417. //We don't currently support this
  418. }
  419. /**
  420. * This is called when the application is reopened.
  421. *
  422. * @param event an ApplicationEvent object
  423. */
  424. public void handleReopenApplication(final ApplicationEvent event) {
  425. //We don't currently support this
  426. }
  427. /** {@inheritDoc} */
  428. @Override
  429. public void processEvent(final ActionType type, final StringBuffer format,
  430. final Object... arguments) {
  431. if (type == CoreActionType.CLIENT_OPENED) {
  432. synchronized (addresses) {
  433. clientOpened = true;
  434. for (URI addr : addresses) {
  435. ServerManager.getServerManager().connectToAddress(addr);
  436. }
  437. addresses.clear();
  438. }
  439. }
  440. }
  441. /**
  442. * Callback from JNI library.
  443. * If called before the client has finished opening, the URL will be added
  444. * to a list that will be connected to once the CLIENT_OPENED action is
  445. * called. Otherwise we connect right away.
  446. *
  447. * @param url The irc url to connect to.
  448. */
  449. public void handleOpenURL(final String url) {
  450. if (!isApple()) {
  451. return;
  452. }
  453. synchronized (addresses) {
  454. try {
  455. final URI addr = NewServer.getURI(url);
  456. if (clientOpened) {
  457. // When the JNI callback is called there is no
  458. // ContextClassLoader set, which causes an NPE in
  459. // IconManager if no servers have been connected to yet.
  460. if (Thread.currentThread().getContextClassLoader()
  461. == null) {
  462. Thread.currentThread().setContextClassLoader(
  463. ClassLoader.getSystemClassLoader());
  464. }
  465. ServerManager.getServerManager().connectToAddress(addr);
  466. } else {
  467. addresses.add(addr);
  468. }
  469. } catch (URISyntaxException iae) {
  470. //Do nothing
  471. }
  472. }
  473. }
  474. /**
  475. * Register the getURL Callback.
  476. *
  477. * @return 0 on success, 1 on failure.
  478. */
  479. private synchronized native int registerOpenURLCallback();
  480. }