/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.actionbarsherlock.internal.view.menu;
import java.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.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
import android.view.KeyEvent;
/**
* An implementation of the {@link android.view.Menu} interface for use in
* inflating menu XML resources to be added to a third-party action bar.
*
* @author Jake Wharton <jakewharton@gmail.com>
* @see <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/com/android/internal/view/menu/MenuBuilder.java">com.android.internal.view.menu.MenuBuilder</a>
*/
public class MenuBuilder implements Menu {
private static final int DEFAULT_ITEM_ID = 0;
private static final int DEFAULT_GROUP_ID = 0;
private static final int DEFAULT_ORDER = 0;
public static final int NUM_TYPES = 2;
public static final int TYPE_ACTION_BAR = 0;
public static final int TYPE_NATIVE = 1;
/**
* This is the part of an order integer that the user can provide.
* @hide
*/
static final int USER_MASK = 0x0000ffff;
/**
* Bit shift of the user portion of the order integer.
* @hide
*/
static final int USER_SHIFT = 0;
/**
* This is the part of an order integer that supplies the category of the
* item.
* @hide
*/
static final int CATEGORY_MASK = 0xffff0000;
/**
* Bit shift of the category portion of the order integer.
* @hide
*/
static final int CATEGORY_SHIFT = 16;
private static final int[] CATEGORY_TO_ORDER = new int[] {
1, /* No category */
4, /* CONTAINER */
5, /* SYSTEM */
3, /* SECONDARY */
2, /* ALTERNATIVE */
0, /* SELECTED_ALTERNATIVE */
};
public interface Callback {
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
}
/** Context used for resolving any resources. */
private final Context mContext;
/** Child {@link ActionBarMenuItem} items. */
private final ArrayList<MenuItemImpl> mItems;
/** Menu callback that will receive various events. */
private Callback mCallback;
private boolean mShowsActionItemText;
/**
* Create a new action bar menu.
*
* @param context Context used if resource resolution is required.
*/
public MenuBuilder(Context context) {
this.mContext = context;
this.mItems = new ArrayList<MenuItemImpl>();
}
/**
* Adds an item to the menu. The other add methods funnel to this.
*
* @param itemId Unique item ID.
* @param groupId Group ID.
* @param order Order.
* @param title Item title.
* @return MenuItem instance.
*/
private MenuItem addInternal(int itemId, int groupId, int order, CharSequence title) {
final int ordering = getOrdering(order);
final MenuItemImpl item = new MenuItemImpl(this, groupId, itemId, order, ordering, title, MenuItem.SHOW_AS_ACTION_NEVER);
mItems.add(findInsertIndex(mItems, ordering), item);
return item;
}
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;
}
/**
* 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 >= CATEGORY_TO_ORDER.length) {
throw new IllegalArgumentException("order does not contain a valid category.");
}
return (CATEGORY_TO_ORDER[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
}
public void setCallback(Callback callback) {
mCallback = callback;
}
public Callback getCallback() {
return mCallback;
}
public boolean getShowsActionItemText() {
return mShowsActionItemText;
}
public void setShowsActionItemText(boolean showsActionItemText) {
mShowsActionItemText = showsActionItemText;
}
/**
* Gets the root menu (if this is a submenu, find its root menu).
*
* @return The root menu.
*/
public MenuBuilder getRootMenu() {
return this;
}
/**
* Get a list of the items contained in this menu.
*
* @return List of {@link MenuItemImpl}s.
*/
public final List<MenuItemImpl> getItems() {
return this.mItems;
}
final MenuItemImpl remove(int index) {
return this.mItems.remove(index);
}
final Context getContext() {
return this.mContext;
}
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);
}
}
}
// ** Menu Methods ** \\
@Override
public MenuItem add(int titleResourceId) {
return addInternal(0, 0, 0, mContext.getResources().getString(titleResourceId));
}
@Override
public MenuItem add(int groupId, int itemId, int order, int titleResourceId) {
return addInternal(itemId, groupId, order, mContext.getResources().getString(titleResourceId));
}
@Override
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
return addInternal(itemId, groupId, order, title);
}
@Override
public MenuItem add(CharSequence title) {
return addInternal(0, 0, 0, title);
}
@Override
public SubMenuBuilder addSubMenu(CharSequence title) {
return this.addSubMenu(DEFAULT_GROUP_ID, DEFAULT_ITEM_ID, DEFAULT_ORDER, title);
}
@Override
public SubMenuBuilder addSubMenu(int titleResourceId) {
return this.addSubMenu(DEFAULT_GROUP_ID, DEFAULT_ITEM_ID, DEFAULT_ORDER, titleResourceId);
}
@Override
public SubMenuBuilder addSubMenu(int groupId, int itemId, int order, int titleResourceId) {
String title = this.mContext.getResources().getString(titleResourceId);
return this.addSubMenu(groupId, itemId, order, title);
}
@Override
public SubMenuBuilder addSubMenu(int groupId, int itemId, int order, CharSequence title) {
MenuItemImpl item = (MenuItemImpl)this.add(groupId, itemId, order, title);
SubMenuBuilder subMenu = new SubMenuBuilder(this.mContext, this, item);
item.setSubMenu(subMenu);
return subMenu;
}
@Override
public void clear() {
this.mItems.clear();
}
@Override
public void close() {}
@Override
public MenuItemImpl findItem(int itemId) {
for (MenuItemImpl item : this.mItems) {
if (item.getItemId() == itemId) {
return item;
}
}
return null;
}
@Override
public MenuItemImpl getItem(int index) {
return this.mItems.get(index);
}
@Override
public boolean hasVisibleItems() {
for (MenuItem item : this.mItems) {
if (item.isVisible()) {
return true;
}
}
return false;
}
@Override
public void removeItem(int itemId) {
final int size = this.mItems.size();
for (int i = 0; i < size; i++) {
if (this.mItems.get(i).getItemId() == itemId) {
this.mItems.remove(i);
return;
}
}
}
@Override
public int size() {
return this.mItems.size();
}
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, android.view.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;
}
@Override
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean performIdentifierAction(int id, int flags) {
throw new RuntimeException("Method not supported.");
}
@Override
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
return false;
}
@Override
public void removeGroup(int groupId) {
for (int i = mItems.size() - 1; i > 0; i--) {
if (mItems.get(i).getGroupId() == groupId) {
mItems.remove(i);
}
}
}
@Override
public void setGroupCheckable(int groupId, boolean checkable, boolean exclusive) {
final int N = mItems.size();
for (int i = 0; i < N; i++) {
MenuItemImpl item = mItems.get(i);
if (item.getGroupId() == groupId) {
item.setExclusiveCheckable(exclusive);
item.setCheckable(checkable);
}
}
}
@Override
public void setGroupEnabled(int groupId, boolean enabled) {
final int size = this.mItems.size();
for (int i = 0; i < size; i++) {
MenuItemImpl item = mItems.get(i);
if (item.getGroupId() == groupId) {
item.setEnabled(enabled);
}
}
}
@Override
public void setGroupVisible(int groupId, boolean visible) {
final int size = this.mItems.size();
for (int i = 0; i < size; i++) {
MenuItemImpl item = mItems.get(i);
if (item.getGroupId() == groupId) {
item.setVisible(visible);
}
}
}
@Override
public void setQwertyMode(boolean isQwerty) {
throw new RuntimeException("Method not supported.");
}
}