• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * History:
17  * Original code by the <a href="http://www.simidude.com/blog/2008/macify-a-swt-application-in-a-cross-platform-way/">CarbonUIEnhancer from Agynami</a>
18  * with the implementation being modified from the <a href="http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.ui.cocoa/src/org/eclipse/ui/internal/cocoa/CocoaUIEnhancer.java">org.eclipse.ui.internal.cocoa.CocoaUIEnhancer</a>,
19  * then modified by http://www.transparentech.com/opensource/cocoauienhancer to use reflection
20  * rather than 'link' to SWT cocoa, and finally modified to be usable by the SwtMenuBar project.
21  */
22 
23 package com.android.menubar.internal;
24 
25 import com.android.menubar.IMenuBarCallback;
26 import com.android.menubar.IMenuBarEnhancer;
27 
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.internal.C;
30 import org.eclipse.swt.internal.Callback;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.swt.widgets.Event;
33 import org.eclipse.swt.widgets.Listener;
34 import org.eclipse.swt.widgets.Menu;
35 
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 
39 public class MenuBarEnhancerCocoa implements IMenuBarEnhancer {
40 
41     private static final long kAboutMenuItem = 0;
42     private static final long kPreferencesMenuItem = 2;
43     // private static final long kServicesMenuItem = 4;
44     // private static final long kHideApplicationMenuItem = 6;
45     private static final long kQuitMenuItem = 10;
46 
47     static long mSelPreferencesMenuItemSelected;
48     static long mSelAboutMenuItemSelected;
49     static Callback mProc3Args;
50 
51     private String mAppName;
52 
53     /**
54      * Class invoked via the Callback object to run the about and preferences
55      * actions.
56      * <p>
57      * If you don't use JFace in your application (SWT only), change the
58      * {@link org.eclipse.jface.action.IAction}s to
59      * {@link org.eclipse.swt.widgets.Listener}s.
60      * </p>
61      */
62     private static class ActionProctarget {
63         private final IMenuBarCallback mCallbacks;
64 
ActionProctarget(IMenuBarCallback callbacks)65         public ActionProctarget(IMenuBarCallback callbacks) {
66             mCallbacks = callbacks;
67         }
68 
69         /**
70          * Will be called on 32bit SWT.
71          */
72         @SuppressWarnings("unused")
actionProc(int id, int sel, int arg0)73         public int actionProc(int id, int sel, int arg0) {
74             return (int) actionProc((long) id, (long) sel, (long) arg0);
75         }
76 
77         /**
78          * Will be called on 64bit SWT.
79          */
actionProc(long id, long sel, long arg0)80         public long actionProc(long id, long sel, long arg0) {
81             if (sel == mSelAboutMenuItemSelected) {
82                 mCallbacks.onAboutMenuSelected();
83             } else if (sel == mSelPreferencesMenuItemSelected) {
84                 mCallbacks.onPreferencesMenuSelected();
85             } else {
86                 // Unknown selection!
87             }
88             // Return value is not used.
89             return 0;
90         }
91     }
92 
93     /**
94      * Construct a new CocoaUIEnhancer.
95      *
96      * @param mAppName The name of the application. It will be used to customize
97      *            the About and Quit menu items. If you do not wish to customize
98      *            the About and Quit menu items, just pass <tt>null</tt> here.
99      */
MenuBarEnhancerCocoa()100     public MenuBarEnhancerCocoa() {
101     }
102 
getMenuBarMode()103     public MenuBarMode getMenuBarMode() {
104         return MenuBarMode.MAC_OS;
105     }
106 
107     /**
108      * Setup the About and Preferences native menut items with the
109      * given application name and links them to the callback.
110      *
111      * @param appName The application name.
112      * @param display The SWT display. Must not be null.
113      * @param callbacks The callbacks invoked by the menus.
114      */
setupMenu( String appName, Display display, IMenuBarCallback callbacks)115     public void setupMenu(
116             String appName,
117             Display display,
118             IMenuBarCallback callbacks) {
119 
120         mAppName = appName;
121 
122         // This is our callback object whose 'actionProc' method will be called
123         // when the About or Preferences menuItem is invoked.
124         ActionProctarget target = new ActionProctarget(callbacks);
125 
126         try {
127             // Initialize the menuItems.
128             initialize(target);
129         } catch (Exception e) {
130             throw new IllegalStateException(e);
131         }
132 
133         // Schedule disposal of callback object
134         display.disposeExec(new Runnable() {
135             public void run() {
136                 invoke(mProc3Args, "dispose");
137             }
138         });
139     }
140 
initialize(Object callbackObject)141     private void initialize(Object callbackObject)
142             throws Exception {
143 
144         Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS");
145 
146         // Register names in objective-c.
147         if (mSelAboutMenuItemSelected == 0) {
148             mSelPreferencesMenuItemSelected = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$
149             mSelAboutMenuItemSelected = registerName(osCls, "aboutMenuItemSelected:");             //$NON-NLS-1$
150         }
151 
152         // Create an SWT Callback object that will invoke the actionProc method
153         // of our internal callback Object.
154         mProc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$
155         Method getAddress = Callback.class.getMethod("getAddress", new Class[0]);
156         Object object = getAddress.invoke(mProc3Args, (Object[]) null);
157         long proc3 = convertToLong(object);
158         if (proc3 == 0) {
159             SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
160         }
161 
162         Class<?> nsMenuCls        = classForName("org.eclipse.swt.internal.cocoa.NSMenu");
163         Class<?> nsMenuitemCls    = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem");
164         Class<?> nsStringCls      = classForName("org.eclipse.swt.internal.cocoa.NSString");
165         Class<?> nsApplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication");
166 
167         // Instead of creating a new delegate class in objective-c,
168         // just use the current SWTApplicationDelegate. An instance of this
169         // is a field of the Cocoa Display object and is already the target
170         // for the menuItems. So just get this class and add the new methods
171         // to it.
172         object = invoke(osCls, "objc_lookUpClass", new Object[] {
173             "SWTApplicationDelegate"
174         });
175         long cls = convertToLong(object);
176 
177         // Add the action callbacks for Preferences and About menu items.
178         invoke(osCls, "class_addMethod",
179                 new Object[] {
180                     wrapPointer(cls),
181                     wrapPointer(mSelPreferencesMenuItemSelected),
182                     wrapPointer(proc3), "@:@"}); //$NON-NLS-1$
183         invoke(osCls,  "class_addMethod",
184                 new Object[] {
185                     wrapPointer(cls),
186                     wrapPointer(mSelAboutMenuItemSelected),
187                     wrapPointer(proc3), "@:@"}); //$NON-NLS-1$
188 
189         // Get the Mac OS X Application menu.
190         Object sharedApplication = invoke(nsApplicationCls, "sharedApplication");
191         Object mainMenu = invoke(sharedApplication, "mainMenu");
192         Object mainMenuItem = invoke(nsMenuCls, mainMenu, "itemAtIndex", new Object[] {
193             wrapPointer(0)
194         });
195         Object appMenu = invoke(mainMenuItem, "submenu");
196 
197         // Create the About <application-name> menu command
198         Object aboutMenuItem =
199                 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
200                     wrapPointer(kAboutMenuItem)
201                 });
202         if (mAppName != null) {
203             Object nsStr = invoke(nsStringCls, "stringWith", new Object[] {
204                 "About " + mAppName
205             });
206             invoke(nsMenuitemCls, aboutMenuItem, "setTitle", new Object[] {
207                 nsStr
208             });
209         }
210         // Rename the quit action.
211         if (mAppName != null) {
212             Object quitMenuItem =
213                     invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
214                         wrapPointer(kQuitMenuItem)
215                     });
216             Object nsStr = invoke(nsStringCls, "stringWith", new Object[] {
217                 "Quit " + mAppName
218             });
219             invoke(nsMenuitemCls, quitMenuItem, "setTitle", new Object[] {
220                 nsStr
221             });
222         }
223 
224         // Enable the Preferences menuItem.
225         Object prefMenuItem =
226                 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
227                     wrapPointer(kPreferencesMenuItem)
228                 });
229         invoke(nsMenuitemCls, prefMenuItem, "setEnabled", new Object[] {
230             true
231         });
232 
233         // Set the action to execute when the About or Preferences menuItem is
234         // invoked.
235         //
236         // We don't need to set the target here as the current target is the
237         // SWTApplicationDelegate and we have registerd the new selectors on
238         // it. So just set the new action to invoke the selector.
239         invoke(nsMenuitemCls, prefMenuItem, "setAction",
240                 new Object[] {
241                     wrapPointer(mSelPreferencesMenuItemSelected)
242                 });
243         invoke(nsMenuitemCls, aboutMenuItem, "setAction",
244                 new Object[] {
245                     wrapPointer(mSelAboutMenuItemSelected)
246                 });
247     }
248 
registerName(Class<?> osCls, String name)249     private long registerName(Class<?> osCls, String name)
250             throws IllegalArgumentException, SecurityException, IllegalAccessException,
251             InvocationTargetException, NoSuchMethodException {
252         Object object = invoke(osCls, "sel_registerName", new Object[] {
253             name
254         });
255         return convertToLong(object);
256     }
257 
convertToLong(Object object)258     private long convertToLong(Object object) {
259         if (object instanceof Integer) {
260             Integer i = (Integer) object;
261             return i.longValue();
262         }
263         if (object instanceof Long) {
264             Long l = (Long) object;
265             return l.longValue();
266         }
267         return 0;
268     }
269 
wrapPointer(long value)270     private static Object wrapPointer(long value) {
271         Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class;
272         if (PTR_CLASS == long.class) {
273             return new Long(value);
274         } else {
275             return new Integer((int) value);
276         }
277     }
278 
invoke(Class<?> clazz, String methodName, Object[] args)279     private static Object invoke(Class<?> clazz, String methodName, Object[] args) {
280         return invoke(clazz, null, methodName, args);
281     }
282 
invoke(Class<?> clazz, Object target, String methodName, Object[] args)283     private static Object invoke(Class<?> clazz, Object target, String methodName, Object[] args) {
284         try {
285             Class<?>[] signature = new Class<?>[args.length];
286             for (int i = 0; i < args.length; i++) {
287                 Class<?> thisClass = args[i].getClass();
288                 if (thisClass == Integer.class)
289                     signature[i] = int.class;
290                 else if (thisClass == Long.class)
291                     signature[i] = long.class;
292                 else if (thisClass == Byte.class)
293                     signature[i] = byte.class;
294                 else if (thisClass == Boolean.class)
295                     signature[i] = boolean.class;
296                 else
297                     signature[i] = thisClass;
298             }
299             Method method = clazz.getMethod(methodName, signature);
300             return method.invoke(target, args);
301         } catch (Exception e) {
302             throw new IllegalStateException(e);
303         }
304     }
305 
classForName(String classname)306     private Class<?> classForName(String classname) {
307         try {
308             Class<?> cls = Class.forName(classname);
309             return cls;
310         } catch (ClassNotFoundException e) {
311             throw new IllegalStateException(e);
312         }
313     }
314 
invoke(Class<?> cls, String methodName)315     private Object invoke(Class<?> cls, String methodName) {
316         return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null);
317     }
318 
invoke(Class<?> cls, String methodName, Class<?>[] paramTypes, Object... arguments)319     private Object invoke(Class<?> cls, String methodName, Class<?>[] paramTypes,
320             Object... arguments) {
321         try {
322             Method m = cls.getDeclaredMethod(methodName, paramTypes);
323             return m.invoke(null, arguments);
324         } catch (Exception e) {
325             throw new IllegalStateException(e);
326         }
327     }
328 
invoke(Object obj, String methodName)329     private Object invoke(Object obj, String methodName) {
330         return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null);
331     }
332 
invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments)333     private Object invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments) {
334         try {
335             Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes);
336             return m.invoke(obj, arguments);
337         } catch (Exception e) {
338             throw new IllegalStateException(e);
339         }
340     }
341 }
342