blob: 6d398601011e909db05cd880aa1713c8384b51c6 [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 Powell8028dd32010-07-15 10:16:33 -070022import android.view.KeyEvent;
Adam Powell696cba52011-03-29 10:38:16 -070023import android.view.LayoutInflater;
Adam Powell91511032011-07-13 10:24:06 -070024import android.view.MenuItem;
Adam Powell42675342010-07-09 18:02:59 -070025import android.view.View;
26import android.view.View.MeasureSpec;
Adam Powell696cba52011-03-29 10:38:16 -070027import android.view.ViewGroup;
Adam Powell04587962010-11-11 22:15:07 -080028import android.view.ViewTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070029import android.widget.AdapterView;
Adam Powell696cba52011-03-29 10:38:16 -070030import android.widget.BaseAdapter;
Adam Powell91511032011-07-13 10:24:06 -070031import android.widget.FrameLayout;
Adam Powell696cba52011-03-29 10:38:16 -070032import android.widget.ListAdapter;
Adam Powell42675342010-07-09 18:02:59 -070033import android.widget.ListPopupWindow;
Adam Powell6c6f5752010-08-20 18:34:46 -070034import android.widget.PopupWindow;
Adam Powell42675342010-07-09 18:02:59 -070035
Adam Powell696cba52011-03-29 10:38:16 -070036import java.util.ArrayList;
Adam Powell8028dd32010-07-15 10:16:33 -070037
Adam Powell42675342010-07-09 18:02:59 -070038/**
Adam Powell696cba52011-03-29 10:38:16 -070039 * Presents a menu as a small, simple popup anchored to another view.
Adam Powell42675342010-07-09 18:02:59 -070040 * @hide
41 */
Adam Powell04587962010-11-11 22:15:07 -080042public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
Adam Powell4afd62b2011-02-18 15:02:18 -080043 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
Adam Powell696cba52011-03-29 10:38:16 -070044 View.OnAttachStateChangeListener, MenuPresenter {
Adam Powell42675342010-07-09 18:02:59 -070045 private static final String TAG = "MenuPopupHelper";
46
Adam Powell696cba52011-03-29 10:38:16 -070047 static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
48
Alan Viverette0bce6ab2013-06-26 17:46:16 -070049 private final Context mContext;
50 private final LayoutInflater mInflater;
51 private final MenuBuilder mMenu;
52 private final MenuAdapter mAdapter;
53 private final boolean mOverflowOnly;
54 private final int mPopupMaxWidth;
55
Adam Powell4afd62b2011-02-18 15:02:18 -080056 private View mAnchorView;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070057 private ListPopupWindow mPopup;
Adam Powell04587962010-11-11 22:15:07 -080058 private ViewTreeObserver mTreeObserver;
Adam Powell696cba52011-03-29 10:38:16 -070059 private Callback mPresenterCallback;
Adam Powelle2b03a62011-01-25 14:39:02 -080060
Adam Powell91511032011-07-13 10:24:06 -070061 boolean mForceShowIcon;
62
63 private ViewGroup mMeasureParent;
64
Alan Viverette0bce6ab2013-06-26 17:46:16 -070065 /** Whether the cached content width value is valid. */
66 private boolean mHasContentWidth;
67
68 /** Cached content width from {@link #measureContentWidth}. */
69 private int mContentWidth;
70
Adam Powell8028dd32010-07-15 10:16:33 -070071 public MenuPopupHelper(Context context, MenuBuilder menu) {
72 this(context, menu, null, false);
73 }
74
75 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
76 this(context, menu, anchorView, false);
77 }
78
79 public MenuPopupHelper(Context context, MenuBuilder menu,
80 View anchorView, boolean overflowOnly) {
Adam Powell42675342010-07-09 18:02:59 -070081 mContext = context;
Adam Powell696cba52011-03-29 10:38:16 -070082 mInflater = LayoutInflater.from(context);
Adam Powell8028dd32010-07-15 10:16:33 -070083 mMenu = menu;
Alan Viverette0bce6ab2013-06-26 17:46:16 -070084 mAdapter = new MenuAdapter(mMenu);
Adam Powell8028dd32010-07-15 10:16:33 -070085 mOverflowOnly = overflowOnly;
Adam Powell42675342010-07-09 18:02:59 -070086
Adam Powell38639b12011-06-17 16:02:32 -070087 final Resources res = context.getResources();
88 mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
89 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
Adam Powell8028dd32010-07-15 10:16:33 -070090
Adam Powell4afd62b2011-02-18 15:02:18 -080091 mAnchorView = anchorView;
Adam Powell696cba52011-03-29 10:38:16 -070092
93 menu.addMenuPresenter(this);
Adam Powell42675342010-07-09 18:02:59 -070094 }
95
Adam Powellf0ad6e62011-01-10 17:14:06 -080096 public void setAnchorView(View anchor) {
Adam Powell4afd62b2011-02-18 15:02:18 -080097 mAnchorView = anchor;
Adam Powellf0ad6e62011-01-10 17:14:06 -080098 }
99
Adam Powell91511032011-07-13 10:24:06 -0700100 public void setForceShowIcon(boolean forceShow) {
101 mForceShowIcon = forceShow;
102 }
103
Adam Powell42675342010-07-09 18:02:59 -0700104 public void show() {
Adam Powell5e3f2842011-01-07 17:16:56 -0800105 if (!tryShow()) {
106 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
107 }
108 }
109
110 public boolean tryShow() {
Adam Powell0b2d3062010-09-14 16:15:02 -0700111 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
Adam Powell8515ee82010-11-30 14:09:55 -0800112 mPopup.setOnDismissListener(this);
Adam Powell696cba52011-03-29 10:38:16 -0700113 mPopup.setOnItemClickListener(this);
Adam Powell696cba52011-03-29 10:38:16 -0700114 mPopup.setAdapter(mAdapter);
Adam Powell42675342010-07-09 18:02:59 -0700115 mPopup.setModal(true);
116
Adam Powell4afd62b2011-02-18 15:02:18 -0800117 View anchor = mAnchorView;
Adam Powell04587962010-11-11 22:15:07 -0800118 if (anchor != null) {
Adam Powell4afd62b2011-02-18 15:02:18 -0800119 final boolean addGlobalListener = mTreeObserver == null;
120 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
121 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
122 anchor.addOnAttachStateChangeListener(this);
Adam Powell04587962010-11-11 22:15:07 -0800123 mPopup.setAnchorView(anchor);
Adam Powell4be0d522010-08-03 17:53:14 -0700124 } else {
Adam Powell5e3f2842011-01-07 17:16:56 -0800125 return false;
Adam Powell8028dd32010-07-15 10:16:33 -0700126 }
Adam Powell42675342010-07-09 18:02:59 -0700127
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700128 if (!mHasContentWidth) {
129 mContentWidth = measureContentWidth();
130 mHasContentWidth = true;
131 }
132
133 mPopup.setContentWidth(mContentWidth);
Adam Powellaa0b92c2010-12-13 22:38:53 -0800134 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powell42675342010-07-09 18:02:59 -0700135 mPopup.show();
Adam Powell8028dd32010-07-15 10:16:33 -0700136 mPopup.getListView().setOnKeyListener(this);
Adam Powell5e3f2842011-01-07 17:16:56 -0800137 return true;
Adam Powell42675342010-07-09 18:02:59 -0700138 }
139
140 public void dismiss() {
Adam Powell3d3da272010-08-11 18:06:17 -0700141 if (isShowing()) {
142 mPopup.dismiss();
143 }
Adam Powell8515ee82010-11-30 14:09:55 -0800144 }
145
146 public void onDismiss() {
147 mPopup = null;
Adam Powell42b91bb2011-06-21 18:32:26 -0700148 mMenu.close();
Adam Powell4afd62b2011-02-18 15:02:18 -0800149 if (mTreeObserver != null) {
150 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
Adam Powellca51e872011-02-14 19:54:29 -0800151 mTreeObserver.removeGlobalOnLayoutListener(this);
Adam Powell4afd62b2011-02-18 15:02:18 -0800152 mTreeObserver = null;
Adam Powelled8b4032010-11-16 10:22:35 -0800153 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800154 mAnchorView.removeOnAttachStateChangeListener(this);
Adam Powell42675342010-07-09 18:02:59 -0700155 }
156
Adam Powell8028dd32010-07-15 10:16:33 -0700157 public boolean isShowing() {
158 return mPopup != null && mPopup.isShowing();
159 }
160
Adam Powell696cba52011-03-29 10:38:16 -0700161 @Override
Adam Powell42675342010-07-09 18:02:59 -0700162 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Adam Powell696cba52011-03-29 10:38:16 -0700163 MenuAdapter adapter = mAdapter;
164 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
Adam Powell42675342010-07-09 18:02:59 -0700165 }
166
Adam Powell8028dd32010-07-15 10:16:33 -0700167 public boolean onKey(View v, int keyCode, KeyEvent event) {
168 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
169 dismiss();
170 return true;
171 }
172 return false;
173 }
174
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700175 private int measureContentWidth() {
Adam Powell42675342010-07-09 18:02:59 -0700176 // Menus don't tend to be long, so this is more sane than it looks.
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700177 int maxWidth = 0;
Adam Powell42675342010-07-09 18:02:59 -0700178 View itemView = null;
Adam Powell50f784c2010-12-19 16:12:19 -0800179 int itemType = 0;
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700180
181 final ListAdapter adapter = mAdapter;
182 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
183 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
Adam Powell42675342010-07-09 18:02:59 -0700184 final int count = adapter.getCount();
185 for (int i = 0; i < count; i++) {
Adam Powell50f784c2010-12-19 16:12:19 -0800186 final int positionType = adapter.getItemViewType(i);
187 if (positionType != itemType) {
188 itemType = positionType;
189 itemView = null;
190 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700191
Adam Powell91511032011-07-13 10:24:06 -0700192 if (mMeasureParent == null) {
193 mMeasureParent = new FrameLayout(mContext);
194 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700195
Adam Powell91511032011-07-13 10:24:06 -0700196 itemView = adapter.getView(i, itemView, mMeasureParent);
Adam Powell42675342010-07-09 18:02:59 -0700197 itemView.measure(widthMeasureSpec, heightMeasureSpec);
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700198
199 final int itemWidth = itemView.getMeasuredWidth();
200 if (itemWidth >= mPopupMaxWidth) {
201 return mPopupMaxWidth;
202 } else if (itemWidth > maxWidth) {
203 maxWidth = itemWidth;
204 }
Adam Powell42675342010-07-09 18:02:59 -0700205 }
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700206
207 return maxWidth;
Adam Powell42675342010-07-09 18:02:59 -0700208 }
Adam Powell04587962010-11-11 22:15:07 -0800209
210 @Override
211 public void onGlobalLayout() {
Adam Powell4afd62b2011-02-18 15:02:18 -0800212 if (isShowing()) {
213 final View anchor = mAnchorView;
Adam Powellca51e872011-02-14 19:54:29 -0800214 if (anchor == null || !anchor.isShown()) {
Adam Powell04587962010-11-11 22:15:07 -0800215 dismiss();
Adam Powellca51e872011-02-14 19:54:29 -0800216 } else if (isShowing()) {
Adam Powellaa0b92c2010-12-13 22:38:53 -0800217 // Recompute window size and position
218 mPopup.show();
Adam Powell04587962010-11-11 22:15:07 -0800219 }
220 }
221 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800222
223 @Override
224 public void onViewAttachedToWindow(View v) {
225 }
226
227 @Override
228 public void onViewDetachedFromWindow(View v) {
229 if (mTreeObserver != null) {
230 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
231 mTreeObserver.removeGlobalOnLayoutListener(this);
232 }
233 v.removeOnAttachStateChangeListener(this);
234 }
Adam Powell696cba52011-03-29 10:38:16 -0700235
236 @Override
237 public void initForMenu(Context context, MenuBuilder menu) {
238 // Don't need to do anything; we added as a presenter in the constructor.
239 }
240
241 @Override
242 public MenuView getMenuView(ViewGroup root) {
243 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
244 }
245
246 @Override
247 public void updateMenuView(boolean cleared) {
Alan Viverette0bce6ab2013-06-26 17:46:16 -0700248 mHasContentWidth = false;
249
250 if (mAdapter != null) {
251 mAdapter.notifyDataSetChanged();
252 }
Adam Powell696cba52011-03-29 10:38:16 -0700253 }
254
255 @Override
256 public void setCallback(Callback cb) {
257 mPresenterCallback = cb;
258 }
259
260 @Override
261 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
262 if (subMenu.hasVisibleItems()) {
263 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
264 subPopup.setCallback(mPresenterCallback);
Adam Powell91511032011-07-13 10:24:06 -0700265
266 boolean preserveIconSpacing = false;
267 final int count = subMenu.size();
268 for (int i = 0; i < count; i++) {
269 MenuItem childItem = subMenu.getItem(i);
270 if (childItem.isVisible() && childItem.getIcon() != null) {
271 preserveIconSpacing = true;
272 break;
273 }
274 }
275 subPopup.setForceShowIcon(preserveIconSpacing);
276
Adam Powell696cba52011-03-29 10:38:16 -0700277 if (subPopup.tryShow()) {
278 if (mPresenterCallback != null) {
279 mPresenterCallback.onOpenSubMenu(subMenu);
280 }
281 return true;
282 }
283 }
284 return false;
285 }
286
287 @Override
288 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
289 // Only care about the (sub)menu we're presenting.
290 if (menu != mMenu) return;
291
292 dismiss();
293 if (mPresenterCallback != null) {
294 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
295 }
296 }
297
298 @Override
299 public boolean flagActionItems() {
300 return false;
301 }
302
Adam Powell8d02dea2011-05-31 21:35:13 -0700303 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
304 return false;
305 }
306
307 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
308 return false;
309 }
310
Adam Powell275702c2011-09-23 17:34:04 -0700311 @Override
312 public int getId() {
313 return 0;
314 }
315
316 @Override
317 public Parcelable onSaveInstanceState() {
318 return null;
319 }
320
321 @Override
322 public void onRestoreInstanceState(Parcelable state) {
323 }
324
Adam Powell696cba52011-03-29 10:38:16 -0700325 private class MenuAdapter extends BaseAdapter {
326 private MenuBuilder mAdapterMenu;
Adam Powell275702c2011-09-23 17:34:04 -0700327 private int mExpandedIndex = -1;
Adam Powell696cba52011-03-29 10:38:16 -0700328
329 public MenuAdapter(MenuBuilder menu) {
330 mAdapterMenu = menu;
Adam Powell275702c2011-09-23 17:34:04 -0700331 findExpandedIndex();
Adam Powell696cba52011-03-29 10:38:16 -0700332 }
333
334 public int getCount() {
335 ArrayList<MenuItemImpl> items = mOverflowOnly ?
336 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700337 if (mExpandedIndex < 0) {
338 return items.size();
339 }
340 return items.size() - 1;
Adam Powell696cba52011-03-29 10:38:16 -0700341 }
342
343 public MenuItemImpl getItem(int position) {
344 ArrayList<MenuItemImpl> items = mOverflowOnly ?
345 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700346 if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
347 position++;
348 }
Adam Powell696cba52011-03-29 10:38:16 -0700349 return items.get(position);
350 }
351
352 public long getItemId(int position) {
353 // Since a menu item's ID is optional, we'll use the position as an
354 // ID for the item in the AdapterView
355 return position;
356 }
357
358 public View getView(int position, View convertView, ViewGroup parent) {
359 if (convertView == null) {
360 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
361 }
362
363 MenuView.ItemView itemView = (MenuView.ItemView) convertView;
Adam Powell91511032011-07-13 10:24:06 -0700364 if (mForceShowIcon) {
365 ((ListMenuItemView) convertView).setForceShowIcon(true);
366 }
Adam Powell696cba52011-03-29 10:38:16 -0700367 itemView.initialize(getItem(position), 0);
368 return convertView;
369 }
Adam Powell275702c2011-09-23 17:34:04 -0700370
371 void findExpandedIndex() {
372 final MenuItemImpl expandedItem = mMenu.getExpandedItem();
373 if (expandedItem != null) {
374 final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
375 final int count = items.size();
376 for (int i = 0; i < count; i++) {
377 final MenuItemImpl item = items.get(i);
378 if (item == expandedItem) {
379 mExpandedIndex = i;
380 return;
381 }
382 }
383 }
384 mExpandedIndex = -1;
385 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700386
Adam Powell275702c2011-09-23 17:34:04 -0700387 @Override
Adam Powell76889f32012-05-08 22:22:52 -0700388 public void notifyDataSetChanged() {
389 findExpandedIndex();
390 super.notifyDataSetChanged();
Adam Powell275702c2011-09-23 17:34:04 -0700391 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700392 }
Adam Powell42675342010-07-09 18:02:59 -0700393}