blob: c6d386d1fa481663f66389419b78126028195443 [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
Adam Powell696cba52011-03-29 10:38:16 -070019import com.android.internal.view.menu.MenuView.ItemView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020
Adam Powell151af192010-05-04 14:44:45 -070021import android.content.ActivityNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Intent;
23import android.graphics.drawable.Drawable;
Adam Powell151af192010-05-04 14:44:45 -070024import android.util.Log;
Adam Powell696cba52011-03-29 10:38:16 -070025import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.view.LayoutInflater;
27import android.view.MenuItem;
28import android.view.SubMenu;
29import android.view.View;
30import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32/**
33 * @hide
34 */
35public final class MenuItemImpl implements MenuItem {
Adam Powell151af192010-05-04 14:44:45 -070036 private static final String TAG = "MenuItemImpl";
37
Adam Powelld8404b22010-10-13 14:26:41 -070038 private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
39 SHOW_AS_ACTION_IF_ROOM |
40 SHOW_AS_ACTION_ALWAYS;
41
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 private final int mId;
43 private final int mGroup;
44 private final int mCategoryOrder;
45 private final int mOrdering;
46 private CharSequence mTitle;
47 private CharSequence mTitleCondensed;
48 private Intent mIntent;
49 private char mShortcutNumericChar;
50 private char mShortcutAlphabeticChar;
51
52 /** The icon's drawable which is only created as needed */
53 private Drawable mIconDrawable;
54 /**
55 * The icon's resource ID which is used to get the Drawable when it is
56 * needed (if the Drawable isn't already obtained--only one of the two is
57 * needed).
58 */
59 private int mIconResId = NO_ICON;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
61 /** The menu to which this item belongs */
62 private MenuBuilder mMenu;
63 /** If this item should launch a sub menu, this is the sub menu to launch */
64 private SubMenuBuilder mSubMenu;
65
66 private Runnable mItemCallback;
67 private MenuItem.OnMenuItemClickListener mClickListener;
68
69 private int mFlags = ENABLED;
70 private static final int CHECKABLE = 0x00000001;
71 private static final int CHECKED = 0x00000002;
72 private static final int EXCLUSIVE = 0x00000004;
73 private static final int HIDDEN = 0x00000008;
74 private static final int ENABLED = 0x00000010;
Adam Powell96675b12010-06-10 18:58:59 -070075 private static final int IS_ACTION = 0x00000020;
Adam Powell89e06452010-06-23 20:24:52 -070076
Adam Powell96675b12010-06-10 18:58:59 -070077 private int mShowAsAction = SHOW_AS_ACTION_NEVER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078
Adam Powellcf78b3e2010-09-12 18:25:23 -070079 private View mActionView;
80
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 /** Used for the icon resource ID if this item does not have an icon */
82 static final int NO_ICON = 0;
83
84 /**
85 * Current use case is for context menu: Extra information linked to the
86 * View that added this item to the context menu.
87 */
88 private ContextMenuInfo mMenuInfo;
89
90 private static String sPrependShortcutLabel;
91 private static String sEnterShortcutLabel;
92 private static String sDeleteShortcutLabel;
93 private static String sSpaceShortcutLabel;
94
95
96 /**
97 * Instantiates this menu item. The constructor
98 * {@link #MenuItemData(MenuBuilder, int, int, int, CharSequence, int)} is
99 * preferred due to lazy loading of the icon Drawable.
100 *
101 * @param menu
102 * @param group Item ordering grouping control. The item will be added after
103 * all other items whose order is <= this number, and before any
104 * that are larger than it. This can also be used to define
105 * groups of items for batch state changes. Normally use 0.
106 * @param id Unique item ID. Use 0 if you do not need a unique ID.
107 * @param categoryOrder The ordering for this item.
108 * @param title The text to display for the item.
109 */
110 MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
Adam Powell4d9861e2010-08-17 11:14:40 -0700111 CharSequence title, int showAsAction) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
113 if (sPrependShortcutLabel == null) {
114 // This is instantiated from the UI thread, so no chance of sync issues
115 sPrependShortcutLabel = menu.getContext().getResources().getString(
116 com.android.internal.R.string.prepend_shortcut_label);
117 sEnterShortcutLabel = menu.getContext().getResources().getString(
118 com.android.internal.R.string.menu_enter_shortcut_label);
119 sDeleteShortcutLabel = menu.getContext().getResources().getString(
120 com.android.internal.R.string.menu_delete_shortcut_label);
121 sSpaceShortcutLabel = menu.getContext().getResources().getString(
122 com.android.internal.R.string.menu_space_shortcut_label);
123 }
124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 mMenu = menu;
126 mId = id;
127 mGroup = group;
128 mCategoryOrder = categoryOrder;
129 mOrdering = ordering;
130 mTitle = title;
Adam Powell4d9861e2010-08-17 11:14:40 -0700131 mShowAsAction = showAsAction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 }
133
134 /**
135 * Invokes the item by calling various listeners or callbacks.
136 *
137 * @return true if the invocation was handled, false otherwise
138 */
139 public boolean invoke() {
140 if (mClickListener != null &&
141 mClickListener.onMenuItemClick(this)) {
142 return true;
143 }
144
Adam Powell696cba52011-03-29 10:38:16 -0700145 if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 return true;
147 }
148
149 if (mItemCallback != null) {
150 mItemCallback.run();
151 return true;
152 }
153
154 if (mIntent != null) {
Adam Powell151af192010-05-04 14:44:45 -0700155 try {
156 mMenu.getContext().startActivity(mIntent);
157 return true;
158 } catch (ActivityNotFoundException e) {
159 Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
160 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 }
162
163 return false;
164 }
165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 public boolean isEnabled() {
167 return (mFlags & ENABLED) != 0;
168 }
169
170 public MenuItem setEnabled(boolean enabled) {
171 if (enabled) {
172 mFlags |= ENABLED;
173 } else {
174 mFlags &= ~ENABLED;
175 }
176
Adam Powell696cba52011-03-29 10:38:16 -0700177 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178
179 return this;
180 }
181
182 public int getGroupId() {
183 return mGroup;
184 }
185
186 @ViewDebug.CapturedViewProperty
187 public int getItemId() {
188 return mId;
189 }
190
191 public int getOrder() {
192 return mCategoryOrder;
193 }
194
195 public int getOrdering() {
196 return mOrdering;
197 }
198
199 public Intent getIntent() {
200 return mIntent;
201 }
202
203 public MenuItem setIntent(Intent intent) {
204 mIntent = intent;
205 return this;
206 }
207
208 Runnable getCallback() {
209 return mItemCallback;
210 }
211
212 public MenuItem setCallback(Runnable callback) {
213 mItemCallback = callback;
214 return this;
215 }
216
217 public char getAlphabeticShortcut() {
218 return mShortcutAlphabeticChar;
219 }
220
221 public MenuItem setAlphabeticShortcut(char alphaChar) {
222 if (mShortcutAlphabeticChar == alphaChar) return this;
223
224 mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
225
Adam Powell696cba52011-03-29 10:38:16 -0700226 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227
228 return this;
229 }
230
231 public char getNumericShortcut() {
232 return mShortcutNumericChar;
233 }
234
235 public MenuItem setNumericShortcut(char numericChar) {
236 if (mShortcutNumericChar == numericChar) return this;
237
238 mShortcutNumericChar = numericChar;
239
Adam Powell696cba52011-03-29 10:38:16 -0700240 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241
242 return this;
243 }
244
245 public MenuItem setShortcut(char numericChar, char alphaChar) {
246 mShortcutNumericChar = numericChar;
247 mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
248
Adam Powell696cba52011-03-29 10:38:16 -0700249 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
251 return this;
252 }
253
254 /**
255 * @return The active shortcut (based on QWERTY-mode of the menu).
256 */
257 char getShortcut() {
258 return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
259 }
260
261 /**
262 * @return The label to show for the shortcut. This includes the chording
263 * key (for example 'Menu+a'). Also, any non-human readable
264 * characters should be human readable (for example 'Menu+enter').
265 */
266 String getShortcutLabel() {
267
268 char shortcut = getShortcut();
269 if (shortcut == 0) {
270 return "";
271 }
272
273 StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
274 switch (shortcut) {
275
276 case '\n':
277 sb.append(sEnterShortcutLabel);
278 break;
279
280 case '\b':
281 sb.append(sDeleteShortcutLabel);
282 break;
283
284 case ' ':
285 sb.append(sSpaceShortcutLabel);
286 break;
287
288 default:
289 sb.append(shortcut);
290 break;
291 }
292
293 return sb.toString();
294 }
295
296 /**
297 * @return Whether this menu item should be showing shortcuts (depends on
298 * whether the menu should show shortcuts and whether this item has
299 * a shortcut defined)
300 */
301 boolean shouldShowShortcut() {
302 // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
303 return mMenu.isShortcutsVisible() && (getShortcut() != 0);
304 }
305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 public SubMenu getSubMenu() {
307 return mSubMenu;
308 }
309
310 public boolean hasSubMenu() {
311 return mSubMenu != null;
312 }
313
314 void setSubMenu(SubMenuBuilder subMenu) {
315 if ((mMenu != null) && (mMenu instanceof SubMenu)) {
316 throw new UnsupportedOperationException(
317 "Attempt to add a sub-menu to a sub-menu.");
318 }
319
320 mSubMenu = subMenu;
321
322 subMenu.setHeaderTitle(getTitle());
323 }
324
325 @ViewDebug.CapturedViewProperty
326 public CharSequence getTitle() {
327 return mTitle;
328 }
329
330 /**
331 * Gets the title for a particular {@link ItemView}
332 *
333 * @param itemView The ItemView that is receiving the title
334 * @return Either the title or condensed title based on what the ItemView
335 * prefers
336 */
337 CharSequence getTitleForItemView(MenuView.ItemView itemView) {
338 return ((itemView != null) && itemView.prefersCondensedTitle())
339 ? getTitleCondensed()
340 : getTitle();
341 }
342
343 public MenuItem setTitle(CharSequence title) {
344 mTitle = title;
345
Adam Powell696cba52011-03-29 10:38:16 -0700346 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347
348 if (mSubMenu != null) {
349 mSubMenu.setHeaderTitle(title);
350 }
351
352 return this;
353 }
354
355 public MenuItem setTitle(int title) {
356 return setTitle(mMenu.getContext().getString(title));
357 }
358
359 public CharSequence getTitleCondensed() {
360 return mTitleCondensed != null ? mTitleCondensed : mTitle;
361 }
362
363 public MenuItem setTitleCondensed(CharSequence title) {
364 mTitleCondensed = title;
365
366 // Could use getTitle() in the loop below, but just cache what it would do here
367 if (title == null) {
368 title = mTitle;
369 }
370
Adam Powell696cba52011-03-29 10:38:16 -0700371 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372
373 return this;
374 }
375
376 public Drawable getIcon() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 if (mIconDrawable != null) {
378 return mIconDrawable;
379 }
380
381 if (mIconResId != NO_ICON) {
382 return mMenu.getResources().getDrawable(mIconResId);
383 }
384
385 return null;
386 }
387
388 public MenuItem setIcon(Drawable icon) {
389 mIconResId = NO_ICON;
390 mIconDrawable = icon;
Adam Powell696cba52011-03-29 10:38:16 -0700391 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392
393 return this;
394 }
395
396 public MenuItem setIcon(int iconResId) {
397 mIconDrawable = null;
398 mIconResId = iconResId;
399
400 // If we have a view, we need to push the Drawable to them
Adam Powell696cba52011-03-29 10:38:16 -0700401 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402
403 return this;
404 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405
406 public boolean isCheckable() {
407 return (mFlags & CHECKABLE) == CHECKABLE;
408 }
409
410 public MenuItem setCheckable(boolean checkable) {
411 final int oldFlags = mFlags;
412 mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
413 if (oldFlags != mFlags) {
Adam Powell696cba52011-03-29 10:38:16 -0700414 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 }
416
417 return this;
418 }
419
Adam Powell696cba52011-03-29 10:38:16 -0700420 public void setExclusiveCheckable(boolean exclusive) {
421 mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 }
423
424 public boolean isExclusiveCheckable() {
425 return (mFlags & EXCLUSIVE) != 0;
426 }
427
428 public boolean isChecked() {
429 return (mFlags & CHECKED) == CHECKED;
430 }
431
432 public MenuItem setChecked(boolean checked) {
433 if ((mFlags & EXCLUSIVE) != 0) {
434 // Call the method on the Menu since it knows about the others in this
435 // exclusive checkable group
436 mMenu.setExclusiveItemChecked(this);
437 } else {
438 setCheckedInt(checked);
439 }
440
441 return this;
442 }
443
444 void setCheckedInt(boolean checked) {
445 final int oldFlags = mFlags;
446 mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
447 if (oldFlags != mFlags) {
Adam Powell696cba52011-03-29 10:38:16 -0700448 mMenu.onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 }
450 }
451
452 public boolean isVisible() {
453 return (mFlags & HIDDEN) == 0;
454 }
455
456 /**
457 * Changes the visibility of the item. This method DOES NOT notify the
458 * parent menu of a change in this item, so this should only be called from
459 * methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)}
460 * instead.
461 *
462 * @param shown Whether to show (true) or hide (false).
463 * @return Whether the item's shown state was changed
464 */
465 boolean setVisibleInt(boolean shown) {
466 final int oldFlags = mFlags;
467 mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
468 return oldFlags != mFlags;
469 }
470
471 public MenuItem setVisible(boolean shown) {
472 // Try to set the shown state to the given state. If the shown state was changed
473 // (i.e. the previous state isn't the same as given state), notify the parent menu that
474 // the shown state has changed for this item
475 if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
476
477 return this;
478 }
479
480 public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
481 mClickListener = clickListener;
482 return this;
483 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484
485 @Override
486 public String toString() {
487 return mTitle.toString();
488 }
489
490 void setMenuInfo(ContextMenuInfo menuInfo) {
491 mMenuInfo = menuInfo;
492 }
493
494 public ContextMenuInfo getMenuInfo() {
495 return mMenuInfo;
496 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497
498 /**
Adam Powell696cba52011-03-29 10:38:16 -0700499 * @return Whether the menu should show icons for menu items.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 */
Adam Powell696cba52011-03-29 10:38:16 -0700501 public boolean shouldShowIcon() {
502 return mMenu.getOptionalIconsVisible();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 }
Adam Powell96675b12010-06-10 18:58:59 -0700504
505 public boolean isActionButton() {
506 return (mFlags & IS_ACTION) == IS_ACTION || requiresActionButton();
507 }
508
509 public boolean requestsActionButton() {
Adam Powelld8404b22010-10-13 14:26:41 -0700510 return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
Adam Powell96675b12010-06-10 18:58:59 -0700511 }
512
513 public boolean requiresActionButton() {
Adam Powelld8404b22010-10-13 14:26:41 -0700514 return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
Adam Powell96675b12010-06-10 18:58:59 -0700515 }
516
517 public void setIsActionButton(boolean isActionButton) {
518 if (isActionButton) {
519 mFlags |= IS_ACTION;
520 } else {
521 mFlags &= ~IS_ACTION;
522 }
523 }
Adam Powell89e06452010-06-23 20:24:52 -0700524
Adam Powelld8404b22010-10-13 14:26:41 -0700525 public boolean showsTextAsAction() {
Adam Powell45c515b2011-04-21 18:50:20 -0700526 return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT &&
527 mMenu.getContext().getResources().getBoolean(
528 com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
Adam Powelld8404b22010-10-13 14:26:41 -0700529 }
530
Adam Powell96675b12010-06-10 18:58:59 -0700531 public void setShowAsAction(int actionEnum) {
Adam Powelld8404b22010-10-13 14:26:41 -0700532 switch (actionEnum & SHOW_AS_ACTION_MASK) {
533 case SHOW_AS_ACTION_ALWAYS:
534 case SHOW_AS_ACTION_IF_ROOM:
535 case SHOW_AS_ACTION_NEVER:
536 // Looks good!
537 break;
538
539 default:
540 // Mutually exclusive options selected!
541 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
542 + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
543 }
Adam Powell96675b12010-06-10 18:58:59 -0700544 mShowAsAction = actionEnum;
545 mMenu.onItemActionRequestChanged(this);
546 }
Adam Powellcf78b3e2010-09-12 18:25:23 -0700547
548 public MenuItem setActionView(View view) {
549 mActionView = view;
Adam Powellabbcc242011-01-24 11:48:54 -0800550 mMenu.onItemActionRequestChanged(this);
Adam Powellcf78b3e2010-09-12 18:25:23 -0700551 return this;
552 }
553
Adam Powell3f476b32011-01-03 19:25:36 -0800554 public MenuItem setActionView(int resId) {
Adam Powelld16c9882011-01-07 11:12:02 -0800555 LayoutInflater inflater = LayoutInflater.from(mMenu.getContext());
Adam Powell696cba52011-03-29 10:38:16 -0700556 // TODO - Fix for proper parent. Lazily inflate in the presenter.
557 setActionView(inflater.inflate(resId, null));
Adam Powell3f476b32011-01-03 19:25:36 -0800558 return this;
559 }
560
Adam Powellcf78b3e2010-09-12 18:25:23 -0700561 public View getActionView() {
562 return mActionView;
563 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564}