Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / android-libraries / ActionBarSherlock / src / com / actionbarsherlock / view / MenuInflater.java
diff --git a/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java b/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java
new file mode 100644 (file)
index 0000000..9694597
--- /dev/null
@@ -0,0 +1,472 @@
+/*\r
+ * Copyright (C) 2006 The Android Open Source Project\r
+ *               2011 Jake Wharton\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.actionbarsherlock.view;\r
+\r
+import java.io.IOException;\r
+import java.lang.reflect.Constructor;\r
+import java.lang.reflect.Method;\r
+import org.xmlpull.v1.XmlPullParser;\r
+import org.xmlpull.v1.XmlPullParserException;\r
+import android.content.Context;\r
+import android.content.res.TypedArray;\r
+import android.content.res.XmlResourceParser;\r
+import android.util.AttributeSet;\r
+import android.util.Log;\r
+import android.util.TypedValue;\r
+import android.util.Xml;\r
+import android.view.InflateException;\r
+import android.view.View;\r
+\r
+import com.actionbarsherlock.R;\r
+import com.actionbarsherlock.internal.view.menu.MenuItemImpl;\r
+\r
+/**\r
+ * This class is used to instantiate menu XML files into Menu objects.\r
+ * <p>\r
+ * For performance reasons, menu inflation relies heavily on pre-processing of\r
+ * XML files that is done at build time. Therefore, it is not currently possible\r
+ * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;\r
+ * it only works with an XmlPullParser returned from a compiled resource (R.\r
+ * <em>something</em> file.)\r
+ */\r
+public class MenuInflater {\r
+    private static final String LOG_TAG = "MenuInflater";\r
+\r
+    /** Menu tag name in XML. */\r
+    private static final String XML_MENU = "menu";\r
+\r
+    /** Group tag name in XML. */\r
+    private static final String XML_GROUP = "group";\r
+\r
+    /** Item tag name in XML. */\r
+    private static final String XML_ITEM = "item";\r
+\r
+    private static final int NO_ID = 0;\r
+\r
+    private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};\r
+\r
+    private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE;\r
+\r
+    private final Object[] mActionViewConstructorArguments;\r
+\r
+    private final Object[] mActionProviderConstructorArguments;\r
+\r
+    private Context mContext;\r
+\r
+    /**\r
+     * Constructs a menu inflater.\r
+     *\r
+     * @see Activity#getMenuInflater()\r
+     */\r
+    public MenuInflater(Context context) {\r
+        mContext = context;\r
+        mActionViewConstructorArguments = new Object[] {context};\r
+        mActionProviderConstructorArguments = mActionViewConstructorArguments;\r
+    }\r
+\r
+    /**\r
+     * Inflate a menu hierarchy from the specified XML resource. Throws\r
+     * {@link InflateException} if there is an error.\r
+     *\r
+     * @param menuRes Resource ID for an XML layout resource to load (e.g.,\r
+     *            <code>R.menu.main_activity</code>)\r
+     * @param menu The Menu to inflate into. The items and submenus will be\r
+     *            added to this Menu.\r
+     */\r
+    public void inflate(int menuRes, Menu menu) {\r
+        XmlResourceParser parser = null;\r
+        try {\r
+            parser = mContext.getResources().getLayout(menuRes);\r
+            AttributeSet attrs = Xml.asAttributeSet(parser);\r
+\r
+            parseMenu(parser, attrs, menu);\r
+        } catch (XmlPullParserException e) {\r
+            throw new InflateException("Error inflating menu XML", e);\r
+        } catch (IOException e) {\r
+            throw new InflateException("Error inflating menu XML", e);\r
+        } finally {\r
+            if (parser != null) parser.close();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Called internally to fill the given menu. If a sub menu is seen, it will\r
+     * call this recursively.\r
+     */\r
+    private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)\r
+            throws XmlPullParserException, IOException {\r
+        MenuState menuState = new MenuState(menu);\r
+\r
+        int eventType = parser.getEventType();\r
+        String tagName;\r
+        boolean lookingForEndOfUnknownTag = false;\r
+        String unknownTagName = null;\r
+\r
+        // This loop will skip to the menu start tag\r
+        do {\r
+            if (eventType == XmlPullParser.START_TAG) {\r
+                tagName = parser.getName();\r
+                if (tagName.equals(XML_MENU)) {\r
+                    // Go to next tag\r
+                    eventType = parser.next();\r
+                    break;\r
+                }\r
+\r
+                throw new RuntimeException("Expecting menu, got " + tagName);\r
+            }\r
+            eventType = parser.next();\r
+        } while (eventType != XmlPullParser.END_DOCUMENT);\r
+\r
+        boolean reachedEndOfMenu = false;\r
+        while (!reachedEndOfMenu) {\r
+            switch (eventType) {\r
+                case XmlPullParser.START_TAG:\r
+                    if (lookingForEndOfUnknownTag) {\r
+                        break;\r
+                    }\r
+\r
+                    tagName = parser.getName();\r
+                    if (tagName.equals(XML_GROUP)) {\r
+                        menuState.readGroup(attrs);\r
+                    } else if (tagName.equals(XML_ITEM)) {\r
+                        menuState.readItem(attrs);\r
+                    } else if (tagName.equals(XML_MENU)) {\r
+                        // A menu start tag denotes a submenu for an item\r
+                        SubMenu subMenu = menuState.addSubMenuItem();\r
+\r
+                        // Parse the submenu into returned SubMenu\r
+                        parseMenu(parser, attrs, subMenu);\r
+                    } else {\r
+                        lookingForEndOfUnknownTag = true;\r
+                        unknownTagName = tagName;\r
+                    }\r
+                    break;\r
+\r
+                case XmlPullParser.END_TAG:\r
+                    tagName = parser.getName();\r
+                    if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {\r
+                        lookingForEndOfUnknownTag = false;\r
+                        unknownTagName = null;\r
+                    } else if (tagName.equals(XML_GROUP)) {\r
+                        menuState.resetGroup();\r
+                    } else if (tagName.equals(XML_ITEM)) {\r
+                        // Add the item if it hasn't been added (if the item was\r
+                        // a submenu, it would have been added already)\r
+                        if (!menuState.hasAddedItem()) {\r
+                            if (menuState.itemActionProvider != null &&\r
+                                    menuState.itemActionProvider.hasSubMenu()) {\r
+                                menuState.addSubMenuItem();\r
+                            } else {\r
+                                menuState.addItem();\r
+                            }\r
+                        }\r
+                    } else if (tagName.equals(XML_MENU)) {\r
+                        reachedEndOfMenu = true;\r
+                    }\r
+                    break;\r
+\r
+                case XmlPullParser.END_DOCUMENT:\r
+                    throw new RuntimeException("Unexpected end of document");\r
+            }\r
+\r
+            eventType = parser.next();\r
+        }\r
+    }\r
+\r
+    private static class InflatedOnMenuItemClickListener\r
+            implements MenuItem.OnMenuItemClickListener {\r
+        private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };\r
+\r
+        private Context mContext;\r
+        private Method mMethod;\r
+\r
+        public InflatedOnMenuItemClickListener(Context context, String methodName) {\r
+            mContext = context;\r
+            Class<?> c = context.getClass();\r
+            try {\r
+                mMethod = c.getMethod(methodName, PARAM_TYPES);\r
+            } catch (Exception e) {\r
+                InflateException ex = new InflateException(\r
+                        "Couldn't resolve menu item onClick handler " + methodName +\r
+                        " in class " + c.getName());\r
+                ex.initCause(e);\r
+                throw ex;\r
+            }\r
+        }\r
+\r
+        public boolean onMenuItemClick(MenuItem item) {\r
+            try {\r
+                if (mMethod.getReturnType() == Boolean.TYPE) {\r
+                    return (Boolean) mMethod.invoke(mContext, item);\r
+                } else {\r
+                    mMethod.invoke(mContext, item);\r
+                    return true;\r
+                }\r
+            } catch (Exception e) {\r
+                throw new RuntimeException(e);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * State for the current menu.\r
+     * <p>\r
+     * Groups can not be nested unless there is another menu (which will have\r
+     * its state class).\r
+     */\r
+    private class MenuState {\r
+        private Menu menu;\r
+\r
+        /*\r
+         * Group state is set on items as they are added, allowing an item to\r
+         * override its group state. (As opposed to set on items at the group end tag.)\r
+         */\r
+        private int groupId;\r
+        private int groupCategory;\r
+        private int groupOrder;\r
+        private int groupCheckable;\r
+        private boolean groupVisible;\r
+        private boolean groupEnabled;\r
+\r
+        private boolean itemAdded;\r
+        private int itemId;\r
+        private int itemCategoryOrder;\r
+        private CharSequence itemTitle;\r
+        private CharSequence itemTitleCondensed;\r
+        private int itemIconResId;\r
+        private char itemAlphabeticShortcut;\r
+        private char itemNumericShortcut;\r
+        /**\r
+         * Sync to attrs.xml enum:\r
+         * - 0: none\r
+         * - 1: all\r
+         * - 2: exclusive\r
+         */\r
+        private int itemCheckable;\r
+        private boolean itemChecked;\r
+        private boolean itemVisible;\r
+        private boolean itemEnabled;\r
+\r
+        /**\r
+         * Sync to attrs.xml enum, values in MenuItem:\r
+         * - 0: never\r
+         * - 1: ifRoom\r
+         * - 2: always\r
+         * - -1: Safe sentinel for "no value".\r
+         */\r
+        private int itemShowAsAction;\r
+\r
+        private int itemActionViewLayout;\r
+        private String itemActionViewClassName;\r
+        private String itemActionProviderClassName;\r
+\r
+        private String itemListenerMethodName;\r
+\r
+        private ActionProvider itemActionProvider;\r
+\r
+        private static final int defaultGroupId = NO_ID;\r
+        private static final int defaultItemId = NO_ID;\r
+        private static final int defaultItemCategory = 0;\r
+        private static final int defaultItemOrder = 0;\r
+        private static final int defaultItemCheckable = 0;\r
+        private static final boolean defaultItemChecked = false;\r
+        private static final boolean defaultItemVisible = true;\r
+        private static final boolean defaultItemEnabled = true;\r
+\r
+        public MenuState(final Menu menu) {\r
+            this.menu = menu;\r
+\r
+            resetGroup();\r
+        }\r
+\r
+        public void resetGroup() {\r
+            groupId = defaultGroupId;\r
+            groupCategory = defaultItemCategory;\r
+            groupOrder = defaultItemOrder;\r
+            groupCheckable = defaultItemCheckable;\r
+            groupVisible = defaultItemVisible;\r
+            groupEnabled = defaultItemEnabled;\r
+        }\r
+\r
+        /**\r
+         * Called when the parser is pointing to a group tag.\r
+         */\r
+        public void readGroup(AttributeSet attrs) {\r
+            TypedArray a = mContext.obtainStyledAttributes(attrs,\r
+                    R.styleable.SherlockMenuGroup);\r
+\r
+            groupId = a.getResourceId(R.styleable.SherlockMenuGroup_android_id, defaultGroupId);\r
+            groupCategory = a.getInt(R.styleable.SherlockMenuGroup_android_menuCategory, defaultItemCategory);\r
+            groupOrder = a.getInt(R.styleable.SherlockMenuGroup_android_orderInCategory, defaultItemOrder);\r
+            groupCheckable = a.getInt(R.styleable.SherlockMenuGroup_android_checkableBehavior, defaultItemCheckable);\r
+            groupVisible = a.getBoolean(R.styleable.SherlockMenuGroup_android_visible, defaultItemVisible);\r
+            groupEnabled = a.getBoolean(R.styleable.SherlockMenuGroup_android_enabled, defaultItemEnabled);\r
+\r
+            a.recycle();\r
+        }\r
+\r
+        /**\r
+         * Called when the parser is pointing to an item tag.\r
+         */\r
+        public void readItem(AttributeSet attrs) {\r
+            TypedArray a = mContext.obtainStyledAttributes(attrs,\r
+                    R.styleable.SherlockMenuItem);\r
+\r
+            // Inherit attributes from the group as default value\r
+            itemId = a.getResourceId(R.styleable.SherlockMenuItem_android_id, defaultItemId);\r
+            final int category = a.getInt(R.styleable.SherlockMenuItem_android_menuCategory, groupCategory);\r
+            final int order = a.getInt(R.styleable.SherlockMenuItem_android_orderInCategory, groupOrder);\r
+            itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);\r
+            itemTitle = a.getText(R.styleable.SherlockMenuItem_android_title);\r
+            itemTitleCondensed = a.getText(R.styleable.SherlockMenuItem_android_titleCondensed);\r
+            itemIconResId = a.getResourceId(R.styleable.SherlockMenuItem_android_icon, 0);\r
+            itemAlphabeticShortcut =\r
+                    getShortcut(a.getString(R.styleable.SherlockMenuItem_android_alphabeticShortcut));\r
+            itemNumericShortcut =\r
+                    getShortcut(a.getString(R.styleable.SherlockMenuItem_android_numericShortcut));\r
+            if (a.hasValue(R.styleable.SherlockMenuItem_android_checkable)) {\r
+                // Item has attribute checkable, use it\r
+                itemCheckable = a.getBoolean(R.styleable.SherlockMenuItem_android_checkable, false) ? 1 : 0;\r
+            } else {\r
+                // Item does not have attribute, use the group's (group can have one more state\r
+                // for checkable that represents the exclusive checkable)\r
+                itemCheckable = groupCheckable;\r
+            }\r
+\r
+            itemChecked = a.getBoolean(R.styleable.SherlockMenuItem_android_checked, defaultItemChecked);\r
+            itemVisible = a.getBoolean(R.styleable.SherlockMenuItem_android_visible, groupVisible);\r
+            itemEnabled = a.getBoolean(R.styleable.SherlockMenuItem_android_enabled, groupEnabled);\r
+\r
+            TypedValue value = new TypedValue();\r
+            a.getValue(R.styleable.SherlockMenuItem_android_showAsAction, value);\r
+            itemShowAsAction = value.type == TypedValue.TYPE_INT_HEX ? value.data : -1;\r
+\r
+            itemListenerMethodName = a.getString(R.styleable.SherlockMenuItem_android_onClick);\r
+            itemActionViewLayout = a.getResourceId(R.styleable.SherlockMenuItem_android_actionLayout, 0);\r
+            itemActionViewClassName = a.getString(R.styleable.SherlockMenuItem_android_actionViewClass);\r
+            itemActionProviderClassName = a.getString(R.styleable.SherlockMenuItem_android_actionProviderClass);\r
+\r
+            final boolean hasActionProvider = itemActionProviderClassName != null;\r
+            if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {\r
+                itemActionProvider = newInstance(itemActionProviderClassName,\r
+                            ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,\r
+                            mActionProviderConstructorArguments);\r
+            } else {\r
+                if (hasActionProvider) {\r
+                    Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."\r
+                            + " Action view already specified.");\r
+                }\r
+                itemActionProvider = null;\r
+            }\r
+\r
+            a.recycle();\r
+\r
+            itemAdded = false;\r
+        }\r
+\r
+        private char getShortcut(String shortcutString) {\r
+            if (shortcutString == null) {\r
+                return 0;\r
+            } else {\r
+                return shortcutString.charAt(0);\r
+            }\r
+        }\r
+\r
+        private void setItem(MenuItem item) {\r
+            item.setChecked(itemChecked)\r
+                .setVisible(itemVisible)\r
+                .setEnabled(itemEnabled)\r
+                .setCheckable(itemCheckable >= 1)\r
+                .setTitleCondensed(itemTitleCondensed)\r
+                .setIcon(itemIconResId)\r
+                .setAlphabeticShortcut(itemAlphabeticShortcut)\r
+                .setNumericShortcut(itemNumericShortcut);\r
+\r
+            if (itemShowAsAction >= 0) {\r
+                item.setShowAsAction(itemShowAsAction);\r
+            }\r
+\r
+            if (itemListenerMethodName != null) {\r
+                if (mContext.isRestricted()) {\r
+                    throw new IllegalStateException("The android:onClick attribute cannot "\r
+                            + "be used within a restricted context");\r
+                }\r
+                item.setOnMenuItemClickListener(\r
+                        new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));\r
+            }\r
+\r
+            if (itemCheckable >= 2) {\r
+                if (item instanceof MenuItemImpl) {\r
+                    MenuItemImpl impl = (MenuItemImpl) item;\r
+                    impl.setExclusiveCheckable(true);\r
+                } else {\r
+                    menu.setGroupCheckable(groupId, true, true);\r
+                }\r
+            }\r
+\r
+            boolean actionViewSpecified = false;\r
+            if (itemActionViewClassName != null) {\r
+                View actionView = (View) newInstance(itemActionViewClassName,\r
+                        ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);\r
+                item.setActionView(actionView);\r
+                actionViewSpecified = true;\r
+            }\r
+            if (itemActionViewLayout > 0) {\r
+                if (!actionViewSpecified) {\r
+                    item.setActionView(itemActionViewLayout);\r
+                    actionViewSpecified = true;\r
+                } else {\r
+                    Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."\r
+                            + " Action view already specified.");\r
+                }\r
+            }\r
+            if (itemActionProvider != null) {\r
+                item.setActionProvider(itemActionProvider);\r
+            }\r
+        }\r
+\r
+        public void addItem() {\r
+            itemAdded = true;\r
+            setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));\r
+        }\r
+\r
+        public SubMenu addSubMenuItem() {\r
+            itemAdded = true;\r
+            SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);\r
+            setItem(subMenu.getItem());\r
+            return subMenu;\r
+        }\r
+\r
+        public boolean hasAddedItem() {\r
+            return itemAdded;\r
+        }\r
+\r
+        @SuppressWarnings("unchecked")\r
+        private <T> T newInstance(String className, Class<?>[] constructorSignature,\r
+                Object[] arguments) {\r
+            try {\r
+                Class<?> clazz = mContext.getClassLoader().loadClass(className);\r
+                Constructor<?> constructor = clazz.getConstructor(constructorSignature);\r
+                return (T) constructor.newInstance(arguments);\r
+            } catch (Exception e) {\r
+                Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);\r
+            }\r
+            return null;\r
+        }\r
+    }\r
+}\r