Skip to content
Snippets Groups Projects
OSXAdapter.java 7.51 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    package uk.ac.sanger.artemis.components;
    
    import java.lang.reflect.*;
    
    public class OSXAdapter implements InvocationHandler 
    
    {
      protected Object targetObject;
      protected Method targetMethod;
      protected String proxySignature;
      static Object macOSXApplication;
    
      // Each OSXAdapter has the name of the EAWT method it intends to listen for
      // (handleAbout, for example),
      // the Object that will ultimately perform the task, and the Method to be
      // called on that Object
      protected OSXAdapter(String proxySignature, Object target, Method handler)
      {
        this.proxySignature = proxySignature;
        this.targetObject = target;
        this.targetMethod = handler;
      }
    
      // Pass this method an Object and Method equipped to perform application
      // shutdown logic
      // The method passed should return a boolean stating whether or not the quit
      // should occur
      public static void setQuitHandler(Object target, Method quitHandler)
      {
        setHandler(new OSXAdapter("handleQuit", target, quitHandler));
      }
    
      // Pass this method an Object and Method equipped to display application info
      // They will be called when the About menu item is selected from the
      // application menu
      public static void setAboutHandler(Object target, Method aboutHandler)
      {
        boolean enableAboutMenu = (target != null && aboutHandler != null);
        if(enableAboutMenu)
          setHandler(new OSXAdapter("handleAbout", target, aboutHandler));
    
        // If we're setting a handler, enable the About menu item by calling
        // com.apple.eawt.Application reflectively
        try
        {
          Method enableAboutMethod = macOSXApplication.getClass()
              .getDeclaredMethod("setEnabledAboutMenu",
                  new Class[] { boolean.class });
          enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean
              .valueOf(enableAboutMenu) });
        }
        catch(Exception ex)
        {
          System.err.println("OSXAdapter could not access the About Menu");
          ex.printStackTrace();
        }
      }
    
      // Pass this method an Object and a Method equipped to display application
      // options
      // They will be called when the Preferences menu item is selected from the
      // application menu
      public static void setPreferencesHandler(Object target, Method prefsHandler)
      {
        boolean enablePrefsMenu = (target != null && prefsHandler != null);
        if(enablePrefsMenu)
          setHandler(new OSXAdapter("handlePreferences", target, prefsHandler));
    
        // If we're setting a handler, enable the Preferences menu item by calling
        // com.apple.eawt.Application reflectively
        try
        {
          Method enablePrefsMethod = macOSXApplication.getClass()
              .getDeclaredMethod("setEnabledPreferencesMenu",
                  new Class[] { boolean.class });
          enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean
              .valueOf(enablePrefsMenu) });
        }
        catch(Exception ex)
        {
          System.err.println("OSXAdapter could not access the About Menu");
          ex.printStackTrace();
        }
      }
    
      // Pass this method an Object and a Method equipped to handle document 
      // events from the Finder. Documents are registered with the Finder via 
      // the CFBundleDocumentTypes dictionary in the application Info.plist
      public static void setFileHandler(Object target, Method fileHandler)
      {
        setHandler(new OSXAdapter("handleOpenFile", target, fileHandler)
        {
          // Override OSXAdapter.callTarget to send information on the
          // file to be opened
          public boolean callTarget(Object appleEvent)
          {
            if(appleEvent != null)
            {
              try
              {
                Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod(
                    "getFilename", (Class[]) null);
                String filename = (String) getFilenameMethod.invoke(appleEvent,
                    (Object[]) null);
                this.targetMethod.invoke(this.targetObject,
                    new Object[] { filename });
              }
              catch(Exception ex)
              {
                ex.printStackTrace();
              }
            }
            return true;
          }
        });
      }
        
      // setHandler creates a Proxy object from the passed OSXAdapter and adds it as
      // an ApplicationListener
      public static void setHandler(OSXAdapter adapter)
      {
        try
        {
          Class applicationClass = Class.forName("com.apple.eawt.Application");
          if(macOSXApplication == null)
            macOSXApplication = applicationClass.getConstructor((Class[]) null)
                .newInstance((Object[]) null);
    
          Class applicationListenerClass = Class
              .forName("com.apple.eawt.ApplicationListener");
          Method addListenerMethod = applicationClass.getDeclaredMethod(
              "addApplicationListener", new Class[] { applicationListenerClass });
          // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
          Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class
              .getClassLoader(), new Class[] { applicationListenerClass }, adapter);
          addListenerMethod.invoke(macOSXApplication,
              new Object[] { osxAdapterProxy });
        }
        catch(ClassNotFoundException cnfe)
        {
          System.err.println(
              "This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled ("
                  + cnfe + ")");
        }
        catch(Exception ex)
        { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods
          System.err.println("Mac OS X Adapter could not talk to EAWT:");
          ex.printStackTrace();
        }
      }
    
      // Override this method to perform any operations on the event 
      // that comes with the various callbacks
      // See setFileHandler above for an example
      public boolean callTarget(Object appleEvent)
          throws InvocationTargetException, IllegalAccessException
      {
        Object result = targetMethod.invoke(targetObject, (Object[]) null);
        if(result == null)
          return true;
    
        return Boolean.valueOf(result.toString()).booleanValue();
      }
    
      // InvocationHandler implementation
      // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable
      {
        if(isCorrectMethod(method, args))
        {
          boolean handled = callTarget(args[0]);
          setApplicationEventHandled(args[0], handled);
        }
        // All of the ApplicationListener methods are void; return null regardless of what happens
        return null;
      }
    
      // Compare the method that was called to the intended method when the OSXAdapter instance was created
      // (e.g. handleAbout, handleQuit, handleOpenFile, etc.)
      protected boolean isCorrectMethod(Method method, Object[] args)
      {
        return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
      }
    
      // It is important to mark the ApplicationEvent as handled and cancel the default behavior
      // This method checks for a boolean result from the proxy method and sets the event accordingly
      protected void setApplicationEventHandled(Object event, boolean handled)
      {
        if(event != null)
        {
          try
          {
            Method setHandledMethod = event.getClass().getDeclaredMethod(
                "setHandled", new Class[] { boolean.class });
            // If the target method returns a boolean, use that as a hint
            setHandledMethod.invoke(event,
                new Object[] { Boolean.valueOf(handled) });
          }
          catch(Exception ex)
          {
            System.err
                .println("OSXAdapter was unable to handle an ApplicationEvent: "
                    + event);
            ex.printStackTrace();
          }
        }
      }
    }