/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.ri.util; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.logging.Level; import java.util.logging.Logger; /** * MacOSAdapter helps setup the viewer to look look and feel like a native macOS application. */ public class MacOSAdapter implements InvocationHandler { private static final Logger logger = Logger.getLogger(MacOSAdapter.class.toString()); protected Object targetObject; protected Method targetMethod; protected String proxySignature; protected static Object macOSXApplication; // 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 MacOSAdapter("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 MacOSAdapter("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", boolean.class); enableAboutMethod.invoke(macOSXApplication, enableAboutMenu); } catch (Exception ex) { logger.log(Level.FINE, "MacOSAdapter could not access the About Menu", ex); } } // 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 MacOSAdapter("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", boolean.class); enablePrefsMethod.invoke(macOSXApplication, enablePrefsMenu); } catch (Exception ex) { logger.log(Level.FINE, "MacOSAdapter could not access the About Menu", ex); } } // 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 bundle's Info.plist public static void setFileHandler(Object target, Method fileHandler) { setHandler(new MacOSAdapter("handleOpenFile", target, fileHandler) { // Override MacOSAdapter.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, filename); } catch (Exception ex) { // no need to report. } } return true; } }); } // setHandler creates a Proxy object from the passed MacOSAdapter and adds it as an ApplicationListener public static void setHandler(MacOSAdapter 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", applicationListenerClass); // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener Object osxAdapterProxy = Proxy.newProxyInstance(MacOSAdapter.class.getClassLoader(), new Class[]{applicationListenerClass}, adapter); addListenerMethod.invoke(macOSXApplication, 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 logger.log(Level.FINE, "Mac OS X Adapter could not talk to EAWT", ex); } } // Each MacOSAdapter 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 MacOSAdapter(String proxySignature, Object target, Method handler) { this.proxySignature = proxySignature; this.targetObject = target; this.targetMethod = handler; } // 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()); } // 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 MacOSAdapter 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", boolean.class); // If the target method returns a boolean, use that as a hint setHandledMethod.invoke(event, handled); } catch (Exception ex) { logger.log(Level.FINE, "MacOSAdapter was unable to handle an ApplicationEvent", ex); } } } }