package ie.dcu.swt; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.eclipse.jface.action.IAction; 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.Listener; /** * Provide a hook to connecting the Preferences, About and Quit menu items of * the Mac OS X Application menu when using the SWT Cocoa bindings. *
* This code does not require the Cocoa SWT JAR in order to be compiled as it * uses reflection to access the Cocoa specific API methods. It does, however, * depend on JFace (for IAction), but you could easily modify the code to use * SWT Listeners instead in order to use this class in SWT only applications. *
*/ public class CocoaUIEnhancer { private static final int kAboutMenuItem = 0; private static final int kPreferencesMenuItem = 2; private static final int kQuitMenuItem = 10; static long sel_toolbarButtonClicked_; static long sel_preferencesMenuItemSelected_; static long sel_aboutMenuItemSelected_; static Callback proc3Args; private final String appName; /** * Construct a new CocoaUIEnhancer. * * @param appName * 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 null here. */ public CocoaUIEnhancer(String appName) { this.appName = appName; } /** * Hook the given Listener to the Mac OS X application Quit menu and the * IActions to the About and Preferences menus. * * @param display * The Display to use. * @param quitListener * The listener to invoke when the Quit menu is invoked. * @param aboutAction * The action to run when the About menu is invoked. * @param preferencesAction * The action to run when the Preferences menu is invoked. */ public void hookApplicationMenu(Display display, Listener quitListener, final IAction aboutAction, final IAction preferencesAction) { Object target = new Object() { // For the 32 bit JVM @SuppressWarnings("unused") int actionProc(int id, int sel, int arg) { return (int) actionProc((long) id, (long) sel, (long) arg); } // For the 64 bit JVM long actionProc(long id, long sel, long arg) { if (sel == sel_aboutMenuItemSelected_) { aboutAction.run(); } else if (sel == sel_preferencesMenuItemSelected_) { preferencesAction.run(); } return 99; } }; try { // Initialize the menuItems. initialize(target); } catch (Exception e) { throw new IllegalStateException(e); } // Connect the quit/exit menu. if (!display.isDisposed()) { display.addListener(SWT.Close, quitListener); } // Schedule disposal of callback object display.disposeExec(new Runnable() { public void run() { invoke(proc3Args, "dispose"); } }); } private void initialize(Object callbackObject) throws Exception { Class> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); // Register names in objective-c. if (sel_toolbarButtonClicked_ == 0) { // sel_toolbarButtonClicked_ = registerName( osCls, "toolbarButtonClicked:" ); //$NON-NLS-1$ sel_preferencesMenuItemSelected_ = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$ sel_aboutMenuItemSelected_ = registerName(osCls, "aboutMenuItemSelected:"); //$NON-NLS-1$ } // Create an SWT Callback object that will invoke the actionProc method // of our internal callbackObject. proc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$ Method getAddress = Callback.class .getMethod("getAddress", new Class[0]); Object object = getAddress.invoke(proc3Args, (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(sel_preferencesMenuItemSelected_), wrapPointer(proc3), "@:@" }); //$NON-NLS-1$ invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_aboutMenuItemSelected_), 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