Home | History | Annotate | Download | only in menu
      1 /*
      2  * Copyright (C) 2010 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.android.internal.view.menu;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.content.res.Resources;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Rect;
     24 import android.graphics.drawable.Drawable;
     25 import android.text.TextUtils;
     26 import android.util.AttributeSet;
     27 import android.view.Gravity;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.accessibility.AccessibilityEvent;
     31 import android.widget.TextView;
     32 import android.widget.Toast;
     33 
     34 /**
     35  * @hide
     36  */
     37 public class ActionMenuItemView extends TextView
     38         implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
     39         ActionMenuView.ActionMenuChildView {
     40     private static final String TAG = "ActionMenuItemView";
     41 
     42     private MenuItemImpl mItemData;
     43     private CharSequence mTitle;
     44     private Drawable mIcon;
     45     private MenuBuilder.ItemInvoker mItemInvoker;
     46 
     47     private boolean mAllowTextWithIcon;
     48     private boolean mExpandedFormat;
     49     private int mMinWidth;
     50     private int mSavedPaddingLeft;
     51 
     52     private static final int MAX_ICON_SIZE = 32; // dp
     53     private int mMaxIconSize;
     54 
     55     public ActionMenuItemView(Context context) {
     56         this(context, null);
     57     }
     58 
     59     public ActionMenuItemView(Context context, AttributeSet attrs) {
     60         this(context, attrs, 0);
     61     }
     62 
     63     public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
     64         super(context, attrs, defStyle);
     65         final Resources res = context.getResources();
     66         mAllowTextWithIcon = res.getBoolean(
     67                 com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
     68         TypedArray a = context.obtainStyledAttributes(attrs,
     69                 com.android.internal.R.styleable.ActionMenuItemView, 0, 0);
     70         mMinWidth = a.getDimensionPixelSize(
     71                 com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
     72         a.recycle();
     73 
     74         final float density = res.getDisplayMetrics().density;
     75         mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
     76 
     77         setOnClickListener(this);
     78         setOnLongClickListener(this);
     79 
     80         mSavedPaddingLeft = -1;
     81     }
     82 
     83     @Override
     84     public void onConfigurationChanged(Configuration newConfig) {
     85         super.onConfigurationChanged(newConfig);
     86 
     87         mAllowTextWithIcon = getContext().getResources().getBoolean(
     88                 com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
     89         updateTextButtonVisibility();
     90     }
     91 
     92     @Override
     93     public void setPadding(int l, int t, int r, int b) {
     94         mSavedPaddingLeft = l;
     95         super.setPadding(l, t, r, b);
     96     }
     97 
     98     public MenuItemImpl getItemData() {
     99         return mItemData;
    100     }
    101 
    102     public void initialize(MenuItemImpl itemData, int menuType) {
    103         mItemData = itemData;
    104 
    105         setIcon(itemData.getIcon());
    106         setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
    107         setId(itemData.getItemId());
    108 
    109         setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
    110         setEnabled(itemData.isEnabled());
    111     }
    112 
    113     public void onClick(View v) {
    114         if (mItemInvoker != null) {
    115             mItemInvoker.invokeItem(mItemData);
    116         }
    117     }
    118 
    119     public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
    120         mItemInvoker = invoker;
    121     }
    122 
    123     public boolean prefersCondensedTitle() {
    124         return true;
    125     }
    126 
    127     public void setCheckable(boolean checkable) {
    128         // TODO Support checkable action items
    129     }
    130 
    131     public void setChecked(boolean checked) {
    132         // TODO Support checkable action items
    133     }
    134 
    135     public void setExpandedFormat(boolean expandedFormat) {
    136         if (mExpandedFormat != expandedFormat) {
    137             mExpandedFormat = expandedFormat;
    138             if (mItemData != null) {
    139                 mItemData.actionFormatChanged();
    140             }
    141         }
    142     }
    143 
    144     private void updateTextButtonVisibility() {
    145         boolean visible = !TextUtils.isEmpty(mTitle);
    146         visible &= mIcon == null ||
    147                 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
    148 
    149         setText(visible ? mTitle : null);
    150     }
    151 
    152     public void setIcon(Drawable icon) {
    153         mIcon = icon;
    154         if (icon != null) {
    155             int width = icon.getIntrinsicWidth();
    156             int height = icon.getIntrinsicHeight();
    157             if (width > mMaxIconSize) {
    158                 final float scale = (float) mMaxIconSize / width;
    159                 width = mMaxIconSize;
    160                 height *= scale;
    161             }
    162             if (height > mMaxIconSize) {
    163                 final float scale = (float) mMaxIconSize / height;
    164                 height = mMaxIconSize;
    165                 width *= scale;
    166             }
    167             icon.setBounds(0, 0, width, height);
    168         }
    169         setCompoundDrawables(icon, null, null, null);
    170 
    171         updateTextButtonVisibility();
    172     }
    173 
    174     public boolean hasText() {
    175         return !TextUtils.isEmpty(getText());
    176     }
    177 
    178     public void setShortcut(boolean showShortcut, char shortcutKey) {
    179         // Action buttons don't show text for shortcut keys.
    180     }
    181 
    182     public void setTitle(CharSequence title) {
    183         mTitle = title;
    184 
    185         setContentDescription(mTitle);
    186         updateTextButtonVisibility();
    187     }
    188 
    189     @Override
    190     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    191         onPopulateAccessibilityEvent(event);
    192         return true;
    193     }
    194 
    195     @Override
    196     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    197         super.onPopulateAccessibilityEvent(event);
    198         final CharSequence cdesc = getContentDescription();
    199         if (!TextUtils.isEmpty(cdesc)) {
    200             event.getText().add(cdesc);
    201         }
    202     }
    203 
    204     @Override
    205     public boolean dispatchHoverEvent(MotionEvent event) {
    206         // Don't allow children to hover; we want this to be treated as a single component.
    207         return onHoverEvent(event);
    208     }
    209 
    210     public boolean showsIcon() {
    211         return true;
    212     }
    213 
    214     public boolean needsDividerBefore() {
    215         return hasText() && mItemData.getIcon() == null;
    216     }
    217 
    218     public boolean needsDividerAfter() {
    219         return hasText();
    220     }
    221 
    222     @Override
    223     public boolean onLongClick(View v) {
    224         if (hasText()) {
    225             // Don't show the cheat sheet for items that already show text.
    226             return false;
    227         }
    228 
    229         final int[] screenPos = new int[2];
    230         final Rect displayFrame = new Rect();
    231         getLocationOnScreen(screenPos);
    232         getWindowVisibleDisplayFrame(displayFrame);
    233 
    234         final Context context = getContext();
    235         final int width = getWidth();
    236         final int height = getHeight();
    237         final int midy = screenPos[1] + height / 2;
    238         final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
    239 
    240         Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
    241         if (midy < displayFrame.height()) {
    242             // Show along the top; follow action buttons
    243             cheatSheet.setGravity(Gravity.TOP | Gravity.END,
    244                     screenWidth - screenPos[0] - width / 2, height);
    245         } else {
    246             // Show along the bottom center
    247             cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
    248         }
    249         cheatSheet.show();
    250         return true;
    251     }
    252 
    253     @Override
    254     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    255         if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
    256             // Fill all available height.
    257             heightMeasureSpec = MeasureSpec.makeMeasureSpec(
    258                     MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
    259         }
    260         final boolean textVisible = hasText();
    261         if (textVisible && mSavedPaddingLeft >= 0) {
    262             super.setPadding(mSavedPaddingLeft, getPaddingTop(),
    263                     getPaddingRight(), getPaddingBottom());
    264         }
    265 
    266         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    267 
    268         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    269         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    270         final int oldMeasuredWidth = getMeasuredWidth();
    271         final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
    272                 : mMinWidth;
    273 
    274         if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
    275             // Remeasure at exactly the minimum width.
    276             super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
    277                     heightMeasureSpec);
    278         }
    279 
    280         if (!textVisible && mIcon != null) {
    281             // TextView won't center compound drawables in both dimensions without
    282             // a little coercion. Pad in to center the icon after we've measured.
    283             final int w = getMeasuredWidth();
    284             final int dw = mIcon.getBounds().width();
    285             super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
    286         }
    287     }
    288 }
    289