summaryrefslogtreecommitdiffstats
path: root/core/java/com/android/internal/view/menu/MenuItemImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/com/android/internal/view/menu/MenuItemImpl.java')
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java634
1 files changed, 634 insertions, 0 deletions
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
new file mode 100644
index 0000000..c89f2e9
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public final class MenuItemImpl implements MenuItem {
+ private final int mId;
+ private final int mGroup;
+ private final int mCategoryOrder;
+ private final int mOrdering;
+ private CharSequence mTitle;
+ private CharSequence mTitleCondensed;
+ private Intent mIntent;
+ private char mShortcutNumericChar;
+ private char mShortcutAlphabeticChar;
+
+ /** The icon's drawable which is only created as needed */
+ private Drawable mIconDrawable;
+ /**
+ * The icon's resource ID which is used to get the Drawable when it is
+ * needed (if the Drawable isn't already obtained--only one of the two is
+ * needed).
+ */
+ private int mIconResId = NO_ICON;
+
+ /** The (cached) menu item views for this item */
+ private WeakReference<ItemView> mItemViews[];
+
+ /** The menu to which this item belongs */
+ private MenuBuilder mMenu;
+ /** If this item should launch a sub menu, this is the sub menu to launch */
+ private SubMenuBuilder mSubMenu;
+
+ private Runnable mItemCallback;
+ private MenuItem.OnMenuItemClickListener mClickListener;
+
+ private int mFlags = ENABLED;
+ private static final int CHECKABLE = 0x00000001;
+ private static final int CHECKED = 0x00000002;
+ private static final int EXCLUSIVE = 0x00000004;
+ private static final int HIDDEN = 0x00000008;
+ private static final int ENABLED = 0x00000010;
+
+ /** Used for the icon resource ID if this item does not have an icon */
+ static final int NO_ICON = 0;
+
+ /**
+ * Current use case is for context menu: Extra information linked to the
+ * View that added this item to the context menu.
+ */
+ private ContextMenuInfo mMenuInfo;
+
+ private static String sPrependShortcutLabel;
+ private static String sEnterShortcutLabel;
+ private static String sDeleteShortcutLabel;
+ private static String sSpaceShortcutLabel;
+
+
+ /**
+ * Instantiates this menu item. The constructor
+ * {@link #MenuItemData(MenuBuilder, int, int, int, CharSequence, int)} is
+ * preferred due to lazy loading of the icon Drawable.
+ *
+ * @param menu
+ * @param group Item ordering grouping control. The item will be added after
+ * all other items whose order is <= this number, and before any
+ * that are larger than it. This can also be used to define
+ * groups of items for batch state changes. Normally use 0.
+ * @param id Unique item ID. Use 0 if you do not need a unique ID.
+ * @param categoryOrder The ordering for this item.
+ * @param title The text to display for the item.
+ */
+ MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+ CharSequence title) {
+
+ if (sPrependShortcutLabel == null) {
+ // This is instantiated from the UI thread, so no chance of sync issues
+ sPrependShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.prepend_shortcut_label);
+ sEnterShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_enter_shortcut_label);
+ sDeleteShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_delete_shortcut_label);
+ sSpaceShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_space_shortcut_label);
+ }
+
+ mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
+ mMenu = menu;
+ mId = id;
+ mGroup = group;
+ mCategoryOrder = categoryOrder;
+ mOrdering = ordering;
+ mTitle = title;
+ }
+
+ /**
+ * Invokes the item by calling various listeners or callbacks.
+ *
+ * @return true if the invocation was handled, false otherwise
+ */
+ public boolean invoke() {
+ if (mClickListener != null &&
+ mClickListener.onMenuItemClick(this)) {
+ return true;
+ }
+
+ MenuBuilder.Callback callback = mMenu.getCallback();
+ if (callback != null &&
+ callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+ return true;
+ }
+
+ if (mItemCallback != null) {
+ mItemCallback.run();
+ return true;
+ }
+
+ if (mIntent != null) {
+ mMenu.getContext().startActivity(mIntent);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean hasItemView(int menuType) {
+ return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
+ }
+
+ public boolean isEnabled() {
+ return (mFlags & ENABLED) != 0;
+ }
+
+ public MenuItem setEnabled(boolean enabled) {
+ if (enabled) {
+ mFlags |= ENABLED;
+ } else {
+ mFlags &= ~ENABLED;
+ }
+
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ // If the item view prefers a condensed title, only set this title if there
+ // is no condensed title for this item
+ if (hasItemView(i)) {
+ mItemViews[i].get().setEnabled(enabled);
+ }
+ }
+
+ return this;
+ }
+
+ public int getGroupId() {
+ return mGroup;
+ }
+
+ public int getItemId() {
+ return mId;
+ }
+
+ public int getOrder() {
+ return mCategoryOrder;
+ }
+
+ public int getOrdering() {
+ return mOrdering;
+ }
+
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ public MenuItem setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ Runnable getCallback() {
+ return mItemCallback;
+ }
+
+ public MenuItem setCallback(Runnable callback) {
+ mItemCallback = callback;
+ return this;
+ }
+
+ public char getAlphabeticShortcut() {
+ return mShortcutAlphabeticChar;
+ }
+
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ if (mShortcutAlphabeticChar == alphaChar) return this;
+
+ mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
+
+ refreshShortcutOnItemViews();
+
+ return this;
+ }
+
+ public char getNumericShortcut() {
+ return mShortcutNumericChar;
+ }
+
+ public MenuItem setNumericShortcut(char numericChar) {
+ if (mShortcutNumericChar == numericChar) return this;
+
+ mShortcutNumericChar = numericChar;
+
+ refreshShortcutOnItemViews();
+
+ return this;
+ }
+
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
+
+ refreshShortcutOnItemViews();
+
+ return this;
+ }
+
+ /**
+ * @return The active shortcut (based on QWERTY-mode of the menu).
+ */
+ char getShortcut() {
+ return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
+ }
+
+ /**
+ * @return The label to show for the shortcut. This includes the chording
+ * key (for example 'Menu+a'). Also, any non-human readable
+ * characters should be human readable (for example 'Menu+enter').
+ */
+ String getShortcutLabel() {
+
+ char shortcut = getShortcut();
+ if (shortcut == 0) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
+ switch (shortcut) {
+
+ case '\n':
+ sb.append(sEnterShortcutLabel);
+ break;
+
+ case '\b':
+ sb.append(sDeleteShortcutLabel);
+ break;
+
+ case ' ':
+ sb.append(sSpaceShortcutLabel);
+ break;
+
+ default:
+ sb.append(shortcut);
+ break;
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * @return Whether this menu item should be showing shortcuts (depends on
+ * whether the menu should show shortcuts and whether this item has
+ * a shortcut defined)
+ */
+ boolean shouldShowShortcut() {
+ // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
+ return mMenu.isShortcutsVisible() && (getShortcut() != 0);
+ }
+
+ /**
+ * Refreshes the shortcut shown on the ItemViews. This method retrieves current
+ * shortcut state (mode and shown) from the menu that contains this item.
+ */
+ private void refreshShortcutOnItemViews() {
+ refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
+ }
+
+ /**
+ * Refreshes the shortcut shown on the ItemViews. This is usually called by
+ * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
+ * views, so it passes arguments rather than each item calling a method on the menu to get
+ * the same values.
+ *
+ * @param menuShortcutShown The menu's shortcut shown mode. In addition,
+ * this method will ensure this item has a shortcut before it
+ * displays the shortcut.
+ * @param isQwertyMode Whether the shortcut mode is qwerty mode
+ */
+ void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
+ final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
+
+ // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
+ final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
+
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ if (hasItemView(i)) {
+ mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
+ }
+ }
+ }
+
+ public SubMenu getSubMenu() {
+ return mSubMenu;
+ }
+
+ public boolean hasSubMenu() {
+ return mSubMenu != null;
+ }
+
+ void setSubMenu(SubMenuBuilder subMenu) {
+ if ((mMenu != null) && (mMenu instanceof SubMenu)) {
+ throw new UnsupportedOperationException(
+ "Attempt to add a sub-menu to a sub-menu.");
+ }
+
+ mSubMenu = subMenu;
+
+ subMenu.setHeaderTitle(getTitle());
+ }
+
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Gets the title for a particular {@link ItemView}
+ *
+ * @param itemView The ItemView that is receiving the title
+ * @return Either the title or condensed title based on what the ItemView
+ * prefers
+ */
+ CharSequence getTitleForItemView(MenuView.ItemView itemView) {
+ return ((itemView != null) && itemView.prefersCondensedTitle())
+ ? getTitleCondensed()
+ : getTitle();
+ }
+
+ public MenuItem setTitle(CharSequence title) {
+ mTitle = title;
+
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ // If the item view prefers a condensed title, only set this title if there
+ // is no condensed title for this item
+ if (!hasItemView(i)) {
+ continue;
+ }
+
+ ItemView itemView = mItemViews[i].get();
+ if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
+ itemView.setTitle(title);
+ }
+ }
+
+ if (mSubMenu != null) {
+ mSubMenu.setHeaderTitle(title);
+ }
+
+ return this;
+ }
+
+ public MenuItem setTitle(int title) {
+ return setTitle(mMenu.getContext().getString(title));
+ }
+
+ public CharSequence getTitleCondensed() {
+ return mTitleCondensed != null ? mTitleCondensed : mTitle;
+ }
+
+ public MenuItem setTitleCondensed(CharSequence title) {
+ mTitleCondensed = title;
+
+ // Could use getTitle() in the loop below, but just cache what it would do here
+ if (title == null) {
+ title = mTitle;
+ }
+
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ // Refresh those item views that prefer a condensed title
+ if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
+ mItemViews[i].get().setTitle(title);
+ }
+ }
+
+ return this;
+ }
+
+ public Drawable getIcon() {
+
+ if (mIconDrawable != null) {
+ return mIconDrawable;
+ }
+
+ if (mIconResId != NO_ICON) {
+ return mMenu.getResources().getDrawable(mIconResId);
+ }
+
+ return null;
+ }
+
+ public MenuItem setIcon(Drawable icon) {
+ mIconResId = NO_ICON;
+ mIconDrawable = icon;
+ setIconOnViews(icon);
+
+ return this;
+ }
+
+ public MenuItem setIcon(int iconResId) {
+ mIconDrawable = null;
+ mIconResId = iconResId;
+
+ // If we have a view, we need to push the Drawable to them
+ if (haveAnyOpenedIconCapableItemViews()) {
+ Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
+ : null;
+ setIconOnViews(drawable);
+ }
+
+ return this;
+ }
+
+ private void setIconOnViews(Drawable icon) {
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ // Refresh those item views that are able to display an icon
+ if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
+ mItemViews[i].get().setIcon(icon);
+ }
+ }
+ }
+
+ private boolean haveAnyOpenedIconCapableItemViews() {
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isCheckable() {
+ return (mFlags & CHECKABLE) == CHECKABLE;
+ }
+
+ public MenuItem setCheckable(boolean checkable) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+ if (oldFlags != mFlags) {
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ if (hasItemView(i)) {
+ mItemViews[i].get().setCheckable(checkable);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ public void setExclusiveCheckable(boolean exclusive)
+ {
+ mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ }
+
+ public boolean isExclusiveCheckable() {
+ return (mFlags & EXCLUSIVE) != 0;
+ }
+
+ public boolean isChecked() {
+ return (mFlags & CHECKED) == CHECKED;
+ }
+
+ public MenuItem setChecked(boolean checked) {
+ if ((mFlags & EXCLUSIVE) != 0) {
+ // Call the method on the Menu since it knows about the others in this
+ // exclusive checkable group
+ mMenu.setExclusiveItemChecked(this);
+ } else {
+ setCheckedInt(checked);
+ }
+
+ return this;
+ }
+
+ void setCheckedInt(boolean checked) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+ if (oldFlags != mFlags) {
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ if (hasItemView(i)) {
+ mItemViews[i].get().setChecked(checked);
+ }
+ }
+ }
+ }
+
+ public boolean isVisible() {
+ return (mFlags & HIDDEN) == 0;
+ }
+
+ /**
+ * Changes the visibility of the item. This method DOES NOT notify the
+ * parent menu of a change in this item, so this should only be called from
+ * methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)}
+ * instead.
+ *
+ * @param shown Whether to show (true) or hide (false).
+ * @return Whether the item's shown state was changed
+ */
+ boolean setVisibleInt(boolean shown) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
+ if (oldFlags != mFlags) {
+ final int visibility = (shown) ? View.VISIBLE : View.GONE;
+
+ for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+ if (hasItemView(i)) {
+ ((View) mItemViews[i].get()).setVisibility(visibility);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public MenuItem setVisible(boolean shown) {
+ // Try to set the shown state to the given state. If the shown state was changed
+ // (i.e. the previous state isn't the same as given state), notify the parent menu that
+ // the shown state has changed for this item
+ if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
+
+ return this;
+ }
+
+ public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
+ mClickListener = clickListener;
+ return this;
+ }
+
+ View getItemView(int menuType, ViewGroup parent) {
+ if (!hasItemView(menuType)) {
+ mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
+ }
+
+ return (View) mItemViews[menuType].get();
+ }
+
+ /**
+ * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
+ * @param menuType The type of menu to get a View for (must be one of
+ * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+ * {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
+ * @return The inflated {@link MenuView.ItemView} that is ready for use
+ */
+ private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
+ // Create the MenuView
+ MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
+ .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
+ itemView.initialize(this, menuType);
+ return itemView;
+ }
+
+ void clearItemViews() {
+ for (int i = mItemViews.length - 1; i >= 0; i--) {
+ mItemViews[i] = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mTitle.toString();
+ }
+
+ void setMenuInfo(ContextMenuInfo menuInfo) {
+ mMenuInfo = menuInfo;
+ }
+
+ public ContextMenuInfo getMenuInfo() {
+ return mMenuInfo;
+ }
+
+ /**
+ * Returns a LayoutInflater that is themed for the given menu type.
+ *
+ * @param menuType The type of menu.
+ * @return A LayoutInflater.
+ */
+ public LayoutInflater getLayoutInflater(int menuType) {
+ return mMenu.getMenuType(menuType).getInflater();
+ }
+
+ /**
+ * @return Whether the given menu type should show icons for menu items.
+ */
+ public boolean shouldShowIcon(int menuType) {
+ return menuType == MenuBuilder.TYPE_ICON || mMenu.getOptionalIconsVisible();
+ }
+}