/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * History: * 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> * 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>, * then modified by http://www.transparentech.com/opensource/cocoauienhancer to use reflection * rather than 'link' to SWT cocoa, and finally modified to be usable by the SwtMenuBar project. */ package com.android.menubar.internal; import com.android.menubar.IMenuBarCallback; import com.android.menubar.IMenuBarEnhancer; import org.eclipse.swt.SWT; import org.eclipse.swt.internal.C; import org.eclipse.swt.internal.Callback; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MenuBarEnhancerCocoa implements IMenuBarEnhancer { private static final long kAboutMenuItem = 0; private static final long kPreferencesMenuItem = 2; // private static final long kServicesMenuItem = 4; // private static final long kHideApplicationMenuItem = 6; private static final long kQuitMenuItem = 10; static long mSelPreferencesMenuItemSelected; static long mSelAboutMenuItemSelected; static Callback mProc3Args; private String mAppName; /** * Class invoked via the Callback object to run the about and preferences * actions. * <p> * If you don't use JFace in your application (SWT only), change the * {@link org.eclipse.jface.action.IAction}s to * {@link org.eclipse.swt.widgets.Listener}s. * </p> */ private static class ActionProctarget { private final IMenuBarCallback mCallbacks; public ActionProctarget(IMenuBarCallback callbacks) { mCallbacks = callbacks; } /** * Will be called on 32bit SWT. */ @SuppressWarnings("unused") public int actionProc(int id, int sel, int arg0) { return (int) actionProc((long) id, (long) sel, (long) arg0); } /** * Will be called on 64bit SWT. */ public long actionProc(long id, long sel, long arg0) { if (sel == mSelAboutMenuItemSelected) { mCallbacks.onAboutMenuSelected(); } else if (sel == mSelPreferencesMenuItemSelected) { mCallbacks.onPreferencesMenuSelected(); } else { // Unknown selection! } // Return value is not used. return 0; } } /** * Construct a new CocoaUIEnhancer. * * @param mAppName The name of the application. It will be used to customize * the About and Quit menu items. If you do not wish to customize * the About and Quit menu items, just pass <tt>null</tt> here. */ public MenuBarEnhancerCocoa() { } public MenuBarMode getMenuBarMode() { return MenuBarMode.MAC_OS; } /** * Setup the About and Preferences native menut items with the * given application name and links them to the callback. * * @param appName The application name. * @param display The SWT display. Must not be null. * @param callbacks The callbacks invoked by the menus. */ public void setupMenu( String appName, Display display, IMenuBarCallback callbacks) { mAppName = appName; // This is our callback object whose 'actionProc' method will be called // when the About or Preferences menuItem is invoked. ActionProctarget target = new ActionProctarget(callbacks); try { // Initialize the menuItems. initialize(target); } catch (Exception e) { throw new IllegalStateException(e); } // Schedule disposal of callback object display.disposeExec(new Runnable() { public void run() { invoke(mProc3Args, "dispose"); } }); } private void initialize(Object callbackObject) throws Exception { Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); // Register names in objective-c. if (mSelAboutMenuItemSelected == 0) { mSelPreferencesMenuItemSelected = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$ mSelAboutMenuItemSelected = registerName(osCls, "aboutMenuItemSelected:"); //$NON-NLS-1$ } // Create an SWT Callback object that will invoke the actionProc method // of our internal callback Object. mProc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$ Method getAddress = Callback.class.getMethod("getAddress", new Class[0]); Object object = getAddress.invoke(mProc3Args, (Object[]) null); long proc3 = convertToLong(object); if (proc3 == 0) { SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); } Class<?> nsMenuCls = classForName("org.eclipse.swt.internal.cocoa.NSMenu"); Class<?> nsMenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem"); Class<?> nsStringCls = classForName("org.eclipse.swt.internal.cocoa.NSString"); Class<?> nsApplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication"); // Instead of creating a new delegate class in objective-c, // just use the current SWTApplicationDelegate. An instance of this // is a field of the Cocoa Display object and is already the target // for the menuItems. So just get this class and add the new methods // to it. object = invoke(osCls, "objc_lookUpClass", new Object[] { "SWTApplicationDelegate" }); long cls = convertToLong(object); // Add the action callbacks for Preferences and About menu items. invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(mSelPreferencesMenuItemSelected), wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(mSelAboutMenuItemSelected), wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ // Get the Mac OS X Application menu. Object sharedApplication = invoke(nsApplicationCls, "sharedApplication"); Object mainMenu = invoke(sharedApplication, "mainMenu"); Object mainMenuItem = invoke(nsMenuCls, mainMenu, "itemAtIndex", new Object[] { wrapPointer(0) }); Object appMenu = invoke(mainMenuItem, "submenu"); // Create the About <application-name> menu command Object aboutMenuItem = invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { wrapPointer(kAboutMenuItem) }); if (mAppName != null) { Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { "About " + mAppName }); invoke(nsMenuitemCls, aboutMenuItem, "setTitle", new Object[] { nsStr }); } // Rename the quit action. if (mAppName != null) { Object quitMenuItem = invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { wrapPointer(kQuitMenuItem) }); Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { "Quit " + mAppName }); invoke(nsMenuitemCls, quitMenuItem, "setTitle", new Object[] { nsStr }); } // Enable the Preferences menuItem. Object prefMenuItem = invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { wrapPointer(kPreferencesMenuItem) }); invoke(nsMenuitemCls, prefMenuItem, "setEnabled", new Object[] { true }); // Set the action to execute when the About or Preferences menuItem is // invoked. // // We don't need to set the target here as the current target is the // SWTApplicationDelegate and we have registered the new selectors on // it. So just set the new action to invoke the selector. invoke(nsMenuitemCls, prefMenuItem, "setAction", new Object[] { wrapPointer(mSelPreferencesMenuItemSelected) }); invoke(nsMenuitemCls, aboutMenuItem, "setAction", new Object[] { wrapPointer(mSelAboutMenuItemSelected) }); } private long registerName(Class<?> osCls, String name) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Object object = invoke(osCls, "sel_registerName", new Object[] { name }); return convertToLong(object); } private long convertToLong(Object object) { if (object instanceof Integer) { Integer i = (Integer) object; return i.longValue(); } if (object instanceof Long) { Long l = (Long) object; return l.longValue(); } return 0; } private static Object wrapPointer(long value) { Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; if (PTR_CLASS == long.class) { return new Long(value); } else { return new Integer((int) value); } } private static Object invoke(Class<?> clazz, String methodName, Object[] args) { return invoke(clazz, null, methodName, args); } private static Object invoke(Class<?> clazz, Object target, String methodName, Object[] args) { try { Class<?>[] signature = new Class<?>[args.length]; for (int i = 0; i < args.length; i++) { Class<?> thisClass = args[i].getClass(); if (thisClass == Integer.class) signature[i] = int.class; else if (thisClass == Long.class) signature[i] = long.class; else if (thisClass == Byte.class) signature[i] = byte.class; else if (thisClass == Boolean.class) signature[i] = boolean.class; else signature[i] = thisClass; } Method method = clazz.getMethod(methodName, signature); return method.invoke(target, args); } catch (Exception e) { throw new IllegalStateException(e); } } private Class<?> classForName(String classname) { try { Class<?> cls = Class.forName(classname); return cls; } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } } private Object invoke(Class<?> cls, String methodName) { return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null); } private Object invoke(Class<?> cls, String methodName, Class<?>[] paramTypes, Object... arguments) { try { Method m = cls.getDeclaredMethod(methodName, paramTypes); return m.invoke(null, arguments); } catch (Exception e) { throw new IllegalStateException(e); } } private Object invoke(Object obj, String methodName) { return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null); } private Object invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments) { try { Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes); return m.invoke(obj, arguments); } catch (Exception e) { throw new IllegalStateException(e); } } }