OSDN Git Service

swtmenubar library for the SDK.
authorRaphael Moll <ralf@android.com>
Thu, 14 Apr 2011 06:51:39 +0000 (23:51 -0700)
committerRaphael Moll <ralf@android.com>
Fri, 15 Apr 2011 18:52:59 +0000 (11:52 -0700)
This little EPL library provides a way for the SDK apps to integrate
with the Mac menu bar -- that is correctly hook into the About and
Preferences menu items.

On other platforms (Windows, Linux), corresponding items are
added to the provided menu.

The library provides both a Carbon and a Cocoa implementation.
However the Cocoa implemented is currently commented out since
we only link with a Carbon-aware version of SWT.jar.

Added a README that explain how to use this.

Change-Id: I4b8457f0269946df056b5dd597c7263da1c4e784

13 files changed:
build/tools.atree
swtmenubar/.classpath [new file with mode: 0644]
swtmenubar/.gitignore [new file with mode: 0644]
swtmenubar/.project [new file with mode: 0644]
swtmenubar/Android.mk [new file with mode: 0644]
swtmenubar/MODULE_LICENSE_EPL [new file with mode: 0644]
swtmenubar/NOTICE [new file with mode: 0644]
swtmenubar/README [new file with mode: 0755]
swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java [new file with mode: 0755]
swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java.txt [new file with mode: 0755]
swtmenubar/src/com/android/menubar/IMenuBarCallback.java [new file with mode: 0644]
swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java [new file with mode: 0644]
swtmenubar/src/com/android/menubar/MenuBarEnhancer.java [new file with mode: 0644]

index 36913a8..89481fc 100644 (file)
@@ -77,6 +77,7 @@ sdk/files/android.el tools/lib/android.el
 # Java Libraries for the tools
 framework/androidprefs.jar       tools/lib/androidprefs.jar
 framework/common.jar             tools/lib/common.jar
+framework/swtmenubar.jar         tools/lib/swtmenubar.jar
 sdk/apkbuilder/etc/apkbuilder    tools/apkbuilder
 framework/sdkstats.jar           tools/lib/sdkstats.jar
 framework/archquery.jar          tools/lib/archquery.jar
