package de.snertlab.xdccBee.tools;
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.
* <p>
* 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.
* </p>
* <p>
* This code was influenced 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>.
* </p>
*/
public class CocoaUIEnhancer {
static Callback proc3Args;
static long sel_aboutMenuItemSelected_;
static long sel_preferencesMenuItemSelected_;
static long sel_toolbarButtonClicked_;
private static final int kAboutMenuItem = 0;
private static final int kPreferencesMenuItem = 2;
private static final int kQuitMenuItem = 10;
private static final int kHideAppMenuItem = 6;
private static final int kServicesMenuItem = 4;
private static final int kHideOthersMenuItem = 7;
private static final int kShowAllMenuItem = 8;
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 static Object invoke(Class<?> clazz, String methodName,
Object[] args) {
return invoke(clazz, null, methodName, args);
}
private static Object wrapPointer(long value) {
Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class;
if (PTR_CLASS == long.class)
return Long.valueOf(value);
else
return Integer.valueOf((int) value);
}
final private 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 <code>null</code> 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. This
* cannot be <code>null</code> or the SWT classes will complain.
* @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) {
// This is our callbackObject whose 'actionProc' method will be called
// when
// the About or Preferences menuItem is invoked.
// Connect the given IAction objects to the actionProce method.
Object target = new Object() {
@SuppressWarnings("unused")
int actionProc(int id, int sel, int arg0) {
// Casts the parameters to long so and use the method for 64bit
// cocoa.
return (int) actionProc((long) id, (long) sel, (long) arg0);
}
long actionProc(long id, long sel, long arg0) {
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"); //$NON-NLS-1$
}
});
}
private Class<?> classForName(String classname) {
try {
Class<?> cls = Class.forName(classname);
return cls;
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
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 void initialize(Object callbackObject) throws Exception {
Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); //$NON-NLS-1$
// Register names in objective-c.
if (sel_toolbarButtonClicked_ == 0) {
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]); //$NON-NLS-1$
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"); //$NON-NLS-1$
Class<?> nsmenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem"); //$NON-NLS-1$
Class<?> nsstringCls = classForName("org.eclipse.swt.internal.cocoa.NSString"); //$NON-NLS-1$
Class<?> nsapplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication"); //$NON-NLS-1$
// 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", //$NON-NLS-1$
new Object[] { "SWTApplicationDelegate" }); //$NON-NLS-1$
long cls = convertToLong(object);
// Add the action callbacks for Preferences and About menu items.
invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), //$NON-NLS-1$
wrapPointer(sel_preferencesMenuItemSelected_),
wrapPointer(proc3), "@:@" }); //$NON-NLS-1$
invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls), //$NON-NLS-1$
wrapPointer(sel_aboutMenuItemSelected_), wrapPointer(proc3),
"@:@" }); //$NON-NLS-1$
// Get the Mac OS X Application menu.
Object sharedApplication = invoke(nsapplicationCls, "sharedApplication"); //$NON-NLS-1$
Object mainMenu = invoke(sharedApplication, "mainMenu"); //$NON-NLS-1$
Object mainMenuItem = invoke(nsmenuCls, mainMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(0) });
Object appMenu = invoke(mainMenuItem, "submenu"); //$NON-NLS-1$
// Create the About <application-name> menu command
Object aboutMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kAboutMenuItem) });
if (appName != null) {
Object nsStr = invoke(nsstringCls,
"stringWith", new Object[] { "Über " + appName //$NON-NLS-1$ //$NON-NLS-2$
});
invoke(nsmenuitemCls, aboutMenuItem,
"setTitle", new Object[] { nsStr }); //$NON-NLS-1$
Object quitMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kQuitMenuItem) });
nsStr = invoke(nsstringCls,
"stringWith", new Object[] { appName + " beenden" //$NON-NLS-1$ //$NON-NLS-2$
});
invoke(nsmenuitemCls, quitMenuItem,
"setTitle", new Object[] { nsStr }); //$NON-NLS-1$
}
// Set MainMenu Title to appName
Object nsStr = invoke(nsstringCls, "stringWith", new Object[] { appName //$NON-NLS-1$ //$NON-NLS-2$
});
invoke(nsmenuCls, appMenu, "setTitle", new Object[] { nsStr }); //$NON-NLS-1$
// Enable the Preferences menuItem.
nsStr = invoke(nsstringCls,
"stringWith", new Object[] { "Einstellungen ..." //$NON-NLS-1$ //$NON-NLS-2$
});
Object prefMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kPreferencesMenuItem) });
invoke(nsmenuitemCls, prefMenuItem, "setEnabled", new Object[] { true }); //$NON-NLS-1$
invoke(nsmenuitemCls, prefMenuItem, "setTitle", new Object[] { nsStr }); //$NON-NLS-1$
// Set Title of Services
nsStr = invoke(nsstringCls, "stringWith", new Object[] { "Dienste" //$NON-NLS-1$ //$NON-NLS-2$
});
Object prefServiceItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kServicesMenuItem) });
invoke(nsmenuitemCls, prefServiceItem,
"setTitle", new Object[] { nsStr }); //$NON-NLS-1$
invoke(nsmenuitemCls, prefServiceItem,
"setEnabled", new Object[] { false }); //$NON-NLS-1$
// Set Title of Hide <application-name> menu command
nsStr = invoke(nsstringCls,
"stringWith", new Object[] { appName + " ausblenden" //$NON-NLS-1$ //$NON-NLS-2$
});
Object prefHideItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kHideAppMenuItem) });
invoke(nsmenuitemCls, prefHideItem, "setTitle", new Object[] { nsStr }); //$NON-NLS-1$
// Set Title of Hide others
nsStr = invoke(nsstringCls,
"stringWith", new Object[] { "Andere ausblenden" //$NON-NLS-1$ //$NON-NLS-2$
});
Object prefHideOthersItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kHideOthersMenuItem) });
invoke(nsmenuitemCls, prefHideOthersItem,
"setTitle", new Object[] { nsStr }); //$NON-NLS-1$
// Set Title of Show All
nsStr = invoke(nsstringCls,
"stringWith", new Object[] { "Alle einblenden" //$NON-NLS-1$ //$NON-NLS-2$
});
Object prefShowAllItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$
new Object[] { wrapPointer(kShowAllMenuItem) });
invoke(nsmenuitemCls, prefShowAllItem,
"setTitle", new Object[] { nsStr }); //$NON-NLS-1$
// 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 registerd the new selectors on it. So just set the new
// action
// to invoke the
// selector.
invoke(nsmenuitemCls, prefMenuItem, "setAction", //$NON-NLS-1$
new Object[] { wrapPointer(sel_preferencesMenuItemSelected_) });
invoke(nsmenuitemCls, aboutMenuItem, "setAction", //$NON-NLS-1$
new Object[] { wrapPointer(sel_aboutMenuItemSelected_) });
}
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);
}
}
private long registerName(Class<?> osCls, String name)
throws IllegalArgumentException, SecurityException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Object object = invoke(osCls, "sel_registerName", new Object[] { name }); //$NON-NLS-1$
return convertToLong(object);
}
}