MenuItemImpl.java revision d713f4f0643a5f6409b431f643f1ad25dba6c22f
1/* 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 android.support.v7.internal.view.menu; 18 19import android.content.ActivityNotFoundException; 20import android.content.Context; 21import android.content.Intent; 22import android.graphics.drawable.Drawable; 23import android.support.v4.view.ActionProvider; 24import android.support.v4.internal.view.SupportMenuItem; 25import android.support.v4.view.MenuItemCompat; 26import android.util.Log; 27import android.view.ContextMenu.ContextMenuInfo; 28import android.view.LayoutInflater; 29import android.view.MenuItem; 30import android.view.SubMenu; 31import android.view.View; 32import android.view.ViewDebug; 33import android.widget.LinearLayout; 34 35/** 36 * @hide 37 */ 38public final class MenuItemImpl implements SupportMenuItem { 39 40 private static final String TAG = "MenuItemImpl"; 41 42 private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER | 43 SHOW_AS_ACTION_IF_ROOM | 44 SHOW_AS_ACTION_ALWAYS; 45 46 private final int mId; 47 private final int mGroup; 48 private final int mCategoryOrder; 49 private final int mOrdering; 50 private CharSequence mTitle; 51 private CharSequence mTitleCondensed; 52 private Intent mIntent; 53 private char mShortcutNumericChar; 54 private char mShortcutAlphabeticChar; 55 56 /** 57 * The icon's drawable which is only created as needed 58 */ 59 private Drawable mIconDrawable; 60 61 /** 62 * The icon's resource ID which is used to get the Drawable when it is needed (if the Drawable 63 * isn't already obtained--only one of the two is needed). 64 */ 65 private int mIconResId = NO_ICON; 66 67 /** 68 * The menu to which this item belongs 69 */ 70 private MenuBuilder mMenu; 71 72 /** 73 * If this item should launch a sub menu, this is the sub menu to launch 74 */ 75 private SubMenuBuilder mSubMenu; 76 77 private Runnable mItemCallback; 78 private SupportMenuItem.OnMenuItemClickListener mClickListener; 79 80 private int mFlags = ENABLED; 81 private static final int CHECKABLE = 0x00000001; 82 private static final int CHECKED = 0x00000002; 83 private static final int EXCLUSIVE = 0x00000004; 84 private static final int HIDDEN = 0x00000008; 85 private static final int ENABLED = 0x00000010; 86 private static final int IS_ACTION = 0x00000020; 87 88 private int mShowAsAction = SHOW_AS_ACTION_NEVER; 89 90 private View mActionView; 91 private ActionProvider mActionProvider; 92 private MenuItemCompat.OnActionExpandListener mOnActionExpandListener; 93 private boolean mIsActionViewExpanded = false; 94 95 /** 96 * Used for the icon resource ID if this item does not have an icon 97 */ 98 static final int NO_ICON = 0; 99 100 /** 101 * Current use case is for context menu: Extra information linked to the View that added this 102 * item to the context menu. 103 */ 104 private ContextMenuInfo mMenuInfo; 105 106 private static String sPrependShortcutLabel; 107 private static String sEnterShortcutLabel; 108 private static String sDeleteShortcutLabel; 109 private static String sSpaceShortcutLabel; 110 111 112 /** 113 * Instantiates this menu item. 114 * 115 * @param group Item ordering grouping control. The item will be added after all other 116 * items whose order is <= this number, and before any that are larger than 117 * it. This can also be used to define groups of items for batch state 118 * changes. Normally use 0. 119 * @param id Unique item ID. Use 0 if you do not need a unique ID. 120 * @param categoryOrder The ordering for this item. 121 * @param title The text to display for the item. 122 */ 123 MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, 124 CharSequence title, int showAsAction) { 125 126 /*if (sPrependShortcutLabel == null) { 127 // This is instantiated from the UI thread, so no chance of sync issues 128 sPrependShortcutLabel = menu.getContext().getResources().getString( 129 com.android.internal.R.string.prepend_shortcut_label); 130 sEnterShortcutLabel = menu.getContext().getResources().getString( 131 com.android.internal.R.string.menu_enter_shortcut_label); 132 sDeleteShortcutLabel = menu.getContext().getResources().getString( 133 com.android.internal.R.string.menu_delete_shortcut_label); 134 sSpaceShortcutLabel = menu.getContext().getResources().getString( 135 com.android.internal.R.string.menu_space_shortcut_label); 136 }*/ 137 138 mMenu = menu; 139 mId = id; 140 mGroup = group; 141 mCategoryOrder = categoryOrder; 142 mOrdering = ordering; 143 mTitle = title; 144 mShowAsAction = showAsAction; 145 } 146 147 /** 148 * Invokes the item by calling various listeners or callbacks. 149 * 150 * @return true if the invocation was handled, false otherwise 151 */ 152 public boolean invoke() { 153 if (mClickListener != null && 154 mClickListener.onMenuItemClick(this)) { 155 return true; 156 } 157 158 if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) { 159 return true; 160 } 161 162 if (mItemCallback != null) { 163 mItemCallback.run(); 164 return true; 165 } 166 167 if (mIntent != null) { 168 try { 169 mMenu.getContext().startActivity(mIntent); 170 return true; 171 } catch (ActivityNotFoundException e) { 172 Log.e(TAG, "Can't find activity to handle intent; ignoring", e); 173 } 174 } 175 176 if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) { 177 return true; 178 } 179 180 return false; 181 } 182 183 @Override 184 public boolean isEnabled() { 185 return (mFlags & ENABLED) != 0; 186 } 187 188 @Override 189 public MenuItem setEnabled(boolean enabled) { 190 if (enabled) { 191 mFlags |= ENABLED; 192 } else { 193 mFlags &= ~ENABLED; 194 } 195 196 mMenu.onItemsChanged(false); 197 198 return this; 199 } 200 201 @Override 202 public int getGroupId() { 203 return mGroup; 204 } 205 206 @Override 207 @ViewDebug.CapturedViewProperty 208 public int getItemId() { 209 return mId; 210 } 211 212 @Override 213 public int getOrder() { 214 return mCategoryOrder; 215 } 216 217 public int getOrdering() { 218 return mOrdering; 219 } 220 221 @Override 222 public Intent getIntent() { 223 return mIntent; 224 } 225 226 @Override 227 public MenuItem setIntent(Intent intent) { 228 mIntent = intent; 229 return this; 230 } 231 232 Runnable getCallback() { 233 return mItemCallback; 234 } 235 236 public MenuItem setCallback(Runnable callback) { 237 mItemCallback = callback; 238 return this; 239 } 240 241 @Override 242 public char getAlphabeticShortcut() { 243 return mShortcutAlphabeticChar; 244 } 245 246 @Override 247 public MenuItem setAlphabeticShortcut(char alphaChar) { 248 if (mShortcutAlphabeticChar == alphaChar) { 249 return this; 250 } 251 252 mShortcutAlphabeticChar = Character.toLowerCase(alphaChar); 253 254 mMenu.onItemsChanged(false); 255 256 return this; 257 } 258 259 @Override 260 public char getNumericShortcut() { 261 return mShortcutNumericChar; 262 } 263 264 @Override 265 public MenuItem setNumericShortcut(char numericChar) { 266 if (mShortcutNumericChar == numericChar) { 267 return this; 268 } 269 270 mShortcutNumericChar = numericChar; 271 272 mMenu.onItemsChanged(false); 273 274 return this; 275 } 276 277 @Override 278 public MenuItem setShortcut(char numericChar, char alphaChar) { 279 mShortcutNumericChar = numericChar; 280 mShortcutAlphabeticChar = Character.toLowerCase(alphaChar); 281 282 mMenu.onItemsChanged(false); 283 284 return this; 285 } 286 287 /** 288 * @return The active shortcut (based on QWERTY-mode of the menu). 289 */ 290 char getShortcut() { 291 //return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar); 292 return mShortcutAlphabeticChar; 293 } 294 295 /** 296 * @return The label to show for the shortcut. This includes the chording key (for example 297 * 'Menu+a'). Also, any non-human readable characters should be human readable (for 298 * example 'Menu+enter'). 299 */ 300 String getShortcutLabel() { 301 302 char shortcut = getShortcut(); 303 if (shortcut == 0) { 304 return ""; 305 } 306 307 StringBuilder sb = new StringBuilder(sPrependShortcutLabel); 308 switch (shortcut) { 309 310 case '\n': 311 sb.append(sEnterShortcutLabel); 312 break; 313 314 case '\b': 315 sb.append(sDeleteShortcutLabel); 316 break; 317 318 case ' ': 319 sb.append(sSpaceShortcutLabel); 320 break; 321 322 default: 323 sb.append(shortcut); 324 break; 325 } 326 327 return sb.toString(); 328 } 329 330 /** 331 * @return Whether this menu item should be showing shortcuts (depends on whether the menu 332 * should show shortcuts and whether this item has a shortcut defined) 333 */ 334 boolean shouldShowShortcut() { 335 // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut 336 return mMenu.isShortcutsVisible() && (getShortcut() != 0); 337 } 338 339 @Override 340 public SubMenu getSubMenu() { 341 return mSubMenu; 342 } 343 344 @Override 345 public boolean hasSubMenu() { 346 return mSubMenu != null; 347 } 348 349 void setSubMenu(SubMenuBuilder subMenu) { 350 mSubMenu = subMenu; 351 352 subMenu.setHeaderTitle(getTitle()); 353 } 354 355 @Override 356 @ViewDebug.CapturedViewProperty 357 public CharSequence getTitle() { 358 return mTitle; 359 } 360 361 /** 362 * Gets the title for a particular {@link MenuView.ItemView} 363 * 364 * @param itemView The ItemView that is receiving the title 365 * @return Either the title or condensed title based on what the ItemView prefers 366 */ 367 CharSequence getTitleForItemView(MenuView.ItemView itemView) { 368 return ((itemView != null) && itemView.prefersCondensedTitle()) 369 ? getTitleCondensed() 370 : getTitle(); 371 } 372 373 @Override 374 public MenuItem setTitle(CharSequence title) { 375 mTitle = title; 376 377 mMenu.onItemsChanged(false); 378 379 if (mSubMenu != null) { 380 mSubMenu.setHeaderTitle(title); 381 } 382 383 return this; 384 } 385 386 @Override 387 public MenuItem setTitle(int title) { 388 return setTitle(mMenu.getContext().getString(title)); 389 } 390 391 @Override 392 public CharSequence getTitleCondensed() { 393 return mTitleCondensed != null ? mTitleCondensed : mTitle; 394 } 395 396 @Override 397 public MenuItem setTitleCondensed(CharSequence title) { 398 mTitleCondensed = title; 399 400 // Could use getTitle() in the loop below, but just cache what it would do here 401 if (title == null) { 402 title = mTitle; 403 } 404 405 mMenu.onItemsChanged(false); 406 407 return this; 408 } 409 410 @Override 411 public Drawable getIcon() { 412 if (mIconDrawable != null) { 413 return mIconDrawable; 414 } 415 416 if (mIconResId != NO_ICON) { 417 Drawable icon = mMenu.getResources().getDrawable(mIconResId); 418 mIconResId = NO_ICON; 419 mIconDrawable = icon; 420 return icon; 421 } 422 423 return null; 424 } 425 426 @Override 427 public MenuItem setIcon(Drawable icon) { 428 mIconResId = NO_ICON; 429 mIconDrawable = icon; 430 mMenu.onItemsChanged(false); 431 432 return this; 433 } 434 435 @Override 436 public MenuItem setIcon(int iconResId) { 437 mIconDrawable = null; 438 mIconResId = iconResId; 439 440 // If we have a view, we need to push the Drawable to them 441 mMenu.onItemsChanged(false); 442 443 return this; 444 } 445 446 @Override 447 public boolean isCheckable() { 448 return (mFlags & CHECKABLE) == CHECKABLE; 449 } 450 451 @Override 452 public MenuItem setCheckable(boolean checkable) { 453 final int oldFlags = mFlags; 454 mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); 455 if (oldFlags != mFlags) { 456 mMenu.onItemsChanged(false); 457 } 458 459 return this; 460 } 461 462 public void setExclusiveCheckable(boolean exclusive) { 463 mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0); 464 } 465 466 public boolean isExclusiveCheckable() { 467 return (mFlags & EXCLUSIVE) != 0; 468 } 469 470 @Override 471 public boolean isChecked() { 472 return (mFlags & CHECKED) == CHECKED; 473 } 474 475 @Override 476 public MenuItem setChecked(boolean checked) { 477 if ((mFlags & EXCLUSIVE) != 0) { 478 // Call the method on the Menu since it knows about the others in this 479 // exclusive checkable group 480 mMenu.setExclusiveItemChecked(this); 481 } else { 482 setCheckedInt(checked); 483 } 484 485 return this; 486 } 487 488 void setCheckedInt(boolean checked) { 489 final int oldFlags = mFlags; 490 mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0); 491 if (oldFlags != mFlags) { 492 mMenu.onItemsChanged(false); 493 } 494 } 495 496 @Override 497 public boolean isVisible() { 498 if (mActionProvider != null && mActionProvider.overridesItemVisibility()) { 499 return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible(); 500 } 501 return (mFlags & HIDDEN) == 0; 502 } 503 504 /** 505 * Changes the visibility of the item. This method DOES NOT notify the parent menu of a change 506 * in this item, so this should only be called from methods that will eventually trigger this 507 * change. If unsure, use {@link #setVisible(boolean)} instead. 508 * 509 * @param shown Whether to show (true) or hide (false). 510 * @return Whether the item's shown state was changed 511 */ 512 boolean setVisibleInt(boolean shown) { 513 final int oldFlags = mFlags; 514 mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN); 515 return oldFlags != mFlags; 516 } 517 518 @Override 519 public MenuItem setVisible(boolean shown) { 520 // Try to set the shown state to the given state. If the shown state was changed 521 // (i.e. the previous state isn't the same as given state), notify the parent menu that 522 // the shown state has changed for this item 523 if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this); 524 525 return this; 526 } 527 528 @Override 529 public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) { 530 mClickListener = clickListener; 531 return this; 532 } 533 534 @Override 535 public String toString() { 536 return mTitle.toString(); 537 } 538 539 void setMenuInfo(ContextMenuInfo menuInfo) { 540 mMenuInfo = menuInfo; 541 } 542 543 @Override 544 public ContextMenuInfo getMenuInfo() { 545 return mMenuInfo; 546 } 547 548 public void actionFormatChanged() { 549 mMenu.onItemActionRequestChanged(this); 550 } 551 552 /** 553 * @return Whether the menu should show icons for menu items. 554 */ 555 public boolean shouldShowIcon() { 556 return mMenu.getOptionalIconsVisible(); 557 } 558 559 public boolean isActionButton() { 560 return (mFlags & IS_ACTION) == IS_ACTION; 561 } 562 563 public boolean requestsActionButton() { 564 return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM; 565 } 566 567 public boolean requiresActionButton() { 568 return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS; 569 } 570 571 public void setIsActionButton(boolean isActionButton) { 572 if (isActionButton) { 573 mFlags |= IS_ACTION; 574 } else { 575 mFlags &= ~IS_ACTION; 576 } 577 } 578 579 public boolean showsTextAsAction() { 580 return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT; 581 } 582 583 @Override 584 public void setShowAsAction(int actionEnum) { 585 switch (actionEnum & SHOW_AS_ACTION_MASK) { 586 case SHOW_AS_ACTION_ALWAYS: 587 case SHOW_AS_ACTION_IF_ROOM: 588 case SHOW_AS_ACTION_NEVER: 589 // Looks good! 590 break; 591 592 default: 593 // Mutually exclusive options selected! 594 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM," 595 + " and SHOW_AS_ACTION_NEVER are mutually exclusive."); 596 } 597 mShowAsAction = actionEnum; 598 mMenu.onItemActionRequestChanged(this); 599 } 600 601 @Override 602 public SupportMenuItem setActionView(View view) { 603 mActionView = view; 604 mActionProvider = null; 605 if (view != null && view.getId() == View.NO_ID && mId > 0) { 606 view.setId(mId); 607 } 608 mMenu.onItemActionRequestChanged(this); 609 return this; 610 } 611 612 @Override 613 public SupportMenuItem setActionView(int resId) { 614 final Context context = mMenu.getContext(); 615 final LayoutInflater inflater = LayoutInflater.from(context); 616 setActionView(inflater.inflate(resId, new LinearLayout(context), false)); 617 return this; 618 } 619 620 @Override 621 public View getActionView() { 622 if (mActionView != null) { 623 return mActionView; 624 } else if (mActionProvider != null) { 625 mActionView = mActionProvider.onCreateActionView(this); 626 return mActionView; 627 } else { 628 return null; 629 } 630 } 631 632 @Override 633 public MenuItem setActionProvider(android.view.ActionProvider actionProvider) { 634 throw new UnsupportedOperationException( 635 "Implementation should use setSupportActionProvider!"); 636 } 637 638 @Override 639 public android.view.ActionProvider getActionProvider() { 640 throw new UnsupportedOperationException( 641 "Implementation should use getSupportActionProvider!"); 642 } 643 644 @Override 645 public ActionProvider getSupportActionProvider() { 646 return mActionProvider; 647 } 648 649 @Override 650 public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) { 651 if (mActionProvider == actionProvider) { 652 return this; 653 } 654 655 mActionView = null; 656 if (mActionProvider != null) { 657 mActionProvider.setVisibilityListener(null); 658 } 659 mActionProvider = actionProvider; 660 mMenu.onItemsChanged(true); // Measurement can be changed 661 if (actionProvider != null) { 662 actionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() { 663 @Override 664 public void onActionProviderVisibilityChanged(boolean isVisible) { 665 mMenu.onItemVisibleChanged(MenuItemImpl.this); 666 } 667 }); 668 } 669 return this; 670 } 671 672 @Override 673 public SupportMenuItem setShowAsActionFlags(int actionEnum) { 674 setShowAsAction(actionEnum); 675 return this; 676 } 677 678 @Override 679 public boolean expandActionView() { 680 if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) { 681 return false; 682 } 683 684 if (mOnActionExpandListener == null || 685 mOnActionExpandListener.onMenuItemActionExpand(this)) { 686 return mMenu.expandItemActionView(this); 687 } 688 689 return false; 690 } 691 692 @Override 693 public boolean collapseActionView() { 694 if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) { 695 return false; 696 } 697 if (mActionView == null) { 698 // We're already collapsed if we have no action view. 699 return true; 700 } 701 702 if (mOnActionExpandListener == null || 703 mOnActionExpandListener.onMenuItemActionCollapse(this)) { 704 return mMenu.collapseItemActionView(this); 705 } 706 707 return false; 708 } 709 710 @Override 711 public SupportMenuItem setSupportOnActionExpandListener( 712 MenuItemCompat.OnActionExpandListener listener) { 713 mOnActionExpandListener = listener; 714 return this; 715 } 716 717 public boolean hasCollapsibleActionView() { 718 return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null; 719 } 720 721 public void setActionViewExpanded(boolean isExpanded) { 722 mIsActionViewExpanded = isExpanded; 723 mMenu.onItemsChanged(false); 724 } 725 726 @Override 727 public boolean isActionViewExpanded() { 728 return mIsActionViewExpanded; 729 } 730 731 @Override 732 public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) { 733 throw new UnsupportedOperationException( 734 "Implementation should use setSupportOnActionExpandListener!"); 735 } 736} 737