package eu.jucy.gui.mac; /******************************************************************************* * Copyright (c) 2008, 2009 Adobe Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Adobe Systems, Inc. - initial API and implementation * IBM Corporation - cleanup *******************************************************************************/ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.MissingResourceException; import java.util.ResourceBundle; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.NotEnabledException; import org.eclipse.core.commands.NotHandledException; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.runtime.IProduct; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.action.ActionContributionItem; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IMenuManager; import org.eclipse.swt.SWT; import org.eclipse.swt.internal.C; import org.eclipse.swt.internal.Callback; import org.eclipse.swt.internal.cocoa.NSApplication; import org.eclipse.swt.internal.cocoa.NSButton; import org.eclipse.swt.internal.cocoa.NSControl; import org.eclipse.swt.internal.cocoa.NSMenu; import org.eclipse.swt.internal.cocoa.NSMenuItem; import org.eclipse.swt.internal.cocoa.NSString; import org.eclipse.swt.internal.cocoa.NSToolbar; import org.eclipse.swt.internal.cocoa.NSView; import org.eclipse.swt.internal.cocoa.NSWindow; import org.eclipse.swt.internal.cocoa.OS; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IStartup; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.WorkbenchWindow; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.statushandlers.StatusManager; /** * The CocoaUIEnhancer provides the standard "About" and "Preference" menu items * and links them to the corresponding workbench commands. * This must be done in a MacOS X fragment because SWT doesn't provide an abstraction * for the (MacOS X only) application menu and we have to use MacOS specific natives. * The fragment is for the org.eclipse.ui plug-in because we need access to the * Workbench "About" and "Preference" actions. * * @noreference this class is not intended to be referenced by any client. * @since 1.0 */ public class CocoaUIEnhancer implements IStartup { private static final int kAboutMenuItem = 0; private static final int kPreferencesMenuItem = 2; private static final int kServicesMenuItem = 4; private static final int kHideApplicationMenuItem = 6; private static final int kQuitMenuItem = 10; static long sel_toolbarButtonClicked_; static long sel_preferencesMenuItemSelected_; static long sel_aboutMenuItemSelected_; private static final long NSWindowToolbarButton = 3; /* This callback is not freed */ static Callback proc3Args; static final byte[] SWT_OBJECT = {'S', 'W', 'T', '_', 'O', 'B', 'J', 'E', 'C', 'T', '\0'}; private void init() throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { // These should either move out of Display or be accessible to this class. byte[] types = {'*','\0'}; int size = C.PTR_SIZEOF, align = C.PTR_SIZEOF == 4 ? 2 : 3; Class clazz = CocoaUIEnhancer.class; proc3Args = new Callback(clazz, "actionProc", 3); //$NON-NLS-1$ //call getAddress Method getAddress = Callback.class.getMethod("getAddress", new Class[0]); Object object = getAddress.invoke(proc3Args, null); long proc3 = convertToLong(object); if (proc3 == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); //call objc_allocateClassPair Field field = OS.class.getField("class_NSObject"); Object fieldObj = field.get(OS.class); object = invokeMethod(OS.class, "objc_allocateClassPair", new Object[] { fieldObj, "SWTCocoaEnhancerDelegate", wrapPointer(0) }); long cls = convertToLong(object); invokeMethod(OS.class, "class_addIvar", new Object[] { wrapPointer(cls), SWT_OBJECT, wrapPointer(size), new Byte((byte) align), types }); // Add the action callback invokeMethod( OS.class, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_toolbarButtonClicked_), wrapPointer(proc3), "@:@" }); //$NON-NLS-1$ invokeMethod(OS.class, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_preferencesMenuItemSelected_), wrapPointer(proc3), "@:@" }); //$NON-NLS-1$ invokeMethod( OS.class, "class_addMethod", new Object[] { wrapPointer(cls), wrapPointer(sel_aboutMenuItemSelected_), wrapPointer(proc3), "@:@" }); //$NON-NLS-1$ invokeMethod(OS.class, "objc_registerClassPair", new Object[] { wrapPointer(cls) }); } SWTCocoaEnhancerDelegate delegate; private long delegateJniRef; /** * Class that is able to intercept and handle OS events from the toolbar and menu. * * @since 3.1 */ private static final String RESOURCE_BUNDLE = "Messages"; //$NON-NLS-1$ private String fAboutActionName; private String fQuitActionName; private String fHideActionName; /** * Default constructor */ public CocoaUIEnhancer() { IProduct product = Platform.getProduct(); String productName = null; if (product != null) productName = product.getName(); ResourceBundle resourceBundle = ResourceBundle.getBundle(RESOURCE_BUNDLE); try { if (productName != null) { String format = resourceBundle.getString("AboutAction.format"); //$NON-NLS-1$ if (format != null) fAboutActionName= MessageFormat.format(format, new Object[] { productName } ); } if (fAboutActionName == null) fAboutActionName = resourceBundle.getString("AboutAction.name"); //$NON-NLS-1$ } catch (MissingResourceException e) { log(e); } if (fAboutActionName == null) fAboutActionName = "About"; //$NON-NLS-1$ if (productName != null) { try { // prime the format Hide <app name> String format = resourceBundle.getString("HideAction.format"); //$NON-NLS-1$ if (format != null) fHideActionName = MessageFormat.format(format, new Object[] { productName }); } catch (MissingResourceException e) { log(e); } try { // prime the format Quit <app name> String format = resourceBundle.getString("QuitAction.format"); //$NON-NLS-1$ if (format != null) fQuitActionName = MessageFormat.format(format, new Object[] { productName }); } catch (MissingResourceException e) { log(e); } } try { if (sel_toolbarButtonClicked_ == 0) { sel_toolbarButtonClicked_ = registerName("toolbarButtonClicked:"); //$NON-NLS-1$ sel_preferencesMenuItemSelected_ = registerName("preferencesMenuItemSelected:"); //$NON-NLS-1$ sel_aboutMenuItemSelected_ = registerName("aboutMenuItemSelected:"); //$NON-NLS-1$ init(); } } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } private long registerName(String name) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Class clazz = OS.class; Object object = invokeMethod(clazz, "sel_registerName", new Object[] {name}); return convertToLong(object); } /* (non-Javadoc) * @see org.eclipse.ui.IStartup#earlyStartup() */ public void earlyStartup() { final Display display = Display.getDefault(); display.syncExec(new Runnable() { public void run() { // DCClient.logger.info("in startup"); try { delegate = new SWTCocoaEnhancerDelegate(); delegate.alloc().init(); //call OS.NewGlobalRef Method method = OS.class.getMethod("NewGlobalRef", new Class[] { Object.class }); Object object = method.invoke(OS.class, new Object[] {CocoaUIEnhancer.this}); delegateJniRef = convertToLong(object); } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } if (delegateJniRef == 0) SWT.error(SWT.ERROR_NO_HANDLES); try { Field idField = SWTCocoaEnhancerDelegate.class.getField("id"); Object idValue = idField.get(delegate); invokeMethod(OS.class, "object_setInstanceVariable", new Object[] { idValue, SWT_OBJECT, wrapPointer(delegateJniRef) }); hookApplicationMenu(); hookWorkbenchListener(); // schedule disposal of callback object display.disposeExec(new Runnable() { public void run() { if (delegateJniRef != 0) { try { invokeMethod(OS.class, "DeleteGlobalRef", new Object[] {wrapPointer(delegateJniRef)}); } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } delegateJniRef = 0; if (delegate != null) delegate.release(); delegate = null; } }); // modify all shells opened on startup IWorkbenchWindow[] windows = PlatformUI.getWorkbench() .getWorkbenchWindows(); for (int i = 0; i < windows.length; i++) { modifyWindowShell(windows[i]); } } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } // DCClient.logger.info("End register"); } }); } void log(Exception e) { StatusUtil.handleStatus(e, StatusManager.LOG); } /** * Hooks a listener that tweaks newly opened workbench window shells with * the proper OS flags. * * @since 3.2 */ protected void hookWorkbenchListener() { PlatformUI.getWorkbench().addWindowListener(new IWindowListener() { public void windowActivated(IWorkbenchWindow window) { // no-op } public void windowDeactivated(IWorkbenchWindow window) { // no-op } public void windowClosed(IWorkbenchWindow window) { // no-op } public void windowOpened(IWorkbenchWindow window) { modifyWindowShell(window); }}); } /** * Modify the given workbench window shell bits to show the tool bar toggle * button. * * @param window * the window to modify * @since 3.2 */ protected void modifyWindowShell(IWorkbenchWindow window) { // only add the button when either the cool bar or perspective bar // is initially visible. This is so that RCP applications can choose to use // this fragment without fear that their explicitly invisible bars // can't be shown. boolean coolBarInitiallyVsible = ((WorkbenchWindow) window) .getCoolBarVisible(); boolean perspectiveBarInitiallyVsible = ((WorkbenchWindow) window) .getPerspectiveBarVisible(); if (coolBarInitiallyVsible || perspectiveBarInitiallyVsible) { // Add an empty, hidden tool bar to the window. Without this the // tool bar button at the top right of the window will not appear // even when setShowsToolbarButton(true) is called. NSToolbar dummyBar = new NSToolbar(); dummyBar.alloc(); dummyBar.initWithIdentifier(NSString.stringWith("SWTToolbar")); //$NON-NLS-1$ dummyBar.setVisible(false); Shell shell = window.getShell(); NSWindow nsWindow; NSView view = null; try { view = (NSView) Class.forName("org.eclipse.swt.widgets.Shell").getField("view").get(shell); } catch (Exception e1) { log(e1); } nsWindow = view.window(); nsWindow.setToolbar(dummyBar); dummyBar.release(); nsWindow.setShowsToolbarButton(true); // Override the target and action of the toolbar button so we can control it. try { Object fieldValue = wrapPointer(NSWindowToolbarButton); NSButton toolbarButton = (NSButton) invokeMethod(NSWindow.class, nsWindow, "standardWindowButton", new Object[] {fieldValue}); if (toolbarButton != null) { toolbarButton.setTarget(delegate); invokeMethod(NSControl.class, toolbarButton, "setAction", new Object[] { wrapPointer(sel_toolbarButtonClicked_) }); } } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } } private void hookApplicationMenu() { try { // create About Eclipse menu command NSMenu mainMenu = NSApplication.sharedApplication().mainMenu(); NSMenuItem mainMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, mainMenu, "itemAtIndex", new Object[] {wrapPointer(0)}); NSMenu appMenu = mainMenuItem.submenu(); // add the about action NSMenuItem aboutMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kAboutMenuItem)}); aboutMenuItem.setTitle(NSString.stringWith(fAboutActionName)); // rename the hide action if we have an override string if (fHideActionName != null) { NSMenuItem hideMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kHideApplicationMenuItem)}); hideMenuItem.setTitle(NSString.stringWith(fHideActionName)); } // rename the quit action if we have an override string if (fQuitActionName != null) { NSMenuItem quitMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kQuitMenuItem)}); quitMenuItem.setTitle(NSString.stringWith(fQuitActionName)); } // enable pref menu NSMenuItem prefMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kPreferencesMenuItem)}); prefMenuItem.setEnabled(true); // disable services menu NSMenuItem servicesMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kServicesMenuItem)}); servicesMenuItem.setEnabled(false); // Register as a target on the prefs and quit items. prefMenuItem.setTarget(delegate); invokeMethod(NSMenuItem.class, prefMenuItem, "setAction", new Object[] {wrapPointer(sel_preferencesMenuItemSelected_)}); aboutMenuItem.setTarget(delegate); invokeMethod(NSMenuItem.class, aboutMenuItem, "setAction", new Object[] {wrapPointer(sel_aboutMenuItemSelected_)}); } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } /** * Locate an action with the given id in the current menu bar and run it. */ @SuppressWarnings("unused") private void runAction(String actionId) { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window != null) { IMenuManager manager = ((WorkbenchWindow)window).getActionBars().getMenuManager(); IAction action = findAction(actionId, manager); if (action != null && action.isEnabled()) { try { NSMenu mainMenu = NSApplication.sharedApplication().mainMenu(); NSMenuItem mainMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, mainMenu, "itemAtIndex", new Object[] {wrapPointer(0)}); NSMenu appMenu = mainMenuItem.submenu(); NSMenuItem aboutMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kAboutMenuItem)}); NSMenuItem prefMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "itemAtIndex", new Object[] {wrapPointer(kPreferencesMenuItem)}); try { prefMenuItem.setEnabled(false); aboutMenuItem.setEnabled(false); action.run(); } finally { prefMenuItem.setEnabled(true); aboutMenuItem.setEnabled(true); } } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } } } private void runCommand(String commandId) { IWorkbench workbench = PlatformUI.getWorkbench(); if (workbench == null) return; IWorkbenchWindow activeWorkbenchWindow = workbench .getActiveWorkbenchWindow(); if (activeWorkbenchWindow == null) return; IHandlerService commandService = (IHandlerService) activeWorkbenchWindow .getService(IHandlerService.class); if (commandService != null) { try { commandService.executeCommand(commandId, null); } catch (ExecutionException e) { } catch (NotDefinedException e) { } catch (NotEnabledException e) { } catch (NotHandledException e) { } } } /** * Find the action with the given ID by recursively crawling the provided * menu manager. If the action cannot be found <code>null</code> is * returned. * * @param actionId * the id to search for * @param manager * the manager to search * @return the action or <code>null</code> */ private IAction findAction(String actionId, IMenuManager manager) { IContributionItem[] items = manager.getItems(); for (int i = 0; i < items.length; i++) { IContributionItem item = items[i]; if (item instanceof ActionContributionItem) { ActionContributionItem aci = (ActionContributionItem) item; String id = aci.getId(); if (id != null && id.equals(actionId)) return aci.getAction(); } else if (item instanceof IMenuManager) { IAction found = findAction(actionId, (IMenuManager) item); if (found != null) return found; } } return null; } /* * Action implementations for the toolbar button and preferences and about menu items */ void toolbarButtonClicked (NSControl source) { try { NSWindow window = source.window(); Field idField = NSWindow.class.getField("id"); Object idValue = idField.get(window); Display display = Display.getCurrent(); Widget widget = (Widget) invokeMethod(Display.class, display, "findWidget", new Object[] { idValue }); if (!(widget instanceof Shell)) { return; } Shell shell = (Shell) widget; IWorkbenchWindow[] windows = PlatformUI.getWorkbench() .getWorkbenchWindows(); for (int i = 0; i < windows.length; i++) { if (windows[i].getShell() == shell) { runCommand("org.eclipse.ui.ToggleCoolbarAction"); //$NON-NLS-1$ } } } catch (Exception e) { // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception // not expected to happen at all. log(e); } } void preferencesMenuItemSelected() { runCommand("eu.jucy.gui.preferences");//$NON-NLS-1$ } void aboutMenuItemSelected() { runCommand("org.eclipse.ui.help.aboutAction");//$NON-NLS-1$ } static int actionProc(int id, int sel, int arg0) throws Exception { return (int) actionProc((long)id, (long)sel, (long)arg0); } static long actionProc(long id, long sel, long arg0) throws Exception { long [] jniRef = OS_object_getInstanceVariable(id, SWT_OBJECT); if (jniRef[0] == 0) return 0; CocoaUIEnhancer delegate = (CocoaUIEnhancer) invokeMethod(OS.class, "JNIGetObject", new Object[] { wrapPointer(jniRef[0]) }); if (sel == sel_toolbarButtonClicked_) { NSControl source = new_NSControl(arg0); delegate.toolbarButtonClicked(source); } else if (sel == sel_preferencesMenuItemSelected_) { delegate.preferencesMenuItemSelected(); } else if (sel == sel_aboutMenuItemSelected_) { delegate.aboutMenuItemSelected(); } return 0; } // The following methods reflectively call corresponding methods in the OS // class, using ints or longs as required based on platform. private static NSControl new_NSControl(long arg0) throws NoSuchMethodException, InstantiationException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Class clazz = NSControl.class; Class PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; Constructor constructor = clazz .getConstructor(new Class[] { PTR_CLASS }); return (NSControl) constructor.newInstance(new Object[] { wrapPointer(arg0) }); } /** * Specialized method. It's behavior is isolated and different enough from the usual invocation that custom code is warranted. */ private static long[] OS_object_getInstanceVariable(long delegateId, byte[] name) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { Class clazz = OS.class; Method method = null; Class PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; if (PTR_CLASS == long.class) { method = clazz.getMethod("object_getInstanceVariable", new Class[] { long.class, byte[].class, long[].class }); long[] resultPtr = new long[1]; method.invoke(null, new Object[] { new Long(delegateId), name, resultPtr }); return resultPtr; } else { method = clazz.getMethod("object_getInstanceVariable", new Class[] { int.class, byte[].class, int[].class }); int[] resultPtr = new int[1]; method.invoke(null, new Object[] { new Integer((int) delegateId), name, resultPtr }); return new long[] { resultPtr[0] }; } } 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 invokeMethod(Class clazz, String methodName, Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { return invokeMethod(clazz, null, methodName, args); } private static Object invokeMethod(Class clazz, Object target, String methodName, Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { 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 signature[i] = thisClass; } Method method = clazz.getMethod(methodName, signature); return method.invoke(target, args); } 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); } }