blob: 3cceebeab443bb0ace8e805a5651706ce80ce1a3 [file] [log] [blame]
Adam Powell96675b12010-06-10 18:58:59 -07001/*
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
17package com.android.internal.view.menu;
18
19import android.content.Context;
Adam Powelld5c81db2012-08-02 14:35:26 -070020import android.content.res.Configuration;
Adam Powell35aecd52011-07-01 13:43:49 -070021import android.content.res.Resources;
Adam Powell24340af2011-10-31 22:07:44 -070022import android.content.res.TypedArray;
Adam Powell7b5e9e62011-08-15 19:04:08 -070023import android.graphics.Rect;
Adam Powell96675b12010-06-10 18:58:59 -070024import android.graphics.drawable.Drawable;
Gilles Debunnec619e742011-05-31 15:49:51 -070025import android.text.TextUtils;
Adam Powell96675b12010-06-10 18:58:59 -070026import android.util.AttributeSet;
Adam Powell7b5e9e62011-08-15 19:04:08 -070027import android.view.Gravity;
Adam Powell7bc3ca02011-08-26 18:29:58 -070028import android.view.MotionEvent;
Adam Powell96675b12010-06-10 18:58:59 -070029import android.view.View;
Adam Powell7bc3ca02011-08-26 18:29:58 -070030import android.view.accessibility.AccessibilityEvent;
Adam Powellfa18d182014-01-07 15:56:59 -080031import android.widget.ActionMenuView;
Alan Viverette56110722014-01-10 14:03:21 -080032import android.widget.ListPopupWindow;
Adam Powell75d022a2012-03-06 12:04:07 -080033import android.widget.TextView;
Adam Powell7b5e9e62011-08-15 19:04:08 -070034import android.widget.Toast;
Alan Viverette56110722014-01-10 14:03:21 -080035import android.widget.ListPopupWindow.ForwardingListener;
Adam Powell96675b12010-06-10 18:58:59 -070036
37/**
38 * @hide
39 */
Adam Powell75d022a2012-03-06 12:04:07 -080040public class ActionMenuItemView extends TextView
Adam Powell7b5e9e62011-08-15 19:04:08 -070041 implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
42 ActionMenuView.ActionMenuChildView {
Adam Powell96675b12010-06-10 18:58:59 -070043 private static final String TAG = "ActionMenuItemView";
Jeff Sharkeya8a72c32010-07-31 18:27:13 -070044
Adam Powell96675b12010-06-10 18:58:59 -070045 private MenuItemImpl mItemData;
46 private CharSequence mTitle;
Adam Powell75d022a2012-03-06 12:04:07 -080047 private Drawable mIcon;
Adam Powell96675b12010-06-10 18:58:59 -070048 private MenuBuilder.ItemInvoker mItemInvoker;
Alan Viverette56110722014-01-10 14:03:21 -080049 private ForwardingListener mForwardingListener;
50 private PopupCallback mPopupCallback;
Jeff Sharkeya8a72c32010-07-31 18:27:13 -070051
Adam Powell35aecd52011-07-01 13:43:49 -070052 private boolean mAllowTextWithIcon;
Adam Powell35aecd52011-07-01 13:43:49 -070053 private boolean mExpandedFormat;
Adam Powell24340af2011-10-31 22:07:44 -070054 private int mMinWidth;
Adam Powell75d022a2012-03-06 12:04:07 -080055 private int mSavedPaddingLeft;
Adam Powell1f9c7af2010-08-03 11:26:07 -070056
Adam Powellc0047d42012-07-30 12:18:15 -070057 private static final int MAX_ICON_SIZE = 32; // dp
58 private int mMaxIconSize;
59
Adam Powell96675b12010-06-10 18:58:59 -070060 public ActionMenuItemView(Context context) {
61 this(context, null);
62 }
Jeff Sharkeya8a72c32010-07-31 18:27:13 -070063
Adam Powell96675b12010-06-10 18:58:59 -070064 public ActionMenuItemView(Context context, AttributeSet attrs) {
Adam Powell35aecd52011-07-01 13:43:49 -070065 this(context, attrs, 0);
Adam Powell96675b12010-06-10 18:58:59 -070066 }
Jeff Sharkeya8a72c32010-07-31 18:27:13 -070067
Alan Viverette617feb92013-09-09 18:09:13 -070068 public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
69 this(context, attrs, defStyleAttr, 0);
70 }
71
72 public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
73 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell35aecd52011-07-01 13:43:49 -070074 final Resources res = context.getResources();
75 mAllowTextWithIcon = res.getBoolean(
76 com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
Alan Viverette617feb92013-09-09 18:09:13 -070077 final TypedArray a = context.obtainStyledAttributes(attrs,
78 com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes);
Adam Powell24340af2011-10-31 22:07:44 -070079 mMinWidth = a.getDimensionPixelSize(
80 com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
81 a.recycle();
Adam Powell96675b12010-06-10 18:58:59 -070082
Adam Powellc0047d42012-07-30 12:18:15 -070083 final float density = res.getDisplayMetrics().density;
84 mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
85
Adam Powell6bddd872011-03-24 15:34:54 -070086 setOnClickListener(this);
Adam Powell7b5e9e62011-08-15 19:04:08 -070087 setOnLongClickListener(this);
Adam Powell75d022a2012-03-06 12:04:07 -080088
Adam Powell367ee322012-05-06 18:32:33 -070089 mSavedPaddingLeft = -1;
90 }
91
92 @Override
Adam Powelld5c81db2012-08-02 14:35:26 -070093 public void onConfigurationChanged(Configuration newConfig) {
94 super.onConfigurationChanged(newConfig);
95
96 mAllowTextWithIcon = getContext().getResources().getBoolean(
97 com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
98 updateTextButtonVisibility();
99 }
100
101 @Override
Adam Powell367ee322012-05-06 18:32:33 -0700102 public void setPadding(int l, int t, int r, int b) {
103 mSavedPaddingLeft = l;
104 super.setPadding(l, t, r, b);
Adam Powell1f9c7af2010-08-03 11:26:07 -0700105 }
106
Adam Powell96675b12010-06-10 18:58:59 -0700107 public MenuItemImpl getItemData() {
108 return mItemData;
109 }
110
Alan Viverette56110722014-01-10 14:03:21 -0800111 @Override
Adam Powell96675b12010-06-10 18:58:59 -0700112 public void initialize(MenuItemImpl itemData, int menuType) {
113 mItemData = itemData;
Jeff Sharkeya8a72c32010-07-31 18:27:13 -0700114
Adam Powell96675b12010-06-10 18:58:59 -0700115 setIcon(itemData.getIcon());
Adam Powelle7d46842011-01-13 21:36:09 -0800116 setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
Adam Powell96675b12010-06-10 18:58:59 -0700117 setId(itemData.getItemId());
Jeff Sharkeya8a72c32010-07-31 18:27:13 -0700118
Adam Powell96675b12010-06-10 18:58:59 -0700119 setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
120 setEnabled(itemData.isEnabled());
Alan Viverette56110722014-01-10 14:03:21 -0800121
122 if (itemData.hasSubMenu()) {
123 if (mForwardingListener == null) {
124 mForwardingListener = new ActionMenuItemForwardingListener();
125 }
126 }
Adam Powell96675b12010-06-10 18:58:59 -0700127 }
Jeff Sharkeya8a72c32010-07-31 18:27:13 -0700128
Alan Viverette56110722014-01-10 14:03:21 -0800129 @Override
130 public boolean onTouchEvent(MotionEvent e) {
131 if (mItemData.hasSubMenu() && mForwardingListener != null
132 && mForwardingListener.onTouch(this, e)) {
133 return true;
134 }
135 return super.onTouchEvent(e);
136 }
137
138 @Override
Adam Powell1f9c7af2010-08-03 11:26:07 -0700139 public void onClick(View v) {
140 if (mItemInvoker != null) {
141 mItemInvoker.invokeItem(mItemData);
Adam Powell96675b12010-06-10 18:58:59 -0700142 }
143 }
Jeff Sharkeya8a72c32010-07-31 18:27:13 -0700144
Adam Powell96675b12010-06-10 18:58:59 -0700145 public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
146 mItemInvoker = invoker;
147 }
148
Alan Viverette56110722014-01-10 14:03:21 -0800149 public void setPopupCallback(PopupCallback popupCallback) {
150 mPopupCallback = popupCallback;
151 }
152
Adam Powell96675b12010-06-10 18:58:59 -0700153 public boolean prefersCondensedTitle() {
Adam Powelle7d46842011-01-13 21:36:09 -0800154 return true;
Adam Powell96675b12010-06-10 18:58:59 -0700155 }
156
157 public void setCheckable(boolean checkable) {
158 // TODO Support checkable action items
159 }
160
161 public void setChecked(boolean checked) {
162 // TODO Support checkable action items
163 }
164
Adam Powell35aecd52011-07-01 13:43:49 -0700165 public void setExpandedFormat(boolean expandedFormat) {
166 if (mExpandedFormat != expandedFormat) {
167 mExpandedFormat = expandedFormat;
168 if (mItemData != null) {
169 mItemData.actionFormatChanged();
170 }
171 }
172 }
173
Gilles Debunnec619e742011-05-31 15:49:51 -0700174 private void updateTextButtonVisibility() {
Adam Powell75d022a2012-03-06 12:04:07 -0800175 boolean visible = !TextUtils.isEmpty(mTitle);
176 visible &= mIcon == null ||
Adam Powell35aecd52011-07-01 13:43:49 -0700177 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
178
Adam Powell75d022a2012-03-06 12:04:07 -0800179 setText(visible ? mTitle : null);
Gilles Debunnec619e742011-05-31 15:49:51 -0700180 }
181
Adam Powell96675b12010-06-10 18:58:59 -0700182 public void setIcon(Drawable icon) {
Adam Powell75d022a2012-03-06 12:04:07 -0800183 mIcon = icon;
Adam Powellc1eea132012-07-31 11:27:19 -0700184 if (icon != null) {
185 int width = icon.getIntrinsicWidth();
186 int height = icon.getIntrinsicHeight();
187 if (width > mMaxIconSize) {
188 final float scale = (float) mMaxIconSize / width;
189 width = mMaxIconSize;
190 height *= scale;
191 }
192 if (height > mMaxIconSize) {
193 final float scale = (float) mMaxIconSize / height;
194 height = mMaxIconSize;
195 width *= scale;
196 }
197 icon.setBounds(0, 0, width, height);
Adam Powellc0047d42012-07-30 12:18:15 -0700198 }
Adam Powellc0047d42012-07-30 12:18:15 -0700199 setCompoundDrawables(icon, null, null, null);
Adam Powell9f125d32011-01-14 15:36:56 -0800200
Gilles Debunnec619e742011-05-31 15:49:51 -0700201 updateTextButtonVisibility();
Adam Powell96675b12010-06-10 18:58:59 -0700202 }
Gilles Debunnec619e742011-05-31 15:49:51 -0700203
Adam Powellbe4d68e2010-10-08 18:16:34 -0700204 public boolean hasText() {
Adam Powell75d022a2012-03-06 12:04:07 -0800205 return !TextUtils.isEmpty(getText());
Adam Powellbe4d68e2010-10-08 18:16:34 -0700206 }
Adam Powell96675b12010-06-10 18:58:59 -0700207
208 public void setShortcut(boolean showShortcut, char shortcutKey) {
209 // Action buttons don't show text for shortcut keys.
210 }
211
212 public void setTitle(CharSequence title) {
213 mTitle = title;
Jeff Sharkeya8a72c32010-07-31 18:27:13 -0700214
Adam Powell7bc3ca02011-08-26 18:29:58 -0700215 setContentDescription(mTitle);
Gilles Debunnec619e742011-05-31 15:49:51 -0700216 updateTextButtonVisibility();
Adam Powell96675b12010-06-10 18:58:59 -0700217 }
218
Adam Powell7bc3ca02011-08-26 18:29:58 -0700219 @Override
220 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
221 onPopulateAccessibilityEvent(event);
222 return true;
223 }
224
225 @Override
226 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
227 super.onPopulateAccessibilityEvent(event);
228 final CharSequence cdesc = getContentDescription();
229 if (!TextUtils.isEmpty(cdesc)) {
230 event.getText().add(cdesc);
231 }
232 }
233
234 @Override
235 public boolean dispatchHoverEvent(MotionEvent event) {
236 // Don't allow children to hover; we want this to be treated as a single component.
237 return onHoverEvent(event);
238 }
239
Adam Powell96675b12010-06-10 18:58:59 -0700240 public boolean showsIcon() {
241 return true;
242 }
Adam Powell696cba52011-03-29 10:38:16 -0700243
244 public boolean needsDividerBefore() {
245 return hasText() && mItemData.getIcon() == null;
246 }
247
248 public boolean needsDividerAfter() {
249 return hasText();
250 }
Adam Powell7b5e9e62011-08-15 19:04:08 -0700251
252 @Override
253 public boolean onLongClick(View v) {
254 if (hasText()) {
255 // Don't show the cheat sheet for items that already show text.
256 return false;
257 }
258
259 final int[] screenPos = new int[2];
260 final Rect displayFrame = new Rect();
261 getLocationOnScreen(screenPos);
262 getWindowVisibleDisplayFrame(displayFrame);
263
264 final Context context = getContext();
265 final int width = getWidth();
266 final int height = getHeight();
267 final int midy = screenPos[1] + height / 2;
268 final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
269
270 Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
271 if (midy < displayFrame.height()) {
272 // Show along the top; follow action buttons
Fabrice Di Meglioaac0d4e2012-07-19 19:21:26 -0700273 cheatSheet.setGravity(Gravity.TOP | Gravity.END,
Adam Powell7b5e9e62011-08-15 19:04:08 -0700274 screenWidth - screenPos[0] - width / 2, height);
275 } else {
276 // Show along the bottom center
277 cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
278 }
279 cheatSheet.show();
280 return true;
281 }
Adam Powell24340af2011-10-31 22:07:44 -0700282
283 @Override
284 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powelld5c81db2012-08-02 14:35:26 -0700285 if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
286 // Fill all available height.
287 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
288 MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
289 }
Adam Powell75d022a2012-03-06 12:04:07 -0800290 final boolean textVisible = hasText();
Adam Powell367ee322012-05-06 18:32:33 -0700291 if (textVisible && mSavedPaddingLeft >= 0) {
292 super.setPadding(mSavedPaddingLeft, getPaddingTop(),
293 getPaddingRight(), getPaddingBottom());
Adam Powell75d022a2012-03-06 12:04:07 -0800294 }
295
Adam Powell24340af2011-10-31 22:07:44 -0700296 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
297
298 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
Adam Powell75d022a2012-03-06 12:04:07 -0800299 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell24340af2011-10-31 22:07:44 -0700300 final int oldMeasuredWidth = getMeasuredWidth();
Adam Powell75d022a2012-03-06 12:04:07 -0800301 final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
Adam Powell24340af2011-10-31 22:07:44 -0700302 : mMinWidth;
303
304 if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
305 // Remeasure at exactly the minimum width.
306 super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
307 heightMeasureSpec);
308 }
Adam Powell75d022a2012-03-06 12:04:07 -0800309
310 if (!textVisible && mIcon != null) {
311 // TextView won't center compound drawables in both dimensions without
312 // a little coercion. Pad in to center the icon after we've measured.
313 final int w = getMeasuredWidth();
Adam Powellc0047d42012-07-30 12:18:15 -0700314 final int dw = mIcon.getBounds().width();
Adam Powell367ee322012-05-06 18:32:33 -0700315 super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
Adam Powell75d022a2012-03-06 12:04:07 -0800316 }
Adam Powell24340af2011-10-31 22:07:44 -0700317 }
Alan Viverette56110722014-01-10 14:03:21 -0800318
319 private class ActionMenuItemForwardingListener extends ForwardingListener {
320 public ActionMenuItemForwardingListener() {
321 super(ActionMenuItemView.this);
322 }
323
324 @Override
325 public ListPopupWindow getPopup() {
326 if (mPopupCallback != null) {
327 return mPopupCallback.getPopup();
328 }
329 return null;
330 }
331
332 @Override
333 protected boolean onForwardingStarted() {
334 // Call the invoker, then check if the expected popup is showing.
335 if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
336 final ListPopupWindow popup = getPopup();
337 return popup != null && popup.isShowing();
338 }
339 return false;
340 }
341
342 @Override
343 protected boolean onForwardingStopped() {
344 final ListPopupWindow popup = getPopup();
345 if (popup != null) {
346 popup.dismiss();
347 return true;
348 }
349 return false;
350 }
351 }
352
353 public static abstract class PopupCallback {
354 public abstract ListPopupWindow getPopup();
355 }
Adam Powell96675b12010-06-10 18:58:59 -0700356}