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