diff --git a/swtmenubar/.classpath b/swtmenubar/.classpath
new file mode 100644 (file)
index 0000000..827ba88
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry excluding="org/eclipse/ui/internal/cocoa/CocoaUIEnhancer.java" kind="src" path="src"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/swtmenubar/.gitignore b/swtmenubar/.gitignore
new file mode 100644 (file)
index 0000000..ba077a4
--- /dev/null
@@ -0,0 +1 @@
+bin
diff --git a/swtmenubar/.project b/swtmenubar/.project
new file mode 100644 (file)
index 0000000..484282a
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>SwtMenuBar</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swtmenubar/Android.mk b/swtmenubar/Android.mk
new file mode 100644 (file)
index 0000000..333684f
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/org/documents/epl-v10.php
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+ifeq ($(HOST_OS),darwin)
+LOCAL_SRC_FILES += $(call all-java-files-under, src-$(HOST_OS))
+LOCAL_JAVA_RESOURCE_DIRS += src-$(HOST_OS)
+endif
+
+LOCAL_MODULE := swtmenubar
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := swt
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/swtmenubar/MODULE_LICENSE_EPL b/swtmenubar/MODULE_LICENSE_EPL
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/swtmenubar/NOTICE b/swtmenubar/NOTICE
new file mode 100644 (file)
index 0000000..49c101d
--- /dev/null
@@ -0,0 +1,224 @@
+*Eclipse Public License - v 1.0*
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+*1. DEFINITIONS*
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and
+are distributed by that particular Contributor. A Contribution
+'originates' from a Contributor if it was added to the Program by such
+Contributor itself or anyone acting on such Contributor's behalf.
+Contributions do not include additions to the Program which: (i) are
+separate modules of software distributed in conjunction with the Program
+under their own license agreement, and (ii) are not derivative works of
+the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+*2. GRANT OF RIGHTS*
+
+a) Subject to the terms of this Agreement, each Contributor hereby
+grants Recipient a non-exclusive, worldwide, royalty-free copyright
+license to reproduce, prepare derivative works of, publicly display,
+publicly perform, distribute and sublicense the Contribution of such
+Contributor, if any, and such derivative works, in source code and
+object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby
+grants Recipient a non-exclusive, worldwide, royalty-free patent license
+under Licensed Patents to make, use, sell, offer to sell, import and
+otherwise transfer the Contribution of such Contributor, if any, in
+source code and object code form. This patent license shall apply to the
+combination of the Contribution and the Program if, at the time the
+Contribution is added by the Contributor, such addition of the
+Contribution causes such combination to be covered by the Licensed
+Patents. The patent license shall not apply to any other combinations
+which include the Contribution. No hardware per se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the
+licenses to its Contributions set forth herein, no assurances are
+provided by any Contributor that the Program does not infringe the
+patent or other intellectual property rights of any other entity. Each
+Contributor disclaims any liability to Recipient for claims brought by
+any other entity based on infringement of intellectual property rights
+or otherwise. As a condition to exercising the rights and licenses
+granted hereunder, each Recipient hereby assumes sole responsibility to
+secure any other intellectual property rights needed, if any. For
+example, if a third party patent license is required to allow Recipient
+to distribute the Program, it is Recipient's responsibility to acquire
+that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright
+license set forth in this Agreement.
+
+*3. REQUIREMENTS*
+
+A Contributor may choose to distribute the Program in object code form
+under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties
+and conditions, express and implied, including warranties or conditions
+of title and non-infringement, and implied warranties or conditions of
+merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and
+consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are
+offered by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable
+manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+*4. COMMERCIAL DISTRIBUTION*
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program, the
+Contributor who includes the Program in a commercial product offering
+should do so in a manner which does not create potential liability for
+other Contributors. Therefore, if a Contributor includes the Program in
+a commercial product offering, such Contributor ("Commercial
+Contributor") hereby agrees to defend and indemnify every other
+Contributor ("Indemnified Contributor") against any losses, damages and
+costs (collectively "Losses") arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the
+Program in a commercial product offering. The obligations in this
+section do not apply to any claims or Losses relating to any actual or
+alleged intellectual property infringement. In order to qualify, an
+Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial
+Contributor to control, and cooperate with the Commercial Contributor
+in, the defense and any related settlement negotiations. The Indemnified
+Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.
+
+*5. NO WARRANTY*
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
+ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
+OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR
+A PARTICULAR PURPOSE. Each Recipient is solely responsible for
+determining the appropriateness of using and distributing the Program
+and assumes all risks associated with its exercise of rights under this
+Agreement , including but not limited to the risks and costs of program
+errors, compliance with applicable laws, damage to or loss of data,
+programs or equipment, and unavailability or interruption of operations.
+
+*6. DISCLAIMER OF LIABILITY*
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+*7. GENERAL*
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including
+a cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails
+to comply with any of the material terms or conditions of this Agreement
+and does not cure such failure in a reasonable period of time after
+becoming aware of such noncompliance. If all Recipient's rights under
+this Agreement terminate, Recipient agrees to cease use and distribution
+of the Program as soon as reasonably practicable. However, Recipient's
+obligations under this Agreement and any licenses granted by Recipient
+relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and may
+only be modified in the following manner. The Agreement Steward reserves
+the right to publish new versions (including revisions) of this
+Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the
+initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is
+published, Contributor may elect to distribute the Program (including
+its Contributions) under the new version. Except as expressly stated in
+Sections 2(a) and 2(b) above, Recipient receives no rights or licenses
+to the intellectual property of any Contributor under this Agreement,
+whether expressly, by implication, estoppel or otherwise. All rights in
+the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to
+this Agreement will bring a legal action under this Agreement more than
+one year after the cause of action arose. Each party waives its rights
+to a jury trial in any resulting litigation.
+
+
diff --git a/swtmenubar/README b/swtmenubar/README
new file mode 100755 (executable)
index 0000000..cc9fa61
--- /dev/null
@@ -0,0 +1,65 @@
+Using the Eclipse project SwtMenuBar
+------------------------------------
+
+This project provides a platform-specific way to hook into
+the default OS menu bar.
+
+On MacOS, it allows an SWT app to have an About menu item
+and to hook into the default Preferences menu item.
+
+On Windows and Linux, an SWT Menu should be provided (typically
+named "Tools") into which the About and Options menu items
+will be added.
+
+
+Consequently the implementation contains platform-specific source
+folders for the Java files that rely on a platform-specific version
+of SWT.jar.
+
+Right now we have the following source folders:
+- src/        - Generic implementation for all platforms.
+- src-darwin/ - Implementation for MacOS Carbon.
+
+*Only* the default "src/" folder is declared in the project .classpath
+so that the project can be opened in Eclipse on any platform and still
+work. However that means that on MacOS the custom src-darwin folder is
+not used by default.
+
+
+
+1- To build the library:
+
+Do not use Eclipse to build the library. Instead use the makefile:
+
+$ cd $TOP_OF_ANDROID_TREE
+$ . build/envsetup.sh && lunch sdk-eng
+$ make swtmenubar
+
+This will create a Jar in <Android tree>/out/host/<platform>/framework/
+that can then be included in the target application.
+
+
+2- To use the library in a target application:
+
+Build the swtmenubar library as explained in step 1.
+
+In the target application, define a classpath variable in Eclipse:
+- Open Preferences > Java > Build Path > Classpath Variables
+- Create a new classpath variable named ANDROID_OUT_FRAMEWORK
+- Set its folder value to <Android tree>/out/host/<platform>/framework
+
+Then add a variable to the Build Path of the target project:
+- Open Project > Properties > Java Build Path
+- Select the "Libraries" tab
+- Use "Add Variable"
+- Select ANDROID_OUT_FRAMEWORK
+- Select "Extend..."
+- Select swtmenubar.jar (which you previously built at step 1)
+
+
+Remember that if you then edit the SwtMenuBar project in Eclipse
+you will need to rebuild it using the command-line before the
+changes are propagated in the target applications that uses it.
+
+--
+EOF
diff --git a/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java
new file mode 100755 (executable)
index 0000000..328597a
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.menubar.internal;
+
+import com.android.menubar.IMenuBarCallback;
+import com.android.menubar.IMenuBarEnhancer;
+
+import org.eclipse.swt.internal.Callback;
+import org.eclipse.swt.internal.carbon.HICommand;
+import org.eclipse.swt.internal.carbon.OS;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+
+
+/**
+ * Implementation of IMenuBarEnhancer for MacOS Carbon SWT.
+ */
+public final class MenuBarEnhancerCarbon implements IMenuBarEnhancer {
+
+    private static final int kHICommandPreferences = ('p'<<24) + ('r'<<16) + ('e'<<8) + 'f';
+    private static final int kHICommandAbout       = ('a'<<24) + ('b'<<16) + ('o'<<8) + 'u';
+    private static final int kHICommandServices    = ('s'<<24) + ('e'<<16) + ('r'<<8) + 'v';
+
+    public MenuBarEnhancerCarbon() {
+    }
+
+    public void setupMenu(
+            String appName,
+            Menu swtMenu,
+            final IMenuBarCallback callbacks) {
+        final Display display = swtMenu.getDisplay();
+
+        // Callback target
+        Object target = new Object() {
+           @SuppressWarnings("unused")
+           int commandProc(int nextHandler, int theEvent, int userData) {
+              if (OS.GetEventKind(theEvent) == OS.kEventProcessCommand) {
+                 HICommand command = new HICommand();
+                 OS.GetEventParameter(
+                         theEvent,
+                         OS.kEventParamDirectObject,
+                         OS.typeHICommand,
+                         null,
+                         HICommand.sizeof,
+                         null,
+                         command);
+                 switch (command.commandID) {
+                 case kHICommandPreferences:
+                     callbacks.onPreferencesMenuSelected();
+                   return OS.eventNotHandledErr; // TODO wrong
+                 case kHICommandAbout:
+                     callbacks.onAboutMenuSelected();
+                     return OS.eventNotHandledErr;// TODO wrong
+                 default:
+                    break;
+                 }
+              }
+              return OS.eventNotHandledErr;
+           }
+        };
+
+        final Callback commandCallback= new Callback(target, "commandProc", 3); //$NON-NLS-1$
+        int commandProc = commandCallback.getAddress();
+        if (commandProc == 0) {
+           commandCallback.dispose();
+           log(callbacks, "%1$s: commandProc hook failed.", getClass().getSimpleName()); //$NON-NLS-1$
+           return;  // give up
+        }
+
+        // Install event handler for commands
+        int[] mask = new int[] {
+           OS.kEventClassCommand, OS.kEventProcessCommand
+        };
+        OS.InstallEventHandler(
+                OS.GetApplicationEventTarget(), commandProc, mask.length / 2, mask, 0, null);
+
+        // create About Eclipse menu command
+        int[] outMenu = new int[1];
+        short[] outIndex = new short[1];
+        if (OS.GetIndMenuItemWithCommandID(
+                0, kHICommandPreferences, 1, outMenu, outIndex) == OS.noErr && outMenu[0] != 0) {
+           int menu = outMenu[0];
+
+           // add About menu item (which isn't present by default)
+           String about = "About " + appName;
+           int l = about.length();
+           char buffer[] = new char[l];
+           about.getChars(0, l, buffer, 0);
+           int str = OS.CFStringCreateWithCharacters(OS.kCFAllocatorDefault, buffer, l);
+           OS.InsertMenuItemTextWithCFString(menu, str, (short) 0, 0, kHICommandAbout);
+           OS.CFRelease(str);
+
+           // add separator between About & Preferences
+           OS.InsertMenuItemTextWithCFString(menu, 0, (short) 1, OS.kMenuItemAttrSeparator, 0);
+
+           // enable pref menu
+           OS.EnableMenuCommand(menu, kHICommandPreferences);
+
+           // disable services menu
+           OS.DisableMenuCommand(menu, kHICommandServices);
+        }
+
+        // schedule disposal of callback object
+        display.disposeExec(
+           new Runnable() {
+              public void run() {
+                 commandCallback.dispose();
+              }
+           }
+        );
+    }
+
+    private void log(IMenuBarCallback callbacks, String format, Object... args) {
+        callbacks.printError(format , args);
+    }
+
+}
diff --git a/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java.txt b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java.txt
new file mode 100755 (executable)
index 0000000..591f253
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.menubar.internal;
+
+import com.android.menubar.IMenuBarCallback;
+import com.android.menubar.IMenuBarEnhancer;
+import com.android.menubar.MenuBarEnhancer;
+
+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.NSMenu;
+import org.eclipse.swt.internal.cocoa.NSMenuItem;
+import org.eclipse.swt.internal.cocoa.NSObject;
+import org.eclipse.swt.internal.cocoa.OS;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+/**
+ * Implementation of IMenuBarEnhancer for MacOS Cocoa SWT
+ * <p/>
+ * Note: this is currently deactivated since we are not currently
+ * shipping a version of SWT.jar that has cocoa support (only carbon).
+ */
+public final class MenuBarEnhancerCocoa implements IMenuBarEnhancer {
+
+    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;
+
+    private EnhancerDelegate mDelegate;
+    private long mDelegateJniRef;
+    private IMenuBarCallback mCallbacks;
+
+    public static class EnhancerDelegate extends NSObject {
+        public EnhancerDelegate() {
+            super();
+        }
+        public EnhancerDelegate(int id) {
+            super(id);
+        }
+
+    }
+    static long sel_preferencesMenuItemSelected_;
+    static long sel_aboutMenuItemSelected_;
+
+    /* This callback is not freed */
+    static Callback proc3Args;
+    static final byte[] SWT_OBJECT = { 'S', 'W', 'T', '_', 'O', 'B', 'J', 'E', 'C', 'T', '\0' };
+
+    public MenuBarEnhancerCocoa() {
+    }
+
+    public void setupMenu(
+            String appName,
+            Menu swtMenu,
+            IMenuBarCallback callbacks) {
+        mCallbacks = callbacks;
+        final Display display = swtMenu.getDisplay();
+
+        init1();
+
+        try {
+            mDelegate = new EnhancerDelegate();
+            mDelegate.alloc().init();
+            //call OS.NewGlobalRef
+            Method method = OS.class.getMethod("NewGlobalRef", new Class[] { Object.class });
+            Object object = method.invoke(OS.class, new Object[] { this });
+            mDelegateJniRef = convertToLong(object);
+        } catch (Exception e) {
+            // theoretically, one of SecurityException, Illegal*Exception,
+            // InvocationTargetException, NoSuch*Exception
+            // not expected to happen at all.
+            log(e);
+        }
+
+        if (mDelegateJniRef == 0) {
+            SWT.error(SWT.ERROR_NO_HANDLES);
+        }
+
+        try {
+            Field idField = EnhancerDelegate.class.getField("id");
+            Object idValue = idField.get(mDelegate);
+            invokeMethod(OS.class, "object_setInstanceVariable",
+                    new Object[] { idValue, SWT_OBJECT, wrapPointer(mDelegateJniRef) });
+
+            hookApplicationMenu(appName);
+
+            // schedule disposal of callback object
+            display.disposeExec(new Runnable() {
+                public void run() {
+                    if (mDelegateJniRef != 0) {
+                        try {
+                            invokeMethod(OS.class, "DeleteGlobalRef", new Object[] { wrapPointer(mDelegateJniRef) });
+                        } catch (Exception e) {
+                            // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception
+                            // not expected to happen at all.
+                            log(e);
+                        }
+                    }
+                    mDelegateJniRef = 0;
+
+                    if (mDelegate != null) {
+                        mDelegate.release();
+                        mDelegate = null;
+                    }
+                }
+            });
+        } 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);
+    }
+
+    private void init1() {
+        try {
+            if (sel_aboutMenuItemSelected_ == 0) {
+                sel_preferencesMenuItemSelected_ = registerName("preferencesMenuItemSelected:"); //$NON-NLS-1$
+                sel_aboutMenuItemSelected_ = registerName("aboutMenuItemSelected:"); //$NON-NLS-1$
+                init2();
+            }
+        } catch (Exception e) {
+            // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception
+            // not expected to happen at all.
+            log(e);
+        }
+    }
+
+    private void init2() 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, align = C.PTR_SIZEOF == 4 ? 2 : 3;
+
+        Class clazz = this.getClass();
+
+        proc3Args = new Callback(clazz, "actionProc", 3); //$NON-NLS-1$
+        //call getAddress
+        Method getAddress = Callback.class.getMethod("getAddress", new Class[0]);   //$NON-NLS-1$
+        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");  //$NON-NLS-1$
+        Object fieldObj = field.get(OS.class);
+        object = invokeMethod(OS.class, "objc_allocateClassPair",   //$NON-NLS-1$
+                new Object[] { fieldObj, "SWTCocoaEnhancerDelegate", wrapPointer(0) }); //$NON-NLS-1$
+        long cls = convertToLong(object);
+
+        invokeMethod(OS.class, "class_addIvar", new Object[] {      //$NON-NLS-1$
+                wrapPointer(cls), SWT_OBJECT, wrapPointer(size),
+                new Byte((byte) align), types });
+
+        // Add the action callback
+        invokeMethod(OS.class, "class_addMethod", new Object[] {    //$NON-NLS-1$
+                wrapPointer(cls),
+                wrapPointer(sel_preferencesMenuItemSelected_),
+                wrapPointer(proc3), "@:@" }); //$NON-NLS-1$
+        invokeMethod(OS.class, "class_addMethod", new Object[] {    //$NON-NLS-1$
+            wrapPointer(cls),
+            wrapPointer(sel_aboutMenuItemSelected_),
+            wrapPointer(proc3), "@:@" }); //$NON-NLS-1$
+
+        invokeMethod(OS.class, "objc_registerClassPair",            //$NON-NLS-1$
+                new Object[] { wrapPointer(cls) });
+    }
+
+    private void log(Exception e) {
+        mCallbacks.printError("%1$s: %2$s", getClass().getSimpleName(), e.toString()); //$NON-NLS-1$
+    }
+
+    private void hookApplicationMenu(String appName) {
+        try {
+            // create About Eclipse menu command
+            NSMenu mainMenu = NSApplication.sharedApplication().mainMenu();
+            NSMenuItem mainMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, mainMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(0)});  //$NON-NLS-1$
+            NSMenu appMenu = mainMenuItem.submenu();
+
+            // add the about action
+            NSMenuItem aboutMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(kAboutMenuItem)});
+            aboutMenuItem.setTitle(NSString.stringWith("About " + appName));  //$NON-NLS-1$
+
+            // enable pref menu
+            NSMenuItem prefMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(kPreferencesMenuItem)});  //$NON-NLS-1$
+            prefMenuItem.setEnabled(true);
+
+            // disable services menu
+            NSMenuItem servicesMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(kServicesMenuItem)});  //$NON-NLS-1$
+            servicesMenuItem.setEnabled(false);
+
+            // Register as a target on the prefs and quit items.
+            prefMenuItem.setTarget(mDelegate);
+            invokeMethod(NSMenuItem.class, prefMenuItem,
+                    "setAction", new Object[] {wrapPointer(sel_preferencesMenuItemSelected_)});  //$NON-NLS-1$
+            aboutMenuItem.setTarget(mDelegate);
+            invokeMethod(NSMenuItem.class, aboutMenuItem,
+                    "setAction", new Object[] {wrapPointer(sel_aboutMenuItemSelected_)});       //$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() {
+        try {
+            NSMenu mainMenu = NSApplication.sharedApplication().mainMenu();
+            NSMenuItem mainMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, mainMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(0)});                      //$NON-NLS-1$
+            NSMenu appMenu = mainMenuItem.submenu();
+            NSMenuItem prefMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu, "" + //$NON-NLS-1$
+                    "itemAtIndex", new Object[] {wrapPointer(kPreferencesMenuItem)});    //$NON-NLS-1$
+            try {
+                prefMenuItem.setEnabled(false);
+
+                mCallbacks.onPreferencesMenuSelected();
+            }
+            finally {
+                prefMenuItem.setEnabled(true);
+            }
+        } catch (Exception e) {
+            // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception
+            // not expected to happen at all.
+            log(e);
+        }
+    }
+
+    void aboutMenuItemSelected() {
+        try {
+            NSMenu mainMenu = NSApplication.sharedApplication().mainMenu();
+            NSMenuItem mainMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, mainMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(0)});              //$NON-NLS-1$
+            NSMenu appMenu = mainMenuItem.submenu();
+            NSMenuItem aboutMenuItem = (NSMenuItem) invokeMethod(NSMenu.class, appMenu,
+                    "itemAtIndex", new Object[] {wrapPointer(kAboutMenuItem)}); //$NON-NLS-1$
+            try {
+                aboutMenuItem.setEnabled(false);
+
+                mCallbacks.onAboutMenuSelected();
+            }
+            finally {
+                aboutMenuItem.setEnabled(true);
+            }
+        } catch (Exception e) {
+            // theoretically, one of SecurityException,Illegal*Exception,InvocationTargetException,NoSuch*Exception
+            // not expected to happen at all.
+            log(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 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);
+        }
+    }
+}
diff --git a/swtmenubar/src/com/android/menubar/IMenuBarCallback.java b/swtmenubar/src/com/android/menubar/IMenuBarCallback.java
new file mode 100644 (file)
index 0000000..b0d6568
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.menubar;
+
+
+
+/**
+ * Callbacks used by {@link IMenuBarEnhancer}.
+ */
+public interface IMenuBarCallback {
+    /**
+     * Invoked when the About menu item is selected by the user.
+     */
+    abstract public void onAboutMenuSelected();
+
+    /**
+     * Invoked when the Preferences or Options menu item is selected by the user.
+     */
+    abstract public void onPreferencesMenuSelected();
+
+    /**
+     * Used by the enhancer implementations to report errors.
+     *
+     * @param format A printf-like format string.
+     * @param args The parameters for the printf-like format string.
+     */
+    abstract public void printError(String format, Object...args);
+}
diff --git a/swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java b/swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java
new file mode 100644 (file)
index 0000000..1d587e9
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.menubar;
+
+import org.eclipse.swt.widgets.Menu;
+
+
+/**
+ * Interface to the platform-specific MenuBarEnhancer implementation returned by
+ * {@link MenuBarEnhancer#setupMenu}.
+ */
+public interface IMenuBarEnhancer {
+
+    /**
+     * Updates the menu bar to provide an About menu item and a Preferences menu item.
+     * Depending on the platform, the menu items might be decorated with the
+     * given {@code appName}.
+     * <p/>
+     * Users should not call this directly.
+     * {@link MenuBarEnhancer#setupMenu} should be used instead.
+     *
+     * @param appName Name used for the About menu item and similar. Must not be null.
+     * @param swtMenu For non-mac platform this is the menu where the "About" and
+     *          the "Preferences" menu items are created. Must not be null.
+     * @param callbacks Callbacks called when "About" and "Preferences" menu items are invoked.
+     *          Must not be null.
+     */
+    public void setupMenu(
+            String appName,
+            Menu swtMenu,
+            IMenuBarCallback callbacks);
+}
diff --git a/swtmenubar/src/com/android/menubar/MenuBarEnhancer.java b/swtmenubar/src/com/android/menubar/MenuBarEnhancer.java
new file mode 100644 (file)
index 0000000..e40fbe0
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.menubar;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+
+/**
+ * On Mac, {@link MenuBarEnhancer#setupMenu} plugs a listener on the About and the
+ * Preferences menu items of the standard "application" menu in the menu bar.
+ * On Windows or Linux, it adds relevant items to a given {@link Menu} linked to
+ * the same listeners.
+ */
+public final class MenuBarEnhancer {
+
+    private MenuBarEnhancer() {
+    }
+
+    /**
+     * Creates an instance of {@link IMenuBarEnhancer} specific to the current platform
+     * and invoke its {@link IMenuBarEnhancer#setupMenu} to updates the menu bar.
+     * <p/>
+     * Depending on the platform, this will either hook into the existing About menu item
+     * and a Preferences or Options menu item or add new ones to the given {@code swtMenu}.
+     * Depending on the platform, the menu items might be decorated with the
+     * given {@code appName}.
+     * <p/>
+     * Potential errors are reported through {@link IMenuBarCallback}.
+     *
+     * @param appName Name used for the About menu item and similar. Must not be null.
+     * @param swtMenu For non-mac platform this is the menu where the "About" and
+     *          the "Options" menu items are created. Typically the menu might be
+     *          called "Tools". Must not be null.
+     * @param callbacks Callbacks called when "About" and "Preferences" menu items are invoked.
+     *          Must not be null.
+     * @return A actual {@link IMenuBarEnhancer} implementation. Never null.
+     *          This is currently not of any use for the caller but is left in case
+     *          we want to expand the functionality later.
+     */
+    public static IMenuBarEnhancer setupMenu(
+            String appName,
+            Menu swtMenu,
+            IMenuBarCallback callbacks) {
+
+        IMenuBarEnhancer enhancer = null;
+        String p = SWT.getPlatform();
+        String className = null;
+        if ("carbon".equals(p)) {                                                 //$NON-NLS-1$
+            className = "com.android.menubar.internal.MenuBarEnhancerCarbon";     //$NON-NLS-1$
+        } else if ("cocoa".equals(p)) {                                           //$NON-NLS-1$
+            // Note: we have a Cocoa implementation that is currently disabled
+            // since the SWT.jar that we use only contain Carbon implementations.
+            //
+            // className = "com.android.menubar.internal.MenuBarEnhancerCocoa";   //$NON-NLS-1$
+        }
+
+        if (className != null) {
+            try {
+                Class<?> clazz = p.getClass().forName(className);
+                enhancer = (IMenuBarEnhancer) clazz.newInstance();
+            } catch (Exception e) {
+                // Log an error and fallback on the default implementation.
+                callbacks.printError(
+                        "Failed to instantiate %1$s: %2$s",                       //$NON-NLS-1$
+                        className,
+                        e.toString());
+            }
+        }
+
+        // Default implementation for other platforms
+        if (enhancer == null) {
+            enhancer = new IMenuBarEnhancer() {
+                public void setupMenu(
+                        String appName,
+                        Menu menu,
+                        final IMenuBarCallback callbacks) {
+                    new MenuItem(menu, SWT.SEPARATOR);
+
+                    // Note: we use "Preferences" on Mac and "Options" on Windows/Linux.
+                    final MenuItem pref = new MenuItem(menu, SWT.NONE);
+                    pref.setText("Options...");
+
+                    final MenuItem about = new MenuItem(menu, SWT.NONE);
+                    about.setText("About...");
+
+                    pref.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            try {
+                                pref.setEnabled(false);
+                                callbacks.onPreferencesMenuSelected();
+                                super.widgetSelected(e);
+                            } finally {
+                                pref.setEnabled(true);
+                            }
+                        }
+                    });
+
+                    about.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            try {
+                                about.setEnabled(false);
+                                callbacks.onAboutMenuSelected();
+                                super.widgetSelected(e);
+                            } finally {
+                                about.setEnabled(true);
+                            }
+                        }
+                    });
+                }
+            };
+        }
+
+        enhancer.setupMenu(appName, swtMenu, callbacks);
+        return enhancer;
+    }
+
+}