/* * Copyright (C) 2006 The Android Open Source Project * 2011 Jake Wharton * * 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.actionbarsherlock.internal.view.menu; import java.lang.ref.WeakReference; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.Drawable; import android.support.v4.view.MenuItem; import android.util.Log; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.View; /** * An implementation of the {@link android.view.MenuItem} interface for use in * inflating menu XML resources to be added to a third-party action bar. * * @author Jake Wharton <jakewharton@gmail.com> * @see <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/com/android/internal/view/menu/MenuItemImpl.java">com.android.internal.view.menu.MenuItemImpl</a> */ public final class MenuItemImpl implements MenuItem { private static final String TAG = "MenuItemImpl"; private final MenuBuilder mMenu; private final int mItemId; private final int mGroupId; private final int mCategoryOrder; private final int mOrdering; private Intent mIntent; private CharSequence mTitle; private CharSequence mTitleCondensed; private char mNumericalShortcut; private char mAlphabeticalShortcut; private int mShowAsAction; private SubMenuBuilder mSubMenu; private Runnable mItemCallback; private OnMenuItemClickListener mClickListener; private Drawable mIcon; private int mIconRes = View.NO_ID; private View mActionView; private int mActionViewRes = View.NO_ID; int mFlags = ENABLED; static final int CHECKABLE = 0x01; static final int CHECKED = 0x02; static final int EXCLUSIVE = 0x04; static final int HIDDEN = 0x08; static final int ENABLED = 0x10; static final int IS_ACTION = 0x20; private final WeakReference<MenuView.ItemView>[] mItemViews; private final DialogInterface.OnClickListener subMenuClick = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int index) { dialog.dismiss(); mSubMenu.getItem(index).invoke(); } }; private final DialogInterface.OnMultiChoiceClickListener subMenuMultiClick = new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int index, boolean isChecked) { dialog.dismiss(); mSubMenu.getItem(index).setChecked(isChecked); } }; /** * Create a new action bar menu item. * * @param context Context used if resource resolution is required. * @param itemId A unique ID. Used in the activity callback. * @param groupId Group ID. Currently unused. * @param order Item order. Currently unused. * @param title Title of the item. */ @SuppressWarnings("unchecked") public MenuItemImpl(MenuBuilder menu, int groupId, int itemId, int order, int ordering, CharSequence title, int showAsAction) { mMenu = menu; mItemId = itemId; mGroupId = groupId; mCategoryOrder = order; mOrdering = ordering; mTitle = title; mShowAsAction = showAsAction; mItemViews = new WeakReference[MenuBuilder.NUM_TYPES]; } public boolean invoke() { if (hasSubMenu()) { AlertDialog.Builder builder = new AlertDialog.Builder(mMenu.getContext()); builder.setTitle(getTitle()); final boolean isExclusive = mSubMenu.getItem(0).isExclusiveCheckable(); final boolean isCheckable = mSubMenu.getItem(0).isCheckable(); final CharSequence[] titles = getSubMenuTitles(); if (isExclusive) { builder.setSingleChoiceItems(titles, getSubMenuSelected(), subMenuClick); } else if (isCheckable) { builder.setMultiChoiceItems(titles, getSubMenuChecked(), subMenuMultiClick); } else { builder.setItems(titles, subMenuClick); } builder.show(); return true; } if (mClickListener != null && mClickListener.onMenuItemClick(this)) { return true; } MenuBuilder.Callback callback = mMenu.getRootMenu().getCallback(); if (callback != null && callback.onMenuItemSelected(mMenu.getRootMenu(), this)) { return true; } if (mItemCallback != null) { mItemCallback.run(); return true; } if (mIntent != null) { try { mMenu.getContext().startActivity(mIntent); return true; } catch (ActivityNotFoundException e) { Log.e(TAG, "Can't find activity to handle intent; ignoring", e); } } return false; } private CharSequence[] getSubMenuTitles() { final int count = mSubMenu.size(); CharSequence[] list = new CharSequence[count]; for (int i = 0; i < count; i++) { list[i] = mSubMenu.getItem(i).getTitle(); } return list; } private int getSubMenuSelected() { final int count = mSubMenu.size(); for (int i = 0; i < count; i++) { if (mSubMenu.getItem(i).isChecked()) { return i; } } return -1; } private boolean[] getSubMenuChecked() { final int count = mSubMenu.size(); boolean[] checked = new boolean[count]; for (int i = 0; i < count; i++) { checked[i] = mSubMenu.getItem(i).isChecked(); } return checked; } private boolean hasItemView(int menuType) { return mItemViews[menuType] != null && mItemViews[menuType].get() != null; } public void setItemView(int type, MenuView.ItemView itemView) { mItemViews[type] = new WeakReference<MenuView.ItemView>(itemView); } public void addTo(android.view.Menu menu) { if (hasSubMenu()) { android.view.SubMenu subMenu = menu.addSubMenu(mGroupId, mItemId, mCategoryOrder, mTitle); if (mIconRes != View.NO_ID) { subMenu.setIcon(mIconRes); } else { subMenu.setIcon(mIcon); } for (MenuItemImpl item : mSubMenu.getItems()) { item.addTo(subMenu); } if (mSubMenu.getItem(0).isExclusiveCheckable()) { int checked = getSubMenuSelected(); if (checked != -1) { subMenu.getItem(checked).setChecked(true); } } } else { android.view.MenuItem item = menu.add(mGroupId, mItemId, mCategoryOrder, mTitle) .setAlphabeticShortcut(mAlphabeticalShortcut) .setNumericShortcut(mNumericalShortcut) .setVisible(isVisible()) .setIntent(mIntent) .setCheckable(isCheckable()) .setChecked(isChecked()) .setOnMenuItemClickListener(mClickListener); if (isExclusiveCheckable()) { menu.setGroupCheckable(mGroupId, true, true); } //Create and initialize a native itemview wrapper NativeMenuItemView nativeWrapper = new NativeMenuItemView(item); nativeWrapper.initialize(this, MenuBuilder.TYPE_NATIVE); //Associate the itemview to this so changes will be reflected setItemView(MenuBuilder.TYPE_NATIVE, nativeWrapper); } } /** * Get whether or not this item is being shown on the action bar. * * @return {@code true} if shown, {@code false} otherwise. */ public boolean isShownOnActionBar() { return (mFlags & IS_ACTION) == IS_ACTION; } /** * Denote whether or not this menu item is being shown on the action bar. * * @param isShownOnActionBar {@code true} if shown or {@code false}. */ public void setIsShownOnActionBar(boolean isShownOnActionBar) { mFlags = (mFlags & ~IS_ACTION) | (isShownOnActionBar ? IS_ACTION : 0); } @Override public Intent getIntent() { return this.mIntent; } @Override public int getItemId() { return this.mItemId; } @Override public CharSequence getTitle() { return this.mTitle; } @Override public boolean isEnabled() { return (mFlags & ENABLED) != 0; } @Override public boolean isVisible() { return (mFlags & HIDDEN) == 0; } @Override public MenuItem setEnabled(boolean enabled) { final boolean oldValue = isEnabled(); mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0); if (oldValue != enabled) { for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { if (hasItemView(i)) { mItemViews[i].get().setEnabled(enabled); } } } return this; } @Override public MenuItem setIcon(int iconResourceId) { mIcon = null; mIconRes = iconResourceId; if (mIconRes != View.NO_ID) { setIconOnViews(mMenu.getContext().getResources().getDrawable(mIconRes)); } return this; } @Override public MenuItem setIntent(Intent intent) { mIntent = intent; return this; } @Override public MenuItem setTitle(CharSequence title) { mTitle = title; return this; } @Override public MenuItem setTitle(int titleResourceId) { mTitle = mMenu.getContext().getResources().getString(titleResourceId); return this; } @Override public MenuItem setVisible(boolean visible) { final boolean oldValue = isVisible(); mFlags = (mFlags & ~HIDDEN) | (visible ? 0 : HIDDEN); if (oldValue != visible) { for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { if (hasItemView(i)) { mItemViews[i].get().setVisible(visible); } } } return this; } @Override public boolean isChecked() { return (mFlags & CHECKED) == CHECKED; } @Override public MenuItem setChecked(boolean checked) { if ((mFlags & EXCLUSIVE) == EXCLUSIVE) { // 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 boolean oldValue = isChecked(); mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0); if (oldValue != checked) { for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { if (hasItemView(i)) { mItemViews[i].get().setChecked(checked); } } } } @Override public boolean isCheckable() { return (mFlags & CHECKABLE) == CHECKABLE; } @Override public MenuItem setCheckable(boolean checkable) { final boolean oldValue = isCheckable(); mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); if (oldValue != checkable) { 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) == EXCLUSIVE; } @Override public CharSequence getTitleCondensed() { return mTitleCondensed; } @Override public MenuItem setTitleCondensed(CharSequence title) { mTitleCondensed = title; return this; } @Override public int getGroupId() { return mGroupId; } @Override public int getOrder() { return mCategoryOrder; } public int getOrdering() { return mOrdering; } @Override public SubMenuBuilder getSubMenu() { return mSubMenu; } /** * Set the sub-menu of this item. * * @param subMenu Sub-menu instance. * @return This Item so additional setters can be called. */ MenuItem setSubMenu(SubMenuBuilder subMenu) { mSubMenu = subMenu; return this; } @Override public boolean hasSubMenu() { return (mSubMenu != null) && (mSubMenu.size() > 0); } @Override public char getAlphabeticShortcut() { return mAlphabeticalShortcut; } @Override public char getNumericShortcut() { return mNumericalShortcut; } @Override public MenuItem setAlphabeticShortcut(char alphaChar) { mAlphabeticalShortcut = Character.toLowerCase(alphaChar); return this; } @Override public MenuItem setNumericShortcut(char numericChar) { mNumericalShortcut = numericChar; return this; } @Override public MenuItem setShortcut(char numericChar, char alphaChar) { setNumericShortcut(numericChar); setAlphabeticShortcut(alphaChar); return this; } @Override public void setShowAsAction(int actionEnum) { mShowAsAction = actionEnum; } public int getShowAsAction() { return mShowAsAction; } public boolean showsActionItemText() { return mMenu.getShowsActionItemText(); } @Override public View getActionView() { if (mActionView != null) { return mActionView; } if (mActionViewRes != View.NO_ID) { return LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false); } return null; } @Override public Drawable getIcon() { if (mIcon != null) { return mIcon; } if (mIconRes != View.NO_ID) { return mMenu.getContext().getResources().getDrawable(mIconRes); } return null; } @Override public ContextMenuInfo getMenuInfo() { return null; } @Override public MenuItem setActionView(View view) { mActionView = view; mActionViewRes = View.NO_ID; setActionViewOnViews(mActionView); return this; } @Override public MenuItem setActionView(int resId) { mActionView = null; mActionViewRes = resId; if (mActionViewRes != View.NO_ID) { setActionViewOnViews(LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false)); } return this; } void setActionViewOnViews(View view) { for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { if (hasItemView(i)) { mItemViews[i].get().setActionView(view); } } } @Override public MenuItem setIcon(Drawable icon) { mIcon = icon; mIconRes = View.NO_ID; setIconOnViews(icon); return this; } void setIconOnViews(Drawable icon) { for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { if (hasItemView(i)) { mItemViews[i].get().setIcon(icon); } } } @Override public android.view.MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener menuItemClickListener) { return this.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { return menuItemClickListener.onMenuItemClick(new MenuItemWrapper(item)); } }); } @Override public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) { mClickListener = menuItemClickListener; return this; } /** * Returns the currently set menu click listener for this item. * * @return Click listener or {@code null}. */ public OnMenuItemClickListener getOnMenuItemClickListener() { return mClickListener; } public static final class NativeMenuItemView implements MenuView.ItemView { private final android.view.MenuItem mItem; public NativeMenuItemView(android.view.MenuItem item) { mItem = item; } @Override public MenuItemImpl getItemData() { return null; } @Override public void initialize(MenuItemImpl itemData, int menuType) { setIcon(itemData.getIcon()); setTitle(itemData.getTitle()); setEnabled(itemData.isEnabled()); setCheckable(itemData.isCheckable()); setChecked(itemData.isChecked()); setActionView(itemData.getActionView()); setVisible(itemData.isVisible()); } @Override public boolean prefersCondensedTitle() { return true; } @Override public void setCheckable(boolean checkable) { mItem.setCheckable(checkable); } @Override public void setChecked(boolean checked) { mItem.setChecked(checked); } @Override public void setEnabled(boolean enabled) { mItem.setEnabled(enabled); } @Override public void setIcon(Drawable icon) { mItem.setIcon(icon); } @Override public void setShortcut(boolean showShortcut, char shortcutKey) { //Not supported } @Override public void setTitle(CharSequence title) { mItem.setTitle(title); } @Override public boolean showsIcon() { return true; } @Override public void setActionView(View actionView) { //Not supported } @Override public void setVisible(boolean visible) { mItem.setVisible(visible); } } }