Home | History | Annotate | Download | only in internal
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  *
     16  * History:
     17  * Original code by the <a href="http://www.simidude.com/blog/2008/macify-a-swt-application-in-a-cross-platform-way/">CarbonUIEnhancer from Agynami</a>
     18  * 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>,
     19  * then modified by http://www.transparentech.com/opensource/cocoauienhancer to use reflection
     20  * rather than 'link' to SWT cocoa, and finally modified to be usable by the SwtMenuBar project.
     21  */
     22 
     23 package com.android.menubar.internal;
     24 
     25 import com.android.menubar.IMenuBarCallback;
     26 import com.android.menubar.IMenuBarEnhancer;
     27 
     28 import org.eclipse.swt.SWT;
     29 import org.eclipse.swt.internal.C;
     30 import org.eclipse.swt.internal.Callback;
     31 import org.eclipse.swt.widgets.Display;
     32 import org.eclipse.swt.widgets.Event;
     33 import org.eclipse.swt.widgets.Listener;
     34 import org.eclipse.swt.widgets.Menu;
     35 
     36 import java.lang.reflect.InvocationTargetException;
     37 import java.lang.reflect.Method;
     38 
     39 public class MenuBarEnhancerCocoa implements IMenuBarEnhancer {
     40 
     41     private static final long kAboutMenuItem = 0;
     42     private static final long kPreferencesMenuItem = 2;
     43     // private static final long kServicesMenuItem = 4;
     44     // private static final long kHideApplicationMenuItem = 6;
     45     private static final long kQuitMenuItem = 10;
     46 
     47     static long mSelPreferencesMenuItemSelected;
     48     static long mSelAboutMenuItemSelected;
     49     static Callback mProc3Args;
     50 
     51     private String mAppName;
     52 
     53     /**
     54      * Class invoked via the Callback object to run the about and preferences
     55      * actions.
     56      * <p>
     57      * If you don't use JFace in your application (SWT only), change the
     58      * {@link org.eclipse.jface.action.IAction}s to
     59      * {@link org.eclipse.swt.widgets.Listener}s.
     60      * </p>
     61      */
     62     private static class ActionProctarget {
     63         private final IMenuBarCallback mCallbacks;
     64 
     65         public ActionProctarget(IMenuBarCallback callbacks) {
     66             mCallbacks = callbacks;
     67         }
     68 
     69         /**
     70          * Will be called on 32bit SWT.
     71          */
     72         @SuppressWarnings("unused")
     73         public int actionProc(int id, int sel, int arg0) {
     74             return (int) actionProc((long) id, (long) sel, (long) arg0);
     75         }
     76 
     77         /**
     78          * Will be called on 64bit SWT.
     79          */
     80         public long actionProc(long id, long sel, long arg0) {
     81             if (sel == mSelAboutMenuItemSelected) {
     82                 mCallbacks.onAboutMenuSelected();
     83             } else if (sel == mSelPreferencesMenuItemSelected) {
     84                 mCallbacks.onPreferencesMenuSelected();
     85             } else {
     86                 // Unknown selection!
     87             }
     88             // Return value is not used.
     89             return 0;
     90         }
     91     }
     92 
     93     /**
     94      * Construct a new CocoaUIEnhancer.
     95      *
     96      * @param mAppName The name of the application. It will be used to customize
     97      *            the About and Quit menu items. If you do not wish to customize
     98      *            the About and Quit menu items, just pass <tt>null</tt> here.
     99      */
    100     public MenuBarEnhancerCocoa() {
    101     }
    102 
    103     public MenuBarMode getMenuBarMode() {
    104         return MenuBarMode.MAC_OS;
    105     }
    106 
    107     /**
    108      * Setup the About and Preferences native menut items with the
    109      * given application name and links them to the callback.
    110      *
    111      * @param appName The application name.
    112      * @param display The SWT display. Must not be null.
    113      * @param callbacks The callbacks invoked by the menus.
    114      */
    115     public void setupMenu(
    116             String appName,
    117             Display display,
    118             IMenuBarCallback callbacks) {
    119 
    120         mAppName = appName;
    121 
    122         // This is our callback object whose 'actionProc' method will be called
    123         // when the About or Preferences menuItem is invoked.
    124         ActionProctarget target = new ActionProctarget(callbacks);
    125 
    126         try {
    127             // Initialize the menuItems.
    128             initialize(target);
    129         } catch (Exception e) {
    130             throw new IllegalStateException(e);
    131         }
    132 
    133         // Schedule disposal of callback object
    134         display.disposeExec(new Runnable() {
    135             public void run() {
    136                 invoke(mProc3Args, "dispose");
    137             }
    138         });
    139     }
    140 
    141     private void initialize(Object callbackObject)
    142             throws Exception {
    143 
    144         Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS");
    145 
    146         // Register names in objective-c.
    147         if (mSelAboutMenuItemSelected == 0) {
    148             mSelPreferencesMenuItemSelected = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$
    149             mSelAboutMenuItemSelected = registerName(osCls, "aboutMenuItemSelected:");             //$NON-NLS-1$
    150         }
    151 
    152         // Create an SWT Callback object that will invoke the actionProc method
    153         // of our internal callback Object.
    154         mProc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$
    155         Method getAddress = Callback.class.getMethod("getAddress", new Class[0]);
    156         Object object = getAddress.invoke(mProc3Args, (Object[]) null);
    157         long proc3 = convertToLong(object);
    158         if (proc3 == 0) {
    159             SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
    160         }
    161 
    162         Class<?> nsMenuCls        = classForName("org.eclipse.swt.internal.cocoa.NSMenu");
    163         Class<?> nsMenuitemCls    = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem");
    164         Class<?> nsStringCls      = classForName("org.eclipse.swt.internal.cocoa.NSString");
    165         Class<?> nsApplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication");
    166 
    167         // Instead of creating a new delegate class in objective-c,
    168         // just use the current SWTApplicationDelegate. An instance of this
    169         // is a field of the Cocoa Display object and is already the target
    170         // for the menuItems. So just get this class and add the new methods
    171         // to it.
    172         object = invoke(osCls, "objc_lookUpClass", new Object[] {
    173             "SWTApplicationDelegate"
    174         });
    175         long cls = convertToLong(object);
    176 
    177         // Add the action callbacks for Preferences and About menu items.
    178         invoke(osCls, "class_addMethod",
    179                 new Object[] {
    180                     wrapPointer(cls),
    181                     wrapPointer(mSelPreferencesMenuItemSelected),
    182                     wrapPointer(proc3), "@:@"}); //$NON-NLS-1$
    183         invoke(osCls,  "class_addMethod",
    184                 new Object[] {
    185                     wrapPointer(cls),
    186                     wrapPointer(mSelAboutMenuItemSelected),
    187                     wrapPointer(proc3), "@:@"}); //$NON-NLS-1$
    188 
    189         // Get the Mac OS X Application menu.
    190         Object sharedApplication = invoke(nsApplicationCls, "sharedApplication");
    191         Object mainMenu = invoke(sharedApplication, "mainMenu");
    192         Object mainMenuItem = invoke(nsMenuCls, mainMenu, "itemAtIndex", new Object[] {
    193             wrapPointer(0)
    194         });
    195         Object appMenu = invoke(mainMenuItem, "submenu");
    196 
    197         // Create the About <application-name> menu command
    198         Object aboutMenuItem =
    199                 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
    200                     wrapPointer(kAboutMenuItem)
    201                 });
    202         if (mAppName != null) {
    203             Object nsStr = invoke(nsStringCls, "stringWith", new Object[] {
    204                 "About " + mAppName
    205             });
    206             invoke(nsMenuitemCls, aboutMenuItem, "setTitle", new Object[] {
    207                 nsStr
    208             });
    209         }
    210         // Rename the quit action.
    211         if (mAppName != null) {
    212             Object quitMenuItem =
    213                     invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
    214                         wrapPointer(kQuitMenuItem)
    215                     });
    216             Object nsStr = invoke(nsStringCls, "stringWith", new Object[] {
    217                 "Quit " + mAppName
    218             });
    219             invoke(nsMenuitemCls, quitMenuItem, "setTitle", new Object[] {
    220                 nsStr
    221             });
    222         }
    223 
    224         // Enable the Preferences menuItem.
    225         Object prefMenuItem =
    226                 invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] {
    227                     wrapPointer(kPreferencesMenuItem)
    228                 });
    229         invoke(nsMenuitemCls, prefMenuItem, "setEnabled", new Object[] {
    230             true
    231         });
    232 
    233         // Set the action to execute when the About or Preferences menuItem is
    234         // invoked.
    235         //
    236         // We don't need to set the target here as the current target is the
    237         // SWTApplicationDelegate and we have registerd the new selectors on
    238         // it. So just set the new action to invoke the selector.
    239         invoke(nsMenuitemCls, prefMenuItem, "setAction",
    240                 new Object[] {
    241                     wrapPointer(mSelPreferencesMenuItemSelected)
    242                 });
    243         invoke(nsMenuitemCls, aboutMenuItem, "setAction",
    244                 new Object[] {
    245                     wrapPointer(mSelAboutMenuItemSelected)
    246                 });
    247     }
    248 
    249     private long registerName(Class<?> osCls, String name)
    250             throws IllegalArgumentException, SecurityException, IllegalAccessException,
    251             InvocationTargetException, NoSuchMethodException {
    252         Object object = invoke(osCls, "sel_registerName", new Object[] {
    253             name
    254         });
    255         return convertToLong(object);
    256     }
    257 
    258     private long convertToLong(Object object) {
    259         if (object instanceof Integer) {
    260             Integer i = (Integer) object;
    261             return i.longValue();
    262         }
    263         if (object instanceof Long) {
    264             Long l = (Long) object;
    265             return l.longValue();
    266         }
    267         return 0;
    268     }
    269 
    270     private static Object wrapPointer(long value) {
    271         Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class;
    272         if (PTR_CLASS == long.class) {
    273             return new Long(value);
    274         } else {
    275             return new Integer((int) value);
    276         }
    277     }
    278 
    279     private static Object invoke(Class<?> clazz, String methodName, Object[] args) {
    280         return invoke(clazz, null, methodName, args);
    281     }
    282 
    283     private static Object invoke(Class<?> clazz, Object target, String methodName, Object[] args) {
    284         try {
    285             Class<?>[] signature = new Class<?>[args.length];
    286             for (int i = 0; i < args.length; i++) {
    287                 Class<?> thisClass = args[i].getClass();
    288                 if (thisClass == Integer.class)
    289                     signature[i] = int.class;
    290                 else if (thisClass == Long.class)
    291                     signature[i] = long.class;
    292                 else if (thisClass == Byte.class)
    293                     signature[i] = byte.class;
    294                 else if (thisClass == Boolean.class)
    295                     signature[i] = boolean.class;
    296                 else
    297                     signature[i] = thisClass;
    298             }
    299             Method method = clazz.getMethod(methodName, signature);
    300             return method.invoke(target, args);
    301         } catch (Exception e) {
    302             throw new IllegalStateException(e);
    303         }
    304     }
    305 
    306     private Class<?> classForName(String classname) {
    307         try {
    308             Class<?> cls = Class.forName(classname);
    309             return cls;
    310         } catch (ClassNotFoundException e) {
    311             throw new IllegalStateException(e);
    312         }
    313     }
    314 
    315     private Object invoke(Class<?> cls, String methodName) {
    316         return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null);
    317     }
    318 
    319     private Object invoke(Class<?> cls, String methodName, Class<?>[] paramTypes,
    320             Object... arguments) {
    321         try {
    322             Method m = cls.getDeclaredMethod(methodName, paramTypes);
    323             return m.invoke(null, arguments);
    324         } catch (Exception e) {
    325             throw new IllegalStateException(e);
    326         }
    327     }
    328 
    329     private Object invoke(Object obj, String methodName) {
    330         return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null);
    331     }
    332 
    333     private Object invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments) {
    334         try {
    335             Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes);
    336             return m.invoke(obj, arguments);
    337         } catch (Exception e) {
    338             throw new IllegalStateException(e);
    339         }
    340     }
    341 }
    342