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 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 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 */ 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 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 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 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 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 279 private static Object invoke(Class<?> clazz, String methodName, Object[] args) { 280 return invoke(clazz, null, methodName, args); 281 } 282 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 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 315 private Object invoke(Class<?> cls, String methodName) { 316 return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null); 317 } 318 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 329 private Object invoke(Object obj, String methodName) { 330 return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null); 331 } 332 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