@Override public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { mExpandedActionView = item.getActionView(); mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(/* TODO getResources() */ )); mCurrentExpandedItem = item; if (mExpandedActionView.getParent() != ActionBarView.this) { addView(mExpandedActionView); } if (mExpandedHomeLayout.getParent() != ActionBarView.this) { addView(mExpandedHomeLayout); } mHomeLayout.setVisibility(GONE); if (mTitleLayout != null) mTitleLayout.setVisibility(GONE); if (mTabScrollView != null) mTabScrollView.setVisibility(GONE); if (mSpinner != null) mSpinner.setVisibility(GONE); if (mCustomNavView != null) mCustomNavView.setVisibility(GONE); requestLayout(); item.setActionViewExpanded(true); if (mExpandedActionView instanceof CollapsibleActionView) { ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); } return true; }
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; }
@Override public void onClick(View v) { final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem; if (item != null) { item.collapseActionView(); } }
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 void collapseActionView() { final MenuItemImpl item = mExpandedMenuPresenter == null ? null : mExpandedMenuPresenter.mCurrentExpandedItem; if (item != null) { item.collapseActionView(); } }
private void setItem(MenuItem item) { item.setChecked(itemChecked) .setVisible(itemVisible) .setEnabled(itemEnabled) .setCheckable(itemCheckable >= 1) .setTitleCondensed(itemTitleCondensed) .setIcon(itemIconResId) .setAlphabeticShortcut(itemAlphabeticShortcut) .setNumericShortcut(itemNumericShortcut); if (itemShowAsAction >= 0) { item.setShowAsAction(itemShowAsAction); } if (itemListenerMethodName != null) { if (mContext.isRestricted()) { throw new IllegalStateException( "The android:onClick attribute cannot " + "be used within a restricted context"); } item.setOnMenuItemClickListener( new InflatedOnMenuItemClickListener(mRealOwner, itemListenerMethodName)); } if (itemCheckable >= 2) { if (item instanceof MenuItemImpl) { MenuItemImpl impl = (MenuItemImpl) item; impl.setExclusiveCheckable(true); } else { menu.setGroupCheckable(groupId, true, true); } } boolean actionViewSpecified = false; if (itemActionViewClassName != null) { View actionView = (View) newInstance( itemActionViewClassName, ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments); item.setActionView(actionView); actionViewSpecified = true; } if (itemActionViewLayout > 0) { if (!actionViewSpecified) { item.setActionView(itemActionViewLayout); actionViewSpecified = true; } else { Log.w( LOG_TAG, "Ignoring attribute 'itemActionViewLayout'." + " Action view already specified."); } } if (itemActionProvider != null) { item.setActionProvider(itemActionProvider); } }
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); } } }
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 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 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 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; }
@Override public boolean onMenuItemClick(android.view.MenuItem item) { if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item); final MenuItemImpl sherlockItem = mNativeItemMap.get(item); if (sherlockItem != null) { sherlockItem.invoke(); } else { Log.e(TAG, "Options item \"" + item + "\" not found in mapping"); } return true; // Do not allow continuation of native handling }
@Override public void setShortcut(boolean showShortcut, char shortcutKey) { final int newVisibility = showShortcut && mItemData.shouldShowShortcut() ? View.VISIBLE : View.GONE; if (newVisibility == View.VISIBLE) { mShortcutView.setText(mItemData.getShortcutLabel()); } if (mShortcutView.getVisibility() != newVisibility) { mShortcutView.setVisibility(newVisibility); } }
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); } } }
@Override 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() != View.VISIBLE) { mIconView.setVisibility(View.VISIBLE); } } else { mIconView.setVisibility(View.GONE); } }
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); }
/** 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; }
@Override public void setCheckable(boolean checkable) { if (!checkable && mRadioButton == null && mCheckBox == null) { return; } // 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()) { if (mRadioButton == null) { insertRadioButton(); } compoundButton = mRadioButton; otherCompoundButton = mCheckBox; } else { if (mCheckBox == null) { insertCheckBox(); } compoundButton = mCheckBox; otherCompoundButton = mRadioButton; } if (checkable) { compoundButton.setChecked(mItemData.isChecked()); final int newVisibility = checkable ? View.VISIBLE : View.GONE; if (compoundButton.getVisibility() != newVisibility) { compoundButton.setVisibility(newVisibility); } // Make sure the other compound button isn't visible if (otherCompoundButton != null && otherCompoundButton.getVisibility() != View.GONE) { otherCompoundButton.setVisibility(View.GONE); } } else { if (mCheckBox != null) { mCheckBox.setVisibility(View.GONE); } if (mRadioButton != null) { mRadioButton.setVisibility(View.GONE); } } }
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 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; }
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; }
@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 initialize(MenuItemImpl itemData, int menuType) { mItemData = itemData; 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()); }
/* * 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); } } }
/** * 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; }
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); }
/* * 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; }
@Override 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); }
@Override public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { // Do this before detaching the actionview from the hierarchy, in case // it needs to dismiss the soft keyboard, etc. if (mExpandedActionView instanceof CollapsibleActionView) { ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed(); } removeView(mExpandedActionView); removeView(mExpandedHomeLayout); mExpandedActionView = null; if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) { mHomeLayout.setVisibility(VISIBLE); } if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) { if (mTitleLayout == null) { initTitle(); } else { mTitleLayout.setVisibility(VISIBLE); } } if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { mTabScrollView.setVisibility(VISIBLE); } if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) { mSpinner.setVisibility(VISIBLE); } if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { mCustomNavView.setVisibility(VISIBLE); } mExpandedHomeLayout.setIcon(null); mCurrentExpandedItem = null; requestLayout(); item.setActionViewExpanded(false); return true; }
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; }