blob: aff697ad580c6a12931400dc0e3e30d0b92d94e7 [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;
Adam Powell11ed1d62011-07-11 21:19:59 -070028import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Parcelable;
30import android.util.SparseArray;
Adam Powell961dd112011-07-12 14:25:23 -070031import android.view.ActionProvider;
Adam Powell36fced92011-01-16 15:48:07 -080032import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.view.KeyCharacterMap;
34import android.view.KeyEvent;
35import android.view.Menu;
36import android.view.MenuItem;
37import android.view.SubMenu;
38import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
40import java.lang.ref.WeakReference;
41import java.util.ArrayList;
42import java.util.List;
Adam Powell696cba52011-03-29 10:38:16 -070043import java.util.concurrent.CopyOnWriteArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
45/**
46 * Implementation of the {@link android.view.Menu} interface for creating a
47 * standard menu UI.
48 */
49public class MenuBuilder implements Menu {
Adam Powell89b09da2011-07-27 11:55:29 -070050 private static final String TAG = "MenuBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
Adam Powell11ed1d62011-07-11 21:19:59 -070052 private static final String PRESENTER_KEY = "android:menu:presenters";
Adam Powell038f1c82011-07-21 14:28:10 -070053 private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates";
54 private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview";
Adam Powell11ed1d62011-07-11 21:19:59 -070055
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 private static final int[] sCategoryToOrder = new int[] {
57 1, /* No category */
58 4, /* CONTAINER */
59 5, /* SYSTEM */
60 3, /* SECONDARY */
61 2, /* ALTERNATIVE */
62 0, /* SELECTED_ALTERNATIVE */
63 };
64
65 private final Context mContext;
66 private final Resources mResources;
67
68 /**
69 * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
70 * instead of accessing this directly.
71 */
72 private boolean mQwertyMode;
73
74 /**
75 * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
76 * instead of accessing this directly.
77 */
78 private boolean mShortcutsVisible;
79
80 /**
81 * Callback that will receive the various menu-related events generated by
82 * this class. Use getCallback to get a reference to the callback.
83 */
84 private Callback mCallback;
85
86 /** Contains all of the items for this menu */
87 private ArrayList<MenuItemImpl> mItems;
88
89 /** Contains only the items that are currently visible. This will be created/refreshed from
90 * {@link #getVisibleItems()} */
91 private ArrayList<MenuItemImpl> mVisibleItems;
92 /**
93 * Whether or not the items (or any one item's shown state) has changed since it was last
94 * fetched from {@link #getVisibleItems()}
95 */
96 private boolean mIsVisibleItemsStale;
Adam Powell96675b12010-06-10 18:58:59 -070097
98 /**
99 * Contains only the items that should appear in the Action Bar, if present.
100 */
101 private ArrayList<MenuItemImpl> mActionItems;
102 /**
103 * Contains items that should NOT appear in the Action Bar, if present.
104 */
105 private ArrayList<MenuItemImpl> mNonActionItems;
Adam Powell696cba52011-03-29 10:38:16 -0700106
Adam Powell36fced92011-01-16 15:48:07 -0800107 /**
Adam Powell96675b12010-06-10 18:58:59 -0700108 * Whether or not the items (or any one item's action state) has changed since it was
109 * last fetched.
110 */
111 private boolean mIsActionItemsStale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
113 /**
Adam Powell4d9861e2010-08-17 11:14:40 -0700114 * Default value for how added items should show in the action list.
115 */
116 private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
117
118 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 * Current use case is Context Menus: As Views populate the context menu, each one has
120 * extra information that should be passed along. This is the current menu info that
121 * should be set on all items added to this menu.
122 */
123 private ContextMenuInfo mCurrentMenuInfo;
124
125 /** Header title for menu types that have a header (context and submenus) */
126 CharSequence mHeaderTitle;
127 /** Header icon for menu types that have a header and support icons (context) */
128 Drawable mHeaderIcon;
129 /** Header custom view for menu types that have a header and support custom views (context) */
130 View mHeaderView;
131
132 /**
133 * Contains the state of the View hierarchy for all menu views when the menu
134 * was frozen.
135 */
136 private SparseArray<Parcelable> mFrozenViewStates;
137
138 /**
139 * Prevents onItemsChanged from doing its junk, useful for batching commands
140 * that may individually call onItemsChanged.
141 */
142 private boolean mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700143 private boolean mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144
145 private boolean mOptionalIconsVisible = false;
Adam Powell2fbf4de62010-09-30 15:46:46 -0700146
Adam Powell696cba52011-03-29 10:38:16 -0700147 private boolean mIsClosing = false;
Adam Powell36fced92011-01-16 15:48:07 -0800148
Adam Powell696cba52011-03-29 10:38:16 -0700149 private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
Adam Powell1821ff92011-01-24 21:44:28 -0800150
Adam Powell696cba52011-03-29 10:38:16 -0700151 private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
152 new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
Adam Powell6b0e97c2011-08-03 12:06:32 -0700153
154 /**
155 * Currently expanded menu item; must be collapsed when we clear.
156 */
157 private MenuItemImpl mExpandedItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158
159 /**
Adam Powell696cba52011-03-29 10:38:16 -0700160 * Called by menu to notify of close and selection changes.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 */
162 public interface Callback {
163 /**
164 * Called when a menu item is selected.
165 * @param menu The menu that is the parent of the item
166 * @param item The menu item that is selected
167 * @return whether the menu item selection was handled
168 */
169 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
170
171 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 * Called when the mode of the menu changes (for example, from icon to expanded).
173 *
174 * @param menu the menu that has changed modes
175 */
176 public void onMenuModeChange(MenuBuilder menu);
177 }
178
179 /**
180 * Called by menu items to execute their associated action
181 */
182 public interface ItemInvoker {
183 public boolean invokeItem(MenuItemImpl item);
184 }
185
186 public MenuBuilder(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 mContext = context;
188 mResources = context.getResources();
189
190 mItems = new ArrayList<MenuItemImpl>();
191
192 mVisibleItems = new ArrayList<MenuItemImpl>();
193 mIsVisibleItemsStale = true;
194
Adam Powell96675b12010-06-10 18:58:59 -0700195 mActionItems = new ArrayList<MenuItemImpl>();
196 mNonActionItems = new ArrayList<MenuItemImpl>();
197 mIsActionItemsStale = true;
198
Jeff Brown4aed78b2011-01-14 17:36:55 -0800199 setShortcutsVisibleInner(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
201
Adam Powell4d9861e2010-08-17 11:14:40 -0700202 public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
203 mDefaultShowAsAction = defaultShowAsAction;
204 return this;
205 }
Adam Powell696cba52011-03-29 10:38:16 -0700206
207 /**
208 * Add a presenter to this menu. This will only hold a WeakReference;
209 * you do not need to explicitly remove a presenter, but you can using
210 * {@link #removeMenuPresenter(MenuPresenter)}.
211 *
212 * @param presenter The presenter to add
213 */
214 public void addMenuPresenter(MenuPresenter presenter) {
215 mPresenters.add(new WeakReference<MenuPresenter>(presenter));
216 presenter.initForMenu(mContext, this);
217 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 }
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 /**
Adam Powell696cba52011-03-29 10:38:16 -0700221 * Remove a presenter from this menu. That presenter will no longer
222 * receive notifications of updates to this menu's data.
223 *
224 * @param presenter The presenter to remove
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 */
Adam Powell696cba52011-03-29 10:38:16 -0700226 public void removeMenuPresenter(MenuPresenter presenter) {
227 for (WeakReference<MenuPresenter> ref : mPresenters) {
228 final MenuPresenter item = ref.get();
229 if (item == null || item == presenter) {
230 mPresenters.remove(ref);
231 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 }
234
Adam Powell696cba52011-03-29 10:38:16 -0700235 private void dispatchPresenterUpdate(boolean cleared) {
236 if (mPresenters.isEmpty()) return;
237
Adam Powell640a66e2011-04-29 10:18:53 -0700238 stopDispatchingItemsChanged();
Adam Powell696cba52011-03-29 10:38:16 -0700239 for (WeakReference<MenuPresenter> ref : mPresenters) {
240 final MenuPresenter presenter = ref.get();
241 if (presenter == null) {
242 mPresenters.remove(ref);
243 } else {
244 presenter.updateMenuView(cleared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 }
246 }
Adam Powell640a66e2011-04-29 10:18:53 -0700247 startDispatchingItemsChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 }
249
Adam Powell696cba52011-03-29 10:38:16 -0700250 private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
251 if (mPresenters.isEmpty()) return false;
252
253 boolean result = false;
254
255 for (WeakReference<MenuPresenter> ref : mPresenters) {
256 final MenuPresenter presenter = ref.get();
257 if (presenter == null) {
258 mPresenters.remove(ref);
259 } else if (!result) {
260 result = presenter.onSubMenuSelected(subMenu);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
262 }
Adam Powell696cba52011-03-29 10:38:16 -0700263 return result;
264 }
265
Adam Powell11ed1d62011-07-11 21:19:59 -0700266 private void dispatchSaveInstanceState(Bundle outState) {
267 if (mPresenters.isEmpty()) return;
268
269 SparseArray<Parcelable> presenterStates = new SparseArray<Parcelable>();
270
271 for (WeakReference<MenuPresenter> ref : mPresenters) {
272 final MenuPresenter presenter = ref.get();
273 if (presenter == null) {
274 mPresenters.remove(ref);
275 } else {
276 final int id = presenter.getId();
277 if (id > 0) {
278 final Parcelable state = presenter.onSaveInstanceState();
279 if (state != null) {
280 presenterStates.put(id, state);
281 }
282 }
283 }
284 }
285
286 outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates);
287 }
288
289 private void dispatchRestoreInstanceState(Bundle state) {
290 SparseArray<Parcelable> presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
291
292 if (presenterStates == null || mPresenters.isEmpty()) return;
293
294 for (WeakReference<MenuPresenter> ref : mPresenters) {
295 final MenuPresenter presenter = ref.get();
296 if (presenter == null) {
297 mPresenters.remove(ref);
298 } else {
299 final int id = presenter.getId();
300 if (id > 0) {
301 Parcelable parcel = presenterStates.get(id);
302 if (parcel != null) {
303 presenter.onRestoreInstanceState(parcel);
304 }
305 }
306 }
307 }
308 }
309
310 public void savePresenterStates(Bundle outState) {
311 dispatchSaveInstanceState(outState);
312 }
313
314 public void restorePresenterStates(Bundle state) {
315 dispatchRestoreInstanceState(state);
316 }
317
Adam Powell038f1c82011-07-21 14:28:10 -0700318 public void saveActionViewStates(Bundle outStates) {
319 SparseArray<Parcelable> viewStates = null;
320
321 final int itemCount = size();
322 for (int i = 0; i < itemCount; i++) {
323 final MenuItem item = getItem(i);
324 final View v = item.getActionView();
325 if (v != null && v.getId() != View.NO_ID) {
326 if (viewStates == null) {
327 viewStates = new SparseArray<Parcelable>();
328 }
329 v.saveHierarchyState(viewStates);
330 if (item.isActionViewExpanded()) {
331 outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId());
332 }
333 }
334 if (item.hasSubMenu()) {
335 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
336 subMenu.saveActionViewStates(outStates);
337 }
338 }
339
340 if (viewStates != null) {
341 outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates);
342 }
343 }
344
345 public void restoreActionViewStates(Bundle states) {
346 if (states == null) {
347 return;
348 }
349
350 SparseArray<Parcelable> viewStates = states.getSparseParcelableArray(
351 getActionViewStatesKey());
352
353 final int itemCount = size();
354 for (int i = 0; i < itemCount; i++) {
355 final MenuItem item = getItem(i);
356 final View v = item.getActionView();
357 if (v != null && v.getId() != View.NO_ID) {
358 v.restoreHierarchyState(viewStates);
359 }
360 if (item.hasSubMenu()) {
361 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
362 subMenu.restoreActionViewStates(states);
363 }
364 }
365
366 final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID);
367 if (expandedId > 0) {
368 MenuItem itemToExpand = findItem(expandedId);
369 if (itemToExpand != null) {
370 itemToExpand.expandActionView();
371 }
372 }
373 }
374
375 protected String getActionViewStatesKey() {
376 return ACTION_VIEW_STATES_KEY;
377 }
378
Adam Powell696cba52011-03-29 10:38:16 -0700379 public void setCallback(Callback cb) {
380 mCallback = cb;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 }
382
383 /**
384 * Adds an item to the menu. The other add methods funnel to this.
385 */
386 private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
387 final int ordering = getOrdering(categoryOrder);
388
Adam Powell4d9861e2010-08-17 11:14:40 -0700389 final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
390 ordering, title, mDefaultShowAsAction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391
392 if (mCurrentMenuInfo != null) {
393 // Pass along the current menu info
394 item.setMenuInfo(mCurrentMenuInfo);
395 }
396
397 mItems.add(findInsertIndex(mItems, ordering), item);
Adam Powell696cba52011-03-29 10:38:16 -0700398 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399
400 return item;
401 }
402
403 public MenuItem add(CharSequence title) {
404 return addInternal(0, 0, 0, title);
405 }
406
407 public MenuItem add(int titleRes) {
408 return addInternal(0, 0, 0, mResources.getString(titleRes));
409 }
410
411 public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
412 return addInternal(group, id, categoryOrder, title);
413 }
414
415 public MenuItem add(int group, int id, int categoryOrder, int title) {
416 return addInternal(group, id, categoryOrder, mResources.getString(title));
417 }
418
419 public SubMenu addSubMenu(CharSequence title) {
420 return addSubMenu(0, 0, 0, title);
421 }
422
423 public SubMenu addSubMenu(int titleRes) {
424 return addSubMenu(0, 0, 0, mResources.getString(titleRes));
425 }
426
427 public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
428 final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
429 final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
430 item.setSubMenu(subMenu);
431
432 return subMenu;
433 }
434
435 public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
436 return addSubMenu(group, id, categoryOrder, mResources.getString(title));
437 }
438
439 public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
440 Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
441 PackageManager pm = mContext.getPackageManager();
442 final List<ResolveInfo> lri =
443 pm.queryIntentActivityOptions(caller, specifics, intent, 0);
444 final int N = lri != null ? lri.size() : 0;
445
446 if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
447 removeGroup(group);
448 }
449
450 for (int i=0; i<N; i++) {
451 final ResolveInfo ri = lri.get(i);
452 Intent rintent = new Intent(
453 ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
454 rintent.setComponent(new ComponentName(
455 ri.activityInfo.applicationInfo.packageName,
456 ri.activityInfo.name));
457 final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
458 .setIcon(ri.loadIcon(pm))
459 .setIntent(rintent);
460 if (outSpecificItems != null && ri.specificIndex >= 0) {
461 outSpecificItems[ri.specificIndex] = item;
462 }
463 }
464
465 return N;
466 }
467
468 public void removeItem(int id) {
469 removeItemAtInt(findItemIndex(id), true);
470 }
471
472 public void removeGroup(int group) {
473 final int i = findGroupIndex(group);
474
475 if (i >= 0) {
476 final int maxRemovable = mItems.size() - i;
477 int numRemoved = 0;
478 while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
479 // Don't force update for each one, this method will do it at the end
480 removeItemAtInt(i, false);
481 }
482
483 // Notify menu views
Adam Powell696cba52011-03-29 10:38:16 -0700484 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 }
486 }
487
488 /**
489 * Remove the item at the given index and optionally forces menu views to
490 * update.
491 *
492 * @param index The index of the item to be removed. If this index is
493 * invalid an exception is thrown.
494 * @param updateChildrenOnMenuViews Whether to force update on menu views.
495 * Please make sure you eventually call this after your batch of
496 * removals.
497 */
498 private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
499 if ((index < 0) || (index >= mItems.size())) return;
500
501 mItems.remove(index);
502
Adam Powell696cba52011-03-29 10:38:16 -0700503 if (updateChildrenOnMenuViews) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 }
505
506 public void removeItemAt(int index) {
507 removeItemAtInt(index, true);
508 }
509
510 public void clearAll() {
511 mPreventDispatchingItemsChanged = true;
512 clear();
513 clearHeader();
514 mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700515 mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 onItemsChanged(true);
517 }
518
519 public void clear() {
Adam Powell6b0e97c2011-08-03 12:06:32 -0700520 if (mExpandedItem != null) {
521 collapseItemActionView(mExpandedItem);
522 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 mItems.clear();
524
525 onItemsChanged(true);
526 }
527
528 void setExclusiveItemChecked(MenuItem item) {
529 final int group = item.getGroupId();
530
531 final int N = mItems.size();
532 for (int i = 0; i < N; i++) {
533 MenuItemImpl curItem = mItems.get(i);
534 if (curItem.getGroupId() == group) {
535 if (!curItem.isExclusiveCheckable()) continue;
536 if (!curItem.isCheckable()) continue;
537
538 // Check the item meant to be checked, uncheck the others (that are in the group)
539 curItem.setCheckedInt(curItem == item);
540 }
541 }
542 }
543
544 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
545 final int N = mItems.size();
546
547 for (int i = 0; i < N; i++) {
548 MenuItemImpl item = mItems.get(i);
549 if (item.getGroupId() == group) {
550 item.setExclusiveCheckable(exclusive);
551 item.setCheckable(checkable);
552 }
553 }
554 }
555
556 public void setGroupVisible(int group, boolean visible) {
557 final int N = mItems.size();
558
559 // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
560 // than setVisible and at the end notify of items being changed
561
562 boolean changedAtLeastOneItem = false;
563 for (int i = 0; i < N; i++) {
564 MenuItemImpl item = mItems.get(i);
565 if (item.getGroupId() == group) {
566 if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
567 }
568 }
569
Adam Powell23f4cc02011-08-18 10:30:46 -0700570 if (changedAtLeastOneItem) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571 }
572
573 public void setGroupEnabled(int group, boolean enabled) {
574 final int N = mItems.size();
575
576 for (int i = 0; i < N; i++) {
577 MenuItemImpl item = mItems.get(i);
578 if (item.getGroupId() == group) {
579 item.setEnabled(enabled);
580 }
581 }
582 }
583
584 public boolean hasVisibleItems() {
585 final int size = size();
586
587 for (int i = 0; i < size; i++) {
588 MenuItemImpl item = mItems.get(i);
589 if (item.isVisible()) {
590 return true;
591 }
592 }
593
594 return false;
595 }
596
597 public MenuItem findItem(int id) {
598 final int size = size();
599 for (int i = 0; i < size; i++) {
600 MenuItemImpl item = mItems.get(i);
601 if (item.getItemId() == id) {
602 return item;
603 } else if (item.hasSubMenu()) {
604 MenuItem possibleItem = item.getSubMenu().findItem(id);
605
606 if (possibleItem != null) {
607 return possibleItem;
608 }
609 }
610 }
611
612 return null;
613 }
614
615 public int findItemIndex(int id) {
616 final int size = size();
617
618 for (int i = 0; i < size; i++) {
619 MenuItemImpl item = mItems.get(i);
620 if (item.getItemId() == id) {
621 return i;
622 }
623 }
624
625 return -1;
626 }
627
628 public int findGroupIndex(int group) {
629 return findGroupIndex(group, 0);
630 }
631
632 public int findGroupIndex(int group, int start) {
633 final int size = size();
634
635 if (start < 0) {
636 start = 0;
637 }
638
639 for (int i = start; i < size; i++) {
640 final MenuItemImpl item = mItems.get(i);
641
642 if (item.getGroupId() == group) {
643 return i;
644 }
645 }
646
647 return -1;
648 }
649
650 public int size() {
651 return mItems.size();
652 }
653
654 /** {@inheritDoc} */
655 public MenuItem getItem(int index) {
656 return mItems.get(index);
657 }
658
659 public boolean isShortcutKey(int keyCode, KeyEvent event) {
660 return findItemWithShortcutForKey(keyCode, event) != null;
661 }
662
663 public void setQwertyMode(boolean isQwerty) {
664 mQwertyMode = isQwerty;
Adam Powell696cba52011-03-29 10:38:16 -0700665
666 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 }
668
669 /**
670 * Returns the ordering across all items. This will grab the category from
671 * the upper bits, find out how to order the category with respect to other
672 * categories, and combine it with the lower bits.
673 *
674 * @param categoryOrder The category order for a particular item (if it has
675 * not been or/add with a category, the default category is
676 * assumed).
677 * @return An ordering integer that can be used to order this item across
678 * all the items (even from other categories).
679 */
Adam Powell696cba52011-03-29 10:38:16 -0700680 private static int getOrdering(int categoryOrder) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
682
683 if (index < 0 || index >= sCategoryToOrder.length) {
684 throw new IllegalArgumentException("order does not contain a valid category.");
685 }
686
687 return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
688 }
689
690 /**
691 * @return whether the menu shortcuts are in qwerty mode or not
692 */
693 boolean isQwertyMode() {
694 return mQwertyMode;
695 }
696
697 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 * Sets whether the shortcuts should be visible on menus. Devices without hardware
699 * key input will never make shortcuts visible even if this method is passed 'true'.
700 *
701 * @param shortcutsVisible Whether shortcuts should be visible (if true and a
702 * menu item does not have a shortcut defined, that item will
703 * still NOT show a shortcut)
704 */
705 public void setShortcutsVisible(boolean shortcutsVisible) {
706 if (mShortcutsVisible == shortcutsVisible) return;
Jeff Brown4aed78b2011-01-14 17:36:55 -0800707
708 setShortcutsVisibleInner(shortcutsVisible);
Adam Powell696cba52011-03-29 10:38:16 -0700709 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 }
711
Jeff Brown4aed78b2011-01-14 17:36:55 -0800712 private void setShortcutsVisibleInner(boolean shortcutsVisible) {
713 mShortcutsVisible = shortcutsVisible
714 && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
715 && mResources.getBoolean(
716 com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
717 }
718
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 /**
720 * @return Whether shortcuts should be visible on menus.
721 */
722 public boolean isShortcutsVisible() {
723 return mShortcutsVisible;
724 }
725
726 Resources getResources() {
727 return mResources;
728 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729
730 public Context getContext() {
731 return mContext;
732 }
733
Adam Powell696cba52011-03-29 10:38:16 -0700734 boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
735 return mCallback != null && mCallback.onMenuItemSelected(menu, item);
736 }
737
738 /**
739 * Dispatch a mode change event to this menu's callback.
740 */
741 public void changeMenuMode() {
742 if (mCallback != null) {
743 mCallback.onMenuModeChange(this);
744 }
745 }
746
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
748 for (int i = items.size() - 1; i >= 0; i--) {
749 MenuItemImpl item = items.get(i);
750 if (item.getOrdering() <= ordering) {
751 return i + 1;
752 }
753 }
754
755 return 0;
756 }
757
758 public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
759 final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
760
761 boolean handled = false;
762
763 if (item != null) {
764 handled = performItemAction(item, flags);
765 }
766
767 if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
768 close(true);
769 }
770
771 return handled;
772 }
773
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100774 /*
775 * This function will return all the menu and sub-menu items that can
776 * be directly (the shortcut directly corresponds) and indirectly
777 * (the ALT-enabled char corresponds to the shortcut) associated
778 * with the keyCode.
779 */
Adam Powell696cba52011-03-29 10:38:16 -0700780 void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781 final boolean qwerty = isQwertyMode();
782 final int metaState = event.getMetaState();
783 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
784 // Get the chars associated with the keyCode (i.e using any chording combo)
785 final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
786 // The delete key is not mapped to '\b' so we treat it specially
787 if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
Adam Powell696cba52011-03-29 10:38:16 -0700788 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 }
790
791 // Look for an item whose shortcut is this key.
792 final int N = mItems.size();
793 for (int i = 0; i < N; i++) {
794 MenuItemImpl item = mItems.get(i);
795 if (item.hasSubMenu()) {
Adam Powell696cba52011-03-29 10:38:16 -0700796 ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100798 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
799 if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
800 (shortcutChar != 0) &&
801 (shortcutChar == possibleChars.meta[0]
802 || shortcutChar == possibleChars.meta[2]
803 || (qwerty && shortcutChar == '\b' &&
804 keyCode == KeyEvent.KEYCODE_DEL)) &&
805 item.isEnabled()) {
806 items.add(item);
807 }
808 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100809 }
810
811 /*
812 * We want to return the menu item associated with the key, but if there is no
813 * ambiguity (i.e. there is only one menu item corresponding to the key) we want
814 * to return it even if it's not an exact match; this allow the user to
815 * _not_ use the ALT key for example, making the use of shortcuts slightly more
816 * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
817 * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
818 *
819 * On the other hand, if two (or more) shortcuts corresponds to the same key,
820 * we have to only return the exact match.
821 */
822 MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
823 // Get all items that can be associated directly or indirectly with the keyCode
Adam Powell696cba52011-03-29 10:38:16 -0700824 ArrayList<MenuItemImpl> items = mTempShortcutItemList;
825 items.clear();
826 findItemsWithShortcutForKey(items, keyCode, event);
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100827
Adam Powell696cba52011-03-29 10:38:16 -0700828 if (items.isEmpty()) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100829 return null;
830 }
831
832 final int metaState = event.getMetaState();
833 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
834 // Get the chars associated with the keyCode (i.e using any chording combo)
835 event.getKeyData(possibleChars);
836
837 // If we have only one element, we can safely returns it
Adam Powell696cba52011-03-29 10:38:16 -0700838 final int size = items.size();
839 if (size == 1) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100840 return items.get(0);
841 }
842
843 final boolean qwerty = isQwertyMode();
844 // If we found more than one item associated with the key,
845 // we have to return the exact match
Adam Powell696cba52011-03-29 10:38:16 -0700846 for (int i = 0; i < size; i++) {
847 final MenuItemImpl item = items.get(i);
848 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
849 item.getNumericShortcut();
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100850 if ((shortcutChar == possibleChars.meta[0] &&
851 (metaState & KeyEvent.META_ALT_ON) == 0)
852 || (shortcutChar == possibleChars.meta[2] &&
853 (metaState & KeyEvent.META_ALT_ON) != 0)
854 || (qwerty && shortcutChar == '\b' &&
855 keyCode == KeyEvent.KEYCODE_DEL)) {
856 return item;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858 }
859 return null;
860 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100861
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 public boolean performIdentifierAction(int id, int flags) {
863 // Look for an item whose identifier is the id.
864 return performItemAction(findItem(id), flags);
865 }
866
867 public boolean performItemAction(MenuItem item, int flags) {
868 MenuItemImpl itemImpl = (MenuItemImpl) item;
869
870 if (itemImpl == null || !itemImpl.isEnabled()) {
871 return false;
Adam Powell8d02dea2011-05-31 21:35:13 -0700872 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700873
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 boolean invoked = itemImpl.invoke();
875
Adam Powellf77f4802012-05-14 16:44:43 -0700876 final ActionProvider provider = item.getActionProvider();
877 final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
Adam Powell8d02dea2011-05-31 21:35:13 -0700878 if (itemImpl.hasCollapsibleActionView()) {
879 invoked |= itemImpl.expandActionView();
880 if (invoked) close(true);
Adam Powellf77f4802012-05-14 16:44:43 -0700881 } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 close(false);
883
Adam Powellf77f4802012-05-14 16:44:43 -0700884 if (!itemImpl.hasSubMenu()) {
885 itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
886 }
887
888 final SubMenuBuilder subMenu = (SubMenuBuilder) itemImpl.getSubMenu();
889 if (providerHasSubMenu) {
Adam Powell961dd112011-07-12 14:25:23 -0700890 provider.onPrepareSubMenu(subMenu);
891 }
892 invoked |= dispatchSubMenuSelected(subMenu);
Adam Powell696cba52011-03-29 10:38:16 -0700893 if (!invoked) close(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 } else {
895 if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
896 close(true);
897 }
898 }
899
900 return invoked;
901 }
902
903 /**
904 * Closes the visible menu.
905 *
906 * @param allMenusAreClosing Whether the menus are completely closing (true),
907 * or whether there is another menu coming in this menu's place
908 * (false). For example, if the menu is closing because a
909 * sub menu is about to be shown, <var>allMenusAreClosing</var>
910 * is false.
911 */
912 final void close(boolean allMenusAreClosing) {
Adam Powell696cba52011-03-29 10:38:16 -0700913 if (mIsClosing) return;
914
915 mIsClosing = true;
916 for (WeakReference<MenuPresenter> ref : mPresenters) {
917 final MenuPresenter presenter = ref.get();
918 if (presenter == null) {
919 mPresenters.remove(ref);
920 } else {
921 presenter.onCloseMenu(this, allMenusAreClosing);
922 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 }
Adam Powell696cba52011-03-29 10:38:16 -0700924 mIsClosing = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 }
926
927 /** {@inheritDoc} */
928 public void close() {
929 close(true);
930 }
931
932 /**
933 * Called when an item is added or removed.
934 *
Adam Powell696cba52011-03-29 10:38:16 -0700935 * @param structureChanged true if the menu structure changed,
936 * false if only item properties changed.
Adam Powell23f4cc02011-08-18 10:30:46 -0700937 * (Visibility is a structural property since it affects layout.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 */
Adam Powell696cba52011-03-29 10:38:16 -0700939 void onItemsChanged(boolean structureChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800940 if (!mPreventDispatchingItemsChanged) {
Adam Powell696cba52011-03-29 10:38:16 -0700941 if (structureChanged) {
942 mIsVisibleItemsStale = true;
943 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 }
Adam Powell696cba52011-03-29 10:38:16 -0700945
946 dispatchPresenterUpdate(structureChanged);
947 } else {
948 mItemsChangedWhileDispatchPrevented = true;
949 }
950 }
951
952 /**
953 * Stop dispatching item changed events to presenters until
954 * {@link #startDispatchingItemsChanged()} is called. Useful when
955 * many menu operations are going to be performed as a batch.
956 */
957 public void stopDispatchingItemsChanged() {
Adam Powella86b3502011-04-21 14:49:23 -0700958 if (!mPreventDispatchingItemsChanged) {
959 mPreventDispatchingItemsChanged = true;
960 mItemsChangedWhileDispatchPrevented = false;
961 }
Adam Powell696cba52011-03-29 10:38:16 -0700962 }
963
964 public void startDispatchingItemsChanged() {
965 mPreventDispatchingItemsChanged = false;
966
967 if (mItemsChangedWhileDispatchPrevented) {
968 mItemsChangedWhileDispatchPrevented = false;
969 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970 }
971 }
972
973 /**
974 * Called by {@link MenuItemImpl} when its visible flag is changed.
975 * @param item The item that has gone through a visibility change.
976 */
977 void onItemVisibleChanged(MenuItemImpl item) {
978 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -0700979 mIsVisibleItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -0700980 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800981 }
982
Adam Powell96675b12010-06-10 18:58:59 -0700983 /**
984 * Called by {@link MenuItemImpl} when its action request status is changed.
985 * @param item The item that has gone through a change in action request status.
986 */
987 void onItemActionRequestChanged(MenuItemImpl item) {
988 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -0700989 mIsActionItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -0700990 onItemsChanged(true);
Adam Powell96675b12010-06-10 18:58:59 -0700991 }
992
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993 ArrayList<MenuItemImpl> getVisibleItems() {
994 if (!mIsVisibleItemsStale) return mVisibleItems;
995
996 // Refresh the visible items
997 mVisibleItems.clear();
998
999 final int itemsSize = mItems.size();
1000 MenuItemImpl item;
1001 for (int i = 0; i < itemsSize; i++) {
1002 item = mItems.get(i);
1003 if (item.isVisible()) mVisibleItems.add(item);
1004 }
1005
1006 mIsVisibleItemsStale = false;
Adam Powell96675b12010-06-10 18:58:59 -07001007 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008
1009 return mVisibleItems;
1010 }
Adam Powell36fced92011-01-16 15:48:07 -08001011
1012 /**
1013 * This method determines which menu items get to be 'action items' that will appear
1014 * in an action bar and which items should be 'overflow items' in a secondary menu.
1015 * The rules are as follows:
1016 *
1017 * <p>Items are considered for inclusion in the order specified within the menu.
1018 * There is a limit of mMaxActionItems as a total count, optionally including the overflow
1019 * menu button itself. This is a soft limit; if an item shares a group ID with an item
1020 * previously included as an action item, the new item will stay with its group and become
1021 * an action item itself even if it breaks the max item count limit. This is done to
1022 * limit the conceptual complexity of the items presented within an action bar. Only a few
1023 * unrelated concepts should be presented to the user in this space, and groups are treated
1024 * as a single concept.
1025 *
1026 * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
1027 * limit may be broken by a single item that exceeds the remaining space, but no further
1028 * items may be added. If an item that is part of a group cannot fit within the remaining
1029 * measured width, the entire group will be demoted to overflow. This is done to ensure room
1030 * for navigation and other affordances in the action bar as well as reduce general UI clutter.
1031 *
1032 * <p>The space freed by demoting a full group cannot be consumed by future menu items.
1033 * Once items begin to overflow, all future items become overflow items as well. This is
1034 * to avoid inadvertent reordering that may break the app's intended design.
Adam Powell36fced92011-01-16 15:48:07 -08001035 */
Adam Powell696cba52011-03-29 10:38:16 -07001036 public void flagActionItems() {
Adam Powellda971082013-10-03 18:21:58 -07001037 // Important side effect: if getVisibleItems is stale it may refresh,
1038 // which can affect action items staleness.
1039 final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
1040
Adam Powell96675b12010-06-10 18:58:59 -07001041 if (!mIsActionItemsStale) {
1042 return;
1043 }
Adam Powell8028dd32010-07-15 10:16:33 -07001044
Adam Powell696cba52011-03-29 10:38:16 -07001045 // Presenters flag action items as needed.
1046 boolean flagged = false;
1047 for (WeakReference<MenuPresenter> ref : mPresenters) {
1048 final MenuPresenter presenter = ref.get();
1049 if (presenter == null) {
1050 mPresenters.remove(ref);
Adam Powell8028dd32010-07-15 10:16:33 -07001051 } else {
Adam Powell696cba52011-03-29 10:38:16 -07001052 flagged |= presenter.flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001053 }
1054 }
Adam Powell8028dd32010-07-15 10:16:33 -07001055
Adam Powell696cba52011-03-29 10:38:16 -07001056 if (flagged) {
1057 mActionItems.clear();
1058 mNonActionItems.clear();
Adam Powell696cba52011-03-29 10:38:16 -07001059 final int itemsSize = visibleItems.size();
1060 for (int i = 0; i < itemsSize; i++) {
1061 MenuItemImpl item = visibleItems.get(i);
1062 if (item.isActionButton()) {
1063 mActionItems.add(item);
1064 } else {
1065 mNonActionItems.add(item);
Adam Powell36fced92011-01-16 15:48:07 -08001066 }
Adam Powell96675b12010-06-10 18:58:59 -07001067 }
Adam Powell04dc06f2011-08-22 18:11:11 -07001068 } else {
1069 // Nobody flagged anything, everything is a non-action item.
Adam Powell696cba52011-03-29 10:38:16 -07001070 // (This happens during a first pass with no action-item presenters.)
1071 mActionItems.clear();
1072 mNonActionItems.clear();
1073 mNonActionItems.addAll(getVisibleItems());
Adam Powell96675b12010-06-10 18:58:59 -07001074 }
Adam Powell96675b12010-06-10 18:58:59 -07001075 mIsActionItemsStale = false;
1076 }
1077
Adam Powell696cba52011-03-29 10:38:16 -07001078 ArrayList<MenuItemImpl> getActionItems() {
1079 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001080 return mActionItems;
1081 }
1082
Adam Powell696cba52011-03-29 10:38:16 -07001083 ArrayList<MenuItemImpl> getNonActionItems() {
1084 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001085 return mNonActionItems;
1086 }
Adam Powell36fced92011-01-16 15:48:07 -08001087
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088 public void clearHeader() {
1089 mHeaderIcon = null;
1090 mHeaderTitle = null;
1091 mHeaderView = null;
1092
1093 onItemsChanged(false);
1094 }
1095
1096 private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
1097 final Drawable icon, final View view) {
1098 final Resources r = getResources();
1099
1100 if (view != null) {
1101 mHeaderView = view;
1102
1103 // If using a custom view, then the title and icon aren't used
1104 mHeaderTitle = null;
1105 mHeaderIcon = null;
1106 } else {
1107 if (titleRes > 0) {
1108 mHeaderTitle = r.getText(titleRes);
1109 } else if (title != null) {
1110 mHeaderTitle = title;
1111 }
1112
1113 if (iconRes > 0) {
1114 mHeaderIcon = r.getDrawable(iconRes);
1115 } else if (icon != null) {
1116 mHeaderIcon = icon;
1117 }
1118
1119 // If using the title or icon, then a custom view isn't used
1120 mHeaderView = null;
1121 }
1122
1123 // Notify of change
1124 onItemsChanged(false);
1125 }
1126
1127 /**
1128 * Sets the header's title. This replaces the header view. Called by the
1129 * builder-style methods of subclasses.
1130 *
1131 * @param title The new title.
1132 * @return This MenuBuilder so additional setters can be called.
1133 */
1134 protected MenuBuilder setHeaderTitleInt(CharSequence title) {
1135 setHeaderInternal(0, title, 0, null, null);
1136 return this;
1137 }
1138
1139 /**
1140 * Sets the header's title. This replaces the header view. Called by the
1141 * builder-style methods of subclasses.
1142 *
1143 * @param titleRes The new title (as a resource ID).
1144 * @return This MenuBuilder so additional setters can be called.
1145 */
1146 protected MenuBuilder setHeaderTitleInt(int titleRes) {
1147 setHeaderInternal(titleRes, null, 0, null, null);
1148 return this;
1149 }
1150
1151 /**
1152 * Sets the header's icon. This replaces the header view. Called by the
1153 * builder-style methods of subclasses.
1154 *
1155 * @param icon The new icon.
1156 * @return This MenuBuilder so additional setters can be called.
1157 */
1158 protected MenuBuilder setHeaderIconInt(Drawable icon) {
1159 setHeaderInternal(0, null, 0, icon, null);
1160 return this;
1161 }
1162
1163 /**
1164 * Sets the header's icon. This replaces the header view. Called by the
1165 * builder-style methods of subclasses.
1166 *
1167 * @param iconRes The new icon (as a resource ID).
1168 * @return This MenuBuilder so additional setters can be called.
1169 */
1170 protected MenuBuilder setHeaderIconInt(int iconRes) {
1171 setHeaderInternal(0, null, iconRes, null, null);
1172 return this;
1173 }
1174
1175 /**
1176 * Sets the header's view. This replaces the title and icon. Called by the
1177 * builder-style methods of subclasses.
1178 *
1179 * @param view The new view.
1180 * @return This MenuBuilder so additional setters can be called.
1181 */
1182 protected MenuBuilder setHeaderViewInt(View view) {
1183 setHeaderInternal(0, null, 0, null, view);
1184 return this;
1185 }
1186
1187 public CharSequence getHeaderTitle() {
1188 return mHeaderTitle;
1189 }
1190
1191 public Drawable getHeaderIcon() {
1192 return mHeaderIcon;
1193 }
1194
1195 public View getHeaderView() {
1196 return mHeaderView;
1197 }
1198
1199 /**
1200 * Gets the root menu (if this is a submenu, find its root menu).
1201 * @return The root menu.
1202 */
1203 public MenuBuilder getRootMenu() {
1204 return this;
1205 }
1206
1207 /**
1208 * Sets the current menu info that is set on all items added to this menu
1209 * (until this is called again with different menu info, in which case that
1210 * one will be added to all subsequent item additions).
1211 *
1212 * @param menuInfo The extra menu information to add.
1213 */
1214 public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
1215 mCurrentMenuInfo = menuInfo;
1216 }
1217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 void setOptionalIconsVisible(boolean visible) {
1219 mOptionalIconsVisible = visible;
1220 }
1221
1222 boolean getOptionalIconsVisible() {
1223 return mOptionalIconsVisible;
1224 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001225
1226 public boolean expandItemActionView(MenuItemImpl item) {
1227 if (mPresenters.isEmpty()) return false;
1228
1229 boolean expanded = false;
1230
1231 stopDispatchingItemsChanged();
1232 for (WeakReference<MenuPresenter> ref : mPresenters) {
1233 final MenuPresenter presenter = ref.get();
1234 if (presenter == null) {
1235 mPresenters.remove(ref);
1236 } else if ((expanded = presenter.expandItemActionView(this, item))) {
1237 break;
1238 }
1239 }
1240 startDispatchingItemsChanged();
1241
Adam Powell6b0e97c2011-08-03 12:06:32 -07001242 if (expanded) {
1243 mExpandedItem = item;
1244 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001245 return expanded;
1246 }
1247
1248 public boolean collapseItemActionView(MenuItemImpl item) {
Adam Powell6b0e97c2011-08-03 12:06:32 -07001249 if (mPresenters.isEmpty() || mExpandedItem != item) return false;
Adam Powell8d02dea2011-05-31 21:35:13 -07001250
1251 boolean collapsed = false;
1252
1253 stopDispatchingItemsChanged();
1254 for (WeakReference<MenuPresenter> ref : mPresenters) {
1255 final MenuPresenter presenter = ref.get();
1256 if (presenter == null) {
1257 mPresenters.remove(ref);
1258 } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
1259 break;
1260 }
1261 }
1262 startDispatchingItemsChanged();
1263
Adam Powell6b0e97c2011-08-03 12:06:32 -07001264 if (collapsed) {
1265 mExpandedItem = null;
1266 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001267 return collapsed;
1268 }
Adam Powell275702c2011-09-23 17:34:04 -07001269
1270 public MenuItemImpl getExpandedItem() {
1271 return mExpandedItem;
1272 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273}