diff options
Diffstat (limited to 'pdf-over-gui/src/main/java')
-rw-r--r-- | pdf-over-gui/src/main/java/at/asit/pdfover/gui/osx/CocoaUIEnhancer.java | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/osx/CocoaUIEnhancer.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/osx/CocoaUIEnhancer.java new file mode 100644 index 00000000..d6076272 --- /dev/null +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/osx/CocoaUIEnhancer.java @@ -0,0 +1,372 @@ +/* + * Copyright 2014 by A-SIT, Secure Information Technology Center Austria + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ + +package at.asit.pdfover.gui.osx; + +import java.lang.reflect.Method; + +import org.apache.log4j.Logger; +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; + +import at.asit.pdfover.gui.Constants; +import at.asit.pdfover.gui.utils.Messages; + +/** + * 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. 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.transparentech.com/opensource/cocoauienhancer" + * >CocoaUIEnhancer - Connect the About, Preferences and Quit menus in Mac OS X + * Cocoa SWT and JFace applications</a>. + * </p> + * <p> + * This class works with both the 32-bit and 64-bit versions of the SWT Cocoa + * bindings. + * <p> + * <p> + * This class is released under the Eclipse Public License (<a + * href="http://www.eclipse.org/legal/epl-v10.html">EPL</a>). + */ +public class CocoaUIEnhancer { + static final Logger log = Logger.getLogger(CocoaUIEnhancer.class); + + 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 sel_toolbarButtonClicked_; + static long sel_preferencesMenuItemSelected_; + static long sel_aboutMenuItemSelected_; + static long sel_hideApplicationMenuItemSelected_; + + static Callback proc3Args; + + /** + * Class invoked via the Callback object to run the about and preferences + * actions. + */ + private static class MenuHookObject { + + final Listener about; + final Listener pref; + + public MenuHookObject(final Listener aboutListener, + final Listener preferencesListener) { + this.about = aboutListener; + this.pref = preferencesListener; + } + + /** + * Will be called on 32bit SWT. + * @param id + * @param sel + * @param arg0 + * @return x + */ + @SuppressWarnings("unused") + public int actionProc(final int id, final int sel, final int arg0) { + return (int) this.actionProc((long) id, (long) sel, (long) arg0); + } + + /** + * Will be called on 64bit SWT. + * @param id + * @param sel + * @param arg0 + * @return x + */ + public long actionProc(final long id, final long sel, final long arg0) { + if (sel == sel_aboutMenuItemSelected_) { + if (log.isDebugEnabled()) { + log.debug("[MenuHookObject - actionProc] : About"); //$NON-NLS-1$ + this.about.handleEvent(null); + } + } else if (sel == sel_preferencesMenuItemSelected_) { + if (log.isDebugEnabled()) { + log.debug("[MenuHookObject - actionProc] : Preferences"); //$NON-NLS-1$ + } + this.pref.handleEvent(null); + + } else { + if (log.isDebugEnabled()) { + log.debug("[MenuHookObject - actionProc] : Unknow selection!"); //$NON-NLS-1$ + } + } + // Return value is not used. + return 99; + } + + // Getters and setters + @SuppressWarnings("unused") + public Listener getAbout() { + return this.about; + } + + @SuppressWarnings("unused") + public Listener getPref() { + return this.pref; + } + } + + /** + * 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 aboutListener + * The listener to invoke when the About menu is invoked. + * @param preferencesListener + * The listener to invoke when the Preferences menu is invoked. + */ + public static void hookApplicationMenu(final Display display, + final Listener quitListener, final Listener aboutListener, + final Listener preferencesListener) { + // This is our callbackObject whose 'actionProc' method will be called + // when the About or + // Preferences menuItem is invoked. + final MenuHookObject target = new MenuHookObject(aboutListener, + preferencesListener); + + try { + // Initialize the menuItems. + initialize(target); + } catch (final 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() { + @Override + public void run() { + CocoaUIEnhancer.invoke(proc3Args, "dispose"); //$NON-NLS-1$ + } + }); + } + + private static void initialize(final Object callbackObject) throws Exception { + + final Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); //$NON-NLS-1$ + + // 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$ + final Method getAddress = Callback.class.getMethod("getAddress", //$NON-NLS-1$ + new Class[0]); + Object object = getAddress.invoke(proc3Args, (Object[]) null); + final long proc3 = convertToLong(object); + if (proc3 == 0) { + SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); + } + + final Class<?> nsmenuCls = classForName("org.eclipse.swt.internal.cocoa.NSMenu"); //$NON-NLS-1$ + final Class<?> nsmenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem"); //$NON-NLS-1$ + final Class<?> nsstringCls = classForName("org.eclipse.swt.internal.cocoa.NSString"); //$NON-NLS-1$ + final 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$ + final 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. + final Object sharedApplication = invoke(nsapplicationCls, + "sharedApplication"); //$NON-NLS-1$ + final Object mainMenu = invoke(sharedApplication, "mainMenu"); //$NON-NLS-1$ + final Object mainMenuItem = invoke(nsmenuCls, mainMenu, "itemAtIndex", //$NON-NLS-1$ + new Object[] { wrapPointer(0) }); + final Object appMenu = invoke(mainMenuItem, "submenu"); //$NON-NLS-1$ + + // Create the About <application-name> menu command + final Object aboutMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$ + new Object[] { wrapPointer(kAboutMenuItem) }); + final Object nsStrAbout = invoke(nsstringCls, "stringWith", //$NON-NLS-1$ + new Object[] { String.format(Messages.getString("main.about"), Constants.APP_NAME) }); //$NON-NLS-1$ + invoke(nsmenuitemCls, aboutMenuItem, "setTitle", //$NON-NLS-1$ + new Object[] { nsStrAbout }); + // Rename the quit action. + final Object quitMenuItem = invoke(nsmenuCls, appMenu, + "itemAtIndex", new Object[] { wrapPointer(kQuitMenuItem) }); //$NON-NLS-1$ + final Object nsStrQuit = invoke(nsstringCls, "stringWith", //$NON-NLS-1$ + new Object[] { String.format(Messages.getString("main.quit"), Constants.APP_NAME) }); //$NON-NLS-1$ + invoke(nsmenuitemCls, quitMenuItem, "setTitle", //$NON-NLS-1$ + new Object[] { nsStrQuit }); + + // Rename the hide action. + final Object hideMenuItem = invoke(nsmenuCls, appMenu, + "itemAtIndex", //$NON-NLS-1$ + new Object[] { wrapPointer(kHideApplicationMenuItem) }); + final Object nsStrHide = invoke(nsstringCls, "stringWith", //$NON-NLS-1$ + new Object[] { String.format(Messages.getString("main.hide"), Constants.APP_NAME) }); //$NON-NLS-1$ + invoke(nsmenuitemCls, hideMenuItem, "setTitle", //$NON-NLS-1$ + new Object[] { nsStrHide }); + + // Enable the Preferences menuItem. + final Object prefMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex", //$NON-NLS-1$ + new Object[] { wrapPointer(kPreferencesMenuItem) }); + invoke(nsmenuitemCls, prefMenuItem, "setEnabled", new Object[] { true }); //$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 static long registerName(final Class<?> osCls, final String name) + throws IllegalArgumentException, SecurityException { + final Object object = invoke(osCls, "sel_registerName", //$NON-NLS-1$ + new Object[] { name }); + return convertToLong(object); + } + + private static long convertToLong(final Object object) { + if (object instanceof Integer) { + final Integer i = (Integer) object; + return i.longValue(); + } + if (object instanceof Long) { + final Long l = (Long) object; + return l.longValue(); + } + return 0; + } + + private static Object wrapPointer(final long value) { + final Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; + if (PTR_CLASS == long.class) { + return new Long(value); + } + return new Integer((int) value); + } + + private static Object invoke(final Class<?> clazz, final String methodName, + final Object[] args) { + return invoke(clazz, null, methodName, args); + } + + private static Object invoke(final Class<?> clazz, final Object target, + final String methodName, final Object[] args) { + try { + final Class<?>[] signature = new Class<?>[args.length]; + for (int i = 0; i < args.length; i++) { + final 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; + } + } + final Method method = clazz.getMethod(methodName, signature); + return method.invoke(target, args); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + + private static Class<?> classForName(final String classname) { + try { + final Class<?> cls = Class.forName(classname); + return cls; + } catch (final ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + private static Object invoke(final Class<?> cls, final String methodName) { + return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null); + } + + private static Object invoke(final Class<?> cls, final String methodName, + final Class<?>[] paramTypes, final Object... arguments) { + try { + final Method m = cls.getDeclaredMethod(methodName, paramTypes); + return m.invoke(null, arguments); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + + static Object invoke(final Object obj, final String methodName) { + return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null); + } + + private static Object invoke(final Object obj, final String methodName, + final Class<?>[] paramTypes, final Object... arguments) { + try { + final Method m = obj.getClass().getDeclaredMethod(methodName, + paramTypes); + return m.invoke(obj, arguments); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } +} |