blob: 05e9a66ce5ed4b5a560b44e03a88dfbfa24dd255 [file] [log] [blame]
Adam Powell42675342010-07-09 18:02: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
Adam Powell42675342010-07-09 18:02:59 -070019import android.content.Context;
Adam Powell38639b12011-06-17 16:02:32 -070020import android.content.res.Resources;
Adam Powell11ed1d62011-07-11 21:19:59 -070021import android.os.Parcelable;
Adam Powell54c94de2013-09-26 15:36:34 -070022import android.view.Gravity;
Adam Powell8028dd32010-07-15 10:16:33 -070023import android.view.KeyEvent;
Adam Powell696cba52011-03-29 10:38:16 -070024import android.view.LayoutInflater;
Adam Powell91511032011-07-13 10:24:06 -070025import android.view.MenuItem;
Alan Viverette80e72702013-07-29 11:12:59 -070026import android.view.MotionEvent;
Adam Powell42675342010-07-09 18:02:59 -070027import android.view.View;
28import android.view.View.MeasureSpec;
Adam Powell696cba52011-03-29 10:38:16 -070029import android.view.ViewGroup;
Adam Powell04587962010-11-11 22:15:07 -080030import android.view.ViewTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070031import android.widget.AdapterView;
Adam Powell696cba52011-03-29 10:38:16 -070032import android.widget.BaseAdapter;
Adam Powell91511032011-07-13 10:24:06 -070033import android.widget.FrameLayout;
Adam Powell696cba52011-03-29 10:38:16 -070034import android.widget.ListAdapter;
Adam Powell42675342010-07-09 18:02:59 -070035import android.widget.ListPopupWindow;
Adam Powell6c6f5752010-08-20 18:34:46 -070036import android.widget.PopupWindow;
Adam Powell42675342010-07-09 18:02:59 -070037
Adam Powell696cba52011-03-29 10:38:16 -070038import java.util.ArrayList;
Adam Powell8028dd32010-07-15 10:16:33 -070039
Adam Powell42675342010-07-09 18:02:59 -070040/**
Adam Powell696cba52011-03-29 10:38:16 -070041 * Presents a menu as a small, simple popup anchored to another view.
Adam Powell42675342010-07-09 18:02:59 -070042 * @hide
43 */
Adam Powell04587962010-11-11 22:15:07 -080044public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
Adam Powell4afd62b2011-02-18 15:02:18 -080045 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
Adam Powell696cba52011-03-29 10:38:16 -070046 View.OnAttachStateChangeListener, MenuPresenter {
Adam Powell42675342010-07-09 18:02:59 -070047 private static final String TAG = "MenuPopupHelper";
48
Adam Powell696cba52011-03-29 10:38:16 -070049 static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
50
Alan Viverette0bce6ab2013-06-26 17:46:16 -070051 private final Context mContext;
52 private final LayoutInflater mInflater;
53 private final MenuBuilder mMenu;
54 private final MenuAdapter mAdapter;
55 private final boolean mOverflowOnly;
56 private final int mPopupMaxWidth;
57
Adam Powell4afd62b2011-02-18 15:02:18 -080058 private View mAnchorView;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070059 private ListPopupWindow mPopup;
Adam Powell04587962010-11-11 22:15:07 -080060 private ViewTreeObserver mTreeObserver;
Adam Powell696cba52011-03-29 10:38:16 -070061 private Callback mPresenterCallback;
Adam Powelle2b03a62011-01-25 14:39:02 -080062
Adam Powell91511032011-07-13 10:24:06 -070063 boolean mForceShowIcon;
64
65 private ViewGroup mMeasureParent;
66
Alan Viverette0bce6ab2013-06-26 17:46:16 -070067 /** Whether the cached content width value is valid. */
68 private boolean mHasContentWidth;
69
70 /** Cached content width from {@link #measureContentWidth}. */
71 private int mContentWidth;
72
Adam Powell54c94de2013-09-26 15:36:34 -070073 private int mDropDownGravity = Gravity.NO_GRAVITY;
74
Adam Powell8028dd32010-07-15 10:16:33 -070075 public MenuPopupHelper(Context context, MenuBuilder menu) {
76 this(context, menu, null, false);
77 }
78
79 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
80 this(context, menu, anchorView, false);
81 }
82
83 public MenuPopupHelper(Context context, MenuBuilder menu,
84 View anchorView, boolean overflowOnly) {
Adam Powell42675342010-07-09 18:02:59 -070085 mContext = context;
Adam Powell696cba52011-03-29 10:38:16 -070086 mInflater = LayoutInflater.from(context);
Adam Powell8028dd32010-07-15 10:16:33 -070087 mMenu = menu;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070088 mAdapter = new MenuAdapter(mMenu);
Adam Powell8028dd32010-07-15 10:16:33 -070089 mOverflowOnly = overflowOnly;
Adam Powell42675342010-07-09 18:02:59 -070090
Adam Powell38639b12011-06-17 16:02:32 -070091 final Resources res = context.getResources();
92 mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
93 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
Adam Powell8028dd32010-07-15 10:16:33 -070094
Adam Powell4afd62b2011-02-18 15:02:18 -080095 mAnchorView = anchorView;
Adam Powell696cba52011-03-29 10:38:16 -070096
97 menu.addMenuPresenter(this);
Adam Powell42675342010-07-09 18:02:59 -070098 }
99
Adam Powellf0ad6e62011-01-10 17:14:06 -0800100 public void setAnchorView(View anchor) {
Adam Powell4afd62b2011-02-18 15:02:18 -0800101 mAnchorView = anchor;
Adam Powellf0ad6e62011-01-10 17:14:06 -0800102 }
103
Adam Powell91511032011-07-13 10:24:06 -0700104 public void setForceShowIcon(boolean forceShow) {
105 mForceShowIcon = forceShow;
106 }
107
Adam Powell54c94de2013-09-26 15:36:34 -0700108 public void setGravity(int gravity) {
109 mDropDownGravity = gravity;
110 }
111
Adam Powell42675342010-07-09 18:02:59 -0700112 public void show() {
Adam Powell5e3f2842011-01-07 17:16:56 -0800113 if (!tryShow()) {
114 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
115 }
116 }
117
Alan Viveretteca6a36112013-08-16 14:41:06 -0700118 public ListPopupWindow getPopup() {
119 return mPopup;
120 }
121
Adam Powell5e3f2842011-01-07 17:16:56 -0800122 public boolean tryShow() {
Adam Powell0b2d3062010-09-14 16:15:02 -0700123 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
Adam Powell8515ee82010-11-30 14:09:55 -0800124 mPopup.setOnDismissListener(this);
Adam Powell696cba52011-03-29 10:38:16 -0700125 mPopup.setOnItemClickListener(this);
Adam Powell696cba52011-03-29 10:38:16 -0700126 mPopup.setAdapter(mAdapter);
Adam Powell42675342010-07-09 18:02:59 -0700127 mPopup.setModal(true);
128
Adam Powell4afd62b2011-02-18 15:02:18 -0800129 View anchor = mAnchorView;
Adam Powell04587962010-11-11 22:15:07 -0800130 if (anchor != null) {
Adam Powell4afd62b2011-02-18 15:02:18 -0800131 final boolean addGlobalListener = mTreeObserver == null;
132 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
133 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
134 anchor.addOnAttachStateChangeListener(this);
Adam Powell04587962010-11-11 22:15:07 -0800135 mPopup.setAnchorView(anchor);
Adam Powell54c94de2013-09-26 15:36:34 -0700136 mPopup.setDropDownGravity(mDropDownGravity);
Adam Powell4be0d522010-08-03 17:53:14 -0700137 } else {
Adam Powell5e3f2842011-01-07 17:16:56 -0800138 return false;
Adam Powell8028dd32010-07-15 10:16:33 -0700139 }
Adam Powell42675342010-07-09 18:02:59 -0700140
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700141 if (!mHasContentWidth) {
142 mContentWidth = measureContentWidth();
143 mHasContentWidth = true;
144 }
145
146 mPopup.setContentWidth(mContentWidth);
Adam Powellaa0b92c2010-12-13 22:38:53 -0800147 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powell42675342010-07-09 18:02:59 -0700148 mPopup.show();
Adam Powell8028dd32010-07-15 10:16:33 -0700149 mPopup.getListView().setOnKeyListener(this);
Adam Powell5e3f2842011-01-07 17:16:56 -0800150 return true;
Adam Powell42675342010-07-09 18:02:59 -0700151 }
152
153 public void dismiss() {
Adam Powell3d3da272010-08-11 18:06:17 -0700154 if (isShowing()) {
155 mPopup.dismiss();
156 }
Adam Powell8515ee82010-11-30 14:09:55 -0800157 }
158
159 public void onDismiss() {
160 mPopup = null;
Adam Powell42b91bb2011-06-21 18:32:26 -0700161 mMenu.close();
Adam Powell4afd62b2011-02-18 15:02:18 -0800162 if (mTreeObserver != null) {
163 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
Adam Powellca51e872011-02-14 19:54:29 -0800164 mTreeObserver.removeGlobalOnLayoutListener(this);
Adam Powell4afd62b2011-02-18 15:02:18 -0800165 mTreeObserver = null;
Adam Powelled8b4032010-11-16 10:22:35 -0800166 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800167 mAnchorView.removeOnAttachStateChangeListener(this);
Adam Powell42675342010-07-09 18:02:59 -0700168 }
169
Adam Powell8028dd32010-07-15 10:16:33 -0700170 public boolean isShowing() {
171 return mPopup != null && mPopup.isShowing();
172 }
173
Adam Powell696cba52011-03-29 10:38:16 -0700174 @Override
Adam Powell42675342010-07-09 18:02:59 -0700175 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Adam Powell696cba52011-03-29 10:38:16 -0700176 MenuAdapter adapter = mAdapter;
177 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
Adam Powell42675342010-07-09 18:02:59 -0700178 }
179
Adam Powell8028dd32010-07-15 10:16:33 -0700180 public boolean onKey(View v, int keyCode, KeyEvent event) {
181 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
182 dismiss();
183 return true;
184 }
185 return false;
186 }
187
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700188 private int measureContentWidth() {
Adam Powell42675342010-07-09 18:02:59 -0700189 // Menus don't tend to be long, so this is more sane than it looks.
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700190 int maxWidth = 0;
Adam Powell42675342010-07-09 18:02:59 -0700191 View itemView = null;
Adam Powell50f784c2010-12-19 16:12:19 -0800192 int itemType = 0;
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700193
194 final ListAdapter adapter = mAdapter;
195 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
196 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Adam Powell42675342010-07-09 18:02:59 -0700197 final int count = adapter.getCount();
198 for (int i = 0; i < count; i++) {
Adam Powell50f784c2010-12-19 16:12:19 -0800199 final int positionType = adapter.getItemViewType(i);
200 if (positionType != itemType) {
201 itemType = positionType;
202 itemView = null;
203 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700204
Adam Powell91511032011-07-13 10:24:06 -0700205 if (mMeasureParent == null) {
206 mMeasureParent = new FrameLayout(mContext);
207 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700208
Adam Powell91511032011-07-13 10:24:06 -0700209 itemView = adapter.getView(i, itemView, mMeasureParent);
Adam Powell42675342010-07-09 18:02:59 -0700210 itemView.measure(widthMeasureSpec, heightMeasureSpec);
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700211
212 final int itemWidth = itemView.getMeasuredWidth();
213 if (itemWidth >= mPopupMaxWidth) {
214 return mPopupMaxWidth;
215 } else if (itemWidth > maxWidth) {
216 maxWidth = itemWidth;
217 }
Adam Powell42675342010-07-09 18:02:59 -0700218 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700219
220 return maxWidth;
Adam Powell42675342010-07-09 18:02:59 -0700221 }
Adam Powell04587962010-11-11 22:15:07 -0800222
223 @Override
224 public void onGlobalLayout() {
Adam Powell4afd62b2011-02-18 15:02:18 -0800225 if (isShowing()) {
226 final View anchor = mAnchorView;
Adam Powellca51e872011-02-14 19:54:29 -0800227 if (anchor == null || !anchor.isShown()) {
Adam Powell04587962010-11-11 22:15:07 -0800228 dismiss();
Adam Powellca51e872011-02-14 19:54:29 -0800229 } else if (isShowing()) {
Adam Powellaa0b92c2010-12-13 22:38:53 -0800230 // Recompute window size and position
231 mPopup.show();
Adam Powell04587962010-11-11 22:15:07 -0800232 }
233 }
234 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800235
236 @Override
237 public void onViewAttachedToWindow(View v) {
238 }
239
240 @Override
241 public void onViewDetachedFromWindow(View v) {
242 if (mTreeObserver != null) {
243 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
244 mTreeObserver.removeGlobalOnLayoutListener(this);
245 }
246 v.removeOnAttachStateChangeListener(this);
247 }
Adam Powell696cba52011-03-29 10:38:16 -0700248
249 @Override
250 public void initForMenu(Context context, MenuBuilder menu) {
251 // Don't need to do anything; we added as a presenter in the constructor.
252 }
253
254 @Override
255 public MenuView getMenuView(ViewGroup root) {
256 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
257 }
258
259 @Override
260 public void updateMenuView(boolean cleared) {
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700261 mHasContentWidth = false;
262
263 if (mAdapter != null) {
264 mAdapter.notifyDataSetChanged();
265 }
Adam Powell696cba52011-03-29 10:38:16 -0700266 }
267
268 @Override
269 public void setCallback(Callback cb) {
270 mPresenterCallback = cb;
271 }
272
273 @Override
274 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
275 if (subMenu.hasVisibleItems()) {
276 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
277 subPopup.setCallback(mPresenterCallback);
Adam Powell91511032011-07-13 10:24:06 -0700278
279 boolean preserveIconSpacing = false;
280 final int count = subMenu.size();
281 for (int i = 0; i < count; i++) {
282 MenuItem childItem = subMenu.getItem(i);
283 if (childItem.isVisible() && childItem.getIcon() != null) {
284 preserveIconSpacing = true;
285 break;
286 }
287 }
288 subPopup.setForceShowIcon(preserveIconSpacing);
289
Adam Powell696cba52011-03-29 10:38:16 -0700290 if (subPopup.tryShow()) {
291 if (mPresenterCallback != null) {
292 mPresenterCallback.onOpenSubMenu(subMenu);
293 }
294 return true;
295 }
296 }
297 return false;
298 }
299
300 @Override
301 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
302 // Only care about the (sub)menu we're presenting.
303 if (menu != mMenu) return;
304
305 dismiss();
306 if (mPresenterCallback != null) {
307 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
308 }
309 }
310
311 @Override
312 public boolean flagActionItems() {
313 return false;
314 }
315
Adam Powell8d02dea2011-05-31 21:35:13 -0700316 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
317 return false;
318 }
319
320 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
321 return false;
322 }
323
Adam Powell275702c2011-09-23 17:34:04 -0700324 @Override
325 public int getId() {
326 return 0;
327 }
328
329 @Override
330 public Parcelable onSaveInstanceState() {
331 return null;
332 }
333
334 @Override
335 public void onRestoreInstanceState(Parcelable state) {
336 }
337
Adam Powell696cba52011-03-29 10:38:16 -0700338 private class MenuAdapter extends BaseAdapter {
339 private MenuBuilder mAdapterMenu;
Adam Powell275702c2011-09-23 17:34:04 -0700340 private int mExpandedIndex = -1;
Adam Powell696cba52011-03-29 10:38:16 -0700341
342 public MenuAdapter(MenuBuilder menu) {
343 mAdapterMenu = menu;
Adam Powell275702c2011-09-23 17:34:04 -0700344 findExpandedIndex();
Adam Powell696cba52011-03-29 10:38:16 -0700345 }
346
347 public int getCount() {
348 ArrayList<MenuItemImpl> items = mOverflowOnly ?
349 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700350 if (mExpandedIndex < 0) {
351 return items.size();
352 }
353 return items.size() - 1;
Adam Powell696cba52011-03-29 10:38:16 -0700354 }
355
356 public MenuItemImpl getItem(int position) {
357 ArrayList<MenuItemImpl> items = mOverflowOnly ?
358 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700359 if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
360 position++;
361 }
Adam Powell696cba52011-03-29 10:38:16 -0700362 return items.get(position);
363 }
364
365 public long getItemId(int position) {
366 // Since a menu item's ID is optional, we'll use the position as an
367 // ID for the item in the AdapterView
368 return position;
369 }
370
371 public View getView(int position, View convertView, ViewGroup parent) {
372 if (convertView == null) {
373 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
374 }
375
376 MenuView.ItemView itemView = (MenuView.ItemView) convertView;
Adam Powell91511032011-07-13 10:24:06 -0700377 if (mForceShowIcon) {
378 ((ListMenuItemView) convertView).setForceShowIcon(true);
379 }
Adam Powell696cba52011-03-29 10:38:16 -0700380 itemView.initialize(getItem(position), 0);
381 return convertView;
382 }
Adam Powell275702c2011-09-23 17:34:04 -0700383
384 void findExpandedIndex() {
385 final MenuItemImpl expandedItem = mMenu.getExpandedItem();
386 if (expandedItem != null) {
387 final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
388 final int count = items.size();
389 for (int i = 0; i < count; i++) {
390 final MenuItemImpl item = items.get(i);
391 if (item == expandedItem) {
392 mExpandedIndex = i;
393 return;
394 }
395 }
396 }
397 mExpandedIndex = -1;
398 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700399
Adam Powell275702c2011-09-23 17:34:04 -0700400 @Override
Adam Powell76889f32012-05-08 22:22:52 -0700401 public void notifyDataSetChanged() {
402 findExpandedIndex();
403 super.notifyDataSetChanged();
Adam Powell275702c2011-09-23 17:34:04 -0700404 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700405 }
Adam Powell42675342010-07-09 18:02:59 -0700406}