001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029 /* 030 File: OSXAdapter.java 031 032 Abstract: Hooks existing preferences/about/quit functionality from an 033 existing Java app into handlers for the Mac OS X application menu. 034 Uses a Proxy object to dynamically implement the 035 com.apple.eawt.ApplicationListener interface and register it with the 036 com.apple.eawt.Application object. This allows the complete project 037 to be both built and run on any platform without any stubs or 038 placeholders. Useful for developers looking to implement Mac OS X 039 features while supporting multiple platforms with minimal impact. 040 041 Version: 2.0 042 043 Disclaimer: IMPORTANT: This Apple software is supplied to you by 044 Apple Inc. ("Apple") in consideration of your agreement to the 045 following terms, and your use, installation, modification or 046 redistribution of this Apple software constitutes acceptance of these 047 terms. If you do not agree with these terms, please do not use, 048 install, modify or redistribute this Apple software. 049 050 In consideration of your agreement to abide by the following terms, and 051 subject to these terms, Apple grants you a personal, non-exclusive 052 license, under Apple's copyrights in this original Apple software (the 053 "Apple Software"), to use, reproduce, modify and redistribute the Apple 054 Software, with or without modifications, in source and/or binary forms; 055 provided that if you redistribute the Apple Software in its entirety and 056 without modifications, you must retain this notice and the following 057 text and disclaimers in all such redistributions of the Apple Software. 058 Neither the name, trademarks, service marks or logos of Apple Inc. 059 may be used to endorse or promote products derived from the Apple 060 Software without specific prior written permission from Apple. Except 061 as expressly stated in this notice, no other rights or licenses, express 062 or implied, are granted by Apple herein, including but not limited to 063 any patent rights that may be infringed by your derivative works or by 064 other works in which the Apple Software may be incorporated. 065 066 The Apple Software is provided by Apple on an "AS IS" basis. APPLE 067 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 068 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 069 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 070 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 071 072 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 073 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 074 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 075 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 076 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 077 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 078 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 079 POSSIBILITY OF SUCH DAMAGE. 080 081 Copyright 2003-2007 Apple, Inc., All Rights Reserved 082 083 */ 084 085 package edu.wisc.ssec.mcidasv; 086 087 import java.lang.reflect.*; 088 import java.util.HashMap; 089 090 091 public class OSXAdapter implements InvocationHandler { 092 093 protected Object targetObject; 094 protected Method targetMethod; 095 protected String proxySignature; 096 097 static Object macOSXApplication; 098 099 // Pass this method an Object and Method equipped to perform application shutdown logic 100 // The method passed should return a boolean stating whether or not the quit should occur 101 public static void setQuitHandler(Object target, Method quitHandler) { 102 setHandler(new OSXAdapter("handleQuit", target, quitHandler)); 103 } 104 105 // Pass this method an Object and Method equipped to display application info 106 // They will be called when the About menu item is selected from the application menu 107 public static void setAboutHandler(Object target, Method aboutHandler) { 108 boolean enableAboutMenu = (target != null && aboutHandler != null); 109 if (enableAboutMenu) { 110 setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); 111 } 112 // If we're setting a handler, enable the About menu item by calling 113 // com.apple.eawt.Application reflectively 114 try { 115 Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class }); 116 enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) }); 117 } catch (Exception ex) { 118 System.err.println("OSXAdapter could not access the About Menu"); 119 ex.printStackTrace(); 120 } 121 } 122 123 // Pass this method an Object and a Method equipped to display application options 124 // They will be called when the Preferences menu item is selected from the application menu 125 public static void setPreferencesHandler(Object target, Method prefsHandler) { 126 boolean enablePrefsMenu = (target != null && prefsHandler != null); 127 if (enablePrefsMenu) { 128 setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); 129 } 130 // If we're setting a handler, enable the Preferences menu item by calling 131 // com.apple.eawt.Application reflectively 132 try { 133 Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class }); 134 enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) }); 135 } catch (Exception ex) { 136 System.err.println("OSXAdapter could not access the About Menu"); 137 ex.printStackTrace(); 138 } 139 } 140 141 // Pass this method an Object and a Method equipped to handle document events from the Finder 142 // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the 143 // application bundle's Info.plist 144 public static void setFileHandler(Object target, Method fileHandler) { 145 setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { 146 // Override OSXAdapter.callTarget to send information on the 147 // file to be opened 148 public boolean callTarget(Object appleEvent) { 149 if (appleEvent != null) { 150 try { 151 Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null); 152 String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null); 153 this.targetMethod.invoke(this.targetObject, new Object[] { filename }); 154 } catch (Exception ex) { 155 156 } 157 } 158 return true; 159 } 160 }); 161 } 162 163 // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener 164 public static void setHandler(OSXAdapter adapter) { 165 try { 166 Class applicationClass = Class.forName("com.apple.eawt.Application"); 167 if (macOSXApplication == null) { 168 macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null); 169 } 170 Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); 171 Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass }); 172 // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener 173 Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter); 174 addListenerMethod.invoke(macOSXApplication, new Object[] { osxAdapterProxy }); 175 } catch (ClassNotFoundException cnfe) { 176 System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); 177 } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods 178 System.err.println("Mac OS X Adapter could not talk to EAWT:"); 179 ex.printStackTrace(); 180 } 181 } 182 183 // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), 184 // the Object that will ultimately perform the task, and the Method to be called on that Object 185 protected OSXAdapter(String proxySignature, Object target, Method handler) { 186 this.proxySignature = proxySignature; 187 this.targetObject = target; 188 this.targetMethod = handler; 189 } 190 191 // Override this method to perform any operations on the event 192 // that comes with the various callbacks 193 // See setFileHandler above for an example 194 public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { 195 Object result = targetMethod.invoke(targetObject, (Object[])null); 196 if (result == null) { 197 return true; 198 } 199 return Boolean.valueOf(result.toString()).booleanValue(); 200 } 201 202 // InvocationHandler implementation 203 // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked 204 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { 205 if (isCorrectMethod(method, args)) { 206 boolean handled = callTarget(args[0]); 207 setApplicationEventHandled(args[0], handled); 208 } 209 // All of the ApplicationListener methods are void; return null regardless of what happens 210 return null; 211 } 212 213 // Compare the method that was called to the intended method when the OSXAdapter instance was created 214 // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) 215 protected boolean isCorrectMethod(Method method, Object[] args) { 216 return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); 217 } 218 219 // It is important to mark the ApplicationEvent as handled and cancel the default behavior 220 // This method checks for a boolean result from the proxy method and sets the event accordingly 221 protected void setApplicationEventHandled(Object event, boolean handled) { 222 if (event != null) { 223 try { 224 Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }); 225 // If the target method returns a boolean, use that as a hint 226 setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); 227 } catch (Exception ex) { 228 System.err.println("OSXAdapter was unable to handle an ApplicationEvent: " + event); 229 ex.printStackTrace(); 230 } 231 } 232 } 233 }