blob: 7775f00a50a47ee7e1068823c4b72f04a0304837 [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 */
16package com.android.internal.view.menu;
17
18import android.content.Context;
Adam Powell8028dd32010-07-15 10:16:33 -070019import android.content.res.Configuration;
Adam Powell7ade1be2010-06-17 12:51:21 -070020import android.content.res.Resources;
21import android.content.res.TypedArray;
Adam Powellbe4d68e2010-10-08 18:16:34 -070022import android.graphics.drawable.Drawable;
Adam Powell96675b12010-06-10 18:58:59 -070023import android.util.AttributeSet;
Adam Powell6b336f82010-08-10 20:13:01 -070024import android.view.Gravity;
Adam Powellb3312b82011-01-21 18:12:02 -080025import android.view.SoundEffectConstants;
Adam Powellcf78b3e2010-09-12 18:25:23 -070026import android.view.View;
Adam Powell7ade1be2010-06-17 12:51:21 -070027import android.view.ViewGroup;
Adam Powell8028dd32010-07-15 10:16:33 -070028import android.widget.ImageButton;
Adam Powellbe4d68e2010-10-08 18:16:34 -070029import android.widget.ImageView;
Adam Powell96675b12010-06-10 18:58:59 -070030import android.widget.LinearLayout;
31
32import java.util.ArrayList;
33
34/**
35 * @hide
36 */
37public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
38 private static final String TAG = "ActionMenuView";
Adam Powellbe4d68e2010-10-08 18:16:34 -070039
40 // TODO Theme/style this.
41 private static final int DIVIDER_PADDING = 12; // dips
Adam Powell96675b12010-06-10 18:58:59 -070042
Adam Powell96675b12010-06-10 18:58:59 -070043 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070044
Adam Powell7ade1be2010-06-17 12:51:21 -070045 private int mMaxItems;
Adam Powell36fced92011-01-16 15:48:07 -080046 private int mWidthLimit;
Adam Powell8028dd32010-07-15 10:16:33 -070047 private boolean mReserveOverflow;
48 private OverflowMenuButton mOverflowButton;
Adam Powell6c6f5752010-08-20 18:34:46 -070049 private MenuPopupHelper mOverflowPopup;
Adam Powell345a9f42011-01-06 16:03:30 -080050
Adam Powellbe4d68e2010-10-08 18:16:34 -070051 private float mDividerPadding;
52
53 private Drawable mDivider;
Adam Powell6c6f5752010-08-20 18:34:46 -070054
Adam Powell55767442011-01-24 14:28:37 -080055 private final Runnable mShowOverflow = new Runnable() {
Adam Powell6c6f5752010-08-20 18:34:46 -070056 public void run() {
57 showOverflowMenu();
58 }
59 };
Adam Powell96675b12010-06-10 18:58:59 -070060
Adam Powell8515ee82010-11-30 14:09:55 -080061 private class OpenOverflowRunnable implements Runnable {
62 private MenuPopupHelper mPopup;
63
64 public OpenOverflowRunnable(MenuPopupHelper popup) {
65 mPopup = popup;
66 }
67
68 public void run() {
Adam Powell5e3f2842011-01-07 17:16:56 -080069 if (mPopup.tryShow()) {
70 mOverflowPopup = mPopup;
71 mPostedOpenRunnable = null;
72 }
Adam Powell8515ee82010-11-30 14:09:55 -080073 }
74 }
75
76 private OpenOverflowRunnable mPostedOpenRunnable;
77
Adam Powell96675b12010-06-10 18:58:59 -070078 public ActionMenuView(Context context) {
79 this(context, null);
80 }
81
82 public ActionMenuView(Context context, AttributeSet attrs) {
83 super(context, attrs);
Adam Powell7ade1be2010-06-17 12:51:21 -070084
Adam Powellbe4d68e2010-10-08 18:16:34 -070085 final Resources res = getResources();
Adam Powell773b1b92010-08-20 15:45:24 -070086
87 // Measure for initial configuration
Adam Powellbe4d68e2010-10-08 18:16:34 -070088 mMaxItems = getMaxActionButtons();
Adam Powell773b1b92010-08-20 15:45:24 -070089
90 // TODO There has to be a better way to indicate that we don't have a hard menu key.
Adam Powellbe4d68e2010-10-08 18:16:34 -070091 final int screen = res.getConfiguration().screenLayout;
Adam Powell773b1b92010-08-20 15:45:24 -070092 mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
93 Configuration.SCREENLAYOUT_SIZE_XLARGE;
Adam Powell36fced92011-01-16 15:48:07 -080094 mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
Adam Powellbe4d68e2010-10-08 18:16:34 -070095
96 TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
Adam Powellcf035762010-12-06 11:49:45 -080097 mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
Adam Powellbe4d68e2010-10-08 18:16:34 -070098 a.recycle();
99
Adam Powellbe4d68e2010-10-08 18:16:34 -0700100 mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
Adam Powellf16888f2010-10-11 17:05:29 -0700101
102 setBaselineAligned(false);
Adam Powell773b1b92010-08-20 15:45:24 -0700103 }
104
Adam Powell6c6f5752010-08-20 18:34:46 -0700105 @Override
Adam Powell773b1b92010-08-20 15:45:24 -0700106 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -0800107 super.onConfigurationChanged(newConfig);
Adam Powell773b1b92010-08-20 15:45:24 -0700108 final int screen = newConfig.screenLayout;
109 mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
110 Configuration.SCREENLAYOUT_SIZE_XLARGE;
Adam Powellbe4d68e2010-10-08 18:16:34 -0700111 mMaxItems = getMaxActionButtons();
Adam Powell36fced92011-01-16 15:48:07 -0800112 mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
Adam Powell773b1b92010-08-20 15:45:24 -0700113 if (mMenu != null) {
114 mMenu.setMaxActionItems(mMaxItems);
115 updateChildren(false);
116 }
Adam Powell6c6f5752010-08-20 18:34:46 -0700117
118 if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
119 mOverflowPopup.dismiss();
120 post(mShowOverflow);
121 }
Adam Powell773b1b92010-08-20 15:45:24 -0700122 }
123
Adam Powell8515ee82010-11-30 14:09:55 -0800124 @Override
125 public void onDetachedFromWindow() {
126 super.onDetachedFromWindow();
127 if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
128 mOverflowPopup.dismiss();
129 }
Adam Powell55767442011-01-24 14:28:37 -0800130 removeCallbacks(mShowOverflow);
131 if (mPostedOpenRunnable != null) {
132 removeCallbacks(mPostedOpenRunnable);
133 }
Adam Powell8515ee82010-11-30 14:09:55 -0800134 }
135
Adam Powellbe4d68e2010-10-08 18:16:34 -0700136 private int getMaxActionButtons() {
137 return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
Adam Powell8028dd32010-07-15 10:16:33 -0700138 }
139
140 public boolean isOverflowReserved() {
141 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700142 }
143
Adam Powellb366bba2010-07-20 14:26:38 -0700144 public void setOverflowReserved(boolean reserveOverflow) {
145 mReserveOverflow = reserveOverflow;
146 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700147
Adam Powellf0ad6e62011-01-10 17:14:06 -0800148 public View getOverflowButton() {
149 return mOverflowButton;
150 }
151
Adam Powell7ade1be2010-06-17 12:51:21 -0700152 @Override
153 protected LayoutParams generateDefaultLayoutParams() {
154 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
155 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700156 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700157 return params;
158 }
159
160 @Override
161 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell3f476b32011-01-03 19:25:36 -0800162 if (p instanceof LayoutParams) {
163 LayoutParams result = new LayoutParams((LayoutParams) p);
164 if (result.gravity <= Gravity.NO_GRAVITY) {
165 result.gravity = Gravity.CENTER_VERTICAL;
166 }
167 return result;
168 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700169 return generateDefaultLayoutParams();
170 }
Adam Powell96675b12010-06-10 18:58:59 -0700171
172 public boolean invokeItem(MenuItemImpl item) {
173 return mMenu.performItemAction(item, 0);
174 }
175
176 public int getWindowAnimations() {
177 return 0;
178 }
179
180 public void initialize(MenuBuilder menu, int menuType) {
Adam Powell36fced92011-01-16 15:48:07 -0800181 int width = mWidthLimit;
182 if (mReserveOverflow) {
183 if (mOverflowButton == null) {
184 OverflowMenuButton button = new OverflowMenuButton(mContext);
185 mOverflowButton = button;
186 }
187 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
188 mOverflowButton.measure(spec, spec);
189 width -= mOverflowButton.getMeasuredWidth();
190 }
191
192 menu.setActionWidthLimit(width);
193
Adam Powell7ade1be2010-06-17 12:51:21 -0700194 menu.setMaxActionItems(mMaxItems);
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800195 final boolean cleared = mMenu != menu;
Adam Powell96675b12010-06-10 18:58:59 -0700196 mMenu = menu;
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800197 updateChildren(cleared);
Adam Powell96675b12010-06-10 18:58:59 -0700198 }
199
200 public void updateChildren(boolean cleared) {
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800201 final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
Adam Powell96675b12010-06-10 18:58:59 -0700202 final int itemCount = itemsToShow.size();
203
Adam Powellbe4d68e2010-10-08 18:16:34 -0700204 boolean needsDivider = false;
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800205 int childIndex = 0;
Adam Powell96675b12010-06-10 18:58:59 -0700206 for (int i = 0; i < itemCount; i++) {
Adam Powelle7d46842011-01-13 21:36:09 -0800207 final MenuItemImpl itemData = itemsToShow.get(i);
208 boolean hasDivider = false;
209
Adam Powellbe4d68e2010-10-08 18:16:34 -0700210 if (needsDivider) {
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800211 if (!isDivider(getChildAt(childIndex))) {
212 addView(makeDividerView(), childIndex, makeDividerLayoutParams());
213 }
Adam Powelle7d46842011-01-13 21:36:09 -0800214 hasDivider = true;
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800215 childIndex++;
Adam Powellbe4d68e2010-10-08 18:16:34 -0700216 }
Adam Powelle7d46842011-01-13 21:36:09 -0800217
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800218 View childToAdd = itemData.getActionView();
219 boolean needsPreDivider = false;
220 if (childToAdd != null) {
221 childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
Adam Powellcf78b3e2010-09-12 18:25:23 -0700222 } else {
Adam Powelle7d46842011-01-13 21:36:09 -0800223 ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
224 MenuBuilder.TYPE_ACTION_BUTTON, this);
225 view.setItemInvoker(this);
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800226 needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
227 itemData.getIcon() == null;
Adam Powelle7d46842011-01-13 21:36:09 -0800228 needsDivider = view.hasText();
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800229 childToAdd = view;
Adam Powellcf78b3e2010-09-12 18:25:23 -0700230 }
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800231
232 boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
233
234 if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
235 if (needsPreDivider) childIndex++;
236
237 if (getChildAt(childIndex) != childToAdd) {
238 addView(childToAdd, childIndex);
239 }
240 childIndex++;
Adam Powell96675b12010-06-10 18:58:59 -0700241 }
Adam Powell8028dd32010-07-15 10:16:33 -0700242
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800243 final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
244 final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
245
246 if (hasOverflow != needsOverflow) {
247 if (needsOverflow) {
Adam Powell36fced92011-01-16 15:48:07 -0800248 if (mOverflowButton == null) {
249 OverflowMenuButton button = new OverflowMenuButton(mContext);
250 mOverflowButton = button;
251 }
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800252 boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
253 if (addDivider && itemCount > 0) {
254 addView(makeDividerView(), childIndex, makeDividerLayoutParams());
255 childIndex++;
256 }
257 addView(mOverflowButton, childIndex);
258 childIndex++;
Adam Powell8028dd32010-07-15 10:16:33 -0700259 } else {
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800260 removeView(mOverflowButton);
261 }
262 } else {
263 if (needsOverflow) {
264 boolean overflowDivider = itemCount > 0;
265 boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
266 overflowDivider);
267 if (addDivider && itemCount > 0) {
268 addView(makeDividerView(), childIndex, makeDividerLayoutParams());
269 }
270 if (overflowDivider) {
271 childIndex += 2;
272 } else {
273 childIndex++;
274 }
Adam Powell8028dd32010-07-15 10:16:33 -0700275 }
276 }
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800277
278 while (getChildCount() > childIndex) {
279 removeViewAt(childIndex);
280 }
281 }
282
283 private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
284 final int childCount = getChildCount();
285 boolean found = false;
286 for (int i = start; i < childCount; i++) {
287 final View child = getChildAt(i);
288 if (child == targetChild) {
289 found = true;
290 break;
291 }
292 }
293
294 if (!found) {
295 return needsPreDivider;
296 }
297
298 for (int i = start; i < getChildCount(); ) {
299 final View child = getChildAt(i);
300 if (needsPreDivider && isDivider(child)) {
301 needsPreDivider = false;
302 i++;
303 continue;
304 }
305 if (child == targetChild) break;
306 removeViewAt(i);
307 }
308
309 return needsPreDivider;
310 }
311
312 private static boolean isDivider(View v) {
313 return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
Adam Powell8028dd32010-07-15 10:16:33 -0700314 }
315
316 public boolean showOverflowMenu() {
Adam Powell8515ee82010-11-30 14:09:55 -0800317 if (mOverflowButton != null && !isOverflowMenuShowing()) {
318 mMenu.getCallback().onMenuModeChange(mMenu);
Adam Powellf75eeb22010-08-10 15:59:40 -0700319 return true;
320 }
321 return false;
322 }
323
Adam Powell8515ee82010-11-30 14:09:55 -0800324 public void openOverflowMenu() {
325 OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
326 mPostedOpenRunnable = new OpenOverflowRunnable(popup);
327 // Post this for later; we might still need a layout for the anchor to be right.
328 post(mPostedOpenRunnable);
329 }
330
Adam Powellf6148c52010-08-11 21:10:16 -0700331 public boolean isOverflowMenuShowing() {
Adam Powell8515ee82010-11-30 14:09:55 -0800332 return mOverflowPopup != null && mOverflowPopup.isShowing();
333 }
334
335 public boolean isOverflowMenuOpen() {
336 return mOverflowPopup != null;
Adam Powellf6148c52010-08-11 21:10:16 -0700337 }
338
Adam Powellf75eeb22010-08-10 15:59:40 -0700339 public boolean hideOverflowMenu() {
Adam Powell8515ee82010-11-30 14:09:55 -0800340 if (mPostedOpenRunnable != null) {
341 removeCallbacks(mPostedOpenRunnable);
342 return true;
343 }
344
Adam Powell6c6f5752010-08-20 18:34:46 -0700345 MenuPopupHelper popup = mOverflowPopup;
Adam Powellf75eeb22010-08-10 15:59:40 -0700346 if (popup != null) {
347 popup.dismiss();
Adam Powell8028dd32010-07-15 10:16:33 -0700348 return true;
349 }
350 return false;
Adam Powell96675b12010-06-10 18:58:59 -0700351 }
352
Adam Powellbe4d68e2010-10-08 18:16:34 -0700353 private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
Adam Powell96675b12010-06-10 18:58:59 -0700354 view.setItemInvoker(this);
Adam Powellbe4d68e2010-10-08 18:16:34 -0700355 boolean hasText = view.hasText();
356
357 if (hasText && needsDivider) {
358 addView(makeDividerView(), makeDividerLayoutParams());
359 }
Adam Powell96675b12010-06-10 18:58:59 -0700360 addView(view);
Adam Powellbe4d68e2010-10-08 18:16:34 -0700361 return hasText;
362 }
363
364 private ImageView makeDividerView() {
365 ImageView result = new ImageView(mContext);
366 result.setImageDrawable(mDivider);
367 result.setScaleType(ImageView.ScaleType.FIT_XY);
Adam Powellf2d7a5d2011-02-07 18:05:24 -0800368 result.setId(com.android.internal.R.id.action_menu_divider);
Adam Powellbe4d68e2010-10-08 18:16:34 -0700369 return result;
370 }
371
372 private LayoutParams makeDividerLayoutParams() {
373 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
374 LayoutParams.MATCH_PARENT);
375 params.topMargin = (int) mDividerPadding;
376 params.bottomMargin = (int) mDividerPadding;
377 return params;
378 }
379
Adam Powell3f476b32011-01-03 19:25:36 -0800380 private LayoutParams makeActionViewLayoutParams(View view) {
Adam Powell345a9f42011-01-06 16:03:30 -0800381 return generateLayoutParams(view.getLayoutParams());
Adam Powell96675b12010-06-10 18:58:59 -0700382 }
Adam Powell8028dd32010-07-15 10:16:33 -0700383
384 private class OverflowMenuButton extends ImageButton {
385 public OverflowMenuButton(Context context) {
Adam Powell079fd1c2010-09-08 19:31:29 -0700386 super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
Adam Powell8028dd32010-07-15 10:16:33 -0700387
Adam Powell8028dd32010-07-15 10:16:33 -0700388 setClickable(true);
389 setFocusable(true);
Adam Powell8028dd32010-07-15 10:16:33 -0700390 setVisibility(VISIBLE);
391 setEnabled(true);
392 }
393
394 @Override
395 public boolean performClick() {
396 if (super.performClick()) {
397 return true;
398 }
399
Adam Powellb3312b82011-01-21 18:12:02 -0800400 playSoundEffect(SoundEffectConstants.CLICK);
Adam Powell8515ee82010-11-30 14:09:55 -0800401 showOverflowMenu();
Adam Powell8028dd32010-07-15 10:16:33 -0700402 return true;
403 }
404 }
Adam Powell8515ee82010-11-30 14:09:55 -0800405
406 private class OverflowPopup extends MenuPopupHelper {
407 public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
408 boolean overflowOnly) {
409 super(context, menu, anchorView, overflowOnly);
410 }
411
412 @Override
413 public void onDismiss() {
414 super.onDismiss();
415 mMenu.getCallback().onCloseMenu(mMenu, true);
416 mOverflowPopup = null;
417 }
418 }
Adam Powell96675b12010-06-10 18:58:59 -0700419}