blob: f7b819e5432b9c156b4a7ebf4003f9c5677749b1 [file] [log] [blame]
/*******************************************************************************
* 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
*******************************************************************************/
package org.eclipse.ui.internal.cocoa;
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.Platform;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
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.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.actions.ActionFactory;
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 extends CocoaUtil implements IStartup {
private static final int kAboutMenuItem = 0;
private static final int kPreferencesMenuItem = 2;
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 {
// TODO: These should either move out of Display or be accessible to
// this class.
byte[] types = { '*', '\0' };
int size = C.PTR_SIZEOF;
int 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[] args = makeArgs(fieldObj, "SWTCocoaEnhancerDelegate", wrapPointer(0));
object = invokeMethod(OS.class, "objc_allocateClassPair", args);
long cls = convertToLong(object);
args = makeArgs(wrapPointer(cls), SWT_OBJECT, wrapPointer(size), new Byte((byte) align),
types);
invokeMethod(OS.class, "class_addIvar", args);
// Add the action callback
args = makeArgs(wrapPointer(cls), wrapPointer(sel_toolbarButtonClicked_),
wrapPointer(proc3), "@:@");
invokeMethod(OS.class, "class_addMethod", args); //$NON-NLS-1$
args = makeArgs(wrapPointer(cls), wrapPointer(sel_preferencesMenuItemSelected_),
wrapPointer(proc3), "@:@");
invokeMethod(OS.class, "class_addMethod", args); //$NON-NLS-1$
args = makeArgs(wrapPointer(cls), wrapPointer(sel_aboutMenuItemSelected_),
wrapPointer(proc3), "@:@");
invokeMethod(OS.class, "class_addMethod", args); //$NON-NLS-1$
invokeMethod(OS.class, "objc_registerClassPair", makeArgs(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 = CocoaUIEnhancer.class.getPackage().getName()
+ ".Messages"; //$NON-NLS-1$
private String fAboutActionName;
private String fQuitActionName;
private String fHideActionName;
/**
* Default constructor
*/
public CocoaUIEnhancer() {
String productName = getProductName();
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) {
}
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) {
}
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) {
}
}
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 String getProductName() {
if (Platform.getProduct() != null)
return Platform.getProduct().getName();
return null;
}
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() {
Display display = Display.getDefault();
display.syncExec(new Runnable() {
public void run() {
startupInUI();
}
});
}
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(final 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) {
createDummyToolbar(window);
} else {
// add the dummby toolbar when its shown for the first time
if (!(window instanceof WorkbenchWindow))
return;
final WorkbenchWindow workbenchWindow = (WorkbenchWindow) window;
workbenchWindow.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (WorkbenchWindow.PROP_COOLBAR_VISIBLE.equals(event.getProperty())) {
createDummyToolbar(window);
workbenchWindow.removePropertyChangeListener(this);
}
}
});
}
}
/**
* 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.
*
* @param window
*/
private void createDummyToolbar(IWorkbenchWindow window) {
NSToolbar dummyBar = new NSToolbar();
dummyBar.alloc();
dummyBar.initWithIdentifier(NSString.stringWith("SWTToolbar")); //$NON-NLS-1$
dummyBar.setVisible(false);
Shell shell = window.getShell();
NSWindow nsWindow = shell.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);
// 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);
}
}
private void runCommand(String commandId) {
IWorkbench workbench = PlatformUI.getWorkbench();
IHandlerService service = (IHandlerService) workbench.getService(IHandlerService.class);
try {
service.executeCommand(commandId, null);
} catch (ExecutionException e) {
log(e);
} catch (NotDefinedException e) {
log(e);
} catch (NotEnabledException e) {
log(e);
} catch (NotHandledException e) {
log(e);
}
}
/*
* 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);
}
}
private void createDelegate() throws SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
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);
}
private Runnable createDisposer() {
return new Runnable() {
public void run() {
if (delegateJniRef != 0) {
try {
invokeMethod(OS.class, "DeleteGlobalRef",
new Object[] { CocoaUtil.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;
}
};
}
/**
*
*/
private void modifyShells() {
// modify all shells opened on startup
IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
for (int i = 0; i < windows.length; i++) {
modifyWindowShell(windows[i]);
}
}
private void startupInUI() {
try {
createDelegate();
if (delegateJniRef == 0)
SWT.error(SWT.ERROR_NO_HANDLES);
setDelegate();
hookApplicationMenu();
hookWorkbenchListener();
// schedule disposal of callback object
Runnable disposer = createDisposer();
Display.getDefault().disposeExec(disposer);
modifyShells();
} catch (Exception e) {
// theoretically, one of
// SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception
// not expected to happen at all.
log(e);
}
}
private void setDelegate() throws NoSuchFieldException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Field idField = SWTCocoaEnhancerDelegate.class.getField("id");
Object idValue = idField.get(delegate);
Object[] args = makeArgs(idValue, SWT_OBJECT, wrapPointer(delegateJniRef));
invokeMethod(OS.class, "object_setInstanceVariable", args);
}
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.runCommand(ActionFactory.PREFERENCES.getCommandId());
} else if (sel == sel_aboutMenuItemSelected_) {
delegate.runCommand(ActionFactory.ABOUT.getCommandId());
}
return 0;
}
}