MenuBarEnhancerCocoa.java revision 9d333439029e282e9b8fec93e57ebedeb5c4016a
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 23package com.android.menubar.internal; 24 25import com.android.menubar.IMenuBarCallback; 26import com.android.menubar.IMenuBarEnhancer; 27 28import org.eclipse.swt.SWT; 29import org.eclipse.swt.internal.C; 30import org.eclipse.swt.internal.Callback; 31import org.eclipse.swt.widgets.Display; 32import org.eclipse.swt.widgets.Event; 33import org.eclipse.swt.widgets.Listener; 34import org.eclipse.swt.widgets.Menu; 35 36import java.lang.reflect.InvocationTargetException; 37import java.lang.reflect.Method; 38 39public 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 65 public ActionProctarget(IMenuBarCallback callbacks) { 66 mCallbacks = callbacks; 67 } 68 69 /** 70 * Will be called on 32bit SWT. 71 */ 72 @SuppressWarnings("unused") 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 */ 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 */ 100 public MenuBarEnhancerCocoa() { 101 } 102 103 /** 104 * Setup the About and Preferences native menut items with the 105 * given application name and links them to the callback. 106 * 107 * @param appName The application name. 108 * @param swtMenu The tools menu. Not used here. 109 * @param callbacks The callbacks invoked by the menus. 110 */ 111 public void setupMenu( 112 String appName, 113 Menu swtMenu, 114 IMenuBarCallback callbacks) { 115 116 mAppName = appName; 117 final Display display = swtMenu.getDisplay(); 118 119 // This is our callback object whose 'actionProc' method will be called 120 // when the About or Preferences menuItem is invoked. 121 ActionProctarget target = new ActionProctarget(callbacks); 122 123 try { 124 // Initialize the menuItems. 125 initialize(target); 126 } catch (Exception e) { 127 throw new IllegalStateException(e); 128 } 129 130 // Schedule disposal of callback object 131 display.disposeExec(new Runnable() { 132 public void run() { 133 invoke(mProc3Args, "dispose"); 134 } 135 }); 136 } 137 138 private void initialize(Object callbackObject) 139 throws Exception { 140 141 Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); 142 143 // Register names in objective-c. 144 if (mSelAboutMenuItemSelected == 0) { 145 mSelPreferencesMenuItemSelected = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$ 146 mSelAboutMenuItemSelected = registerName(osCls, "aboutMenuItemSelected:"); //$NON-NLS-1$ 147 } 148 149 // Create an SWT Callback object that will invoke the actionProc method 150 // of our internal callback Object. 151 mProc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$ 152 Method getAddress = Callback.class.getMethod("getAddress", new Class[0]); 153 Object object = getAddress.invoke(mProc3Args, (Object[]) null); 154 long proc3 = convertToLong(object); 155 if (proc3 == 0) { 156 SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); 157 } 158 159 Class<?> nsMenuCls = classForName("org.eclipse.swt.internal.cocoa.NSMenu"); 160 Class<?> nsMenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem"); 161 Class<?> nsStringCls = classForName("org.eclipse.swt.internal.cocoa.NSString"); 162 Class<?> nsApplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication"); 163 164 // Instead of creating a new delegate class in objective-c, 165 // just use the current SWTApplicationDelegate. An instance of this 166 // is a field of the Cocoa Display object and is already the target 167 // for the menuItems. So just get this class and add the new methods 168 // to it. 169 object = invoke(osCls, "objc_lookUpClass", new Object[] { 170 "SWTApplicationDelegate" 171 }); 172 long cls = convertToLong(object); 173 174 // Add the action callbacks for Preferences and About menu items. 175 invoke(osCls, "class_addMethod", 176 new Object[] { 177 wrapPointer(cls), 178 wrapPointer(mSelPreferencesMenuItemSelected), 179 wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ 180 invoke(osCls, "class_addMethod", 181 new Object[] { 182 wrapPointer(cls), 183 wrapPointer(mSelAboutMenuItemSelected), 184 wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ 185 186 // Get the Mac OS X Application menu. 187 Object sharedApplication = invoke(nsApplicationCls, "sharedApplication"); 188 Object mainMenu = invoke(sharedApplication, "mainMenu"); 189 Object mainMenuItem = invoke(nsMenuCls, mainMenu, "itemAtIndex", new Object[] { 190 wrapPointer(0) 191 }); 192 Object appMenu = invoke(mainMenuItem, "submenu"); 193 194 // Create the About <application-name> menu command 195 Object aboutMenuItem = 196 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { 197 wrapPointer(kAboutMenuItem) 198 }); 199 if (mAppName != null) { 200 Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { 201 "About " + mAppName 202 }); 203 invoke(nsMenuitemCls, aboutMenuItem, "setTitle", new Object[] { 204 nsStr 205 }); 206 } 207 // Rename the quit action. 208 if (mAppName != null) { 209 Object quitMenuItem = 210 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { 211 wrapPointer(kQuitMenuItem) 212 }); 213 Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { 214 "Quit " + mAppName 215 }); 216 invoke(nsMenuitemCls, quitMenuItem, "setTitle", new Object[] { 217 nsStr 218 }); 219 } 220 221 // Enable the Preferences menuItem. 222 Object prefMenuItem = 223 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { 224 wrapPointer(kPreferencesMenuItem) 225 }); 226 invoke(nsMenuitemCls, prefMenuItem, "setEnabled", new Object[] { 227 true 228 }); 229 230 // Set the action to execute when the About or Preferences menuItem is 231 // invoked. 232 // 233 // We don't need to set the target here as the current target is the 234 // SWTApplicationDelegate and we have registerd the new selectors on 235 // it. So just set the new action to invoke the selector. 236 invoke(nsMenuitemCls, prefMenuItem, "setAction", 237 new Object[] { 238 wrapPointer(mSelPreferencesMenuItemSelected) 239 }); 240 invoke(nsMenuitemCls, aboutMenuItem, "setAction", 241 new Object[] { 242 wrapPointer(mSelAboutMenuItemSelected) 243 }); 244 } 245 246 private long registerName(Class<?> osCls, String name) 247 throws IllegalArgumentException, SecurityException, IllegalAccessException, 248 InvocationTargetException, NoSuchMethodException { 249 Object object = invoke(osCls, "sel_registerName", new Object[] { 250 name 251 }); 252 return convertToLong(object); 253 } 254 255 private long convertToLong(Object object) { 256 if (object instanceof Integer) { 257 Integer i = (Integer) object; 258 return i.longValue(); 259 } 260 if (object instanceof Long) { 261 Long l = (Long) object; 262 return l.longValue(); 263 } 264 return 0; 265 } 266 267 private static Object wrapPointer(long value) { 268 Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; 269 if (PTR_CLASS == long.class) { 270 return new Long(value); 271 } else { 272 return new Integer((int) value); 273 } 274 } 275 276 private static Object invoke(Class<?> clazz, String methodName, Object[] args) { 277 return invoke(clazz, null, methodName, args); 278 } 279 280 private static Object invoke(Class<?> clazz, Object target, String methodName, Object[] args) { 281 try { 282 Class<?>[] signature = new Class<?>[args.length]; 283 for (int i = 0; i < args.length; i++) { 284 Class<?> thisClass = args[i].getClass(); 285 if (thisClass == Integer.class) 286 signature[i] = int.class; 287 else if (thisClass == Long.class) 288 signature[i] = long.class; 289 else if (thisClass == Byte.class) 290 signature[i] = byte.class; 291 else if (thisClass == Boolean.class) 292 signature[i] = boolean.class; 293 else 294 signature[i] = thisClass; 295 } 296 Method method = clazz.getMethod(methodName, signature); 297 return method.invoke(target, args); 298 } catch (Exception e) { 299 throw new IllegalStateException(e); 300 } 301 } 302 303 private Class<?> classForName(String classname) { 304 try { 305 Class<?> cls = Class.forName(classname); 306 return cls; 307 } catch (ClassNotFoundException e) { 308 throw new IllegalStateException(e); 309 } 310 } 311 312 private Object invoke(Class<?> cls, String methodName) { 313 return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null); 314 } 315 316 private Object invoke(Class<?> cls, String methodName, Class<?>[] paramTypes, 317 Object... arguments) { 318 try { 319 Method m = cls.getDeclaredMethod(methodName, paramTypes); 320 return m.invoke(null, arguments); 321 } catch (Exception e) { 322 throw new IllegalStateException(e); 323 } 324 } 325 326 private Object invoke(Object obj, String methodName) { 327 return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null); 328 } 329 330 private Object invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments) { 331 try { 332 Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes); 333 return m.invoke(obj, arguments); 334 } catch (Exception e) { 335 throw new IllegalStateException(e); 336 } 337 } 338} 339