blob: 14d0ac5838f712d95be4212a9d3e50c02ca05548 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.view.menu;
18
19
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.os.Parcelable;
30import android.util.SparseArray;
Adam Powell36fced92011-01-16 15:48:07 -080031import android.util.SparseBooleanArray;
Adam Powell2fbf4de62010-09-30 15:46:46 -070032import android.util.TypedValue;
Adam Powell36fced92011-01-16 15:48:07 -080033import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.view.ContextThemeWrapper;
35import android.view.KeyCharacterMap;
36import android.view.KeyEvent;
Adam Powell96675b12010-06-10 18:58:59 -070037import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.view.Menu;
39import android.view.MenuItem;
40import android.view.SubMenu;
41import android.view.View;
Adam Powell36fced92011-01-16 15:48:07 -080042import android.view.View.MeasureSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.ViewGroup;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.widget.AdapterView;
45import android.widget.BaseAdapter;
46
47import java.lang.ref.WeakReference;
48import java.util.ArrayList;
49import java.util.List;
Nicolas Roarde0fc8382009-09-24 21:30:06 +010050import java.util.Vector;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
52/**
53 * Implementation of the {@link android.view.Menu} interface for creating a
54 * standard menu UI.
55 */
56public class MenuBuilder implements Menu {
57 private static final String LOGTAG = "MenuBuilder";
58
59 /** The number of different menu types */
Adam Powell42675342010-07-09 18:02:59 -070060 public static final int NUM_TYPES = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 /** The menu type that represents the icon menu view */
62 public static final int TYPE_ICON = 0;
63 /** The menu type that represents the expanded menu view */
64 public static final int TYPE_EXPANDED = 1;
65 /**
66 * The menu type that represents a menu dialog. Examples are context and sub
67 * menus. This menu type will not have a corresponding MenuView, but it will
68 * have an ItemView.
69 */
70 public static final int TYPE_DIALOG = 2;
Adam Powell96675b12010-06-10 18:58:59 -070071 /**
72 * The menu type that represents a button in the application's action bar.
73 */
74 public static final int TYPE_ACTION_BUTTON = 3;
Adam Powell42675342010-07-09 18:02:59 -070075 /**
76 * The menu type that represents a menu popup.
77 */
78 public static final int TYPE_POPUP = 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
80 private static final String VIEWS_TAG = "android:views";
Adam Powell42675342010-07-09 18:02:59 -070081
Adam Powell2fbf4de62010-09-30 15:46:46 -070082 private static final int THEME_SYSTEM_DEFAULT = 0;
83 private static final int THEME_APPLICATION = -1;
84 private static final int THEME_ALERT_DIALOG = -2;
85
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 // Order must be the same order as the TYPE_*
87 static final int THEME_RES_FOR_TYPE[] = new int[] {
88 com.android.internal.R.style.Theme_IconMenu,
89 com.android.internal.R.style.Theme_ExpandedMenu,
Adam Powell2fbf4de62010-09-30 15:46:46 -070090 THEME_ALERT_DIALOG,
91 THEME_APPLICATION,
92 THEME_APPLICATION,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 };
94
95 // Order must be the same order as the TYPE_*
96 static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
97 com.android.internal.R.layout.icon_menu_layout,
98 com.android.internal.R.layout.expanded_menu_layout,
99 0,
Adam Powell96675b12010-06-10 18:58:59 -0700100 com.android.internal.R.layout.action_menu_layout,
Adam Powell42675342010-07-09 18:02:59 -0700101 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 };
103
104 // Order must be the same order as the TYPE_*
105 static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
106 com.android.internal.R.layout.icon_menu_item_layout,
107 com.android.internal.R.layout.list_menu_item_layout,
108 com.android.internal.R.layout.list_menu_item_layout,
Adam Powell96675b12010-06-10 18:58:59 -0700109 com.android.internal.R.layout.action_menu_item_layout,
Adam Powell0b2d3062010-09-14 16:15:02 -0700110 com.android.internal.R.layout.popup_menu_item_layout,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 };
112
113 private static final int[] sCategoryToOrder = new int[] {
114 1, /* No category */
115 4, /* CONTAINER */
116 5, /* SYSTEM */
117 3, /* SECONDARY */
118 2, /* ALTERNATIVE */
119 0, /* SELECTED_ALTERNATIVE */
120 };
121
122 private final Context mContext;
123 private final Resources mResources;
124
125 /**
126 * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
127 * instead of accessing this directly.
128 */
129 private boolean mQwertyMode;
130
131 /**
132 * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
133 * instead of accessing this directly.
134 */
135 private boolean mShortcutsVisible;
136
137 /**
138 * Callback that will receive the various menu-related events generated by
139 * this class. Use getCallback to get a reference to the callback.
140 */
141 private Callback mCallback;
142
143 /** Contains all of the items for this menu */
144 private ArrayList<MenuItemImpl> mItems;
145
146 /** Contains only the items that are currently visible. This will be created/refreshed from
147 * {@link #getVisibleItems()} */
148 private ArrayList<MenuItemImpl> mVisibleItems;
149 /**
150 * Whether or not the items (or any one item's shown state) has changed since it was last
151 * fetched from {@link #getVisibleItems()}
152 */
153 private boolean mIsVisibleItemsStale;
Adam Powell96675b12010-06-10 18:58:59 -0700154
155 /**
156 * Contains only the items that should appear in the Action Bar, if present.
157 */
158 private ArrayList<MenuItemImpl> mActionItems;
159 /**
160 * Contains items that should NOT appear in the Action Bar, if present.
161 */
162 private ArrayList<MenuItemImpl> mNonActionItems;
163 /**
164 * The number of visible action buttons permitted in this menu
165 */
166 private int mMaxActionItems;
167 /**
Adam Powell36fced92011-01-16 15:48:07 -0800168 * The total width limit in pixels for all action items within a menu
169 */
170 private int mActionWidthLimit;
171 /**
Adam Powell96675b12010-06-10 18:58:59 -0700172 * Whether or not the items (or any one item's action state) has changed since it was
173 * last fetched.
174 */
175 private boolean mIsActionItemsStale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
177 /**
Adam Powell8028dd32010-07-15 10:16:33 -0700178 * Whether the process of granting space as action items should reserve a space for
179 * an overflow option in the action list.
180 */
181 private boolean mReserveActionOverflow;
182
183 /**
Adam Powell4d9861e2010-08-17 11:14:40 -0700184 * Default value for how added items should show in the action list.
185 */
186 private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
187
188 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 * Current use case is Context Menus: As Views populate the context menu, each one has
190 * extra information that should be passed along. This is the current menu info that
191 * should be set on all items added to this menu.
192 */
193 private ContextMenuInfo mCurrentMenuInfo;
194
195 /** Header title for menu types that have a header (context and submenus) */
196 CharSequence mHeaderTitle;
197 /** Header icon for menu types that have a header and support icons (context) */
198 Drawable mHeaderIcon;
199 /** Header custom view for menu types that have a header and support custom views (context) */
200 View mHeaderView;
201
202 /**
203 * Contains the state of the View hierarchy for all menu views when the menu
204 * was frozen.
205 */
206 private SparseArray<Parcelable> mFrozenViewStates;
207
208 /**
209 * Prevents onItemsChanged from doing its junk, useful for batching commands
210 * that may individually call onItemsChanged.
211 */
212 private boolean mPreventDispatchingItemsChanged = false;
213
214 private boolean mOptionalIconsVisible = false;
Adam Powell2fbf4de62010-09-30 15:46:46 -0700215
Adam Powell36fced92011-01-16 15:48:07 -0800216 private ViewGroup mMeasureActionButtonParent;
217
Adam Powell1821ff92011-01-24 21:44:28 -0800218 private final WeakReference<MenuAdapter>[] mAdapterCache =
219 new WeakReference[NUM_TYPES];
220 private final WeakReference<OverflowMenuAdapter>[] mOverflowAdapterCache =
221 new WeakReference[NUM_TYPES];
222
Adam Powell36fced92011-01-16 15:48:07 -0800223 // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
224 private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
225
Adam Powell2fbf4de62010-09-30 15:46:46 -0700226 private static int getAlertDialogTheme(Context context) {
227 TypedValue outValue = new TypedValue();
228 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
229 outValue, true);
230 return outValue.resourceId;
231 }
232
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 private MenuType[] mMenuTypes;
234 class MenuType {
235 private int mMenuType;
236
237 /** The layout inflater that uses the menu type's theme */
238 private LayoutInflater mInflater;
239
240 /** The lazily loaded {@link MenuView} */
241 private WeakReference<MenuView> mMenuView;
242
243 MenuType(int menuType) {
244 mMenuType = menuType;
245 }
246
247 LayoutInflater getInflater() {
248 // Create an inflater that uses the given theme for the Views it inflates
249 if (mInflater == null) {
Adam Powell2fbf4de62010-09-30 15:46:46 -0700250 Context wrappedContext;
Adam Powellb3f245c2010-09-10 15:37:56 -0700251 int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
Adam Powell2fbf4de62010-09-30 15:46:46 -0700252 switch (themeResForType) {
253 case THEME_APPLICATION:
Adam Powellbe4d68e2010-10-08 18:16:34 -0700254 wrappedContext = mContext;
Adam Powell2fbf4de62010-09-30 15:46:46 -0700255 break;
256 case THEME_ALERT_DIALOG:
257 wrappedContext = new ContextThemeWrapper(mContext,
258 getAlertDialogTheme(mContext));
259 break;
260 default:
Adam Powellbe4d68e2010-10-08 18:16:34 -0700261 wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
Adam Powell2fbf4de62010-09-30 15:46:46 -0700262 break;
263 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 mInflater = (LayoutInflater) wrappedContext
265 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
266 }
267
268 return mInflater;
269 }
270
271 MenuView getMenuView(ViewGroup parent) {
272 if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
273 return null;
274 }
275
276 synchronized (this) {
277 MenuView menuView = mMenuView != null ? mMenuView.get() : null;
278
279 if (menuView == null) {
280 menuView = (MenuView) getInflater().inflate(
281 LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
282 menuView.initialize(MenuBuilder.this, mMenuType);
283
284 // Cache the view
285 mMenuView = new WeakReference<MenuView>(menuView);
286
287 if (mFrozenViewStates != null) {
288 View view = (View) menuView;
289 view.restoreHierarchyState(mFrozenViewStates);
290
291 // Clear this menu type's frozen state, since we just restored it
292 mFrozenViewStates.remove(view.getId());
293 }
294 }
295
296 return menuView;
297 }
298 }
299
300 boolean hasMenuView() {
301 return mMenuView != null && mMenuView.get() != null;
302 }
303 }
304
305 /**
306 * Called by menu to notify of close and selection changes
307 */
308 public interface Callback {
309 /**
310 * Called when a menu item is selected.
311 * @param menu The menu that is the parent of the item
312 * @param item The menu item that is selected
313 * @return whether the menu item selection was handled
314 */
315 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
316
317 /**
318 * Called when a menu is closed.
319 * @param menu The menu that was closed.
320 * @param allMenusAreClosing Whether the menus are completely closing (true),
321 * or whether there is another menu opening shortly
322 * (false). For example, if the menu is closing because a
323 * sub menu is about to be shown, <var>allMenusAreClosing</var>
324 * is false.
325 */
326 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
327
328 /**
329 * Called when a sub menu is selected. This is a cue to open the given sub menu's decor.
330 * @param subMenu the sub menu that is being opened
331 * @return whether the sub menu selection was handled by the callback
332 */
333 public boolean onSubMenuSelected(SubMenuBuilder subMenu);
334
335 /**
336 * Called when a sub menu is closed
337 * @param menu the sub menu that was closed
338 */
339 public void onCloseSubMenu(SubMenuBuilder menu);
340
341 /**
342 * Called when the mode of the menu changes (for example, from icon to expanded).
343 *
344 * @param menu the menu that has changed modes
345 */
346 public void onMenuModeChange(MenuBuilder menu);
347 }
348
349 /**
350 * Called by menu items to execute their associated action
351 */
352 public interface ItemInvoker {
353 public boolean invokeItem(MenuItemImpl item);
354 }
355
356 public MenuBuilder(Context context) {
357 mMenuTypes = new MenuType[NUM_TYPES];
358
359 mContext = context;
360 mResources = context.getResources();
361
362 mItems = new ArrayList<MenuItemImpl>();
363
364 mVisibleItems = new ArrayList<MenuItemImpl>();
365 mIsVisibleItemsStale = true;
366
Adam Powell96675b12010-06-10 18:58:59 -0700367 mActionItems = new ArrayList<MenuItemImpl>();
368 mNonActionItems = new ArrayList<MenuItemImpl>();
369 mIsActionItemsStale = true;
370
Jeff Brown4aed78b2011-01-14 17:36:55 -0800371 setShortcutsVisibleInner(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 }
373
Adam Powell4d9861e2010-08-17 11:14:40 -0700374 public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
375 mDefaultShowAsAction = defaultShowAsAction;
376 return this;
377 }
378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 public void setCallback(Callback callback) {
380 mCallback = callback;
381 }
382
383 MenuType getMenuType(int menuType) {
384 if (mMenuTypes[menuType] == null) {
385 mMenuTypes[menuType] = new MenuType(menuType);
386 }
387
388 return mMenuTypes[menuType];
389 }
390
391 /**
392 * Gets a menu View that contains this menu's items.
393 *
394 * @param menuType The type of menu to get a View for (must be one of
395 * {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
396 * {@link #TYPE_DIALOG}).
397 * @param parent The ViewGroup that provides a set of LayoutParams values
398 * for this menu view
399 * @return A View for the menu of type <var>menuType</var>
400 */
401 public View getMenuView(int menuType, ViewGroup parent) {
402 // The expanded menu depends on the number if items shown in the icon menu (which
403 // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
404 // wanting to show more icons]). If, for example, the activity goes through
405 // an orientation change while the expanded menu is open, the icon menu's view
406 // won't have an instance anymore; so here we make sure we have an icon menu view (matching
407 // the same parent so the layout parameters from the XML are used). This
408 // will create the icon menu view and cache it (if it doesn't already exist).
409 if (menuType == TYPE_EXPANDED
410 && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
411 getMenuType(TYPE_ICON).getMenuView(parent);
412 }
413
414 return (View) getMenuType(menuType).getMenuView(parent);
415 }
416
417 private int getNumIconMenuItemsShown() {
418 ViewGroup parent = null;
419
420 if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
421 /*
422 * There isn't an icon menu view instantiated, so when we get it
423 * below, it will lazily instantiate it. We should pass a proper
424 * parent so it uses the layout_ attributes present in the XML
425 * layout file.
426 */
427 if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
428 View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
429 parent = (ViewGroup) expandedMenuView.getParent();
430 }
431 }
432
433 return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown();
434 }
435
436 /**
437 * Clears the cached menu views. Call this if the menu views need to another
438 * layout (for example, if the screen size has changed).
439 */
440 public void clearMenuViews() {
441 for (int i = NUM_TYPES - 1; i >= 0; i--) {
442 if (mMenuTypes[i] != null) {
443 mMenuTypes[i].mMenuView = null;
444 }
445 }
446
447 for (int i = mItems.size() - 1; i >= 0; i--) {
448 MenuItemImpl item = mItems.get(i);
449 if (item.hasSubMenu()) {
450 ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
451 }
452 item.clearItemViews();
453 }
454 }
455
456 /**
457 * Adds an item to the menu. The other add methods funnel to this.
458 */
459 private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
460 final int ordering = getOrdering(categoryOrder);
461
Adam Powell4d9861e2010-08-17 11:14:40 -0700462 final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
463 ordering, title, mDefaultShowAsAction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464
465 if (mCurrentMenuInfo != null) {
466 // Pass along the current menu info
467 item.setMenuInfo(mCurrentMenuInfo);
468 }
469
470 mItems.add(findInsertIndex(mItems, ordering), item);
471 onItemsChanged(false);
472
473 return item;
474 }
475
476 public MenuItem add(CharSequence title) {
477 return addInternal(0, 0, 0, title);
478 }
479
480 public MenuItem add(int titleRes) {
481 return addInternal(0, 0, 0, mResources.getString(titleRes));
482 }
483
484 public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
485 return addInternal(group, id, categoryOrder, title);
486 }
487
488 public MenuItem add(int group, int id, int categoryOrder, int title) {
489 return addInternal(group, id, categoryOrder, mResources.getString(title));
490 }
491
492 public SubMenu addSubMenu(CharSequence title) {
493 return addSubMenu(0, 0, 0, title);
494 }
495
496 public SubMenu addSubMenu(int titleRes) {
497 return addSubMenu(0, 0, 0, mResources.getString(titleRes));
498 }
499
500 public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
501 final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
502 final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
503 item.setSubMenu(subMenu);
504
505 return subMenu;
506 }
507
508 public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
509 return addSubMenu(group, id, categoryOrder, mResources.getString(title));
510 }
511
512 public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
513 Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
514 PackageManager pm = mContext.getPackageManager();
515 final List<ResolveInfo> lri =
516 pm.queryIntentActivityOptions(caller, specifics, intent, 0);
517 final int N = lri != null ? lri.size() : 0;
518
519 if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
520 removeGroup(group);
521 }
522
523 for (int i=0; i<N; i++) {
524 final ResolveInfo ri = lri.get(i);
525 Intent rintent = new Intent(
526 ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
527 rintent.setComponent(new ComponentName(
528 ri.activityInfo.applicationInfo.packageName,
529 ri.activityInfo.name));
530 final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
531 .setIcon(ri.loadIcon(pm))
532 .setIntent(rintent);
533 if (outSpecificItems != null && ri.specificIndex >= 0) {
534 outSpecificItems[ri.specificIndex] = item;
535 }
536 }
537
538 return N;
539 }
540
541 public void removeItem(int id) {
542 removeItemAtInt(findItemIndex(id), true);
543 }
544
545 public void removeGroup(int group) {
546 final int i = findGroupIndex(group);
547
548 if (i >= 0) {
549 final int maxRemovable = mItems.size() - i;
550 int numRemoved = 0;
551 while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
552 // Don't force update for each one, this method will do it at the end
553 removeItemAtInt(i, false);
554 }
555
556 // Notify menu views
557 onItemsChanged(false);
558 }
559 }
560
561 /**
562 * Remove the item at the given index and optionally forces menu views to
563 * update.
564 *
565 * @param index The index of the item to be removed. If this index is
566 * invalid an exception is thrown.
567 * @param updateChildrenOnMenuViews Whether to force update on menu views.
568 * Please make sure you eventually call this after your batch of
569 * removals.
570 */
571 private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
572 if ((index < 0) || (index >= mItems.size())) return;
573
574 mItems.remove(index);
575
576 if (updateChildrenOnMenuViews) onItemsChanged(false);
577 }
578
579 public void removeItemAt(int index) {
580 removeItemAtInt(index, true);
581 }
582
583 public void clearAll() {
584 mPreventDispatchingItemsChanged = true;
585 clear();
586 clearHeader();
587 mPreventDispatchingItemsChanged = false;
588 onItemsChanged(true);
589 }
590
591 public void clear() {
592 mItems.clear();
593
594 onItemsChanged(true);
595 }
596
597 void setExclusiveItemChecked(MenuItem item) {
598 final int group = item.getGroupId();
599
600 final int N = mItems.size();
601 for (int i = 0; i < N; i++) {
602 MenuItemImpl curItem = mItems.get(i);
603 if (curItem.getGroupId() == group) {
604 if (!curItem.isExclusiveCheckable()) continue;
605 if (!curItem.isCheckable()) continue;
606
607 // Check the item meant to be checked, uncheck the others (that are in the group)
608 curItem.setCheckedInt(curItem == item);
609 }
610 }
611 }
612
613 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
614 final int N = mItems.size();
615
616 for (int i = 0; i < N; i++) {
617 MenuItemImpl item = mItems.get(i);
618 if (item.getGroupId() == group) {
619 item.setExclusiveCheckable(exclusive);
620 item.setCheckable(checkable);
621 }
622 }
623 }
624
625 public void setGroupVisible(int group, boolean visible) {
626 final int N = mItems.size();
627
628 // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
629 // than setVisible and at the end notify of items being changed
630
631 boolean changedAtLeastOneItem = false;
632 for (int i = 0; i < N; i++) {
633 MenuItemImpl item = mItems.get(i);
634 if (item.getGroupId() == group) {
635 if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
636 }
637 }
638
639 if (changedAtLeastOneItem) onItemsChanged(false);
640 }
641
642 public void setGroupEnabled(int group, boolean enabled) {
643 final int N = mItems.size();
644
645 for (int i = 0; i < N; i++) {
646 MenuItemImpl item = mItems.get(i);
647 if (item.getGroupId() == group) {
648 item.setEnabled(enabled);
649 }
650 }
651 }
652
653 public boolean hasVisibleItems() {
654 final int size = size();
655
656 for (int i = 0; i < size; i++) {
657 MenuItemImpl item = mItems.get(i);
658 if (item.isVisible()) {
659 return true;
660 }
661 }
662
663 return false;
664 }
665
666 public MenuItem findItem(int id) {
667 final int size = size();
668 for (int i = 0; i < size; i++) {
669 MenuItemImpl item = mItems.get(i);
670 if (item.getItemId() == id) {
671 return item;
672 } else if (item.hasSubMenu()) {
673 MenuItem possibleItem = item.getSubMenu().findItem(id);
674
675 if (possibleItem != null) {
676 return possibleItem;
677 }
678 }
679 }
680
681 return null;
682 }
683
684 public int findItemIndex(int id) {
685 final int size = size();
686
687 for (int i = 0; i < size; i++) {
688 MenuItemImpl item = mItems.get(i);
689 if (item.getItemId() == id) {
690 return i;
691 }
692 }
693
694 return -1;
695 }
696
697 public int findGroupIndex(int group) {
698 return findGroupIndex(group, 0);
699 }
700
701 public int findGroupIndex(int group, int start) {
702 final int size = size();
703
704 if (start < 0) {
705 start = 0;
706 }
707
708 for (int i = start; i < size; i++) {
709 final MenuItemImpl item = mItems.get(i);
710
711 if (item.getGroupId() == group) {
712 return i;
713 }
714 }
715
716 return -1;
717 }
718
719 public int size() {
720 return mItems.size();
721 }
722
723 /** {@inheritDoc} */
724 public MenuItem getItem(int index) {
725 return mItems.get(index);
726 }
727
Adam Powell8028dd32010-07-15 10:16:33 -0700728 public MenuItem getOverflowItem(int index) {
729 flagActionItems(true);
730 return mNonActionItems.get(index);
731 }
732
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 public boolean isShortcutKey(int keyCode, KeyEvent event) {
734 return findItemWithShortcutForKey(keyCode, event) != null;
735 }
736
737 public void setQwertyMode(boolean isQwerty) {
738 mQwertyMode = isQwerty;
739
740 refreshShortcuts(isShortcutsVisible(), isQwerty);
741 }
742
743 /**
744 * Returns the ordering across all items. This will grab the category from
745 * the upper bits, find out how to order the category with respect to other
746 * categories, and combine it with the lower bits.
747 *
748 * @param categoryOrder The category order for a particular item (if it has
749 * not been or/add with a category, the default category is
750 * assumed).
751 * @return An ordering integer that can be used to order this item across
752 * all the items (even from other categories).
753 */
754 private static int getOrdering(int categoryOrder)
755 {
756 final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
757
758 if (index < 0 || index >= sCategoryToOrder.length) {
759 throw new IllegalArgumentException("order does not contain a valid category.");
760 }
761
762 return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
763 }
764
765 /**
766 * @return whether the menu shortcuts are in qwerty mode or not
767 */
768 boolean isQwertyMode() {
769 return mQwertyMode;
770 }
771
772 /**
773 * Refreshes the shortcut labels on each of the displayed items. Passes the arguments
774 * so submenus don't need to call their parent menu for the same values.
775 */
776 private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
777 MenuItemImpl item;
778 for (int i = mItems.size() - 1; i >= 0; i--) {
779 item = mItems.get(i);
780
781 if (item.hasSubMenu()) {
782 ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
783 }
784
785 item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
786 }
787 }
788
789 /**
790 * Sets whether the shortcuts should be visible on menus. Devices without hardware
791 * key input will never make shortcuts visible even if this method is passed 'true'.
792 *
793 * @param shortcutsVisible Whether shortcuts should be visible (if true and a
794 * menu item does not have a shortcut defined, that item will
795 * still NOT show a shortcut)
796 */
797 public void setShortcutsVisible(boolean shortcutsVisible) {
798 if (mShortcutsVisible == shortcutsVisible) return;
Jeff Brown4aed78b2011-01-14 17:36:55 -0800799
800 setShortcutsVisibleInner(shortcutsVisible);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 refreshShortcuts(mShortcutsVisible, isQwertyMode());
802 }
803
Jeff Brown4aed78b2011-01-14 17:36:55 -0800804 private void setShortcutsVisibleInner(boolean shortcutsVisible) {
805 mShortcutsVisible = shortcutsVisible
806 && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
807 && mResources.getBoolean(
808 com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
809 }
810
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 /**
812 * @return Whether shortcuts should be visible on menus.
813 */
814 public boolean isShortcutsVisible() {
815 return mShortcutsVisible;
816 }
817
818 Resources getResources() {
819 return mResources;
820 }
821
822 public Callback getCallback() {
823 return mCallback;
824 }
825
826 public Context getContext() {
827 return mContext;
828 }
829
830 private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
831 for (int i = items.size() - 1; i >= 0; i--) {
832 MenuItemImpl item = items.get(i);
833 if (item.getOrdering() <= ordering) {
834 return i + 1;
835 }
836 }
837
838 return 0;
839 }
840
841 public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
842 final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
843
844 boolean handled = false;
845
846 if (item != null) {
847 handled = performItemAction(item, flags);
848 }
849
850 if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
851 close(true);
852 }
853
854 return handled;
855 }
856
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100857 /*
858 * This function will return all the menu and sub-menu items that can
859 * be directly (the shortcut directly corresponds) and indirectly
860 * (the ALT-enabled char corresponds to the shortcut) associated
861 * with the keyCode.
862 */
863 List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 final boolean qwerty = isQwertyMode();
865 final int metaState = event.getMetaState();
866 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
867 // Get the chars associated with the keyCode (i.e using any chording combo)
868 final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
869 // The delete key is not mapped to '\b' so we treat it specially
870 if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
871 return null;
872 }
873
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100874 Vector<MenuItemImpl> items = new Vector();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 // Look for an item whose shortcut is this key.
876 final int N = mItems.size();
877 for (int i = 0; i < N; i++) {
878 MenuItemImpl item = mItems.get(i);
879 if (item.hasSubMenu()) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100880 List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
881 .findItemsWithShortcutForKey(keyCode, event);
882 items.addAll(subMenuItems);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100884 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
885 if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
886 (shortcutChar != 0) &&
887 (shortcutChar == possibleChars.meta[0]
888 || shortcutChar == possibleChars.meta[2]
889 || (qwerty && shortcutChar == '\b' &&
890 keyCode == KeyEvent.KEYCODE_DEL)) &&
891 item.isEnabled()) {
892 items.add(item);
893 }
894 }
895 return items;
896 }
897
898 /*
899 * We want to return the menu item associated with the key, but if there is no
900 * ambiguity (i.e. there is only one menu item corresponding to the key) we want
901 * to return it even if it's not an exact match; this allow the user to
902 * _not_ use the ALT key for example, making the use of shortcuts slightly more
903 * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
904 * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
905 *
906 * On the other hand, if two (or more) shortcuts corresponds to the same key,
907 * we have to only return the exact match.
908 */
909 MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
910 // Get all items that can be associated directly or indirectly with the keyCode
911 List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
912
913 if (items == null) {
914 return null;
915 }
916
917 final int metaState = event.getMetaState();
918 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
919 // Get the chars associated with the keyCode (i.e using any chording combo)
920 event.getKeyData(possibleChars);
921
922 // If we have only one element, we can safely returns it
923 if (items.size() == 1) {
924 return items.get(0);
925 }
926
927 final boolean qwerty = isQwertyMode();
928 // If we found more than one item associated with the key,
929 // we have to return the exact match
930 for (MenuItemImpl item : items) {
931 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
932 if ((shortcutChar == possibleChars.meta[0] &&
933 (metaState & KeyEvent.META_ALT_ON) == 0)
934 || (shortcutChar == possibleChars.meta[2] &&
935 (metaState & KeyEvent.META_ALT_ON) != 0)
936 || (qwerty && shortcutChar == '\b' &&
937 keyCode == KeyEvent.KEYCODE_DEL)) {
938 return item;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 }
940 }
941 return null;
942 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100943
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 public boolean performIdentifierAction(int id, int flags) {
945 // Look for an item whose identifier is the id.
946 return performItemAction(findItem(id), flags);
947 }
948
949 public boolean performItemAction(MenuItem item, int flags) {
950 MenuItemImpl itemImpl = (MenuItemImpl) item;
951
952 if (itemImpl == null || !itemImpl.isEnabled()) {
953 return false;
954 }
955
956 boolean invoked = itemImpl.invoke();
957
958 if (item.hasSubMenu()) {
959 close(false);
960
961 if (mCallback != null) {
962 // Return true if the sub menu was invoked or the item was invoked previously
963 invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
964 || invoked;
965 }
966 } else {
967 if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
968 close(true);
969 }
970 }
971
972 return invoked;
973 }
974
975 /**
976 * Closes the visible menu.
977 *
978 * @param allMenusAreClosing Whether the menus are completely closing (true),
979 * or whether there is another menu coming in this menu's place
980 * (false). For example, if the menu is closing because a
981 * sub menu is about to be shown, <var>allMenusAreClosing</var>
982 * is false.
983 */
984 final void close(boolean allMenusAreClosing) {
985 Callback callback = getCallback();
986 if (callback != null) {
987 callback.onCloseMenu(this, allMenusAreClosing);
988 }
989 }
990
991 /** {@inheritDoc} */
992 public void close() {
993 close(true);
994 }
995
996 /**
997 * Called when an item is added or removed.
998 *
999 * @param cleared Whether the items were cleared or just changed.
1000 */
1001 private void onItemsChanged(boolean cleared) {
1002 if (!mPreventDispatchingItemsChanged) {
1003 if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
Adam Powell96675b12010-06-10 18:58:59 -07001004 if (mIsActionItemsStale == false) mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005
1006 MenuType[] menuTypes = mMenuTypes;
1007 for (int i = 0; i < NUM_TYPES; i++) {
1008 if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
1009 MenuView menuView = menuTypes[i].mMenuView.get();
1010 menuView.updateChildren(cleared);
1011 }
Adam Powell1821ff92011-01-24 21:44:28 -08001012
1013 MenuAdapter adapter = mAdapterCache[i] == null ? null : mAdapterCache[i].get();
1014 if (adapter != null) adapter.notifyDataSetChanged();
1015
1016 adapter = mOverflowAdapterCache[i] == null ? null : mOverflowAdapterCache[i].get();
1017 if (adapter != null) adapter.notifyDataSetChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 }
1019 }
1020 }
1021
1022 /**
1023 * Called by {@link MenuItemImpl} when its visible flag is changed.
1024 * @param item The item that has gone through a visibility change.
1025 */
1026 void onItemVisibleChanged(MenuItemImpl item) {
1027 // Notify of items being changed
1028 onItemsChanged(false);
1029 }
1030
Adam Powell96675b12010-06-10 18:58:59 -07001031 /**
1032 * Called by {@link MenuItemImpl} when its action request status is changed.
1033 * @param item The item that has gone through a change in action request status.
1034 */
1035 void onItemActionRequestChanged(MenuItemImpl item) {
1036 // Notify of items being changed
1037 onItemsChanged(false);
1038 }
1039
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001040 ArrayList<MenuItemImpl> getVisibleItems() {
1041 if (!mIsVisibleItemsStale) return mVisibleItems;
1042
1043 // Refresh the visible items
1044 mVisibleItems.clear();
1045
1046 final int itemsSize = mItems.size();
1047 MenuItemImpl item;
1048 for (int i = 0; i < itemsSize; i++) {
1049 item = mItems.get(i);
1050 if (item.isVisible()) mVisibleItems.add(item);
1051 }
1052
1053 mIsVisibleItemsStale = false;
Adam Powell96675b12010-06-10 18:58:59 -07001054 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055
1056 return mVisibleItems;
1057 }
Adam Powell96675b12010-06-10 18:58:59 -07001058
Adam Powell36fced92011-01-16 15:48:07 -08001059 /**
1060 * @return A fake action button parent view for obtaining child views.
1061 */
1062 private ViewGroup getMeasureActionButtonParent() {
1063 if (mMeasureActionButtonParent == null) {
Adam Powell4192e382011-01-17 13:16:54 -08001064 mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
Adam Powell36fced92011-01-16 15:48:07 -08001065 .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
1066 }
1067 return mMeasureActionButtonParent;
1068 }
1069
1070 /**
1071 * This method determines which menu items get to be 'action items' that will appear
1072 * in an action bar and which items should be 'overflow items' in a secondary menu.
1073 * The rules are as follows:
1074 *
1075 * <p>Items are considered for inclusion in the order specified within the menu.
1076 * There is a limit of mMaxActionItems as a total count, optionally including the overflow
1077 * menu button itself. This is a soft limit; if an item shares a group ID with an item
1078 * previously included as an action item, the new item will stay with its group and become
1079 * an action item itself even if it breaks the max item count limit. This is done to
1080 * limit the conceptual complexity of the items presented within an action bar. Only a few
1081 * unrelated concepts should be presented to the user in this space, and groups are treated
1082 * as a single concept.
1083 *
1084 * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
1085 * limit may be broken by a single item that exceeds the remaining space, but no further
1086 * items may be added. If an item that is part of a group cannot fit within the remaining
1087 * measured width, the entire group will be demoted to overflow. This is done to ensure room
1088 * for navigation and other affordances in the action bar as well as reduce general UI clutter.
1089 *
1090 * <p>The space freed by demoting a full group cannot be consumed by future menu items.
1091 * Once items begin to overflow, all future items become overflow items as well. This is
1092 * to avoid inadvertent reordering that may break the app's intended design.
1093 *
1094 * @param reserveActionOverflow true if an overflow button should consume one space
1095 * in the available item count
1096 */
Adam Powell8028dd32010-07-15 10:16:33 -07001097 private void flagActionItems(boolean reserveActionOverflow) {
1098 if (reserveActionOverflow != mReserveActionOverflow) {
1099 mReserveActionOverflow = reserveActionOverflow;
1100 mIsActionItemsStale = true;
1101 }
1102
Adam Powell96675b12010-06-10 18:58:59 -07001103 if (!mIsActionItemsStale) {
1104 return;
1105 }
Adam Powell8028dd32010-07-15 10:16:33 -07001106
Adam Powell96675b12010-06-10 18:58:59 -07001107 final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
1108 final int itemsSize = visibleItems.size();
1109 int maxActions = mMaxActionItems;
Adam Powell36fced92011-01-16 15:48:07 -08001110 int widthLimit = mActionWidthLimit;
1111 final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1112 final ViewGroup parent = getMeasureActionButtonParent();
Adam Powell8028dd32010-07-15 10:16:33 -07001113
1114 int requiredItems = 0;
1115 int requestedItems = 0;
Adam Powell36fced92011-01-16 15:48:07 -08001116 int firstActionWidth = 0;
Adam Powell8028dd32010-07-15 10:16:33 -07001117 boolean hasOverflow = false;
Adam Powell96675b12010-06-10 18:58:59 -07001118 for (int i = 0; i < itemsSize; i++) {
1119 MenuItemImpl item = visibleItems.get(i);
1120 if (item.requiresActionButton()) {
Adam Powell8028dd32010-07-15 10:16:33 -07001121 requiredItems++;
1122 } else if (item.requestsActionButton()) {
1123 requestedItems++;
1124 } else {
1125 hasOverflow = true;
Adam Powell96675b12010-06-10 18:58:59 -07001126 }
1127 }
Adam Powell8028dd32010-07-15 10:16:33 -07001128
1129 // Reserve a spot for the overflow item if needed.
1130 if (reserveActionOverflow &&
1131 (hasOverflow || requiredItems + requestedItems > maxActions)) {
1132 maxActions--;
1133 }
1134 maxActions -= requiredItems;
1135
Adam Powell36fced92011-01-16 15:48:07 -08001136 final SparseBooleanArray seenGroups = mActionButtonGroups;
1137 seenGroups.clear();
1138
Adam Powell96675b12010-06-10 18:58:59 -07001139 // Flag as many more requested items as will fit.
1140 for (int i = 0; i < itemsSize; i++) {
1141 MenuItemImpl item = visibleItems.get(i);
Adam Powell36fced92011-01-16 15:48:07 -08001142
1143 if (item.requiresActionButton()) {
1144 View v = item.getActionView();
1145 if (v == null) {
1146 v = item.getItemView(TYPE_ACTION_BUTTON, parent);
1147 }
1148 v.measure(querySpec, querySpec);
1149 final int measuredWidth = v.getMeasuredWidth();
1150 widthLimit -= measuredWidth;
1151 if (firstActionWidth == 0) {
1152 firstActionWidth = measuredWidth;
1153 }
1154 final int groupId = item.getGroupId();
1155 if (groupId != 0) {
1156 seenGroups.put(groupId, true);
1157 }
1158 } else if (item.requestsActionButton()) {
1159 // Items in a group with other items that already have an action slot
1160 // can break the max actions rule, but not the width limit.
1161 final int groupId = item.getGroupId();
1162 final boolean inGroup = seenGroups.get(groupId);
1163 boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
Adam Powell96675b12010-06-10 18:58:59 -07001164 maxActions--;
Adam Powell36fced92011-01-16 15:48:07 -08001165
1166 if (isAction) {
1167 View v = item.getActionView();
1168 if (v == null) {
1169 v = item.getItemView(TYPE_ACTION_BUTTON, parent);
1170 }
1171 v.measure(querySpec, querySpec);
1172 final int measuredWidth = v.getMeasuredWidth();
1173 widthLimit -= measuredWidth;
1174 if (firstActionWidth == 0) {
1175 firstActionWidth = measuredWidth;
1176 }
1177
1178 // Did this push the entire first item past halfway?
1179 if (widthLimit + firstActionWidth <= 0) {
1180 isAction = false;
1181 }
1182 }
1183
1184 if (isAction && groupId != 0) {
1185 seenGroups.put(groupId, true);
1186 } else if (inGroup) {
1187 // We broke the width limit. Demote the whole group, they all overflow now.
1188 seenGroups.put(groupId, false);
1189 for (int j = 0; j < i; j++) {
1190 MenuItemImpl areYouMyGroupie = visibleItems.get(j);
1191 if (areYouMyGroupie.getGroupId() == groupId) {
1192 areYouMyGroupie.setIsActionButton(false);
1193 }
1194 }
1195 }
1196
1197 item.setIsActionButton(isAction);
Adam Powell96675b12010-06-10 18:58:59 -07001198 }
1199 }
Adam Powell8028dd32010-07-15 10:16:33 -07001200
Adam Powell96675b12010-06-10 18:58:59 -07001201 mActionItems.clear();
1202 mNonActionItems.clear();
1203 for (int i = 0; i < itemsSize; i++) {
1204 MenuItemImpl item = visibleItems.get(i);
1205 if (item.isActionButton()) {
1206 mActionItems.add(item);
1207 } else {
1208 mNonActionItems.add(item);
1209 }
1210 }
Adam Powell8028dd32010-07-15 10:16:33 -07001211
Adam Powell96675b12010-06-10 18:58:59 -07001212 mIsActionItemsStale = false;
1213 }
1214
Adam Powell8028dd32010-07-15 10:16:33 -07001215 ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
1216 flagActionItems(reserveActionOverflow);
Adam Powell96675b12010-06-10 18:58:59 -07001217 return mActionItems;
1218 }
1219
Adam Powell8028dd32010-07-15 10:16:33 -07001220 ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
1221 flagActionItems(reserveActionOverflow);
Adam Powell96675b12010-06-10 18:58:59 -07001222 return mNonActionItems;
1223 }
1224
1225 void setMaxActionItems(int maxActionItems) {
1226 mMaxActionItems = maxActionItems;
1227 mIsActionItemsStale = true;
1228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229
Adam Powell36fced92011-01-16 15:48:07 -08001230 void setActionWidthLimit(int widthLimit) {
1231 mActionWidthLimit = widthLimit;
1232 mIsActionItemsStale = true;
1233 }
1234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 public void clearHeader() {
1236 mHeaderIcon = null;
1237 mHeaderTitle = null;
1238 mHeaderView = null;
1239
1240 onItemsChanged(false);
1241 }
1242
1243 private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
1244 final Drawable icon, final View view) {
1245 final Resources r = getResources();
1246
1247 if (view != null) {
1248 mHeaderView = view;
1249
1250 // If using a custom view, then the title and icon aren't used
1251 mHeaderTitle = null;
1252 mHeaderIcon = null;
1253 } else {
1254 if (titleRes > 0) {
1255 mHeaderTitle = r.getText(titleRes);
1256 } else if (title != null) {
1257 mHeaderTitle = title;
1258 }
1259
1260 if (iconRes > 0) {
1261 mHeaderIcon = r.getDrawable(iconRes);
1262 } else if (icon != null) {
1263 mHeaderIcon = icon;
1264 }
1265
1266 // If using the title or icon, then a custom view isn't used
1267 mHeaderView = null;
1268 }
1269
1270 // Notify of change
1271 onItemsChanged(false);
1272 }
1273
1274 /**
1275 * Sets the header's title. This replaces the header view. Called by the
1276 * builder-style methods of subclasses.
1277 *
1278 * @param title The new title.
1279 * @return This MenuBuilder so additional setters can be called.
1280 */
1281 protected MenuBuilder setHeaderTitleInt(CharSequence title) {
1282 setHeaderInternal(0, title, 0, null, null);
1283 return this;
1284 }
1285
1286 /**
1287 * Sets the header's title. This replaces the header view. Called by the
1288 * builder-style methods of subclasses.
1289 *
1290 * @param titleRes The new title (as a resource ID).
1291 * @return This MenuBuilder so additional setters can be called.
1292 */
1293 protected MenuBuilder setHeaderTitleInt(int titleRes) {
1294 setHeaderInternal(titleRes, null, 0, null, null);
1295 return this;
1296 }
1297
1298 /**
1299 * Sets the header's icon. This replaces the header view. Called by the
1300 * builder-style methods of subclasses.
1301 *
1302 * @param icon The new icon.
1303 * @return This MenuBuilder so additional setters can be called.
1304 */
1305 protected MenuBuilder setHeaderIconInt(Drawable icon) {
1306 setHeaderInternal(0, null, 0, icon, null);
1307 return this;
1308 }
1309
1310 /**
1311 * Sets the header's icon. This replaces the header view. Called by the
1312 * builder-style methods of subclasses.
1313 *
1314 * @param iconRes The new icon (as a resource ID).
1315 * @return This MenuBuilder so additional setters can be called.
1316 */
1317 protected MenuBuilder setHeaderIconInt(int iconRes) {
1318 setHeaderInternal(0, null, iconRes, null, null);
1319 return this;
1320 }
1321
1322 /**
1323 * Sets the header's view. This replaces the title and icon. Called by the
1324 * builder-style methods of subclasses.
1325 *
1326 * @param view The new view.
1327 * @return This MenuBuilder so additional setters can be called.
1328 */
1329 protected MenuBuilder setHeaderViewInt(View view) {
1330 setHeaderInternal(0, null, 0, null, view);
1331 return this;
1332 }
1333
1334 public CharSequence getHeaderTitle() {
1335 return mHeaderTitle;
1336 }
1337
1338 public Drawable getHeaderIcon() {
1339 return mHeaderIcon;
1340 }
1341
1342 public View getHeaderView() {
1343 return mHeaderView;
1344 }
1345
1346 /**
1347 * Gets the root menu (if this is a submenu, find its root menu).
1348 * @return The root menu.
1349 */
1350 public MenuBuilder getRootMenu() {
1351 return this;
1352 }
1353
1354 /**
1355 * Sets the current menu info that is set on all items added to this menu
1356 * (until this is called again with different menu info, in which case that
1357 * one will be added to all subsequent item additions).
1358 *
1359 * @param menuInfo The extra menu information to add.
1360 */
1361 public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
1362 mCurrentMenuInfo = menuInfo;
1363 }
1364
1365 /**
1366 * Gets an adapter for providing items and their views.
1367 *
1368 * @param menuType The type of menu to get an adapter for.
1369 * @return A {@link MenuAdapter} for this menu with the given menu type.
1370 */
1371 public MenuAdapter getMenuAdapter(int menuType) {
Adam Powell1821ff92011-01-24 21:44:28 -08001372 MenuAdapter adapter = mAdapterCache[menuType] == null ?
1373 null : mAdapterCache[menuType].get();
1374 if (adapter != null) return adapter;
1375
1376 adapter = new MenuAdapter(menuType);
1377 mAdapterCache[menuType] = new WeakReference<MenuAdapter>(adapter);
1378 return adapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379 }
1380
Adam Powell8028dd32010-07-15 10:16:33 -07001381 /**
1382 * Gets an adapter for providing overflow (non-action) items and their views.
1383 *
1384 * @param menuType The type of menu to get an adapter for.
1385 * @return A {@link MenuAdapter} for this menu with the given menu type.
1386 */
1387 public MenuAdapter getOverflowMenuAdapter(int menuType) {
Adam Powell1821ff92011-01-24 21:44:28 -08001388 OverflowMenuAdapter adapter = mOverflowAdapterCache[menuType] == null ?
1389 null : mOverflowAdapterCache[menuType].get();
1390 if (adapter != null) return adapter;
1391
1392 adapter = new OverflowMenuAdapter(menuType);
1393 mOverflowAdapterCache[menuType] = new WeakReference<OverflowMenuAdapter>(adapter);
1394 return adapter;
Adam Powell8028dd32010-07-15 10:16:33 -07001395 }
1396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397 void setOptionalIconsVisible(boolean visible) {
1398 mOptionalIconsVisible = visible;
1399 }
1400
1401 boolean getOptionalIconsVisible() {
1402 return mOptionalIconsVisible;
1403 }
1404
1405 public void saveHierarchyState(Bundle outState) {
1406 SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
1407
1408 MenuType[] menuTypes = mMenuTypes;
1409 for (int i = NUM_TYPES - 1; i >= 0; i--) {
1410 if (menuTypes[i] == null) {
1411 continue;
1412 }
1413
1414 if (menuTypes[i].hasMenuView()) {
1415 ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
1416 }
1417 }
1418
1419 outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
1420 }
1421
1422 public void restoreHierarchyState(Bundle inState) {
1423 // Save this for menu views opened later
1424 SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
1425 .getSparseParcelableArray(VIEWS_TAG);
1426
1427 // Thaw those menu views already open
1428 MenuType[] menuTypes = mMenuTypes;
1429 for (int i = NUM_TYPES - 1; i >= 0; i--) {
1430 if (menuTypes[i] == null) {
1431 continue;
1432 }
1433
1434 if (menuTypes[i].hasMenuView()) {
1435 ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
1436 }
1437 }
1438 }
1439
1440 /**
1441 * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
1442 * source. This adapter will use only the visible/shown items from the menu.
1443 */
1444 public class MenuAdapter extends BaseAdapter {
1445 private int mMenuType;
1446
1447 public MenuAdapter(int menuType) {
1448 mMenuType = menuType;
1449 }
1450
1451 public int getOffset() {
1452 if (mMenuType == TYPE_EXPANDED) {
1453 return getNumIconMenuItemsShown();
1454 } else {
1455 return 0;
1456 }
1457 }
1458
1459 public int getCount() {
1460 return getVisibleItems().size() - getOffset();
1461 }
1462
1463 public MenuItemImpl getItem(int position) {
1464 return getVisibleItems().get(position + getOffset());
1465 }
1466
1467 public long getItemId(int position) {
1468 // Since a menu item's ID is optional, we'll use the position as an
1469 // ID for the item in the AdapterView
1470 return position;
1471 }
1472
1473 public View getView(int position, View convertView, ViewGroup parent) {
Adam Powell42675342010-07-09 18:02:59 -07001474 if (convertView != null) {
1475 MenuView.ItemView itemView = (MenuView.ItemView) convertView;
1476 itemView.getItemData().setItemView(mMenuType, null);
1477
1478 MenuItemImpl item = (MenuItemImpl) getItem(position);
1479 itemView.initialize(item, mMenuType);
1480 item.setItemView(mMenuType, itemView);
1481 return convertView;
1482 } else {
1483 MenuItemImpl item = (MenuItemImpl) getItem(position);
1484 item.setItemView(mMenuType, null);
1485 return item.getItemView(mMenuType, parent);
1486 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001487 }
Adam Powell8028dd32010-07-15 10:16:33 -07001488 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489
Adam Powell8028dd32010-07-15 10:16:33 -07001490 /**
1491 * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
1492 * source for overflow menu items that do not fit in the list of action items.
1493 */
1494 private class OverflowMenuAdapter extends MenuAdapter {
Adam Powell8028dd32010-07-15 10:16:33 -07001495 public OverflowMenuAdapter(int menuType) {
1496 super(menuType);
Adam Powell8028dd32010-07-15 10:16:33 -07001497 }
1498
1499 @Override
1500 public MenuItemImpl getItem(int position) {
Adam Powell1821ff92011-01-24 21:44:28 -08001501 return getNonActionItems(true).get(position);
Adam Powell8028dd32010-07-15 10:16:33 -07001502 }
1503
1504 @Override
1505 public int getCount() {
Adam Powell1821ff92011-01-24 21:44:28 -08001506 return getNonActionItems(true).size();
Adam Powell8028dd32010-07-15 10:16:33 -07001507 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 }
1509}