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 com.android.internal.view.menu;
18
19import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.text.TextUtils;
26import android.util.AttributeSet;
27import android.view.Gravity;
28import android.view.SoundEffectConstants;
29import android.view.View;
30import android.view.ViewDebug;
31import android.widget.TextView;
32import android.text.Layout;
33
34/**
35 * The item view for each item in the {@link IconMenuView}.
36 */
37public final class IconMenuItemView extends TextView implements MenuView.ItemView {
38
39    private static final int NO_ALPHA = 0xFF;
40
41    private IconMenuView mIconMenuView;
42
43    private ItemInvoker mItemInvoker;
44    private MenuItemImpl mItemData;
45
46    private Drawable mIcon;
47
48    private int mTextAppearance;
49    private Context mTextAppearanceContext;
50
51    private float mDisabledAlpha;
52
53    private Rect mPositionIconAvailable = new Rect();
54    private Rect mPositionIconOutput = new Rect();
55
56    private boolean mShortcutCaptionMode;
57    private String mShortcutCaption;
58
59    private static String sPrependShortcutLabel;
60
61    public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
62        super(context, attrs, defStyleAttr, defStyleRes);
63
64        if (sPrependShortcutLabel == null) {
65            /*
66             * Views should only be constructed from the UI thread, so no
67             * synchronization needed
68             */
69            sPrependShortcutLabel = getResources().getString(
70                    com.android.internal.R.string.prepend_shortcut_label);
71        }
72
73        final TypedArray a = context.obtainStyledAttributes(
74                attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes);
75
76        mDisabledAlpha = a.getFloat(
77                com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f);
78        mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
79                                          MenuView_itemTextAppearance, -1);
80        mTextAppearanceContext = context;
81
82        a.recycle();
83    }
84
85    public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
86        this(context, attrs, defStyleAttr, 0);
87    }
88
89    public IconMenuItemView(Context context, AttributeSet attrs) {
90        this(context, attrs, 0);
91    }
92
93    /**
94     * Initializes with the provided title and icon
95     * @param title The title of this item
96     * @param icon The icon of this item
97     */
98    void initialize(CharSequence title, Drawable icon) {
99        setClickable(true);
100        setFocusable(true);
101
102        if (mTextAppearance != -1) {
103            setTextAppearance(mTextAppearanceContext, mTextAppearance);
104        }
105
106        setTitle(title);
107        setIcon(icon);
108
109        if (mItemData != null) {
110            final CharSequence contentDescription = mItemData.getContentDescription();
111            if (TextUtils.isEmpty(contentDescription)) {
112                setContentDescription(title);
113            } else {
114                setContentDescription(contentDescription);
115            }
116            setTooltipText(mItemData.getTooltipText());
117        }
118    }
119
120    public void initialize(MenuItemImpl itemData, int menuType) {
121        mItemData = itemData;
122
123        initialize(itemData.getTitleForItemView(this), itemData.getIcon());
124
125        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
126        setEnabled(itemData.isEnabled());
127    }
128
129    public void setItemData(MenuItemImpl data) {
130        mItemData = data;
131    }
132
133    @Override
134    public boolean performClick() {
135        // Let the view's click listener have top priority (the More button relies on this)
136        if (super.performClick()) {
137            return true;
138        }
139
140        if ((mItemInvoker != null) && (mItemInvoker.invokeItem(mItemData))) {
141            playSoundEffect(SoundEffectConstants.CLICK);
142            return true;
143        } else {
144            return false;
145        }
146    }
147
148    public void setTitle(CharSequence title) {
149
150        if (mShortcutCaptionMode) {
151            /*
152             * Don't set the title directly since it will replace the
153             * shortcut+title being shown. Instead, re-set the shortcut caption
154             * mode so the new title is shown.
155             */
156            setCaptionMode(true);
157
158        } else if (title != null) {
159            setText(title);
160        }
161    }
162
163    void setCaptionMode(boolean shortcut) {
164        /*
165         * If there is no item model, don't do any of the below (for example,
166         * the 'More' item doesn't have a model)
167         */
168        if (mItemData == null) {
169            return;
170        }
171
172        mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut());
173
174        CharSequence text = mItemData.getTitleForItemView(this);
175
176        if (mShortcutCaptionMode) {
177
178            if (mShortcutCaption == null) {
179                mShortcutCaption = mItemData.getShortcutLabel();
180            }
181
182            text = mShortcutCaption;
183        }
184
185        setText(text);
186    }
187
188    public void setIcon(Drawable icon) {
189        mIcon = icon;
190
191        if (icon != null) {
192
193            /* Set the bounds of the icon since setCompoundDrawables needs it. */
194            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
195
196            // Set the compound drawables
197            setCompoundDrawables(null, icon, null, null);
198
199            // When there is an icon, make sure the text is at the bottom
200            setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
201
202            /*
203             * Request a layout to reposition the icon. The positioning of icon
204             * depends on this TextView's line bounds, which is only available
205             * after a layout.
206             */
207            requestLayout();
208        } else {
209            setCompoundDrawables(null, null, null, null);
210
211            // When there is no icon, make sure the text is centered vertically
212            setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
213        }
214    }
215
216    public void setItemInvoker(ItemInvoker itemInvoker) {
217        mItemInvoker = itemInvoker;
218    }
219
220    @ViewDebug.CapturedViewProperty(retrieveReturn = true)
221    public MenuItemImpl getItemData() {
222        return mItemData;
223    }
224
225    @Override
226    public void setVisibility(int v) {
227        super.setVisibility(v);
228
229        if (mIconMenuView != null) {
230            // On visibility change, mark the IconMenuView to refresh itself eventually
231            mIconMenuView.markStaleChildren();
232        }
233    }
234
235    void setIconMenuView(IconMenuView iconMenuView) {
236        mIconMenuView = iconMenuView;
237    }
238
239    @Override
240    protected void drawableStateChanged() {
241        super.drawableStateChanged();
242
243        if (mItemData != null && mIcon != null) {
244            // When disabled, the not-focused state and the pressed state should
245            // drop alpha on the icon
246            final boolean isInAlphaState = !mItemData.isEnabled() && (isPressed() || !isFocused());
247            mIcon.setAlpha(isInAlphaState ? (int) (mDisabledAlpha * NO_ALPHA) : NO_ALPHA);
248        }
249    }
250
251    @Override
252    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
253        super.onLayout(changed, left, top, right, bottom);
254
255        positionIcon();
256    }
257
258    @Override
259    protected void onTextChanged(CharSequence text, int start, int before, int after) {
260        super.onTextChanged(text, start, before, after);
261
262        // our layout params depend on the length of the text
263        setLayoutParams(getTextAppropriateLayoutParams());
264    }
265
266    /**
267     * @return layout params appropriate for this view.  If layout params already exist, it will
268     *         augment them to be appropriate to the current text size.
269     */
270    IconMenuView.LayoutParams getTextAppropriateLayoutParams() {
271        IconMenuView.LayoutParams lp = (IconMenuView.LayoutParams) getLayoutParams();
272        if (lp == null) {
273            // Default layout parameters
274            lp = new IconMenuView.LayoutParams(
275                    IconMenuView.LayoutParams.MATCH_PARENT, IconMenuView.LayoutParams.MATCH_PARENT);
276        }
277
278        // Set the desired width of item
279        lp.desiredWidth = (int) Layout.getDesiredWidth(getText(), 0, getText().length(),
280                getPaint(), getTextDirectionHeuristic());
281
282        return lp;
283    }
284
285    /**
286     * Positions the icon vertically (horizontal centering is taken care of by
287     * the TextView's gravity).
288     */
289    private void positionIcon() {
290
291        if (mIcon == null) {
292            return;
293        }
294
295        // We reuse the output rectangle as a temp rect
296        Rect tmpRect = mPositionIconOutput;
297        getLineBounds(0, tmpRect);
298        mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top);
299        final int layoutDirection = getLayoutDirection();
300        Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.START, mIcon.getIntrinsicWidth(), mIcon
301                .getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput,
302                layoutDirection);
303        mIcon.setBounds(mPositionIconOutput);
304    }
305
306    public void setCheckable(boolean checkable) {
307    }
308
309    public void setChecked(boolean checked) {
310    }
311
312    public void setShortcut(boolean showShortcut, char shortcutKey) {
313
314        if (mShortcutCaptionMode) {
315            /*
316             * Shortcut has changed and we're showing it right now, need to
317             * update (clear the old one first).
318             */
319            mShortcutCaption = null;
320            setCaptionMode(true);
321        }
322    }
323
324    public boolean prefersCondensedTitle() {
325        return true;
326    }
327
328    public boolean showsIcon() {
329        return true;
330    }
331
332}
333