Home | History | Annotate | Download | only in menu
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
      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 
     17 package com.android.internal.view.menu;
     18 
     19 import com.android.internal.view.menu.MenuView.ItemView;
     20 
     21 import android.content.ActivityNotFoundException;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.Log;
     26 import android.view.ActionProvider;
     27 import android.view.ContextMenu.ContextMenuInfo;
     28 import android.view.LayoutInflater;
     29 import android.view.MenuItem;
     30 import android.view.SubMenu;
     31 import android.view.View;
     32 import android.view.ViewDebug;
     33 import android.widget.LinearLayout;
     34 
     35 /**
     36  * @hide
     37  */
     38 public final class MenuItemImpl implements MenuItem {
     39     private static final String TAG = "MenuItemImpl";
     40 
     41     private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
     42             SHOW_AS_ACTION_IF_ROOM |
     43             SHOW_AS_ACTION_ALWAYS;
     44 
     45     private final int mId;
     46     private final int mGroup;
     47     private final int mCategoryOrder;
     48     private final int mOrdering;
     49     private CharSequence mTitle;
     50     private CharSequence mTitleCondensed;
     51     private Intent mIntent;
     52     private char mShortcutNumericChar;
     53     private char mShortcutAlphabeticChar;
     54 
     55     /** The icon's drawable which is only created as needed */
     56     private Drawable mIconDrawable;
     57     /**
     58      * The icon's resource ID which is used to get the Drawable when it is
     59      * needed (if the Drawable isn't already obtained--only one of the two is
     60      * needed).
     61      */
     62     private int mIconResId = NO_ICON;
     63 
     64     /** The menu to which this item belongs */
     65     private MenuBuilder mMenu;
     66     /** If this item should launch a sub menu, this is the sub menu to launch */
     67     private SubMenuBuilder mSubMenu;
     68 
     69     private Runnable mItemCallback;
     70     private MenuItem.OnMenuItemClickListener mClickListener;
     71 
     72     private int mFlags = ENABLED;
     73     private static final int CHECKABLE      = 0x00000001;
     74     private static final int CHECKED        = 0x00000002;
     75     private static final int EXCLUSIVE      = 0x00000004;
     76     private static final int HIDDEN         = 0x00000008;
     77     private static final int ENABLED        = 0x00000010;
     78     private static final int IS_ACTION      = 0x00000020;
     79 
     80     private int mShowAsAction = SHOW_AS_ACTION_NEVER;
     81 
     82     private View mActionView;
     83     private ActionProvider mActionProvider;
     84     private OnActionExpandListener mOnActionExpandListener;
     85     private boolean mIsActionViewExpanded = false;
     86 
     87     /** Used for the icon resource ID if this item does not have an icon */
     88     static final int NO_ICON = 0;
     89 
     90     /**
     91      * Current use case is for context menu: Extra information linked to the
     92      * View that added this item to the context menu.
     93      */
     94     private ContextMenuInfo mMenuInfo;
     95 
     96     private static String sLanguage;
     97     private static String sPrependShortcutLabel;
     98     private static String sEnterShortcutLabel;
     99     private static String sDeleteShortcutLabel;
    100     private static String sSpaceShortcutLabel;
    101 
    102 
    103     /**
    104      * Instantiates this menu item.
    105      *
    106      * @param menu
    107      * @param group Item ordering grouping control. The item will be added after
    108      *            all other items whose order is <= this number, and before any
    109      *            that are larger than it. This can also be used to define
    110      *            groups of items for batch state changes. Normally use 0.
    111      * @param id Unique item ID. Use 0 if you do not need a unique ID.
    112      * @param categoryOrder The ordering for this item.
    113      * @param title The text to display for the item.
    114      */
    115     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
    116             CharSequence title, int showAsAction) {
    117 
    118         String lang = menu.getContext().getResources().getConfiguration().locale.toString();
    119         if (sPrependShortcutLabel == null || !lang.equals(sLanguage)) {
    120             sLanguage = lang;
    121             // This is instantiated from the UI thread, so no chance of sync issues
    122             sPrependShortcutLabel = menu.getContext().getResources().getString(
    123                     com.android.internal.R.string.prepend_shortcut_label);
    124             sEnterShortcutLabel = menu.getContext().getResources().getString(
    125                     com.android.internal.R.string.menu_enter_shortcut_label);
    126             sDeleteShortcutLabel = menu.getContext().getResources().getString(
    127                     com.android.internal.R.string.menu_delete_shortcut_label);
    128             sSpaceShortcutLabel = menu.getContext().getResources().getString(
    129                     com.android.internal.R.string.menu_space_shortcut_label);
    130         }
    131 
    132         mMenu = menu;
    133         mId = id;
    134         mGroup = group;
    135         mCategoryOrder = categoryOrder;
    136         mOrdering = ordering;
    137         mTitle = title;
    138         mShowAsAction = showAsAction;
    139     }
    140 
    141     /**
    142      * Invokes the item by calling various listeners or callbacks.
    143      *
    144      * @return true if the invocation was handled, false otherwise
    145      */
    146     public boolean invoke() {
    147         if (mClickListener != null &&
    148             mClickListener.onMenuItemClick(this)) {
    149             return true;
    150         }
    151 
    152         if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
    153             return true;
    154         }
    155 
    156         if (mItemCallback != null) {
    157             mItemCallback.run();
    158             return true;
    159         }
    160 
    161         if (mIntent != null) {
    162             try {
    163                 mMenu.getContext().startActivity(mIntent);
    164                 return true;
    165             } catch (ActivityNotFoundException e) {
    166                 Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
    167             }
    168         }
    169 
    170         if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
    171             return true;
    172         }
    173 
    174         return false;
    175     }
    176 
    177     public boolean isEnabled() {
    178         return (mFlags & ENABLED) != 0;
    179     }
    180 
    181     public MenuItem setEnabled(boolean enabled) {
    182         if (enabled) {
    183             mFlags |= ENABLED;
    184         } else {
    185             mFlags &= ~ENABLED;
    186         }
    187 
    188         mMenu.onItemsChanged(false);
    189 
    190         return this;
    191     }
    192 
    193     public int getGroupId() {
    194         return mGroup;
    195     }
    196 
    197     @ViewDebug.CapturedViewProperty
    198     public int getItemId() {
    199         return mId;
    200     }
    201 
    202     public int getOrder() {
    203         return mCategoryOrder;
    204     }
    205 
    206     public int getOrdering() {
    207         return mOrdering;
    208     }
    209 
    210     public Intent getIntent() {
    211         return mIntent;
    212     }
    213 
    214     public MenuItem setIntent(Intent intent) {
    215         mIntent = intent;
    216         return this;
    217     }
    218 
    219     Runnable getCallback() {
    220         return mItemCallback;
    221     }
    222 
    223     public MenuItem setCallback(Runnable callback) {
    224         mItemCallback = callback;
    225         return this;
    226     }
    227 
    228     public char getAlphabeticShortcut() {
    229         return mShortcutAlphabeticChar;
    230     }
    231 
    232     public MenuItem setAlphabeticShortcut(char alphaChar) {
    233         if (mShortcutAlphabeticChar == alphaChar) return this;
    234 
    235         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
    236 
    237         mMenu.onItemsChanged(false);
    238 
    239         return this;
    240     }
    241 
    242     public char getNumericShortcut() {
    243         return mShortcutNumericChar;
    244     }
    245 
    246     public MenuItem setNumericShortcut(char numericChar) {
    247         if (mShortcutNumericChar == numericChar) return this;
    248 
    249         mShortcutNumericChar = numericChar;
    250 
    251         mMenu.onItemsChanged(false);
    252 
    253         return this;
    254     }
    255 
    256     public MenuItem setShortcut(char numericChar, char alphaChar) {
    257         mShortcutNumericChar = numericChar;
    258         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
    259 
    260         mMenu.onItemsChanged(false);
    261 
    262         return this;
    263     }
    264 
    265     /**
    266      * @return The active shortcut (based on QWERTY-mode of the menu).
    267      */
    268     char getShortcut() {
    269         return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
    270     }
    271 
    272     /**
    273      * @return The label to show for the shortcut. This includes the chording
    274      *         key (for example 'Menu+a'). Also, any non-human readable
    275      *         characters should be human readable (for example 'Menu+enter').
    276      */
    277     String getShortcutLabel() {
    278 
    279         char shortcut = getShortcut();
    280         if (shortcut == 0) {
    281             return "";
    282         }
    283 
    284         StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
    285         switch (shortcut) {
    286 
    287             case '\n':
    288                 sb.append(sEnterShortcutLabel);
    289                 break;
    290 
    291             case '\b':
    292                 sb.append(sDeleteShortcutLabel);
    293                 break;
    294 
    295             case ' ':
    296                 sb.append(sSpaceShortcutLabel);
    297                 break;
    298 
    299             default:
    300                 sb.append(shortcut);
    301                 break;
    302         }
    303 
    304         return sb.toString();
    305     }
    306 
    307     /**
    308      * @return Whether this menu item should be showing shortcuts (depends on
    309      *         whether the menu should show shortcuts and whether this item has
    310      *         a shortcut defined)
    311      */
    312     boolean shouldShowShortcut() {
    313         // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
    314         return mMenu.isShortcutsVisible() && (getShortcut() != 0);
    315     }
    316 
    317     public SubMenu getSubMenu() {
    318         return mSubMenu;
    319     }
    320 
    321     public boolean hasSubMenu() {
    322         return mSubMenu != null;
    323     }
    324 
    325     void setSubMenu(SubMenuBuilder subMenu) {
    326         mSubMenu = subMenu;
    327 
    328         subMenu.setHeaderTitle(getTitle());
    329     }
    330 
    331     @ViewDebug.CapturedViewProperty
    332     public CharSequence getTitle() {
    333         return mTitle;
    334     }
    335 
    336     /**
    337      * Gets the title for a particular {@link ItemView}
    338      *
    339      * @param itemView The ItemView that is receiving the title
    340      * @return Either the title or condensed title based on what the ItemView
    341      *         prefers
    342      */
    343     CharSequence getTitleForItemView(MenuView.ItemView itemView) {
    344         return ((itemView != null) && itemView.prefersCondensedTitle())
    345                 ? getTitleCondensed()
    346                 : getTitle();
    347     }
    348 
    349     public MenuItem setTitle(CharSequence title) {
    350         mTitle = title;
    351 
    352         mMenu.onItemsChanged(false);
    353 
    354         if (mSubMenu != null) {
    355             mSubMenu.setHeaderTitle(title);
    356         }
    357 
    358         return this;
    359     }
    360 
    361     public MenuItem setTitle(int title) {
    362         return setTitle(mMenu.getContext().getString(title));
    363     }
    364 
    365     public CharSequence getTitleCondensed() {
    366         return mTitleCondensed != null ? mTitleCondensed : mTitle;
    367     }
    368 
    369     public MenuItem setTitleCondensed(CharSequence title) {
    370         mTitleCondensed = title;
    371 
    372         // Could use getTitle() in the loop below, but just cache what it would do here
    373         if (title == null) {
    374             title = mTitle;
    375         }
    376 
    377         mMenu.onItemsChanged(false);
    378 
    379         return this;
    380     }
    381 
    382     public Drawable getIcon() {
    383         if (mIconDrawable != null) {
    384             return mIconDrawable;
    385         }
    386 
    387         if (mIconResId != NO_ICON) {
    388             Drawable icon =  mMenu.getResources().getDrawable(mIconResId);
    389             mIconResId = NO_ICON;
    390             mIconDrawable = icon;
    391             return icon;
    392         }
    393 
    394         return null;
    395     }
    396 
    397     public MenuItem setIcon(Drawable icon) {
    398         mIconResId = NO_ICON;
    399         mIconDrawable = icon;
    400         mMenu.onItemsChanged(false);
    401 
    402         return this;
    403     }
    404 
    405     public MenuItem setIcon(int iconResId) {
    406         mIconDrawable = null;
    407         mIconResId = iconResId;
    408 
    409         // If we have a view, we need to push the Drawable to them
    410         mMenu.onItemsChanged(false);
    411 
    412         return this;
    413     }
    414 
    415     public boolean isCheckable() {
    416         return (mFlags & CHECKABLE) == CHECKABLE;
    417     }
    418 
    419     public MenuItem setCheckable(boolean checkable) {
    420         final int oldFlags = mFlags;
    421         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
    422         if (oldFlags != mFlags) {
    423             mMenu.onItemsChanged(false);
    424         }
    425 
    426         return this;
    427     }
    428 
    429     public void setExclusiveCheckable(boolean exclusive) {
    430         mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
    431     }
    432 
    433     public boolean isExclusiveCheckable() {
    434         return (mFlags & EXCLUSIVE) != 0;
    435     }
    436 
    437     public boolean isChecked() {
    438         return (mFlags & CHECKED) == CHECKED;
    439     }
    440 
    441     public MenuItem setChecked(boolean checked) {
    442         if ((mFlags & EXCLUSIVE) != 0) {
    443             // Call the method on the Menu since it knows about the others in this
    444             // exclusive checkable group
    445             mMenu.setExclusiveItemChecked(this);
    446         } else {
    447             setCheckedInt(checked);
    448         }
    449 
    450         return this;
    451     }
    452 
    453     void setCheckedInt(boolean checked) {
    454         final int oldFlags = mFlags;
    455         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
    456         if (oldFlags != mFlags) {
    457             mMenu.onItemsChanged(false);
    458         }
    459     }
    460 
    461     public boolean isVisible() {
    462         if (mActionProvider != null && mActionProvider.overridesItemVisibility()) {
    463             return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible();
    464         }
    465         return (mFlags & HIDDEN) == 0;
    466     }
    467 
    468     /**
    469      * Changes the visibility of the item. This method DOES NOT notify the
    470      * parent menu of a change in this item, so this should only be called from
    471      * methods that will eventually trigger this change.  If unsure, use {@link #setVisible(boolean)}
    472      * instead.
    473      *
    474      * @param shown Whether to show (true) or hide (false).
    475      * @return Whether the item's shown state was changed
    476      */
    477     boolean setVisibleInt(boolean shown) {
    478         final int oldFlags = mFlags;
    479         mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
    480         return oldFlags != mFlags;
    481     }
    482 
    483     public MenuItem setVisible(boolean shown) {
    484         // Try to set the shown state to the given state. If the shown state was changed
    485         // (i.e. the previous state isn't the same as given state), notify the parent menu that
    486         // the shown state has changed for this item
    487         if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
    488 
    489         return this;
    490     }
    491 
    492    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
    493         mClickListener = clickListener;
    494         return this;
    495     }
    496 
    497     @Override
    498     public String toString() {
    499         return mTitle.toString();
    500     }
    501 
    502     void setMenuInfo(ContextMenuInfo menuInfo) {
    503         mMenuInfo = menuInfo;
    504     }
    505 
    506     public ContextMenuInfo getMenuInfo() {
    507         return mMenuInfo;
    508     }
    509 
    510     public void actionFormatChanged() {
    511         mMenu.onItemActionRequestChanged(this);
    512     }
    513 
    514     /**
    515      * @return Whether the menu should show icons for menu items.
    516      */
    517     public boolean shouldShowIcon() {
    518         return mMenu.getOptionalIconsVisible();
    519     }
    520 
    521     public boolean isActionButton() {
    522         return (mFlags & IS_ACTION) == IS_ACTION;
    523     }
    524 
    525     public boolean requestsActionButton() {
    526         return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
    527     }
    528 
    529     public boolean requiresActionButton() {
    530         return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
    531     }
    532 
    533     public void setIsActionButton(boolean isActionButton) {
    534         if (isActionButton) {
    535             mFlags |= IS_ACTION;
    536         } else {
    537             mFlags &= ~IS_ACTION;
    538         }
    539     }
    540 
    541     public boolean showsTextAsAction() {
    542         return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
    543     }
    544 
    545     public void setShowAsAction(int actionEnum) {
    546         switch (actionEnum & SHOW_AS_ACTION_MASK) {
    547             case SHOW_AS_ACTION_ALWAYS:
    548             case SHOW_AS_ACTION_IF_ROOM:
    549             case SHOW_AS_ACTION_NEVER:
    550                 // Looks good!
    551                 break;
    552 
    553             default:
    554                 // Mutually exclusive options selected!
    555                 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
    556                         + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
    557         }
    558         mShowAsAction = actionEnum;
    559         mMenu.onItemActionRequestChanged(this);
    560     }
    561 
    562     public MenuItem setActionView(View view) {
    563         mActionView = view;
    564         mActionProvider = null;
    565         if (view != null && view.getId() == View.NO_ID && mId > 0) {
    566             view.setId(mId);
    567         }
    568         mMenu.onItemActionRequestChanged(this);
    569         return this;
    570     }
    571 
    572     public MenuItem setActionView(int resId) {
    573         final Context context = mMenu.getContext();
    574         final LayoutInflater inflater = LayoutInflater.from(context);
    575         setActionView(inflater.inflate(resId, new LinearLayout(context), false));
    576         return this;
    577     }
    578 
    579     public View getActionView() {
    580         if (mActionView != null) {
    581             return mActionView;
    582         } else if (mActionProvider != null) {
    583             mActionView = mActionProvider.onCreateActionView(this);
    584             return mActionView;
    585         } else {
    586             return null;
    587         }
    588     }
    589 
    590     public ActionProvider getActionProvider() {
    591         return mActionProvider;
    592     }
    593 
    594     public MenuItem setActionProvider(ActionProvider actionProvider) {
    595         if (mActionProvider != null) {
    596             mActionProvider.setVisibilityListener(null);
    597         }
    598         mActionView = null;
    599         mActionProvider = actionProvider;
    600         mMenu.onItemsChanged(true); // Measurement can be changed
    601         if (mActionProvider != null) {
    602             mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
    603                 @Override public void onActionProviderVisibilityChanged(boolean isVisible) {
    604                     mMenu.onItemVisibleChanged(MenuItemImpl.this);
    605                 }
    606             });
    607         }
    608         return this;
    609     }
    610 
    611     @Override
    612     public MenuItem setShowAsActionFlags(int actionEnum) {
    613         setShowAsAction(actionEnum);
    614         return this;
    615     }
    616 
    617     @Override
    618     public boolean expandActionView() {
    619         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
    620             return false;
    621         }
    622 
    623         if (mOnActionExpandListener == null ||
    624                 mOnActionExpandListener.onMenuItemActionExpand(this)) {
    625             return mMenu.expandItemActionView(this);
    626         }
    627 
    628         return false;
    629     }
    630 
    631     @Override
    632     public boolean collapseActionView() {
    633         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
    634             return false;
    635         }
    636         if (mActionView == null) {
    637             // We're already collapsed if we have no action view.
    638             return true;
    639         }
    640 
    641         if (mOnActionExpandListener == null ||
    642                 mOnActionExpandListener.onMenuItemActionCollapse(this)) {
    643             return mMenu.collapseItemActionView(this);
    644         }
    645 
    646         return false;
    647     }
    648 
    649     @Override
    650     public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
    651         mOnActionExpandListener = listener;
    652         return this;
    653     }
    654 
    655     public boolean hasCollapsibleActionView() {
    656         return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
    657     }
    658 
    659     public void setActionViewExpanded(boolean isExpanded) {
    660         mIsActionViewExpanded = isExpanded;
    661         mMenu.onItemsChanged(false);
    662     }
    663 
    664     public boolean isActionViewExpanded() {
    665         return mIsActionViewExpanded;
    666     }
    667 }
    668