summaryrefslogtreecommitdiffstats
path: root/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu
diff options
context:
space:
mode:
Diffstat (limited to 'actionbarsherlock/src/com/actionbarsherlock/internal/view/menu')
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java264
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java278
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java295
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java721
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java572
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java231
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java278
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java1335
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java647
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java292
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java376
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java148
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java120
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java180
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java134
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java72
16 files changed, 5943 insertions, 0 deletions
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java
new file mode 100644
index 000000000..0354ad1ad
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2010 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.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.KeyEvent;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+/**
+ * @hide
+ */
+public class ActionMenu implements Menu {
+ private Context mContext;
+
+ private boolean mIsQwerty;
+
+ private ArrayList<ActionMenuItem> mItems;
+
+ public ActionMenu(Context context) {
+ mContext = context;
+ mItems = new ArrayList<ActionMenuItem>();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public MenuItem add(CharSequence title) {
+ return add(0, 0, 0, title);
+ }
+
+ public MenuItem add(int titleRes) {
+ return add(0, 0, 0, titleRes);
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, int titleRes) {
+ return add(groupId, itemId, order, mContext.getResources().getString(titleRes));
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
+ ActionMenuItem item = new ActionMenuItem(getContext(),
+ groupId, itemId, 0, order, title);
+ mItems.add(order, item);
+ return item;
+ }
+
+ public int addIntentOptions(int groupId, int itemId, int order,
+ ComponentName caller, Intent[] specifics, Intent intent, int flags,
+ MenuItem[] outSpecificItems) {
+ PackageManager pm = mContext.getPackageManager();
+ final List<ResolveInfo> lri =
+ pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+ final int N = lri != null ? lri.size() : 0;
+
+ if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+ removeGroup(groupId);
+ }
+
+ for (int i=0; i<N; i++) {
+ final ResolveInfo ri = lri.get(i);
+ Intent rintent = new Intent(
+ ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
+ rintent.setComponent(new ComponentName(
+ ri.activityInfo.applicationInfo.packageName,
+ ri.activityInfo.name));
+ final MenuItem item = add(groupId, itemId, order, ri.loadLabel(pm))
+ .setIcon(ri.loadIcon(pm))
+ .setIntent(rintent);
+ if (outSpecificItems != null && ri.specificIndex >= 0) {
+ outSpecificItems[ri.specificIndex] = item;
+ }
+ }
+
+ return N;
+ }
+
+ public SubMenu addSubMenu(CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order,
+ CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public void clear() {
+ mItems.clear();
+ }
+
+ public void close() {
+ }
+
+ private int findItemIndex(int id) {
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).getItemId() == id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public MenuItem findItem(int id) {
+ return mItems.get(findItemIndex(id));
+ }
+
+ public MenuItem getItem(int index) {
+ return mItems.get(index);
+ }
+
+ public boolean hasVisibleItems() {
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).isVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) {
+ // TODO Make this smarter.
+ final boolean qwerty = mIsQwerty;
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ final char shortcut = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
+ if (keyCode == shortcut) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return findItemWithShortcut(keyCode, event) != null;
+ }
+
+ public boolean performIdentifierAction(int id, int flags) {
+ final int index = findItemIndex(id);
+ if (index < 0) {
+ return false;
+ }
+
+ return mItems.get(index).invoke();
+ }
+
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ ActionMenuItem item = findItemWithShortcut(keyCode, event);
+ if (item == null) {
+ return false;
+ }
+
+ return item.invoke();
+ }
+
+ public void removeGroup(int groupId) {
+ final ArrayList<ActionMenuItem> items = mItems;
+ int itemCount = items.size();
+ int i = 0;
+ while (i < itemCount) {
+ if (items.get(i).getGroupId() == groupId) {
+ items.remove(i);
+ itemCount--;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ public void removeItem(int id) {
+ mItems.remove(findItemIndex(id));
+ }
+
+ public void setGroupCheckable(int group, boolean checkable,
+ boolean exclusive) {
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setCheckable(checkable);
+ item.setExclusiveCheckable(exclusive);
+ }
+ }
+ }
+
+ public void setGroupEnabled(int group, boolean enabled) {
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setEnabled(enabled);
+ }
+ }
+ }
+
+ public void setGroupVisible(int group, boolean visible) {
+ final ArrayList<ActionMenuItem> items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setVisible(visible);
+ }
+ }
+ }
+
+ public void setQwertyMode(boolean isQwerty) {
+ mIsQwerty = isQwerty;
+ }
+
+ public int size() {
+ return mItems.size();
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java
new file mode 100644
index 000000000..510b97488
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2010 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.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
+
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+/**
+ * @hide
+ */
+public class ActionMenuItem implements MenuItem {
+ private final int mId;
+ private final int mGroup;
+ //UNUSED private final int mCategoryOrder;
+ private final int mOrdering;
+
+ private CharSequence mTitle;
+ private CharSequence mTitleCondensed;
+ private Intent mIntent;
+ private char mShortcutNumericChar;
+ private char mShortcutAlphabeticChar;
+
+ private Drawable mIconDrawable;
+ //UNUSED private int mIconResId = NO_ICON;
+
+ private Context mContext;
+
+ private MenuItem.OnMenuItemClickListener mClickListener;
+
+ //UNUSED private static final int NO_ICON = 0;
+
+ 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;
+
+ public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
+ CharSequence title) {
+ mContext = context;
+ mId = id;
+ mGroup = group;
+ //UNUSED mCategoryOrder = categoryOrder;
+ mOrdering = ordering;
+ mTitle = title;
+ }
+
+ public char getAlphabeticShortcut() {
+ return mShortcutAlphabeticChar;
+ }
+
+ public int getGroupId() {
+ return mGroup;
+ }
+
+ public Drawable getIcon() {
+ return mIconDrawable;
+ }
+
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ public int getItemId() {
+ return mId;
+ }
+
+ public ContextMenuInfo getMenuInfo() {
+ return null;
+ }
+
+ public char getNumericShortcut() {
+ return mShortcutNumericChar;
+ }
+
+ public int getOrder() {
+ return mOrdering;
+ }
+
+ public SubMenu getSubMenu() {
+ return null;
+ }
+
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ public CharSequence getTitleCondensed() {
+ return mTitleCondensed;
+ }
+
+ public boolean hasSubMenu() {
+ return false;
+ }
+
+ public boolean isCheckable() {
+ return (mFlags & CHECKABLE) != 0;
+ }
+
+ public boolean isChecked() {
+ return (mFlags & CHECKED) != 0;
+ }
+
+ public boolean isEnabled() {
+ return (mFlags & ENABLED) != 0;
+ }
+
+ public boolean isVisible() {
+ return (mFlags & HIDDEN) == 0;
+ }
+
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ mShortcutAlphabeticChar = alphaChar;
+ return this;
+ }
+
+ public MenuItem setCheckable(boolean checkable) {
+ mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+ return this;
+ }
+
+ public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ return this;
+ }
+
+ public MenuItem setChecked(boolean checked) {
+ mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+ return this;
+ }
+
+ public MenuItem setEnabled(boolean enabled) {
+ mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
+ return this;
+ }
+
+ public MenuItem setIcon(Drawable icon) {
+ mIconDrawable = icon;
+ //UNUSED mIconResId = NO_ICON;
+ return this;
+ }
+
+ public MenuItem setIcon(int iconRes) {
+ //UNUSED mIconResId = iconRes;
+ mIconDrawable = mContext.getResources().getDrawable(iconRes);
+ return this;
+ }
+
+ public MenuItem setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ public MenuItem setNumericShortcut(char numericChar) {
+ mShortcutNumericChar = numericChar;
+ return this;
+ }
+
+ public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+ mClickListener = menuItemClickListener;
+ return this;
+ }
+
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = alphaChar;
+ return this;
+ }
+
+ public MenuItem setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ public MenuItem setTitle(int title) {
+ mTitle = mContext.getResources().getString(title);
+ return this;
+ }
+
+ public MenuItem setTitleCondensed(CharSequence title) {
+ mTitleCondensed = title;
+ return this;
+ }
+
+ public MenuItem setVisible(boolean visible) {
+ mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
+ return this;
+ }
+
+ public boolean invoke() {
+ if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
+ return true;
+ }
+
+ if (mIntent != null) {
+ mContext.startActivity(mIntent);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setShowAsAction(int show) {
+ // Do nothing. ActionMenuItems always show as action buttons.
+ }
+
+ public MenuItem setActionView(View actionView) {
+ throw new UnsupportedOperationException();
+ }
+
+ public View getActionView() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setActionView(int resId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ActionProvider getActionProvider() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setActionProvider(ActionProvider actionProvider) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ // No need to save the listener; ActionMenuItem does not support collapsing items.
+ return this;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java
new file mode 100644
index 000000000..dcb50f362
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2010 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.actionbarsherlock.internal.view.menu;
+
+import java.util.HashSet;
+import java.util.Set;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
+import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
+import com.actionbarsherlock.internal.widget.CapitalizingButton;
+
+import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean;
+
+/**
+ * @hide
+ */
+public class ActionMenuItemView extends LinearLayout
+ implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
+ ActionMenuView.ActionMenuChildView, View_HasStateListenerSupport {
+ //UNUSED private static final String TAG = "ActionMenuItemView";
+
+ private MenuItemImpl mItemData;
+ private CharSequence mTitle;
+ private MenuBuilder.ItemInvoker mItemInvoker;
+
+ private ImageButton mImageButton;
+ private CapitalizingButton mTextButton;
+ private boolean mAllowTextWithIcon;
+ private boolean mExpandedFormat;
+ private int mMinWidth;
+
+ private final Set<View_OnAttachStateChangeListener> mListeners = new HashSet<View_OnAttachStateChangeListener>();
+
+ public ActionMenuItemView(Context context) {
+ this(context, null);
+ }
+
+ public ActionMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
+ //TODO super(context, attrs, defStyle);
+ super(context, attrs);
+ mAllowTextWithIcon = getResources_getBoolean(context,
+ R.bool.abs__config_allowActionMenuItemTextWithIcon);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SherlockActionMenuItemView, 0, 0);
+ mMinWidth = a.getDimensionPixelSize(
+ R.styleable.SherlockActionMenuItemView_android_minWidth, 0);
+ a.recycle();
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewDetachedFromWindow(this);
+ }
+ }
+
+ @Override
+ public void onFinishInflate() {
+
+ mImageButton = (ImageButton) findViewById(R.id.abs__imageButton);
+ mTextButton = (CapitalizingButton) findViewById(R.id.abs__textButton);
+ mImageButton.setOnClickListener(this);
+ mTextButton.setOnClickListener(this);
+ mImageButton.setOnLongClickListener(this);
+ setOnClickListener(this);
+ setOnLongClickListener(this);
+ }
+
+ public MenuItemImpl getItemData() {
+ return mItemData;
+ }
+
+ public void initialize(MenuItemImpl itemData, int menuType) {
+ mItemData = itemData;
+
+ setIcon(itemData.getIcon());
+ setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
+ setId(itemData.getItemId());
+
+ setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+ setEnabled(itemData.isEnabled());
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mImageButton.setEnabled(enabled);
+ mTextButton.setEnabled(enabled);
+ }
+
+ public void onClick(View v) {
+ if (mItemInvoker != null) {
+ mItemInvoker.invokeItem(mItemData);
+ }
+ }
+
+ public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
+ mItemInvoker = invoker;
+ }
+
+ public boolean prefersCondensedTitle() {
+ return true;
+ }
+
+ public void setCheckable(boolean checkable) {
+ // TODO Support checkable action items
+ }
+
+ public void setChecked(boolean checked) {
+ // TODO Support checkable action items
+ }
+
+ public void setExpandedFormat(boolean expandedFormat) {
+ if (mExpandedFormat != expandedFormat) {
+ mExpandedFormat = expandedFormat;
+ if (mItemData != null) {
+ mItemData.actionFormatChanged();
+ }
+ }
+ }
+
+ private void updateTextButtonVisibility() {
+ boolean visible = !TextUtils.isEmpty(mTextButton.getText());
+ visible &= mImageButton.getDrawable() == null ||
+ (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
+
+ mTextButton.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ public void setIcon(Drawable icon) {
+ mImageButton.setImageDrawable(icon);
+ if (icon != null) {
+ mImageButton.setVisibility(VISIBLE);
+ } else {
+ mImageButton.setVisibility(GONE);
+ }
+
+ updateTextButtonVisibility();
+ }
+
+ public boolean hasText() {
+ return mTextButton.getVisibility() != GONE;
+ }
+
+ public void setShortcut(boolean showShortcut, char shortcutKey) {
+ // Action buttons don't show text for shortcut keys.
+ }
+
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+
+ mTextButton.setTextCompat(mTitle);
+
+ setContentDescription(mTitle);
+ updateTextButtonVisibility();
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ super.onPopulateAccessibilityEvent(event);
+ }
+ final CharSequence cdesc = getContentDescription();
+ if (!TextUtils.isEmpty(cdesc)) {
+ event.getText().add(cdesc);
+ }
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // Don't allow children to hover; we want this to be treated as a single component.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return onHoverEvent(event);
+ }
+ return false;
+ }
+
+ public boolean showsIcon() {
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return hasText() && mItemData.getIcon() == null;
+ }
+
+ public boolean needsDividerAfter() {
+ return hasText();
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (hasText()) {
+ // Don't show the cheat sheet for items that already show text.
+ return false;
+ }
+
+ final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
+ getLocationOnScreen(screenPos);
+ getWindowVisibleDisplayFrame(displayFrame);
+
+ final Context context = getContext();
+ final int width = getWidth();
+ final int height = getHeight();
+ final int midy = screenPos[1] + height / 2;
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+ Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
+ screenWidth - screenPos[0] - width / 2, height);
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
+ cheatSheet.show();
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int specSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int oldMeasuredWidth = getMeasuredWidth();
+ final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth)
+ : mMinWidth;
+
+ if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
+ // Remeasure at exactly the minimum width.
+ super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 000000000..6f568c698
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2011 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.actionbarsherlock.internal.view.menu;
+
+import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getInteger;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseBooleanArray;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
+import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
+import com.actionbarsherlock.internal.view.menu.ActionMenuView.ActionMenuChildView;
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.MenuItem;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter
+ implements ActionProvider.SubUiVisibilityListener {
+ //UNUSED private static final String TAG = "ActionMenuPresenter";
+
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private boolean mReserveOverflowSet;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+ private boolean mMaxItemsSet;
+ private boolean mStrictWidthLimit;
+ private boolean mWidthLimitSet;
+ private boolean mExpandedActionViewsExclusive;
+
+ private int mMinCellSize;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+
+ final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
+ int mOpenSubMenuId;
+
+ public ActionMenuPresenter(Context context) {
+ super(context, R.layout.abs__action_menu_layout,
+ R.layout.abs__action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+
+ if (!mReserveOverflowSet) {
+ mReserveOverflow = reserveOverflow(mContext);
+ }
+
+ if (!mWidthLimitSet) {
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+ }
+
+ // Measure for initial configuration
+ if (!mMaxItemsSet) {
+ mMaxItems = getResources_getInteger(context, R.integer.abs__max_action_buttons);
+ }
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ }
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ public static boolean reserveOverflow(Context context) {
+ //Check for theme-forced overflow action item
+ TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
+ boolean result = a.getBoolean(R.styleable.SherlockTheme_absForceOverflow, false);
+ a.recycle();
+ if (result) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB);
+ } else {
+ return !HasPermanentMenuKey.get(context);
+ }
+ }
+
+ private static class HasPermanentMenuKey {
+ public static boolean get(Context context) {
+ return ViewConfiguration.get(context).hasPermanentMenuKey();
+ }
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (!mMaxItemsSet) {
+ mMaxItems = getResources_getInteger(mContext,
+ R.integer.abs__max_action_buttons);
+ if (mMenu != null) {
+ mMenu.onItemsChanged(true);
+ }
+ }
+ }
+
+ public void setWidthLimit(int width, boolean strict) {
+ mWidthLimit = width;
+ mStrictWidthLimit = strict;
+ mWidthLimitSet = true;
+ }
+
+ public void setReserveOverflow(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ mReserveOverflowSet = true;
+ }
+
+ public void setItemLimit(int itemCount) {
+ mMaxItems = itemCount;
+ mMaxItemsSet = true;
+ }
+
+ public void setExpandedActionViewsExclusive(boolean isExclusive) {
+ mExpandedActionViewsExclusive = isExclusive;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ View actionView = item.getActionView();
+ if (actionView == null || item.hasCollapsibleActionView()) {
+ if (!(convertView instanceof ActionMenuItemView)) {
+ convertView = null;
+ }
+ actionView = super.getItemView(item, convertView, parent);
+ }
+ actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+
+ final ActionMenuView menuParent = (ActionMenuView) parent;
+ final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+ if (!menuParent.checkLayoutParams(lp)) {
+ actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+ }
+ return actionView;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+
+ final ActionMenuView menuView = (ActionMenuView) mMenuView;
+ ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
+ actionItemView.setItemInvoker(menuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ super.updateMenuView(cleared);
+
+ if (mMenu != null) {
+ final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
+ final int count = actionItems.size();
+ for (int i = 0; i < count; i++) {
+ final ActionProvider provider = actionItems.get(i).getActionProvider();
+ if (provider != null) {
+ provider.setSubUiVisibilityListener(this);
+ }
+ }
+ }
+
+ final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
+ mMenu.getNonActionItems() : null;
+
+ boolean hasOverflow = false;
+ if (mReserveOverflow && nonActionItems != null) {
+ final int count = nonActionItems.size();
+ if (count == 1) {
+ hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
+ } else {
+ hasOverflow = count > 0;
+ }
+ }
+
+ if (hasOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ActionMenuView menuView = (ActionMenuView) mMenuView;
+ menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+
+ ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) {
+ if (mOverflowButton == null) return false;
+ anchor = mOverflowButton;
+ }
+
+ mOpenSubMenuId = subMenu.getItem().getItemId();
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
+ mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ mPostedOpenRunnable = null;
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
+ // Overflow everything if we have an expanded action view and we're
+ // space constrained.
+ maxActions = 0;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ int cellSize = 0;
+ int cellsRemaining = 0;
+ if (mStrictWidthLimit) {
+ cellsRemaining = widthLimit / mMinCellSize;
+ final int cellSizeRemaining = widthLimit % mMinCellSize;
+ cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
+ }
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ cellsRemaining -= ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ item.setIsActionButton(true);
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
+ (!mStrictWidthLimit || cellsRemaining > 0);
+
+ if (isAction) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ final int cells = ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ cellsRemaining -= cells;
+ if (cells == 0) {
+ isAction = false;
+ }
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ if (mStrictWidthLimit) {
+ isAction &= widthLimit >= 0;
+ } else {
+ // Did this push the entire first item past the limit?
+ isAction &= widthLimit + firstActionWidth > 0;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ // Give back the action slot
+ if (areYouMyGroupie.isActionButton()) maxActions++;
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ if (isAction) maxActions--;
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState();
+ state.openSubMenuId = mOpenSubMenuId;
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState saved = (SavedState) state;
+ if (saved.openSubMenuId > 0) {
+ MenuItem item = mMenu.findItem(saved.openSubMenuId);
+ if (item != null) {
+ SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ onSubMenuSelected(subMenu);
+ }
+ }
+ }
+
+ @Override
+ public void onSubUiVisibilityChanged(boolean isVisible) {
+ if (isVisible) {
+ // Not a submenu, but treat it like one.
+ super.onSubMenuSelected(null);
+ } else {
+ mMenu.close(false);
+ }
+ }
+
+ private static class SavedState implements Parcelable {
+ public int openSubMenuId;
+
+ SavedState() {
+ }
+
+ SavedState(Parcel in) {
+ openSubMenuId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(openSubMenuId);
+ }
+
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuChildView, View_HasStateListenerSupport {
+ private final Set<View_OnAttachStateChangeListener> mListeners = new HashSet<View_OnAttachStateChangeListener>();
+
+ public OverflowMenuButton(Context context) {
+ super(context, null, R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return false;
+ }
+
+ public boolean needsDividerAfter() {
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewDetachedFromWindow(this);
+ }
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.remove(listener);
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ setCallback(mPopupPresenterCallback);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ //UNUSED private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ //UNUSED mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+
+ setCallback(mPopupPresenterCallback);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+ setForceShowIcon(preserveIconSpacing);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mActionButtonPopup = null;
+ mOpenSubMenuId = 0;
+ }
+ }
+
+ private class PopupPresenterCallback implements MenuPresenter.Callback {
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null) return false;
+
+ mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (menu instanceof SubMenuBuilder) {
+ ((SubMenuBuilder) menu).getRootMenu().close(false);
+ }
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ final View menuView = (View) mMenuView;
+ if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ }
+ mPostedOpenRunnable = null;
+ }
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
new file mode 100644
index 000000000..e090677a1
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2010 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.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import com.actionbarsherlock.internal.widget.IcsLinearLayout;
+
+/**
+ * @hide
+ */
+public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView {
+ //UNUSED private static final String TAG = "ActionMenuView";
+ private static final boolean IS_FROYO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
+
+ static final int MIN_CELL_SIZE = 56; // dips
+ static final int GENERATED_ITEM_PADDING = 4; // dips
+
+ private MenuBuilder mMenu;
+
+ private boolean mReserveOverflow;
+ private ActionMenuPresenter mPresenter;
+ private boolean mFormatItems;
+ private int mFormatItemsWidth;
+ private int mMinCellSize;
+ private int mGeneratedItemPadding;
+ //UNUSED private int mMeasuredExtraWidth;
+
+ private boolean mFirst = true;
+
+ public ActionMenuView(Context context) {
+ this(context, null);
+ }
+
+ public ActionMenuView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setBaselineAligned(false);
+ final float density = context.getResources().getDisplayMetrics().density;
+ mMinCellSize = (int) (MIN_CELL_SIZE * density);
+ mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+ }
+
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ public boolean isExpandedFormat() {
+ return mFormatItems;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (IS_FROYO) {
+ super.onConfigurationChanged(newConfig);
+ }
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ //Need to trigger a relayout since we may have been added extremely
+ //late in the initial rendering (e.g., when contained in a ViewPager).
+ //See: https://github.com/JakeWharton/ActionBarSherlock/issues/272
+ if (!IS_FROYO && mFirst) {
+ mFirst = false;
+ requestLayout();
+ return;
+ }
+ super.onDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // If we've been given an exact size to match, apply special formatting during layout.
+ final boolean wasFormatted = mFormatItems;
+ mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+
+ if (wasFormatted != mFormatItems) {
+ mFormatItemsWidth = 0; // Reset this when switching modes
+ }
+
+ // Special formatting can change whether items can fit as action buttons.
+ // Kick the menu and update presenters when this changes.
+ final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
+ if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
+ mFormatItemsWidth = widthSize;
+ mMenu.onItemsChanged(true);
+ }
+
+ if (mFormatItems) {
+ onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
+ // We already know the width mode is EXACTLY if we're here.
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ final int heightPadding = getPaddingTop() + getPaddingBottom();
+
+ widthSize -= widthPadding;
+
+ // Divide the view into cells.
+ final int cellCount = widthSize / mMinCellSize;
+ final int cellSizeRemaining = widthSize % mMinCellSize;
+
+ if (cellCount == 0) {
+ // Give up, nothing fits.
+ setMeasuredDimension(widthSize, 0);
+ return;
+ }
+
+ final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
+
+ int cellsRemaining = cellCount;
+ int maxChildHeight = 0;
+ int maxCellsUsed = 0;
+ int expandableItemCount = 0;
+ int visibleItemCount = 0;
+ boolean hasOverflow = false;
+
+ // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
+ long smallestItemsAt = 0;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) continue;
+
+ final boolean isGeneratedItem = child instanceof ActionMenuItemView;
+ visibleItemCount++;
+
+ if (isGeneratedItem) {
+ // Reset padding for generated menu item views; it may change below
+ // and views are recycled.
+ child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.expanded = false;
+ lp.extraPixels = 0;
+ lp.cellsUsed = 0;
+ lp.expandable = false;
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
+
+ // Overflow always gets 1 cell. No more, no less.
+ final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
+
+ final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
+ heightMeasureSpec, heightPadding);
+
+ maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
+ if (lp.expandable) expandableItemCount++;
+ if (lp.isOverflowButton) hasOverflow = true;
+
+ cellsRemaining -= cellsUsed;
+ maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
+ if (cellsUsed == 1) smallestItemsAt |= (1 << i);
+ }
+
+ // When we have overflow and a single expanded (text) item, we want to try centering it
+ // visually in the available space even though overflow consumes some of it.
+ final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
+
+ // Divide space for remaining cells if we have items that can expand.
+ // Try distributing whole leftover cells to smaller items first.
+
+ boolean needsExpansion = false;
+ while (expandableItemCount > 0 && cellsRemaining > 0) {
+ int minCells = Integer.MAX_VALUE;
+ long minCellsAt = 0; // Bit locations are indices of relevant child views
+ int minCellsItemCount = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ // Don't try to expand items that shouldn't.
+ if (!lp.expandable) continue;
+
+ // Mark indices of children that can receive an extra cell.
+ if (lp.cellsUsed < minCells) {
+ minCells = lp.cellsUsed;
+ minCellsAt = 1 << i;
+ minCellsItemCount = 1;
+ } else if (lp.cellsUsed == minCells) {
+ minCellsAt |= 1 << i;
+ minCellsItemCount++;
+ }
+ }
+
+ // Items that get expanded will always be in the set of smallest items when we're done.
+ smallestItemsAt |= minCellsAt;
+
+ if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
+
+ // We have enough cells, all minimum size items will be incremented.
+ minCells++;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if ((minCellsAt & (1 << i)) == 0) {
+ // If this item is already at our small item count, mark it for later.
+ if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
+ continue;
+ }
+
+ if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
+ // Add padding to this item such that it centers.
+ child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
+ }
+ lp.cellsUsed++;
+ lp.expanded = true;
+ cellsRemaining--;
+ }
+
+ needsExpansion = true;
+ }
+
+ // Divide any space left that wouldn't divide along cell boundaries
+ // evenly among the smallest items
+
+ final boolean singleItem = !hasOverflow && visibleItemCount == 1;
+ if (cellsRemaining > 0 && smallestItemsAt != 0 &&
+ (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
+ float expandCount = Long.bitCount(smallestItemsAt);
+
+ if (!singleItem) {
+ // The items at the far edges may only expand by half in order to pin to either side.
+ if ((smallestItemsAt & 1) != 0) {
+ LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
+ LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ }
+
+ final int extraPixels = expandCount > 0 ?
+ (int) (cellsRemaining * cellSize / expandCount) : 0;
+
+ for (int i = 0; i < childCount; i++) {
+ if ((smallestItemsAt & (1 << i)) == 0) continue;
+
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child instanceof ActionMenuItemView) {
+ // If this is one of our views, expand and measure at the larger size.
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ if (i == 0 && !lp.preventEdgeOffset) {
+ // First item gets part of its new padding pushed out of sight.
+ // The last item will get this implicitly from layout.
+ lp.leftMargin = -extraPixels / 2;
+ }
+ needsExpansion = true;
+ } else if (lp.isOverflowButton) {
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ lp.rightMargin = -extraPixels / 2;
+ needsExpansion = true;
+ } else {
+ // If we don't know what it is, give it some margins instead
+ // and let it center within its space. We still want to pin
+ // against the edges.
+ if (i != 0) {
+ lp.leftMargin = extraPixels / 2;
+ }
+ if (i != childCount - 1) {
+ lp.rightMargin = extraPixels / 2;
+ }
+ }
+ }
+
+ cellsRemaining = 0;
+ }
+
+ // Remeasure any items that have had extra space allocated to them.
+ if (needsExpansion) {
+ int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode);
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (!lp.expanded) continue;
+
+ final int width = lp.cellsUsed * cellSize + lp.extraPixels;
+ child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
+ }
+ }
+
+ if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = maxChildHeight;
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
+ }
+
+ /**
+ * Measure a child view to fit within cell-based formatting. The child's width
+ * will be measured to a whole multiple of cellSize.
+ *
+ * <p>Sets the expandable and cellsUsed fields of LayoutParams.
+ *
+ * @param child Child to measure
+ * @param cellSize Size of one cell
+ * @param cellsRemaining Number of cells remaining that this view can expand to fill
+ * @param parentHeightMeasureSpec MeasureSpec used by the parent view
+ * @param parentHeightPadding Padding present in the parent view
+ * @return Number of cells this child was measured to occupy
+ */
+ static int measureChildForCells(View child, int cellSize, int cellsRemaining,
+ int parentHeightMeasureSpec, int parentHeightPadding) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
+ parentHeightPadding;
+ final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
+
+ int cellsUsed = 0;
+ if (cellsRemaining > 0) {
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(
+ cellSize * cellsRemaining, MeasureSpec.AT_MOST);
+ child.measure(childWidthSpec, childHeightSpec);
+
+ final int measuredWidth = child.getMeasuredWidth();
+ cellsUsed = measuredWidth / cellSize;
+ if (measuredWidth % cellSize != 0) cellsUsed++;
+ }
+
+ final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
+ (ActionMenuItemView) child : null;
+ final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText();
+ lp.expandable = expandable;
+
+ lp.cellsUsed = cellsUsed;
+ final int targetWidth = cellsUsed * cellSize;
+ child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ childHeightSpec);
+ return cellsUsed;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mFormatItems) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ final int midVertical = (top + bottom) / 2;
+ final int dividerWidth = 0;//getDividerWidth();
+ int overflowWidth = 0;
+ //UNUSED int nonOverflowWidth = 0;
+ int nonOverflowCount = 0;
+ int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+ boolean hasOverflow = false;
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ LayoutParams p = (LayoutParams) v.getLayoutParams();
+ if (p.isOverflowButton) {
+ overflowWidth = v.getMeasuredWidth();
+ if (hasDividerBeforeChildAt(i)) {
+ overflowWidth += dividerWidth;
+ }
+
+ int height = v.getMeasuredHeight();
+ int r = getWidth() - getPaddingRight() - p.rightMargin;
+ int l = r - overflowWidth;
+ int t = midVertical - (height / 2);
+ int b = t + height;
+ v.layout(l, t, r, b);
+
+ widthRemaining -= overflowWidth;
+ hasOverflow = true;
+ } else {
+ final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+ //UNUSED nonOverflowWidth += size;
+ widthRemaining -= size;
+ //if (hasDividerBeforeChildAt(i)) {
+ //UNUSED nonOverflowWidth += dividerWidth;
+ //}
+ nonOverflowCount++;
+ }
+ }
+
+ if (childCount == 1 && !hasOverflow) {
+ // Center a single child
+ final View v = getChildAt(0);
+ final int width = v.getMeasuredWidth();
+ final int height = v.getMeasuredHeight();
+ final int midHorizontal = (right - left) / 2;
+ final int l = midHorizontal - width / 2;
+ final int t = midVertical - height / 2;
+ v.layout(l, t, l + width, t + height);
+ return;
+ }
+
+ final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
+ final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
+
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin + spacerSize;
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPresenter.dismissPopupMenus();
+ }
+
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public void setOverflowReserved(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER_VERTICAL;
+ return params;
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (p instanceof LayoutParams) {
+ LayoutParams result = new LayoutParams((LayoutParams) p);
+ if (result.gravity <= Gravity.NO_GRAVITY) {
+ result.gravity = Gravity.CENTER_VERTICAL;
+ }
+ return result;
+ }
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p != null && p instanceof LayoutParams;
+ }
+
+ public LayoutParams generateOverflowButtonLayoutParams() {
+ LayoutParams result = generateDefaultLayoutParams();
+ result.isOverflowButton = true;
+ return result;
+ }
+
+ public boolean invokeItem(MenuItemImpl item) {
+ return mMenu.performItemAction(item, 0);
+ }
+
+ public int getWindowAnimations() {
+ return 0;
+ }
+
+ public void initialize(MenuBuilder menu) {
+ mMenu = menu;
+ }
+
+ //@Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
+ }
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
+ }
+ return result;
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
+ }
+
+ public static class LayoutParams extends LinearLayout.LayoutParams {
+ public boolean isOverflowButton;
+ public int cellsUsed;
+ public int extraPixels;
+ public boolean expandable;
+ public boolean preventEdgeOffset;
+
+ public boolean expanded;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super((LinearLayout.LayoutParams) other);
+ isOverflowButton = other.isOverflowButton;
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ isOverflowButton = false;
+ }
+
+ public LayoutParams(int width, int height, boolean isOverflowButton) {
+ super(width, height);
+ this.isOverflowButton = isOverflowButton;
+ }
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 000000000..6da26f2ae
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2011 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.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import android.content.Context;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+
+ protected Context mSystemContext;
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mSystemInflater;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ private int mId;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param context Context for generating system-supplied views
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
+ mSystemContext = context;
+ mSystemInflater = LayoutInflater.from(context);
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return;
+
+ int childIndex = 0;
+ if (mMenu != null) {
+ mMenu.flagActionItems();
+ ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
+ ((MenuView.ItemView) convertView).getItemData() : null;
+ final View itemView = getItemView(item, convertView, parent);
+ if (item != oldItem) {
+ // Don't let old states linger with new data.
+ itemView.setPressed(false);
+ if (IS_HONEYCOMB) itemView.jumpDrawablesToCurrentState();
+ }
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ final ViewGroup currentParent = (ViewGroup) itemView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(itemView);
+ }
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Indended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java
new file mode 100644
index 000000000..ac25c3736
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ListMenuItemView.java
@@ -0,0 +1,278 @@
+/*
+ * 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.actionbarsherlock.internal.view.menu;
+
+import com.actionbarsherlock.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+/**
+ * The item view for each item in the ListView-based MenuViews.
+ */
+public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
+ private MenuItemImpl mItemData;
+
+ private ImageView mIconView;
+ private RadioButton mRadioButton;
+ private TextView mTitleView;
+ private CheckBox mCheckBox;
+ private TextView mShortcutView;
+
+ private Drawable mBackground;
+ private int mTextAppearance;
+ private Context mTextAppearanceContext;
+ private boolean mPreserveIconSpacing;
+
+ //UNUSED private int mMenuType;
+
+ private LayoutInflater mInflater;
+
+ private boolean mForceShowIcon;
+
+ final Context mContext;
+
+ public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs);
+ mContext = context;
+
+ TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.SherlockMenuView, defStyle, 0);
+
+ mBackground = a.getDrawable(R.styleable.SherlockMenuView_itemBackground);
+ mTextAppearance = a.getResourceId(R.styleable.
+ SherlockMenuView_itemTextAppearance, -1);
+ mPreserveIconSpacing = a.getBoolean(
+ R.styleable.SherlockMenuView_preserveIconSpacing, false);
+ mTextAppearanceContext = context;
+
+ a.recycle();
+ }
+
+ public ListMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ setBackgroundDrawable(mBackground);
+
+ mTitleView = (TextView) findViewById(R.id.abs__title);
+ if (mTextAppearance != -1) {
+ mTitleView.setTextAppearance(mTextAppearanceContext,
+ mTextAppearance);
+ }
+
+ mShortcutView = (TextView) findViewById(R.id.abs__shortcut);
+ }
+
+ public void initialize(MenuItemImpl itemData, int menuType) {
+ mItemData = itemData;
+ //UNUSED mMenuType = menuType;
+
+ setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+
+ setTitle(itemData.getTitleForItemView(this));
+ setCheckable(itemData.isCheckable());
+ setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
+ setIcon(itemData.getIcon());
+ setEnabled(itemData.isEnabled());
+ }
+
+ public void setForceShowIcon(boolean forceShow) {
+ mPreserveIconSpacing = mForceShowIcon = forceShow;
+ }
+
+ public void setTitle(CharSequence title) {
+ if (title != null) {
+ mTitleView.setText(title);
+
+ if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE);
+ } else {
+ if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE);
+ }
+ }
+
+ public MenuItemImpl getItemData() {
+ return mItemData;
+ }
+
+ public void setCheckable(boolean checkable) {
+
+ if (!checkable && mRadioButton == null && mCheckBox == null) {
+ return;
+ }
+
+ if (mRadioButton == null) {
+ insertRadioButton();
+ }
+ if (mCheckBox == null) {
+ insertCheckBox();
+ }
+
+ // Depending on whether its exclusive check or not, the checkbox or
+ // radio button will be the one in use (and the other will be otherCompoundButton)
+ final CompoundButton compoundButton;
+ final CompoundButton otherCompoundButton;
+
+ if (mItemData.isExclusiveCheckable()) {
+ compoundButton = mRadioButton;
+ otherCompoundButton = mCheckBox;
+ } else {
+ compoundButton = mCheckBox;
+ otherCompoundButton = mRadioButton;
+ }
+
+ if (checkable) {
+ compoundButton.setChecked(mItemData.isChecked());
+
+ final int newVisibility = checkable ? VISIBLE : GONE;
+ if (compoundButton.getVisibility() != newVisibility) {
+ compoundButton.setVisibility(newVisibility);
+ }
+
+ // Make sure the other compound button isn't visible
+ if (otherCompoundButton.getVisibility() != GONE) {
+ otherCompoundButton.setVisibility(GONE);
+ }
+ } else {
+ mCheckBox.setVisibility(GONE);
+ mRadioButton.setVisibility(GONE);
+ }
+ }
+
+ public void setChecked(boolean checked) {
+ CompoundButton compoundButton;
+
+ if (mItemData.isExclusiveCheckable()) {
+ if (mRadioButton == null) {
+ insertRadioButton();
+ }
+ compoundButton = mRadioButton;
+ } else {
+ if (mCheckBox == null) {
+ insertCheckBox();
+ }
+ compoundButton = mCheckBox;
+ }
+
+ compoundButton.setChecked(checked);
+ }
+
+ public void setShortcut(boolean showShortcut, char shortcutKey) {
+ final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
+ ? VISIBLE : GONE;
+
+ if (newVisibility == VISIBLE) {
+ mShortcutView.setText(mItemData.getShortcutLabel());
+ }
+
+ if (mShortcutView.getVisibility() != newVisibility) {
+ mShortcutView.setVisibility(newVisibility);
+ }
+ }
+
+ public void setIcon(Drawable icon) {
+ final boolean showIcon = mItemData.shouldShowIcon() || mForceShowIcon;
+ if (!showIcon && !mPreserveIconSpacing) {
+ return;
+ }
+
+ if (mIconView == null && icon == null && !mPreserveIconSpacing) {
+ return;
+ }
+
+ if (mIconView == null) {
+ insertIconView();
+ }
+
+ if (icon != null || mPreserveIconSpacing) {
+ mIconView.setImageDrawable(showIcon ? icon : null);
+
+ if (mIconView.getVisibility() != VISIBLE) {
+ mIconView.setVisibility(VISIBLE);
+ }
+ } else {
+ mIconView.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mIconView != null && mPreserveIconSpacing) {
+ // Enforce minimum icon spacing
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
+ if (lp.height > 0 && iconLp.width <= 0) {
+ iconLp.width = lp.height;
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ private void insertIconView() {
+ LayoutInflater inflater = getInflater();
+ mIconView = (ImageView) inflater.inflate(R.layout.abs__list_menu_item_icon,
+ this, false);
+ addView(mIconView, 0);
+ }
+
+ private void insertRadioButton() {
+ LayoutInflater inflater = getInflater();
+ mRadioButton =
+ (RadioButton) inflater.inflate(R.layout.abs__list_menu_item_radio,
+ this, false);
+ addView(mRadioButton);
+ }
+
+ private void insertCheckBox() {
+ LayoutInflater inflater = getInflater();
+ mCheckBox =
+ (CheckBox) inflater.inflate(R.layout.abs__list_menu_item_checkbox,
+ this, false);
+ addView(mCheckBox);
+ }
+
+ public boolean prefersCondensedTitle() {
+ return false;
+ }
+
+ public boolean showsIcon() {
+ return mForceShowIcon;
+ }
+
+ private LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(mContext);
+ }
+ return mInflater;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java
new file mode 100644
index 000000000..179b8f037
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java
@@ -0,0 +1,1335 @@
+/*
+ * 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.actionbarsherlock.internal.view.menu;
+
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+/**
+ * Implementation of the {@link android.view.Menu} interface for creating a
+ * standard menu UI.
+ */
+public class MenuBuilder implements Menu {
+ //UNUSED private static final String TAG = "MenuBuilder";
+
+ private static final String PRESENTER_KEY = "android:menu:presenters";
+ private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates";
+ private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview";
+
+ private static final int[] sCategoryToOrder = new int[] {
+ 1, /* No category */
+ 4, /* CONTAINER */
+ 5, /* SYSTEM */
+ 3, /* SECONDARY */
+ 2, /* ALTERNATIVE */
+ 0, /* SELECTED_ALTERNATIVE */
+ };
+
+ private final Context mContext;
+ private final Resources mResources;
+
+ /**
+ * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
+ * instead of accessing this directly.
+ */
+ private boolean mQwertyMode;
+
+ /**
+ * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
+ * instead of accessing this directly.
+ */
+ private boolean mShortcutsVisible;
+
+ /**
+ * Callback that will receive the various menu-related events generated by
+ * this class. Use getCallback to get a reference to the callback.
+ */
+ private Callback mCallback;
+
+ /** Contains all of the items for this menu */
+ private ArrayList<MenuItemImpl> mItems;
+
+ /** Contains only the items that are currently visible. This will be created/refreshed from
+ * {@link #getVisibleItems()} */
+ private ArrayList<MenuItemImpl> mVisibleItems;
+ /**
+ * Whether or not the items (or any one item's shown state) has changed since it was last
+ * fetched from {@link #getVisibleItems()}
+ */
+ private boolean mIsVisibleItemsStale;
+
+ /**
+ * Contains only the items that should appear in the Action Bar, if present.
+ */
+ private ArrayList<MenuItemImpl> mActionItems;
+ /**
+ * Contains items that should NOT appear in the Action Bar, if present.
+ */
+ private ArrayList<MenuItemImpl> mNonActionItems;
+
+ /**
+ * Whether or not the items (or any one item's action state) has changed since it was
+ * last fetched.
+ */
+ private boolean mIsActionItemsStale;
+
+ /**
+ * Default value for how added items should show in the action list.
+ */
+ private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
+
+ /**
+ * Current use case is Context Menus: As Views populate the context menu, each one has
+ * extra information that should be passed along. This is the current menu info that
+ * should be set on all items added to this menu.
+ */
+ private ContextMenuInfo mCurrentMenuInfo;
+
+ /** Header title for menu types that have a header (context and submenus) */
+ CharSequence mHeaderTitle;
+ /** Header icon for menu types that have a header and support icons (context) */
+ Drawable mHeaderIcon;
+ /** Header custom view for menu types that have a header and support custom views (context) */
+ View mHeaderView;
+
+ /**
+ * Contains the state of the View hierarchy for all menu views when the menu
+ * was frozen.
+ */
+ //UNUSED private SparseArray<Parcelable> mFrozenViewStates;
+
+ /**
+ * Prevents onItemsChanged from doing its junk, useful for batching commands
+ * that may individually call onItemsChanged.
+ */
+ private boolean mPreventDispatchingItemsChanged = false;
+ private boolean mItemsChangedWhileDispatchPrevented = false;
+
+ private boolean mOptionalIconsVisible = false;
+
+ private boolean mIsClosing = false;
+
+ private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
+
+ private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
+ new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
+
+ /**
+ * Currently expanded menu item; must be collapsed when we clear.
+ */
+ private MenuItemImpl mExpandedItem;
+
+ /**
+ * Called by menu to notify of close and selection changes.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu item is selected.
+ * @param menu The menu that is the parent of the item
+ * @param item The menu item that is selected
+ * @return whether the menu item selection was handled
+ */
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
+
+ /**
+ * Called when the mode of the menu changes (for example, from icon to expanded).
+ *
+ * @param menu the menu that has changed modes
+ */
+ public void onMenuModeChange(MenuBuilder menu);
+ }
+
+ /**
+ * Called by menu items to execute their associated action
+ */
+ public interface ItemInvoker {
+ public boolean invokeItem(MenuItemImpl item);
+ }
+
+ public MenuBuilder(Context context) {
+ mContext = context;
+ mResources = context.getResources();
+
+ mItems = new ArrayList<MenuItemImpl>();
+
+ mVisibleItems = new ArrayList<MenuItemImpl>();
+ mIsVisibleItemsStale = true;
+
+ mActionItems = new ArrayList<MenuItemImpl>();
+ mNonActionItems = new ArrayList<MenuItemImpl>();
+ mIsActionItemsStale = true;
+
+ setShortcutsVisibleInner(true);
+ }
+
+ public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
+ mDefaultShowAsAction = defaultShowAsAction;
+ return this;
+ }
+
+ /**
+ * Add a presenter to this menu. This will only hold a WeakReference;
+ * you do not need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
+ *
+ * @param presenter The presenter to add
+ */
+ public void addMenuPresenter(MenuPresenter presenter) {
+ mPresenters.add(new WeakReference<MenuPresenter>(presenter));
+ presenter.initForMenu(mContext, this);
+ mIsActionItemsStale = true;
+ }
+
+ /**
+ * Remove a presenter from this menu. That presenter will no longer
+ * receive notifications of updates to this menu's data.
+ *
+ * @param presenter The presenter to remove
+ */
+ public void removeMenuPresenter(MenuPresenter presenter) {
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter item = ref.get();
+ if (item == null || item == presenter) {
+ mPresenters.remove(ref);
+ }
+ }
+ }
+
+ private void dispatchPresenterUpdate(boolean cleared) {
+ if (mPresenters.isEmpty()) return;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.updateMenuView(cleared);
+ }
+ }
+ startDispatchingItemsChanged();
+ }
+
+ private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean result = false;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if (!result) {
+ result = presenter.onSubMenuSelected(subMenu);
+ }
+ }
+ return result;
+ }
+
+ private void dispatchSaveInstanceState(Bundle outState) {
+ if (mPresenters.isEmpty()) return;
+
+ SparseArray<Parcelable> presenterStates = new SparseArray<Parcelable>();
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ final int id = presenter.getId();
+ if (id > 0) {
+ final Parcelable state = presenter.onSaveInstanceState();
+ if (state != null) {
+ presenterStates.put(id, state);
+ }
+ }
+ }
+ }
+
+ outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates);
+ }
+
+ private void dispatchRestoreInstanceState(Bundle state) {
+ SparseArray<Parcelable> presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
+
+ if (presenterStates == null || mPresenters.isEmpty()) return;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ final int id = presenter.getId();
+ if (id > 0) {
+ Parcelable parcel = presenterStates.get(id);
+ if (parcel != null) {
+ presenter.onRestoreInstanceState(parcel);
+ }
+ }
+ }
+ }
+ }
+
+ public void savePresenterStates(Bundle outState) {
+ dispatchSaveInstanceState(outState);
+ }
+
+ public void restorePresenterStates(Bundle state) {
+ dispatchRestoreInstanceState(state);
+ }
+
+ public void saveActionViewStates(Bundle outStates) {
+ SparseArray<Parcelable> viewStates = null;
+
+ final int itemCount = size();
+ for (int i = 0; i < itemCount; i++) {
+ final MenuItem item = getItem(i);
+ final View v = item.getActionView();
+ if (v != null && v.getId() != View.NO_ID) {
+ if (viewStates == null) {
+ viewStates = new SparseArray<Parcelable>();
+ }
+ v.saveHierarchyState(viewStates);
+ if (item.isActionViewExpanded()) {
+ outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId());
+ }
+ }
+ if (item.hasSubMenu()) {
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ subMenu.saveActionViewStates(outStates);
+ }
+ }
+
+ if (viewStates != null) {
+ outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates);
+ }
+ }
+
+ public void restoreActionViewStates(Bundle states) {
+ if (states == null) {
+ return;
+ }
+
+ SparseArray<Parcelable> viewStates = states.getSparseParcelableArray(
+ getActionViewStatesKey());
+
+ final int itemCount = size();
+ for (int i = 0; i < itemCount; i++) {
+ final MenuItem item = getItem(i);
+ final View v = item.getActionView();
+ if (v != null && v.getId() != View.NO_ID) {
+ v.restoreHierarchyState(viewStates);
+ }
+ if (item.hasSubMenu()) {
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ subMenu.restoreActionViewStates(states);
+ }
+ }
+
+ final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID);
+ if (expandedId > 0) {
+ MenuItem itemToExpand = findItem(expandedId);
+ if (itemToExpand != null) {
+ itemToExpand.expandActionView();
+ }
+ }
+ }
+
+ protected String getActionViewStatesKey() {
+ return ACTION_VIEW_STATES_KEY;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Adds an item to the menu. The other add methods funnel to this.
+ */
+ private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
+ final int ordering = getOrdering(categoryOrder);
+
+ final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
+ ordering, title, mDefaultShowAsAction);
+
+ if (mCurrentMenuInfo != null) {
+ // Pass along the current menu info
+ item.setMenuInfo(mCurrentMenuInfo);
+ }
+
+ mItems.add(findInsertIndex(mItems, ordering), item);
+ onItemsChanged(true);
+
+ return item;
+ }
+
+ public MenuItem add(CharSequence title) {
+ return addInternal(0, 0, 0, title);
+ }
+
+ public MenuItem add(int titleRes) {
+ return addInternal(0, 0, 0, mResources.getString(titleRes));
+ }
+
+ public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
+ return addInternal(group, id, categoryOrder, title);
+ }
+
+ public MenuItem add(int group, int id, int categoryOrder, int title) {
+ return addInternal(group, id, categoryOrder, mResources.getString(title));
+ }
+
+ public SubMenu addSubMenu(CharSequence title) {
+ return addSubMenu(0, 0, 0, title);
+ }
+
+ public SubMenu addSubMenu(int titleRes) {
+ return addSubMenu(0, 0, 0, mResources.getString(titleRes));
+ }
+
+ public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
+ final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
+ final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
+ item.setSubMenu(subMenu);
+
+ return subMenu;
+ }
+
+ public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
+ return addSubMenu(group, id, categoryOrder, mResources.getString(title));
+ }
+
+ public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
+ Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
+ PackageManager pm = mContext.getPackageManager();
+ final List<ResolveInfo> lri =
+ pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+ final int N = lri != null ? lri.size() : 0;
+
+ if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+ removeGroup(group);
+ }
+
+ for (int i=0; i<N; i++) {
+ final ResolveInfo ri = lri.get(i);
+ Intent rintent = new Intent(
+ ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
+ rintent.setComponent(new ComponentName(
+ ri.activityInfo.applicationInfo.packageName,
+ ri.activityInfo.name));
+ final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
+ .setIcon(ri.loadIcon(pm))
+ .setIntent(rintent);
+ if (outSpecificItems != null && ri.specificIndex >= 0) {
+ outSpecificItems[ri.specificIndex] = item;
+ }
+ }
+
+ return N;
+ }
+
+ public void removeItem(int id) {
+ removeItemAtInt(findItemIndex(id), true);
+ }
+
+ public void removeGroup(int group) {
+ final int i = findGroupIndex(group);
+
+ if (i >= 0) {
+ final int maxRemovable = mItems.size() - i;
+ int numRemoved = 0;
+ while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
+ // Don't force update for each one, this method will do it at the end
+ removeItemAtInt(i, false);
+ }
+
+ // Notify menu views
+ onItemsChanged(true);
+ }
+ }
+
+ /**
+ * Remove the item at the given index and optionally forces menu views to
+ * update.
+ *
+ * @param index The index of the item to be removed. If this index is
+ * invalid an exception is thrown.
+ * @param updateChildrenOnMenuViews Whether to force update on menu views.
+ * Please make sure you eventually call this after your batch of
+ * removals.
+ */
+ private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
+ if ((index < 0) || (index >= mItems.size())) return;
+
+ mItems.remove(index);
+
+ if (updateChildrenOnMenuViews) onItemsChanged(true);
+ }
+
+ public void removeItemAt(int index) {
+ removeItemAtInt(index, true);
+ }
+
+ public void clearAll() {
+ mPreventDispatchingItemsChanged = true;
+ clear();
+ clearHeader();
+ mPreventDispatchingItemsChanged = false;
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
+ }
+
+ public void clear() {
+ if (mExpandedItem != null) {
+ collapseItemActionView(mExpandedItem);
+ }
+ mItems.clear();
+
+ onItemsChanged(true);
+ }
+
+ void setExclusiveItemChecked(MenuItem item) {
+ final int group = item.getGroupId();
+
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl curItem = mItems.get(i);
+ if (curItem.getGroupId() == group) {
+ if (!curItem.isExclusiveCheckable()) continue;
+ if (!curItem.isCheckable()) continue;
+
+ // Check the item meant to be checked, uncheck the others (that are in the group)
+ curItem.setCheckedInt(curItem == item);
+ }
+ }
+ }
+
+ public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
+ final int N = mItems.size();
+
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ item.setExclusiveCheckable(exclusive);
+ item.setCheckable(checkable);
+ }
+ }
+ }
+
+ public void setGroupVisible(int group, boolean visible) {
+ final int N = mItems.size();
+
+ // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
+ // than setVisible and at the end notify of items being changed
+
+ boolean changedAtLeastOneItem = false;
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
+ }
+ }
+
+ if (changedAtLeastOneItem) onItemsChanged(true);
+ }
+
+ public void setGroupEnabled(int group, boolean enabled) {
+ final int N = mItems.size();
+
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ item.setEnabled(enabled);
+ }
+ }
+ }
+
+ public boolean hasVisibleItems() {
+ final int size = size();
+
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.isVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public MenuItem findItem(int id) {
+ final int size = size();
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getItemId() == id) {
+ return item;
+ } else if (item.hasSubMenu()) {
+ MenuItem possibleItem = item.getSubMenu().findItem(id);
+
+ if (possibleItem != null) {
+ return possibleItem;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public int findItemIndex(int id) {
+ final int size = size();
+
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getItemId() == id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public int findGroupIndex(int group) {
+ return findGroupIndex(group, 0);
+ }
+
+ public int findGroupIndex(int group, int start) {
+ final int size = size();
+
+ if (start < 0) {
+ start = 0;
+ }
+
+ for (int i = start; i < size; i++) {
+ final MenuItemImpl item = mItems.get(i);
+
+ if (item.getGroupId() == group) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public int size() {
+ return mItems.size();
+ }
+
+ /** {@inheritDoc} */
+ public MenuItem getItem(int index) {
+ return mItems.get(index);
+ }
+
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return findItemWithShortcutForKey(keyCode, event) != null;
+ }
+
+ public void setQwertyMode(boolean isQwerty) {
+ mQwertyMode = isQwerty;
+
+ onItemsChanged(false);
+ }
+
+ /**
+ * Returns the ordering across all items. This will grab the category from
+ * the upper bits, find out how to order the category with respect to other
+ * categories, and combine it with the lower bits.
+ *
+ * @param categoryOrder The category order for a particular item (if it has
+ * not been or/add with a category, the default category is
+ * assumed).
+ * @return An ordering integer that can be used to order this item across
+ * all the items (even from other categories).
+ */
+ private static int getOrdering(int categoryOrder) {
+ final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
+
+ if (index < 0 || index >= sCategoryToOrder.length) {
+ throw new IllegalArgumentException("order does not contain a valid category.");
+ }
+
+ return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
+ }
+
+ /**
+ * @return whether the menu shortcuts are in qwerty mode or not
+ */
+ boolean isQwertyMode() {
+ return mQwertyMode;
+ }
+
+ /**
+ * Sets whether the shortcuts should be visible on menus. Devices without hardware
+ * key input will never make shortcuts visible even if this method is passed 'true'.
+ *
+ * @param shortcutsVisible Whether shortcuts should be visible (if true and a
+ * menu item does not have a shortcut defined, that item will
+ * still NOT show a shortcut)
+ */
+ public void setShortcutsVisible(boolean shortcutsVisible) {
+ if (mShortcutsVisible == shortcutsVisible) return;
+
+ setShortcutsVisibleInner(shortcutsVisible);
+ onItemsChanged(false);
+ }
+
+ private void setShortcutsVisibleInner(boolean shortcutsVisible) {
+ mShortcutsVisible = shortcutsVisible
+ && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
+ && mResources.getBoolean(
+ R.bool.abs__config_showMenuShortcutsWhenKeyboardPresent);
+ }
+
+ /**
+ * @return Whether shortcuts should be visible on menus.
+ */
+ public boolean isShortcutsVisible() {
+ return mShortcutsVisible;
+ }
+
+ Resources getResources() {
+ return mResources;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+ }
+
+ /**
+ * Dispatch a mode change event to this menu's callback.
+ */
+ public void changeMenuMode() {
+ if (mCallback != null) {
+ mCallback.onMenuModeChange(this);
+ }
+ }
+
+ private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
+ for (int i = items.size() - 1; i >= 0; i--) {
+ MenuItemImpl item = items.get(i);
+ if (item.getOrdering() <= ordering) {
+ return i + 1;
+ }
+ }
+
+ return 0;
+ }
+
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
+
+ boolean handled = false;
+
+ if (item != null) {
+ handled = performItemAction(item, flags);
+ }
+
+ if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
+ close(true);
+ }
+
+ return handled;
+ }
+
+ /*
+ * This function will return all the menu and sub-menu items that can
+ * be directly (the shortcut directly corresponds) and indirectly
+ * (the ALT-enabled char corresponds to the shortcut) associated
+ * with the keyCode.
+ */
+ @SuppressWarnings("deprecation")
+ void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
+ final boolean qwerty = isQwertyMode();
+ final int metaState = event.getMetaState();
+ final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
+ // Get the chars associated with the keyCode (i.e using any chording combo)
+ final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
+ // The delete key is not mapped to '\b' so we treat it specially
+ if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
+ return;
+ }
+
+ // Look for an item whose shortcut is this key.
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.hasSubMenu()) {
+ ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
+ }
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+ if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
+ (shortcutChar != 0) &&
+ (shortcutChar == possibleChars.meta[0]
+ || shortcutChar == possibleChars.meta[2]
+ || (qwerty && shortcutChar == '\b' &&
+ keyCode == KeyEvent.KEYCODE_DEL)) &&
+ item.isEnabled()) {
+ items.add(item);
+ }
+ }
+ }
+
+ /*
+ * We want to return the menu item associated with the key, but if there is no
+ * ambiguity (i.e. there is only one menu item corresponding to the key) we want
+ * to return it even if it's not an exact match; this allow the user to
+ * _not_ use the ALT key for example, making the use of shortcuts slightly more
+ * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
+ * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
+ *
+ * On the other hand, if two (or more) shortcuts corresponds to the same key,
+ * we have to only return the exact match.
+ */
+ @SuppressWarnings("deprecation")
+ MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
+ // Get all items that can be associated directly or indirectly with the keyCode
+ ArrayList<MenuItemImpl> items = mTempShortcutItemList;
+ items.clear();
+ findItemsWithShortcutForKey(items, keyCode, event);
+
+ if (items.isEmpty()) {
+ return null;
+ }
+
+ final int metaState = event.getMetaState();
+ final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
+ // Get the chars associated with the keyCode (i.e using any chording combo)
+ event.getKeyData(possibleChars);
+
+ // If we have only one element, we can safely returns it
+ final int size = items.size();
+ if (size == 1) {
+ return items.get(0);
+ }
+
+ final boolean qwerty = isQwertyMode();
+ // If we found more than one item associated with the key,
+ // we have to return the exact match
+ for (int i = 0; i < size; i++) {
+ final MenuItemImpl item = items.get(i);
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
+ if ((shortcutChar == possibleChars.meta[0] &&
+ (metaState & KeyEvent.META_ALT_ON) == 0)
+ || (shortcutChar == possibleChars.meta[2] &&
+ (metaState & KeyEvent.META_ALT_ON) != 0)
+ || (qwerty && shortcutChar == '\b' &&
+ keyCode == KeyEvent.KEYCODE_DEL)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public boolean performIdentifierAction(int id, int flags) {
+ // Look for an item whose identifier is the id.
+ return performItemAction(findItem(id), flags);
+ }
+
+ public boolean performItemAction(MenuItem item, int flags) {
+ MenuItemImpl itemImpl = (MenuItemImpl) item;
+
+ if (itemImpl == null || !itemImpl.isEnabled()) {
+ return false;
+ }
+
+ boolean invoked = itemImpl.invoke();
+
+ if (itemImpl.hasCollapsibleActionView()) {
+ invoked |= itemImpl.expandActionView();
+ if (invoked) close(true);
+ } else if (item.hasSubMenu()) {
+ close(false);
+
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ final ActionProvider provider = item.getActionProvider();
+ if (provider != null && provider.hasSubMenu()) {
+ provider.onPrepareSubMenu(subMenu);
+ }
+ invoked |= dispatchSubMenuSelected(subMenu);
+ if (!invoked) close(true);
+ } else {
+ if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
+ close(true);
+ }
+ }
+
+ return invoked;
+ }
+
+ /**
+ * Closes the visible menu.
+ *
+ * @param allMenusAreClosing Whether the menus are completely closing (true),
+ * or whether there is another menu coming in this menu's place
+ * (false). For example, if the menu is closing because a
+ * sub menu is about to be shown, <var>allMenusAreClosing</var>
+ * is false.
+ */
+ final void close(boolean allMenusAreClosing) {
+ if (mIsClosing) return;
+
+ mIsClosing = true;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.onCloseMenu(this, allMenusAreClosing);
+ }
+ }
+ mIsClosing = false;
+ }
+
+ /** {@inheritDoc} */
+ public void close() {
+ close(true);
+ }
+
+ /**
+ * Called when an item is added or removed.
+ *
+ * @param structureChanged true if the menu structure changed,
+ * false if only item properties changed.
+ * (Visibility is a structural property since it affects layout.)
+ */
+ void onItemsChanged(boolean structureChanged) {
+ if (!mPreventDispatchingItemsChanged) {
+ if (structureChanged) {
+ mIsVisibleItemsStale = true;
+ mIsActionItemsStale = true;
+ }
+
+ dispatchPresenterUpdate(structureChanged);
+ } else {
+ mItemsChangedWhileDispatchPrevented = true;
+ }
+ }
+
+ /**
+ * Stop dispatching item changed events to presenters until
+ * {@link #startDispatchingItemsChanged()} is called. Useful when
+ * many menu operations are going to be performed as a batch.
+ */
+ public void stopDispatchingItemsChanged() {
+ if (!mPreventDispatchingItemsChanged) {
+ mPreventDispatchingItemsChanged = true;
+ mItemsChangedWhileDispatchPrevented = false;
+ }
+ }
+
+ public void startDispatchingItemsChanged() {
+ mPreventDispatchingItemsChanged = false;
+
+ if (mItemsChangedWhileDispatchPrevented) {
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
+ }
+ }
+
+ /**
+ * Called by {@link MenuItemImpl} when its visible flag is changed.
+ * @param item The item that has gone through a visibility change.
+ */
+ void onItemVisibleChanged(MenuItemImpl item) {
+ // Notify of items being changed
+ mIsVisibleItemsStale = true;
+ onItemsChanged(true);
+ }
+
+ /**
+ * Called by {@link MenuItemImpl} when its action request status is changed.
+ * @param item The item that has gone through a change in action request status.
+ */
+ void onItemActionRequestChanged(MenuItemImpl item) {
+ // Notify of items being changed
+ mIsActionItemsStale = true;
+ onItemsChanged(true);
+ }
+
+ ArrayList<MenuItemImpl> getVisibleItems() {
+ if (!mIsVisibleItemsStale) return mVisibleItems;
+
+ // Refresh the visible items
+ mVisibleItems.clear();
+
+ final int itemsSize = mItems.size();
+ MenuItemImpl item;
+ for (int i = 0; i < itemsSize; i++) {
+ item = mItems.get(i);
+ if (item.isVisible()) mVisibleItems.add(item);
+ }
+
+ mIsVisibleItemsStale = false;
+ mIsActionItemsStale = true;
+
+ return mVisibleItems;
+ }
+
+ /**
+ * This method determines which menu items get to be 'action items' that will appear
+ * in an action bar and which items should be 'overflow items' in a secondary menu.
+ * The rules are as follows:
+ *
+ * <p>Items are considered for inclusion in the order specified within the menu.
+ * There is a limit of mMaxActionItems as a total count, optionally including the overflow
+ * menu button itself. This is a soft limit; if an item shares a group ID with an item
+ * previously included as an action item, the new item will stay with its group and become
+ * an action item itself even if it breaks the max item count limit. This is done to
+ * limit the conceptual complexity of the items presented within an action bar. Only a few
+ * unrelated concepts should be presented to the user in this space, and groups are treated
+ * as a single concept.
+ *
+ * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
+ * limit may be broken by a single item that exceeds the remaining space, but no further
+ * items may be added. If an item that is part of a group cannot fit within the remaining
+ * measured width, the entire group will be demoted to overflow. This is done to ensure room
+ * for navigation and other affordances in the action bar as well as reduce general UI clutter.
+ *
+ * <p>The space freed by demoting a full group cannot be consumed by future menu items.
+ * Once items begin to overflow, all future items become overflow items as well. This is
+ * to avoid inadvertent reordering that may break the app's intended design.
+ */
+ public void flagActionItems() {
+ if (!mIsActionItemsStale) {
+ return;
+ }
+
+ // Presenters flag action items as needed.
+ boolean flagged = false;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ flagged |= presenter.flagActionItems();
+ }
+ }
+
+ if (flagged) {
+ mActionItems.clear();
+ mNonActionItems.clear();
+ ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.isActionButton()) {
+ mActionItems.add(item);
+ } else {
+ mNonActionItems.add(item);
+ }
+ }
+ } else {
+ // Nobody flagged anything, everything is a non-action item.
+ // (This happens during a first pass with no action-item presenters.)
+ mActionItems.clear();
+ mNonActionItems.clear();
+ mNonActionItems.addAll(getVisibleItems());
+ }
+ mIsActionItemsStale = false;
+ }
+
+ ArrayList<MenuItemImpl> getActionItems() {
+ flagActionItems();
+ return mActionItems;
+ }
+
+ ArrayList<MenuItemImpl> getNonActionItems() {
+ flagActionItems();
+ return mNonActionItems;
+ }
+
+ public void clearHeader() {
+ mHeaderIcon = null;
+ mHeaderTitle = null;
+ mHeaderView = null;
+
+ onItemsChanged(false);
+ }
+
+ private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
+ final Drawable icon, final View view) {
+ final Resources r = getResources();
+
+ if (view != null) {
+ mHeaderView = view;
+
+ // If using a custom view, then the title and icon aren't used
+ mHeaderTitle = null;
+ mHeaderIcon = null;
+ } else {
+ if (titleRes > 0) {
+ mHeaderTitle = r.getText(titleRes);
+ } else if (title != null) {
+ mHeaderTitle = title;
+ }
+
+ if (iconRes > 0) {
+ mHeaderIcon = r.getDrawable(iconRes);
+ } else if (icon != null) {
+ mHeaderIcon = icon;
+ }
+
+ // If using the title or icon, then a custom view isn't used
+ mHeaderView = null;
+ }
+
+ // Notify of change
+ onItemsChanged(false);
+ }
+
+ /**
+ * Sets the header's title. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param title The new title.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderTitleInt(CharSequence title) {
+ setHeaderInternal(0, title, 0, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's title. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param titleRes The new title (as a resource ID).
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderTitleInt(int titleRes) {
+ setHeaderInternal(titleRes, null, 0, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's icon. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param icon The new icon.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderIconInt(Drawable icon) {
+ setHeaderInternal(0, null, 0, icon, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's icon. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param iconRes The new icon (as a resource ID).
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderIconInt(int iconRes) {
+ setHeaderInternal(0, null, iconRes, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's view. This replaces the title and icon. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param view The new view.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderViewInt(View view) {
+ setHeaderInternal(0, null, 0, null, view);
+ return this;
+ }
+
+ public CharSequence getHeaderTitle() {
+ return mHeaderTitle;
+ }
+
+ public Drawable getHeaderIcon() {
+ return mHeaderIcon;
+ }
+
+ public View getHeaderView() {
+ return mHeaderView;
+ }
+
+ /**
+ * Gets the root menu (if this is a submenu, find its root menu).
+ * @return The root menu.
+ */
+ public MenuBuilder getRootMenu() {
+ return this;
+ }
+
+ /**
+ * Sets the current menu info that is set on all items added to this menu
+ * (until this is called again with different menu info, in which case that
+ * one will be added to all subsequent item additions).
+ *
+ * @param menuInfo The extra menu information to add.
+ */
+ public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
+ mCurrentMenuInfo = menuInfo;
+ }
+
+ void setOptionalIconsVisible(boolean visible) {
+ mOptionalIconsVisible = visible;
+ }
+
+ boolean getOptionalIconsVisible() {
+ return mOptionalIconsVisible;
+ }
+
+ public boolean expandItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean expanded = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((expanded = presenter.expandItemActionView(this, item))) {
+ break;
+ }
+ }
+ startDispatchingItemsChanged();
+
+ if (expanded) {
+ mExpandedItem = item;
+ }
+ return expanded;
+ }
+
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty() || mExpandedItem != item) return false;
+
+ boolean collapsed = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
+ break;
+ }
+ }
+ startDispatchingItemsChanged();
+
+ if (collapsed) {
+ mExpandedItem = null;
+ }
+ return collapsed;
+ }
+
+ public MenuItemImpl getExpandedItem() {
+ return mExpandedItem;
+ }
+
+ public boolean bindNativeOverflow(android.view.Menu menu, android.view.MenuItem.OnMenuItemClickListener listener, HashMap<android.view.MenuItem, MenuItemImpl> map) {
+ final List<MenuItemImpl> nonActionItems = getNonActionItems();
+ if (nonActionItems == null || nonActionItems.size() == 0) {
+ return false;
+ }
+
+ boolean visible = false;
+ menu.clear();
+ for (MenuItemImpl nonActionItem : nonActionItems) {
+ if (!nonActionItem.isVisible()) {
+ continue;
+ }
+ visible = true;
+
+ android.view.MenuItem nativeItem;
+ if (nonActionItem.hasSubMenu()) {
+ android.view.SubMenu nativeSub = menu.addSubMenu(nonActionItem.getGroupId(), nonActionItem.getItemId(),
+ nonActionItem.getOrder(), nonActionItem.getTitle());
+
+ SubMenuBuilder subMenu = (SubMenuBuilder)nonActionItem.getSubMenu();
+ for (MenuItemImpl subItem : subMenu.getVisibleItems()) {
+ android.view.MenuItem nativeSubItem = nativeSub.add(subItem.getGroupId(), subItem.getItemId(),
+ subItem.getOrder(), subItem.getTitle());
+
+ nativeSubItem.setIcon(subItem.getIcon());
+ nativeSubItem.setOnMenuItemClickListener(listener);
+ nativeSubItem.setEnabled(subItem.isEnabled());
+ nativeSubItem.setIntent(subItem.getIntent());
+ nativeSubItem.setNumericShortcut(subItem.getNumericShortcut());
+ nativeSubItem.setAlphabeticShortcut(subItem.getAlphabeticShortcut());
+ nativeSubItem.setTitleCondensed(subItem.getTitleCondensed());
+ nativeSubItem.setCheckable(subItem.isCheckable());
+ nativeSubItem.setChecked(subItem.isChecked());
+
+ if (subItem.isExclusiveCheckable()) {
+ nativeSub.setGroupCheckable(subItem.getGroupId(), true, true);
+ }
+
+ map.put(nativeSubItem, subItem);
+ }
+
+ nativeItem = nativeSub.getItem();
+ } else {
+ nativeItem = menu.add(nonActionItem.getGroupId(), nonActionItem.getItemId(),
+ nonActionItem.getOrder(), nonActionItem.getTitle());
+ }
+ nativeItem.setIcon(nonActionItem.getIcon());
+ nativeItem.setOnMenuItemClickListener(listener);
+ nativeItem.setEnabled(nonActionItem.isEnabled());
+ nativeItem.setIntent(nonActionItem.getIntent());
+ nativeItem.setNumericShortcut(nonActionItem.getNumericShortcut());
+ nativeItem.setAlphabeticShortcut(nonActionItem.getAlphabeticShortcut());
+ nativeItem.setTitleCondensed(nonActionItem.getTitleCondensed());
+ nativeItem.setCheckable(nonActionItem.isCheckable());
+ nativeItem.setChecked(nonActionItem.isChecked());
+
+ if (nonActionItem.isExclusiveCheckable()) {
+ menu.setGroupCheckable(nonActionItem.getGroupId(), true, true);
+ }
+
+ map.put(nativeItem, nonActionItem);
+ }
+ return visible;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java
new file mode 100644
index 000000000..f5359fb40
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java
@@ -0,0 +1,647 @@
+/*
+ * 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.actionbarsherlock.internal.view.menu;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewDebug;
+import android.widget.LinearLayout;
+
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+/**
+ * @hide
+ */
+public final class MenuItemImpl implements MenuItem {
+ private static final String TAG = "MenuItemImpl";
+
+ private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
+ SHOW_AS_ACTION_IF_ROOM |
+ SHOW_AS_ACTION_ALWAYS;
+
+ 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 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;
+ private static final int IS_ACTION = 0x00000020;
+
+ private int mShowAsAction = SHOW_AS_ACTION_NEVER;
+
+ private View mActionView;
+ private ActionProvider mActionProvider;
+ private OnActionExpandListener mOnActionExpandListener;
+ private boolean mIsActionViewExpanded = false;
+
+ /** 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.
+ *
+ * @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, int showAsAction) {
+
+ /* TODO 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);
+ }*/
+
+ mMenu = menu;
+ mId = id;
+ mGroup = group;
+ mCategoryOrder = categoryOrder;
+ mOrdering = ordering;
+ mTitle = title;
+ mShowAsAction = showAsAction;
+ }
+
+ /**
+ * 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;
+ }
+
+ if (mMenu.dispatchMenuItemSelected(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);
+ }
+ }
+
+ if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean isEnabled() {
+ return (mFlags & ENABLED) != 0;
+ }
+
+ public MenuItem setEnabled(boolean enabled) {
+ if (enabled) {
+ mFlags |= ENABLED;
+ } else {
+ mFlags &= ~ENABLED;
+ }
+
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ public int getGroupId() {
+ return mGroup;
+ }
+
+ @ViewDebug.CapturedViewProperty
+ 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);
+
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ public char getNumericShortcut() {
+ return mShortcutNumericChar;
+ }
+
+ public MenuItem setNumericShortcut(char numericChar) {
+ if (mShortcutNumericChar == numericChar) return this;
+
+ mShortcutNumericChar = numericChar;
+
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
+
+ mMenu.onItemsChanged(false);
+
+ 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);
+ }
+
+ public SubMenu getSubMenu() {
+ return mSubMenu;
+ }
+
+ public boolean hasSubMenu() {
+ return mSubMenu != null;
+ }
+
+ void setSubMenu(SubMenuBuilder subMenu) {
+ mSubMenu = subMenu;
+
+ subMenu.setHeaderTitle(getTitle());
+ }
+
+ @ViewDebug.CapturedViewProperty
+ 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;
+
+ mMenu.onItemsChanged(false);
+
+ 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;
+ }
+
+ mMenu.onItemsChanged(false);
+
+ 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;
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ public MenuItem setIcon(int iconResId) {
+ mIconDrawable = null;
+ mIconResId = iconResId;
+
+ // If we have a view, we need to push the Drawable to them
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ 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) {
+ mMenu.onItemsChanged(false);
+ }
+
+ 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) {
+ mMenu.onItemsChanged(false);
+ }
+ }
+
+ 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);
+ return oldFlags != mFlags;
+ }
+
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return mTitle.toString();
+ }
+
+ void setMenuInfo(ContextMenuInfo menuInfo) {
+ mMenuInfo = menuInfo;
+ }
+
+ public ContextMenuInfo getMenuInfo() {
+ return mMenuInfo;
+ }
+
+ public void actionFormatChanged() {
+ mMenu.onItemActionRequestChanged(this);
+ }
+
+ /**
+ * @return Whether the menu should show icons for menu items.
+ */
+ public boolean shouldShowIcon() {
+ return mMenu.getOptionalIconsVisible();
+ }
+
+ public boolean isActionButton() {
+ return (mFlags & IS_ACTION) == IS_ACTION;
+ }
+
+ public boolean requestsActionButton() {
+ return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
+ }
+
+ public boolean requiresActionButton() {
+ return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
+ }
+
+ public void setIsActionButton(boolean isActionButton) {
+ if (isActionButton) {
+ mFlags |= IS_ACTION;
+ } else {
+ mFlags &= ~IS_ACTION;
+ }
+ }
+
+ public boolean showsTextAsAction() {
+ return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
+ }
+
+ public void setShowAsAction(int actionEnum) {
+ switch (actionEnum & SHOW_AS_ACTION_MASK) {
+ case SHOW_AS_ACTION_ALWAYS:
+ case SHOW_AS_ACTION_IF_ROOM:
+ case SHOW_AS_ACTION_NEVER:
+ // Looks good!
+ break;
+
+ default:
+ // Mutually exclusive options selected!
+ throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
+ + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
+ }
+ mShowAsAction = actionEnum;
+ mMenu.onItemActionRequestChanged(this);
+ }
+
+ public MenuItem setActionView(View view) {
+ mActionView = view;
+ mActionProvider = null;
+ if (view != null && view.getId() == View.NO_ID && mId > 0) {
+ view.setId(mId);
+ }
+ mMenu.onItemActionRequestChanged(this);
+ return this;
+ }
+
+ public MenuItem setActionView(int resId) {
+ final Context context = mMenu.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ setActionView(inflater.inflate(resId, new LinearLayout(context), false));
+ return this;
+ }
+
+ public View getActionView() {
+ if (mActionView != null) {
+ return mActionView;
+ } else if (mActionProvider != null) {
+ mActionView = mActionProvider.onCreateActionView();
+ return mActionView;
+ } else {
+ return null;
+ }
+ }
+
+ public ActionProvider getActionProvider() {
+ return mActionProvider;
+ }
+
+ public MenuItem setActionProvider(ActionProvider actionProvider) {
+ mActionView = null;
+ mActionProvider = actionProvider;
+ mMenu.onItemsChanged(true); // Measurement can be changed
+ return this;
+ }
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ setShowAsAction(actionEnum);
+ return this;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
+ return false;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionExpand(this)) {
+ return mMenu.expandItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
+ return false;
+ }
+ if (mActionView == null) {
+ // We're already collapsed if we have no action view.
+ return true;
+ }
+
+ if (mOnActionExpandListener == null ||
+ mOnActionExpandListener.onMenuItemActionCollapse(this)) {
+ return mMenu.collapseItemActionView(this);
+ }
+
+ return false;
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ mOnActionExpandListener = listener;
+ return this;
+ }
+
+ public boolean hasCollapsibleActionView() {
+ return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
+ }
+
+ public void setActionViewExpanded(boolean isExpanded) {
+ mIsActionViewExpanded = isExpanded;
+ mMenu.onItemsChanged(false);
+ }
+
+ public boolean isActionViewExpanded() {
+ return mIsActionViewExpanded;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java
new file mode 100644
index 000000000..907a7aa04
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java
@@ -0,0 +1,292 @@
+package com.actionbarsherlock.internal.view.menu;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import com.actionbarsherlock.internal.view.ActionProviderWrapper;
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+public class MenuItemWrapper implements MenuItem, android.view.MenuItem.OnMenuItemClickListener {
+ private final android.view.MenuItem mNativeItem;
+ private SubMenu mSubMenu = null;
+ private OnMenuItemClickListener mMenuItemClickListener = null;
+ private OnActionExpandListener mActionExpandListener = null;
+ private android.view.MenuItem.OnActionExpandListener mNativeActionExpandListener = null;
+
+
+ public MenuItemWrapper(android.view.MenuItem nativeItem) {
+ if (nativeItem == null) {
+ throw new IllegalStateException("Wrapped menu item cannot be null.");
+ }
+ mNativeItem = nativeItem;
+ }
+
+
+ @Override
+ public int getItemId() {
+ return mNativeItem.getItemId();
+ }
+
+ @Override
+ public int getGroupId() {
+ return mNativeItem.getGroupId();
+ }
+
+ @Override
+ public int getOrder() {
+ return mNativeItem.getOrder();
+ }
+
+ @Override
+ public MenuItem setTitle(CharSequence title) {
+ mNativeItem.setTitle(title);
+ return this;
+ }
+
+ @Override
+ public MenuItem setTitle(int title) {
+ mNativeItem.setTitle(title);
+ return this;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mNativeItem.getTitle();
+ }
+
+ @Override
+ public MenuItem setTitleCondensed(CharSequence title) {
+ mNativeItem.setTitleCondensed(title);
+ return this;
+ }
+
+ @Override
+ public CharSequence getTitleCondensed() {
+ return mNativeItem.getTitleCondensed();
+ }
+
+ @Override
+ public MenuItem setIcon(Drawable icon) {
+ mNativeItem.setIcon(icon);
+ return this;
+ }
+
+ @Override
+ public MenuItem setIcon(int iconRes) {
+ mNativeItem.setIcon(iconRes);
+ return this;
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return mNativeItem.getIcon();
+ }
+
+ @Override
+ public MenuItem setIntent(Intent intent) {
+ mNativeItem.setIntent(intent);
+ return this;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mNativeItem.getIntent();
+ }
+
+ @Override
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mNativeItem.setShortcut(numericChar, alphaChar);
+ return this;
+ }
+
+ @Override
+ public MenuItem setNumericShortcut(char numericChar) {
+ mNativeItem.setNumericShortcut(numericChar);
+ return this;
+ }
+
+ @Override
+ public char getNumericShortcut() {
+ return mNativeItem.getNumericShortcut();
+ }
+
+ @Override
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ mNativeItem.setAlphabeticShortcut(alphaChar);
+ return this;
+ }
+
+ @Override
+ public char getAlphabeticShortcut() {
+ return mNativeItem.getAlphabeticShortcut();
+ }
+
+ @Override
+ public MenuItem setCheckable(boolean checkable) {
+ mNativeItem.setCheckable(checkable);
+ return this;
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return mNativeItem.isCheckable();
+ }
+
+ @Override
+ public MenuItem setChecked(boolean checked) {
+ mNativeItem.setChecked(checked);
+ return this;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mNativeItem.isChecked();
+ }
+
+ @Override
+ public MenuItem setVisible(boolean visible) {
+ mNativeItem.setVisible(visible);
+ return this;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mNativeItem.isVisible();
+ }
+
+ @Override
+ public MenuItem setEnabled(boolean enabled) {
+ mNativeItem.setEnabled(enabled);
+ return this;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mNativeItem.isEnabled();
+ }
+
+ @Override
+ public boolean hasSubMenu() {
+ return mNativeItem.hasSubMenu();
+ }
+
+ @Override
+ public SubMenu getSubMenu() {
+ if (hasSubMenu() && (mSubMenu == null)) {
+ mSubMenu = new SubMenuWrapper(mNativeItem.getSubMenu());
+ }
+ return mSubMenu;
+ }
+
+ @Override
+ public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+ mMenuItemClickListener = menuItemClickListener;
+ //Register ourselves as the listener to proxy
+ mNativeItem.setOnMenuItemClickListener(this);
+ return this;
+ }
+
+ @Override
+ public boolean onMenuItemClick(android.view.MenuItem item) {
+ if (mMenuItemClickListener != null) {
+ return mMenuItemClickListener.onMenuItemClick(this);
+ }
+ return false;
+ }
+
+ @Override
+ public ContextMenuInfo getMenuInfo() {
+ return mNativeItem.getMenuInfo();
+ }
+
+ @Override
+ public void setShowAsAction(int actionEnum) {
+ mNativeItem.setShowAsAction(actionEnum);
+ }
+
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ mNativeItem.setShowAsActionFlags(actionEnum);
+ return this;
+ }
+
+ @Override
+ public MenuItem setActionView(View view) {
+ mNativeItem.setActionView(view);
+ return this;
+ }
+
+ @Override
+ public MenuItem setActionView(int resId) {
+ mNativeItem.setActionView(resId);
+ return this;
+ }
+
+ @Override
+ public View getActionView() {
+ return mNativeItem.getActionView();
+ }
+
+ @Override
+ public MenuItem setActionProvider(ActionProvider actionProvider) {
+ mNativeItem.setActionProvider(new ActionProviderWrapper(actionProvider));
+ return this;
+ }
+
+ @Override
+ public ActionProvider getActionProvider() {
+ android.view.ActionProvider nativeProvider = mNativeItem.getActionProvider();
+ if (nativeProvider != null && nativeProvider instanceof ActionProviderWrapper) {
+ return ((ActionProviderWrapper)nativeProvider).unwrap();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return mNativeItem.expandActionView();
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ return mNativeItem.collapseActionView();
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return mNativeItem.isActionViewExpanded();
+ }
+
+ @Override
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
+ mActionExpandListener = listener;
+
+ if (mNativeActionExpandListener == null) {
+ mNativeActionExpandListener = new android.view.MenuItem.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(android.view.MenuItem menuItem) {
+ if (mActionExpandListener != null) {
+ return mActionExpandListener.onMenuItemActionExpand(MenuItemWrapper.this);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(android.view.MenuItem menuItem) {
+ if (mActionExpandListener != null) {
+ return mActionExpandListener.onMenuItemActionCollapse(MenuItemWrapper.this);
+ }
+ return false;
+ }
+ };
+
+ //Register our inner-class as the listener to proxy method calls
+ mNativeItem.setOnActionExpandListener(mNativeActionExpandListener);
+ }
+
+ return this;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java
new file mode 100644
index 000000000..f030de310
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPopupHelper.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2010 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.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.os.Parcelable;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
+import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
+import com.actionbarsherlock.internal.widget.IcsListPopupWindow;
+import com.actionbarsherlock.view.MenuItem;
+
+/**
+ * Presents a menu as a small, simple popup anchored to another view.
+ * @hide
+ */
+public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
+ ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
+ View_OnAttachStateChangeListener, MenuPresenter {
+ //UNUSED private static final String TAG = "MenuPopupHelper";
+
+ static final int ITEM_LAYOUT = R.layout.abs__popup_menu_item_layout;
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private IcsListPopupWindow mPopup;
+ private MenuBuilder mMenu;
+ private int mPopupMaxWidth;
+ private View mAnchorView;
+ private boolean mOverflowOnly;
+ private ViewTreeObserver mTreeObserver;
+
+ private MenuAdapter mAdapter;
+
+ private Callback mPresenterCallback;
+
+ boolean mForceShowIcon;
+
+ private ViewGroup mMeasureParent;
+
+ public MenuPopupHelper(Context context, MenuBuilder menu) {
+ this(context, menu, null, false);
+ }
+
+ public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+ this(context, menu, anchorView, false);
+ }
+
+ public MenuPopupHelper(Context context, MenuBuilder menu,
+ View anchorView, boolean overflowOnly) {
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ mMenu = menu;
+ mOverflowOnly = overflowOnly;
+
+ final Resources res = context.getResources();
+ mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+ res.getDimensionPixelSize(R.dimen.abs__config_prefDialogWidth));
+
+ mAnchorView = anchorView;
+
+ menu.addMenuPresenter(this);
+ }
+
+ public void setAnchorView(View anchor) {
+ mAnchorView = anchor;
+ }
+
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ public void show() {
+ if (!tryShow()) {
+ throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+ }
+ }
+
+ public boolean tryShow() {
+ mPopup = new IcsListPopupWindow(mContext, null, R.attr.popupMenuStyle);
+ mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
+
+ mAdapter = new MenuAdapter(mMenu);
+ mPopup.setAdapter(mAdapter);
+ mPopup.setModal(true);
+
+ View anchor = mAnchorView;
+ if (anchor != null) {
+ final boolean addGlobalListener = mTreeObserver == null;
+ mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
+ if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
+ ((View_HasStateListenerSupport)anchor).addOnAttachStateChangeListener(this);
+ mPopup.setAnchorView(anchor);
+ } else {
+ return false;
+ }
+
+ mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mPopup.show();
+ mPopup.getListView().setOnKeyListener(this);
+ return true;
+ }
+
+ public void dismiss() {
+ if (isShowing()) {
+ mPopup.dismiss();
+ }
+ }
+
+ public void onDismiss() {
+ mPopup = null;
+ mMenu.close();
+ if (mTreeObserver != null) {
+ if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
+ mTreeObserver.removeGlobalOnLayoutListener(this);
+ mTreeObserver = null;
+ }
+ ((View_HasStateListenerSupport)mAnchorView).removeOnAttachStateChangeListener(this);
+ }
+
+ public boolean isShowing() {
+ return mPopup != null && mPopup.isShowing();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MenuAdapter adapter = mAdapter;
+ adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
+ }
+
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ private int measureContentWidth(ListAdapter adapter) {
+ // Menus don't tend to be long, so this is more sane than it looks.
+ int width = 0;
+ View itemView = null;
+ int itemType = 0;
+ final int widthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+ if (mMeasureParent == null) {
+ mMeasureParent = new FrameLayout(mContext);
+ }
+ itemView = adapter.getView(i, itemView, mMeasureParent);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+ width = Math.max(width, itemView.getMeasuredWidth());
+ }
+ return width;
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ if (isShowing()) {
+ final View anchor = mAnchorView;
+ if (anchor == null || !anchor.isShown()) {
+ dismiss();
+ } else if (isShowing()) {
+ // Recompute window size and position
+ mPopup.show();
+ }
+ }
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ if (mTreeObserver != null) {
+ if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
+ mTreeObserver.removeGlobalOnLayoutListener(this);
+ }
+ ((View_HasStateListenerSupport)v).removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+ subPopup.setCallback(mPresenterCallback);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+ subPopup.setForceShowIcon(preserveIconSpacing);
+
+ if (subPopup.tryShow()) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ private MenuBuilder mAdapterMenu;
+ private int mExpandedIndex = -1;
+
+ public MenuAdapter(MenuBuilder menu) {
+ mAdapterMenu = menu;
+ registerDataSetObserver(new ExpandedIndexObserver());
+ findExpandedIndex();
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ if (mForceShowIcon) {
+ ((ListMenuItemView) convertView).setForceShowIcon(true);
+ }
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ mExpandedIndex = -1;
+ }
+ }
+
+ private class ExpandedIndexObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ mAdapter.findExpandedIndex();
+ }
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
new file mode 100644
index 000000000..c3f35472c
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+
+ /**
+ * Called when a menu item with a collapsable action view should expand its action view.
+ *
+ * @param menu Menu containing the item to be expanded
+ * @param item Item to be expanded
+ * @return true if this presenter expanded the action view, false otherwise.
+ */
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Called when a menu item with a collapsable action view should collapse its action view.
+ *
+ * @param menu Menu containing the item to be collapsed
+ * @param item Item to be collapsed
+ * @return true if this presenter collapsed the action view, false otherwise.
+ */
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Returns an ID for determining how to save/restore instance state.
+ * @return a valid ID value.
+ */
+ public int getId();
+
+ /**
+ * Returns a Parcelable describing the current state of the presenter.
+ * It will be passed to the {@link #onRestoreInstanceState(Parcelable)}
+ * method of the presenter sharing the same ID later.
+ * @return The saved instance state
+ */
+ public Parcelable onSaveInstanceState();
+
+ /**
+ * Supplies the previously saved instance state to be restored.
+ * @param state The previously saved instance state
+ */
+ public void onRestoreInstanceState(Parcelable state);
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java
new file mode 100644
index 000000000..323ba2d88
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuView.java
@@ -0,0 +1,120 @@
+/*
+ * 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.actionbarsherlock.internal.view.menu;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
+ * menu to be functional.
+ *
+ * @hide
+ */
+public interface MenuView {
+ /**
+ * Initializes the menu to the given menu. This should be called after the
+ * view is inflated.
+ *
+ * @param menu The menu that this MenuView should display.
+ */
+ public void initialize(MenuBuilder menu);
+
+ /**
+ * Returns the default animations to be used for this menu when entering/exiting.
+ * @return A resource ID for the default animations to be used for this menu.
+ */
+ public int getWindowAnimations();
+
+ /**
+ * Minimal interface for a menu item view. {@link #initialize(MenuItemImpl, int)} must be called
+ * for the item to be functional.
+ */
+ public interface ItemView {
+ /**
+ * Initializes with the provided MenuItemData. This should be called after the view is
+ * inflated.
+ * @param itemData The item that this ItemView should display.
+ * @param menuType The type of this menu, one of
+ * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+ * {@link MenuBuilder#TYPE_DIALOG}).
+ */
+ public void initialize(MenuItemImpl itemData, int menuType);
+
+ /**
+ * Gets the item data that this view is displaying.
+ * @return the item data, or null if there is not one
+ */
+ public MenuItemImpl getItemData();
+
+ /**
+ * Sets the title of the item view.
+ * @param title The title to set.
+ */
+ public void setTitle(CharSequence title);
+
+ /**
+ * Sets the enabled state of the item view.
+ * @param enabled Whether the item view should be enabled.
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * Displays the checkbox for the item view. This does not ensure the item view will be
+ * checked, for that use {@link #setChecked}.
+ * @param checkable Whether to display the checkbox or to hide it
+ */
+ public void setCheckable(boolean checkable);
+
+ /**
+ * Checks the checkbox for the item view. If the checkbox is hidden, it will NOT be
+ * made visible, call {@link #setCheckable(boolean)} for that.
+ * @param checked Whether the checkbox should be checked
+ */
+ public void setChecked(boolean checked);
+
+ /**
+ * Sets the shortcut for the item.
+ * @param showShortcut Whether a shortcut should be shown(if false, the value of
+ * shortcutKey should be ignored).
+ * @param shortcutKey The shortcut key that should be shown on the ItemView.
+ */
+ public void setShortcut(boolean showShortcut, char shortcutKey);
+
+ /**
+ * Set the icon of this item view.
+ * @param icon The icon of this item. null to hide the icon.
+ */
+ public void setIcon(Drawable icon);
+
+ /**
+ * Whether this item view prefers displaying the condensed title rather
+ * than the normal title. If a condensed title is not available, the
+ * normal title will be used.
+ *
+ * @return Whether this item view prefers displaying the condensed
+ * title.
+ */
+ public boolean prefersCondensedTitle();
+
+ /**
+ * Whether this item view shows an icon.
+ *
+ * @return Whether this item view shows an icon.
+ */
+ public boolean showsIcon();
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java
new file mode 100644
index 000000000..64fc4aeaa
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java
@@ -0,0 +1,180 @@
+package com.actionbarsherlock.internal.view.menu;
+
+import java.util.WeakHashMap;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.view.KeyEvent;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+public class MenuWrapper implements Menu {
+ private final android.view.Menu mNativeMenu;
+
+ private final WeakHashMap<android.view.MenuItem, MenuItem> mNativeMap =
+ new WeakHashMap<android.view.MenuItem, MenuItem>();
+
+
+ public MenuWrapper(android.view.Menu nativeMenu) {
+ mNativeMenu = nativeMenu;
+ }
+
+ public android.view.Menu unwrap() {
+ return mNativeMenu;
+ }
+
+ private MenuItem addInternal(android.view.MenuItem nativeItem) {
+ MenuItem item = new MenuItemWrapper(nativeItem);
+ mNativeMap.put(nativeItem, item);
+ return item;
+ }
+
+ @Override
+ public MenuItem add(CharSequence title) {
+ return addInternal(mNativeMenu.add(title));
+ }
+
+ @Override
+ public MenuItem add(int titleRes) {
+ return addInternal(mNativeMenu.add(titleRes));
+ }
+
+ @Override
+ public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
+ return addInternal(mNativeMenu.add(groupId, itemId, order, title));
+ }
+
+ @Override
+ public MenuItem add(int groupId, int itemId, int order, int titleRes) {
+ return addInternal(mNativeMenu.add(groupId, itemId, order, titleRes));
+ }
+
+ private SubMenu addInternal(android.view.SubMenu nativeSubMenu) {
+ SubMenu subMenu = new SubMenuWrapper(nativeSubMenu);
+ android.view.MenuItem nativeItem = nativeSubMenu.getItem();
+ MenuItem item = subMenu.getItem();
+ mNativeMap.put(nativeItem, item);
+ return subMenu;
+ }
+
+ @Override
+ public SubMenu addSubMenu(CharSequence title) {
+ return addInternal(mNativeMenu.addSubMenu(title));
+ }
+
+ @Override
+ public SubMenu addSubMenu(int titleRes) {
+ return addInternal(mNativeMenu.addSubMenu(titleRes));
+ }
+
+ @Override
+ public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
+ return addInternal(mNativeMenu.addSubMenu(groupId, itemId, order, title));
+ }
+
+ @Override
+ public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
+ return addInternal(mNativeMenu.addSubMenu(groupId, itemId, order, titleRes));
+ }
+
+ @Override
+ public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
+ android.view.MenuItem[] nativeOutItems = new android.view.MenuItem[outSpecificItems.length];
+ int result = mNativeMenu.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, nativeOutItems);
+ for (int i = 0, length = outSpecificItems.length; i < length; i++) {
+ outSpecificItems[i] = new MenuItemWrapper(nativeOutItems[i]);
+ }
+ return result;
+ }
+
+ @Override
+ public void removeItem(int id) {
+ mNativeMenu.removeItem(id);
+ }
+
+ @Override
+ public void removeGroup(int groupId) {
+ mNativeMenu.removeGroup(groupId);
+ }
+
+ @Override
+ public void clear() {
+ mNativeMap.clear();
+ mNativeMenu.clear();
+ }
+
+ @Override
+ public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
+ mNativeMenu.setGroupCheckable(group, checkable, exclusive);
+ }
+
+ @Override
+ public void setGroupVisible(int group, boolean visible) {
+ mNativeMenu.setGroupVisible(group, visible);
+ }
+
+ @Override
+ public void setGroupEnabled(int group, boolean enabled) {
+ mNativeMenu.setGroupEnabled(group, enabled);
+ }
+
+ @Override
+ public boolean hasVisibleItems() {
+ return mNativeMenu.hasVisibleItems();
+ }
+
+ @Override
+ public MenuItem findItem(int id) {
+ android.view.MenuItem nativeItem = mNativeMenu.findItem(id);
+ return findItem(nativeItem);
+ }
+
+ public MenuItem findItem(android.view.MenuItem nativeItem) {
+ if (nativeItem == null) {
+ return null;
+ }
+
+ MenuItem wrapped = mNativeMap.get(nativeItem);
+ if (wrapped != null) {
+ return wrapped;
+ }
+
+ return addInternal(nativeItem);
+ }
+
+ @Override
+ public int size() {
+ return mNativeMenu.size();
+ }
+
+ @Override
+ public MenuItem getItem(int index) {
+ android.view.MenuItem nativeItem = mNativeMenu.getItem(index);
+ return findItem(nativeItem);
+ }
+
+ @Override
+ public void close() {
+ mNativeMenu.close();
+ }
+
+ @Override
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ return mNativeMenu.performShortcut(keyCode, event, flags);
+ }
+
+ @Override
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return mNativeMenu.isShortcutKey(keyCode, event);
+ }
+
+ @Override
+ public boolean performIdentifierAction(int id, int flags) {
+ return mNativeMenu.performIdentifierAction(id, flags);
+ }
+
+ @Override
+ public void setQwertyMode(boolean isQwerty) {
+ mNativeMenu.setQwertyMode(isQwerty);
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java
new file mode 100644
index 000000000..6679cf386
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * 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.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+/**
+ * The model for a sub menu, which is an extension of the menu. Most methods are proxied to
+ * the parent menu.
+ */
+public class SubMenuBuilder extends MenuBuilder implements SubMenu {
+ private MenuBuilder mParentMenu;
+ private MenuItemImpl mItem;
+
+ public SubMenuBuilder(Context context, MenuBuilder parentMenu, MenuItemImpl item) {
+ super(context);
+
+ mParentMenu = parentMenu;
+ mItem = item;
+ }
+
+ @Override
+ public void setQwertyMode(boolean isQwerty) {
+ mParentMenu.setQwertyMode(isQwerty);
+ }
+
+ @Override
+ public boolean isQwertyMode() {
+ return mParentMenu.isQwertyMode();
+ }
+
+ @Override
+ public void setShortcutsVisible(boolean shortcutsVisible) {
+ mParentMenu.setShortcutsVisible(shortcutsVisible);
+ }
+
+ @Override
+ public boolean isShortcutsVisible() {
+ return mParentMenu.isShortcutsVisible();
+ }
+
+ public Menu getParentMenu() {
+ return mParentMenu;
+ }
+
+ public MenuItem getItem() {
+ return mItem;
+ }
+
+ @Override
+ public void setCallback(Callback callback) {
+ mParentMenu.setCallback(callback);
+ }
+
+ @Override
+ public MenuBuilder getRootMenu() {
+ return mParentMenu;
+ }
+
+ @Override
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return super.dispatchMenuItemSelected(menu, item) ||
+ mParentMenu.dispatchMenuItemSelected(menu, item);
+ }
+
+ public SubMenu setIcon(Drawable icon) {
+ mItem.setIcon(icon);
+ return this;
+ }
+
+ public SubMenu setIcon(int iconRes) {
+ mItem.setIcon(iconRes);
+ return this;
+ }
+
+ public SubMenu setHeaderIcon(Drawable icon) {
+ return (SubMenu) super.setHeaderIconInt(icon);
+ }
+
+ public SubMenu setHeaderIcon(int iconRes) {
+ return (SubMenu) super.setHeaderIconInt(iconRes);
+ }
+
+ public SubMenu setHeaderTitle(CharSequence title) {
+ return (SubMenu) super.setHeaderTitleInt(title);
+ }
+
+ public SubMenu setHeaderTitle(int titleRes) {
+ return (SubMenu) super.setHeaderTitleInt(titleRes);
+ }
+
+ public SubMenu setHeaderView(View view) {
+ return (SubMenu) super.setHeaderViewInt(view);
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuItemImpl item) {
+ return mParentMenu.expandItemActionView(item);
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ return mParentMenu.collapseItemActionView(item);
+ }
+
+ @Override
+ public String getActionViewStatesKey() {
+ final int itemId = mItem != null ? mItem.getItemId() : 0;
+ if (itemId == 0) {
+ return null;
+ }
+ return super.getActionViewStatesKey() + ":" + itemId;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java
new file mode 100644
index 000000000..7d307acb1
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/SubMenuWrapper.java
@@ -0,0 +1,72 @@
+package com.actionbarsherlock.internal.view.menu;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.SubMenu;
+
+public class SubMenuWrapper extends MenuWrapper implements SubMenu {
+ private final android.view.SubMenu mNativeSubMenu;
+ private MenuItem mItem = null;
+
+ public SubMenuWrapper(android.view.SubMenu nativeSubMenu) {
+ super(nativeSubMenu);
+ mNativeSubMenu = nativeSubMenu;
+ }
+
+
+ @Override
+ public SubMenu setHeaderTitle(int titleRes) {
+ mNativeSubMenu.setHeaderTitle(titleRes);
+ return this;
+ }
+
+ @Override
+ public SubMenu setHeaderTitle(CharSequence title) {
+ mNativeSubMenu.setHeaderTitle(title);
+ return this;
+ }
+
+ @Override
+ public SubMenu setHeaderIcon(int iconRes) {
+ mNativeSubMenu.setHeaderIcon(iconRes);
+ return this;
+ }
+
+ @Override
+ public SubMenu setHeaderIcon(Drawable icon) {
+ mNativeSubMenu.setHeaderIcon(icon);
+ return this;
+ }
+
+ @Override
+ public SubMenu setHeaderView(View view) {
+ mNativeSubMenu.setHeaderView(view);
+ return this;
+ }
+
+ @Override
+ public void clearHeader() {
+ mNativeSubMenu.clearHeader();
+ }
+
+ @Override
+ public SubMenu setIcon(int iconRes) {
+ mNativeSubMenu.setIcon(iconRes);
+ return this;
+ }
+
+ @Override
+ public SubMenu setIcon(Drawable icon) {
+ mNativeSubMenu.setIcon(icon);
+ return this;
+ }
+
+ @Override
+ public MenuItem getItem() {
+ if (mItem == null) {
+ mItem = new MenuItemWrapper(mNativeSubMenu.getItem());
+ }
+ return mItem;
+ }
+}