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 }