package com.vistatec.ocelot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import javax.swing.JMenu; import javax.swing.KeyStroke; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Additional platform integration for Mac OSX. We load the relevant classes * via reflection to avoid explicit dependencies on the Mac-specific version * of the JDK. */ public class OSXPlatformSupport implements PlatformSupport { private static Logger LOG = LoggerFactory.getLogger(OSXPlatformSupport.class); private static Application app; public synchronized void init(final Ocelot ocelot) { try { if (app == null) { app = new Application(); // Prevent sudden termination: Method disableTerm = app.getAppClass().getDeclaredMethod("disableSuddenTermination"); disableTerm.invoke(app.getAppInstance()); } setQuitHandler(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ocelot.handleApplicationExit(); } }); setAboutHandler(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ocelot.showAbout(); } }); } catch (Exception e) { LOG.warn("Failed to initialize OS X UI features", e); } } @Override public int getPlatformKeyMask() { return KeyEvent.META_MASK; } @Override public boolean isPlatformKeyDown(KeyEvent ke) { return ke.isMetaDown(); } @Override public void setMenuMnemonics(JMenu file, JMenu view, JMenu extensions, JMenu help) { // Mac doesn't use these } @Override public KeyStroke[] getReservedKeys() { return MACOSX_RESERVED_KEYS; } /** * <ul> * <li>Command + Tab - Switch between open applications</li> * <li>Command + Shift + Tab - Switch between open applications in the reverse * direction</li> * <li>Ctrl + Tab - Switches between program groups, tabs, or document * windows</li> * <li>Ctrl + Shift + Tab - Switches between program groups, tabs, or * document windows in the reverse direction</li> * <li>Command + Option + Esc - Open Force Quit window</li> * <li>Command + Space bar - Open Spotlight Search</li> * <li>Command + Q - Quit application</li> * <li>Command + W - Close current application window</li> * <li>Command+C - Copy the selected item</li> * <li>Command+X - Cut the selected item</li> * <li>Command+V - Paste the selected item</li> * <li>Command+A - Select all items in a document or window</li> * </ul> */ private static final KeyStroke[] MACOSX_RESERVED_KEYS = { KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.META_MASK + KeyEvent.SHIFT_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, KeyEvent.META_MASK + KeyEvent.ALT_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.META_MASK) }; /** * Attach a handler to be called from the Mac OSX native * "About" menu item (as opposed to the one in Ocelot's own * menu bar.) * This is done by wrapping the ActionListener to respond to * com.apple.eawt.AboutHandler#handleAbout * * @param al action listener */ public static void setAboutHandler(final ActionListener al) { try { Class<?> aboutHandlerClass = Class.forName("com.apple.eawt.AboutHandler"); InvocationHandler ih = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("handleAbout")) { // Respond to handleAbout(com.apple.eawt.AppEvent.AboutEvent) al.actionPerformed(null); } return null; } }; app.setAppMethod("setAboutHandler", aboutHandlerClass, ih); } catch (Exception e) { LOG.warn("Failed to initialize OS X UI features", e); } } /** * Attach a handler to be called from the Mac OSX native * "Quit" menu item (as opposed to the one in Ocelot's own * menu bar.) * This is done by wrapping the ActionListener to respond * to com.apple.eawt.QuitHandler#handleQuitResquestWith * @param al action listener */ public static void setQuitHandler(final ActionListener al) { try { Class<?> quitHandlerClass = Class.forName("com.apple.eawt.QuitHandler"); InvocationHandler ih = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("handleQuitRequestWith")) { // Invoke our handler, then allow the quit to proceed. al.actionPerformed(null); Class<?> quitResponseClass = Class.forName("com.apple.eawt.QuitResponse"); if (args != null && args.length > 1 && quitResponseClass.isInstance(args[1])) { Method performQuit = quitResponseClass.getDeclaredMethod("performQuit"); performQuit.invoke(args[1]); } } return null; } }; app.setAppMethod("setQuitHandler", quitHandlerClass, ih); } catch (Exception e) { LOG.warn("Failed to initialize OS X UI features", e); } } private static class Application { private Class<?> appClass; private Object appInstance; Application() throws Exception { appClass = Class.forName("com.apple.eawt.Application"); appInstance = appClass.getDeclaredMethod("getApplication").invoke(null); } Class<?> getAppClass() { return appClass; } Object getAppInstance() { return appInstance; } void setAppMethod(String method, Class<?> handlerClass, InvocationHandler handler) throws Exception { Object wrapper = Proxy.newProxyInstance(OSXPlatformSupport.class.getClassLoader(), new Class<?>[] { handlerClass}, handler); Method m = appClass.getDeclaredMethod(method, handlerClass); m.invoke(appInstance, wrapper); } } }