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