blob: cacc86b0f3d9eace36ca10dd50ef4fd843f698a3 [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 Powell275702c2011-09-23 17:34:04 -070021import android.database.DataSetObserver;
Adam Powell11ed1d62011-07-11 21:19:59 -070022import android.os.Parcelable;
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;
Adam Powell42675342010-07-09 18:02:59 -070026import android.view.View;
27import android.view.View.MeasureSpec;
Adam Powell696cba52011-03-29 10:38:16 -070028import android.view.ViewGroup;
Adam Powell04587962010-11-11 22:15:07 -080029import android.view.ViewTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070030import android.widget.AdapterView;
Adam Powell696cba52011-03-29 10:38:16 -070031import android.widget.BaseAdapter;
Adam Powell91511032011-07-13 10:24:06 -070032import android.widget.FrameLayout;
Adam Powell696cba52011-03-29 10:38:16 -070033import android.widget.ListAdapter;
Adam Powell42675342010-07-09 18:02:59 -070034import android.widget.ListPopupWindow;
Adam Powell6c6f5752010-08-20 18:34:46 -070035import android.widget.PopupWindow;
Adam Powell42675342010-07-09 18:02:59 -070036
Adam Powell696cba52011-03-29 10:38:16 -070037import java.util.ArrayList;
Adam Powell8028dd32010-07-15 10:16:33 -070038
Adam Powell42675342010-07-09 18:02:59 -070039/**
Adam Powell696cba52011-03-29 10:38:16 -070040 * Presents a menu as a small, simple popup anchored to another view.
Adam Powell42675342010-07-09 18:02:59 -070041 * @hide
42 */
Adam Powell04587962010-11-11 22:15:07 -080043public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
Adam Powell4afd62b2011-02-18 15:02:18 -080044 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
Adam Powell696cba52011-03-29 10:38:16 -070045 View.OnAttachStateChangeListener, MenuPresenter {
Adam Powell42675342010-07-09 18:02:59 -070046 private static final String TAG = "MenuPopupHelper";
47
Adam Powell696cba52011-03-29 10:38:16 -070048 static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
49
Adam Powell42675342010-07-09 18:02:59 -070050 private Context mContext;
Adam Powell696cba52011-03-29 10:38:16 -070051 private LayoutInflater mInflater;
Adam Powell42675342010-07-09 18:02:59 -070052 private ListPopupWindow mPopup;
Adam Powell8028dd32010-07-15 10:16:33 -070053 private MenuBuilder mMenu;
Adam Powell42675342010-07-09 18:02:59 -070054 private int mPopupMaxWidth;
Adam Powell4afd62b2011-02-18 15:02:18 -080055 private View mAnchorView;
Adam Powell8028dd32010-07-15 10:16:33 -070056 private boolean mOverflowOnly;
Adam Powell04587962010-11-11 22:15:07 -080057 private ViewTreeObserver mTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070058
Adam Powell696cba52011-03-29 10:38:16 -070059 private MenuAdapter mAdapter;
60
61 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
Adam Powell8028dd32010-07-15 10:16:33 -070067 public MenuPopupHelper(Context context, MenuBuilder menu) {
68 this(context, menu, null, false);
69 }
70
71 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
72 this(context, menu, anchorView, false);
73 }
74
75 public MenuPopupHelper(Context context, MenuBuilder menu,
76 View anchorView, boolean overflowOnly) {
Adam Powell42675342010-07-09 18:02:59 -070077 mContext = context;
Adam Powell696cba52011-03-29 10:38:16 -070078 mInflater = LayoutInflater.from(context);
Adam Powell8028dd32010-07-15 10:16:33 -070079 mMenu = menu;
80 mOverflowOnly = overflowOnly;
Adam Powell42675342010-07-09 18:02:59 -070081
Adam Powell38639b12011-06-17 16:02:32 -070082 final Resources res = context.getResources();
83 mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
84 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
Adam Powell8028dd32010-07-15 10:16:33 -070085
Adam Powell4afd62b2011-02-18 15:02:18 -080086 mAnchorView = anchorView;
Adam Powell696cba52011-03-29 10:38:16 -070087
88 menu.addMenuPresenter(this);
Adam Powell42675342010-07-09 18:02:59 -070089 }
90
Adam Powellf0ad6e62011-01-10 17:14:06 -080091 public void setAnchorView(View anchor) {
Adam Powell4afd62b2011-02-18 15:02:18 -080092 mAnchorView = anchor;
Adam Powellf0ad6e62011-01-10 17:14:06 -080093 }
94
Adam Powell91511032011-07-13 10:24:06 -070095 public void setForceShowIcon(boolean forceShow) {
96 mForceShowIcon = forceShow;
97 }
98
Adam Powell42675342010-07-09 18:02:59 -070099 public void show() {
Adam Powell5e3f2842011-01-07 17:16:56 -0800100 if (!tryShow()) {
101 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
102 }
103 }
104
105 public boolean tryShow() {
Adam Powell0b2d3062010-09-14 16:15:02 -0700106 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
Adam Powell8515ee82010-11-30 14:09:55 -0800107 mPopup.setOnDismissListener(this);
Adam Powell696cba52011-03-29 10:38:16 -0700108 mPopup.setOnItemClickListener(this);
Adam Powell42675342010-07-09 18:02:59 -0700109
Adam Powell696cba52011-03-29 10:38:16 -0700110 mAdapter = new MenuAdapter(mMenu);
111 mPopup.setAdapter(mAdapter);
Adam Powell42675342010-07-09 18:02:59 -0700112 mPopup.setModal(true);
113
Adam Powell4afd62b2011-02-18 15:02:18 -0800114 View anchor = mAnchorView;
Adam Powell04587962010-11-11 22:15:07 -0800115 if (anchor != null) {
Adam Powell4afd62b2011-02-18 15:02:18 -0800116 final boolean addGlobalListener = mTreeObserver == null;
117 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
118 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
119 anchor.addOnAttachStateChangeListener(this);
Adam Powell04587962010-11-11 22:15:07 -0800120 mPopup.setAnchorView(anchor);
Adam Powell4be0d522010-08-03 17:53:14 -0700121 } else {
Adam Powell5e3f2842011-01-07 17:16:56 -0800122 return false;
Adam Powell8028dd32010-07-15 10:16:33 -0700123 }
Adam Powell42675342010-07-09 18:02:59 -0700124
Adam Powell696cba52011-03-29 10:38:16 -0700125 mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
Adam Powellaa0b92c2010-12-13 22:38:53 -0800126 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powell42675342010-07-09 18:02:59 -0700127 mPopup.show();
Adam Powell8028dd32010-07-15 10:16:33 -0700128 mPopup.getListView().setOnKeyListener(this);
Adam Powell5e3f2842011-01-07 17:16:56 -0800129 return true;
Adam Powell42675342010-07-09 18:02:59 -0700130 }
131
132 public void dismiss() {
Adam Powell3d3da272010-08-11 18:06:17 -0700133 if (isShowing()) {
134 mPopup.dismiss();
135 }
Adam Powell8515ee82010-11-30 14:09:55 -0800136 }
137
138 public void onDismiss() {
139 mPopup = null;
Adam Powell42b91bb2011-06-21 18:32:26 -0700140 mMenu.close();
Adam Powell4afd62b2011-02-18 15:02:18 -0800141 if (mTreeObserver != null) {
142 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
Adam Powellca51e872011-02-14 19:54:29 -0800143 mTreeObserver.removeGlobalOnLayoutListener(this);
Adam Powell4afd62b2011-02-18 15:02:18 -0800144 mTreeObserver = null;
Adam Powelled8b4032010-11-16 10:22:35 -0800145 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800146 mAnchorView.removeOnAttachStateChangeListener(this);
Adam Powell42675342010-07-09 18:02:59 -0700147 }
148
Adam Powell8028dd32010-07-15 10:16:33 -0700149 public boolean isShowing() {
150 return mPopup != null && mPopup.isShowing();
151 }
152
Adam Powell696cba52011-03-29 10:38:16 -0700153 @Override
Adam Powell42675342010-07-09 18:02:59 -0700154 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Adam Powell696cba52011-03-29 10:38:16 -0700155 MenuAdapter adapter = mAdapter;
156 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
Adam Powell42675342010-07-09 18:02:59 -0700157 }
158
Adam Powell8028dd32010-07-15 10:16:33 -0700159 public boolean onKey(View v, int keyCode, KeyEvent event) {
160 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
161 dismiss();
162 return true;
163 }
164 return false;
165 }
166
Adam Powell696cba52011-03-29 10:38:16 -0700167 private int measureContentWidth(ListAdapter adapter) {
Adam Powell42675342010-07-09 18:02:59 -0700168 // Menus don't tend to be long, so this is more sane than it looks.
169 int width = 0;
170 View itemView = null;
Adam Powell50f784c2010-12-19 16:12:19 -0800171 int itemType = 0;
Adam Powell42675342010-07-09 18:02:59 -0700172 final int widthMeasureSpec =
173 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
174 final int heightMeasureSpec =
175 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
176 final int count = adapter.getCount();
177 for (int i = 0; i < count; i++) {
Adam Powell50f784c2010-12-19 16:12:19 -0800178 final int positionType = adapter.getItemViewType(i);
179 if (positionType != itemType) {
180 itemType = positionType;
181 itemView = null;
182 }
Adam Powell91511032011-07-13 10:24:06 -0700183 if (mMeasureParent == null) {
184 mMeasureParent = new FrameLayout(mContext);
185 }
186 itemView = adapter.getView(i, itemView, mMeasureParent);
Adam Powell42675342010-07-09 18:02:59 -0700187 itemView.measure(widthMeasureSpec, heightMeasureSpec);
188 width = Math.max(width, itemView.getMeasuredWidth());
189 }
190 return width;
191 }
Adam Powell04587962010-11-11 22:15:07 -0800192
193 @Override
194 public void onGlobalLayout() {
Adam Powell4afd62b2011-02-18 15:02:18 -0800195 if (isShowing()) {
196 final View anchor = mAnchorView;
Adam Powellca51e872011-02-14 19:54:29 -0800197 if (anchor == null || !anchor.isShown()) {
Adam Powell04587962010-11-11 22:15:07 -0800198 dismiss();
Adam Powellca51e872011-02-14 19:54:29 -0800199 } else if (isShowing()) {
Adam Powellaa0b92c2010-12-13 22:38:53 -0800200 // Recompute window size and position
201 mPopup.show();
Adam Powell04587962010-11-11 22:15:07 -0800202 }
203 }
204 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800205
206 @Override
207 public void onViewAttachedToWindow(View v) {
208 }
209
210 @Override
211 public void onViewDetachedFromWindow(View v) {
212 if (mTreeObserver != null) {
213 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
214 mTreeObserver.removeGlobalOnLayoutListener(this);
215 }
216 v.removeOnAttachStateChangeListener(this);
217 }
Adam Powell696cba52011-03-29 10:38:16 -0700218
219 @Override
220 public void initForMenu(Context context, MenuBuilder menu) {
221 // Don't need to do anything; we added as a presenter in the constructor.
222 }
223
224 @Override
225 public MenuView getMenuView(ViewGroup root) {
226 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
227 }
228
229 @Override
230 public void updateMenuView(boolean cleared) {
231 if (mAdapter != null) mAdapter.notifyDataSetChanged();
232 }
233
234 @Override
235 public void setCallback(Callback cb) {
236 mPresenterCallback = cb;
237 }
238
239 @Override
240 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
241 if (subMenu.hasVisibleItems()) {
242 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
243 subPopup.setCallback(mPresenterCallback);
Adam Powell91511032011-07-13 10:24:06 -0700244
245 boolean preserveIconSpacing = false;
246 final int count = subMenu.size();
247 for (int i = 0; i < count; i++) {
248 MenuItem childItem = subMenu.getItem(i);
249 if (childItem.isVisible() && childItem.getIcon() != null) {
250 preserveIconSpacing = true;
251 break;
252 }
253 }
254 subPopup.setForceShowIcon(preserveIconSpacing);
255
Adam Powell696cba52011-03-29 10:38:16 -0700256 if (subPopup.tryShow()) {
257 if (mPresenterCallback != null) {
258 mPresenterCallback.onOpenSubMenu(subMenu);
259 }
260 return true;
261 }
262 }
263 return false;
264 }
265
266 @Override
267 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
268 // Only care about the (sub)menu we're presenting.
269 if (menu != mMenu) return;
270
271 dismiss();
272 if (mPresenterCallback != null) {
273 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
274 }
275 }
276
277 @Override
278 public boolean flagActionItems() {
279 return false;
280 }
281
Adam Powell8d02dea2011-05-31 21:35:13 -0700282 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
283 return false;
284 }
285
286 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
287 return false;
288 }
289
Adam Powell275702c2011-09-23 17:34:04 -0700290 @Override
291 public int getId() {
292 return 0;
293 }
294
295 @Override
296 public Parcelable onSaveInstanceState() {
297 return null;
298 }
299
300 @Override
301 public void onRestoreInstanceState(Parcelable state) {
302 }
303
Adam Powell696cba52011-03-29 10:38:16 -0700304 private class MenuAdapter extends BaseAdapter {
305 private MenuBuilder mAdapterMenu;
Adam Powell275702c2011-09-23 17:34:04 -0700306 private int mExpandedIndex = -1;
Adam Powell696cba52011-03-29 10:38:16 -0700307
308 public MenuAdapter(MenuBuilder menu) {
309 mAdapterMenu = menu;
Adam Powell275702c2011-09-23 17:34:04 -0700310 findExpandedIndex();
Adam Powell696cba52011-03-29 10:38:16 -0700311 }
312
313 public int getCount() {
314 ArrayList<MenuItemImpl> items = mOverflowOnly ?
315 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700316 if (mExpandedIndex < 0) {
317 return items.size();
318 }
319 return items.size() - 1;
Adam Powell696cba52011-03-29 10:38:16 -0700320 }
321
322 public MenuItemImpl getItem(int position) {
323 ArrayList<MenuItemImpl> items = mOverflowOnly ?
324 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
Adam Powell275702c2011-09-23 17:34:04 -0700325 if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
326 position++;
327 }
Adam Powell696cba52011-03-29 10:38:16 -0700328 return items.get(position);
329 }
330
331 public long getItemId(int position) {
332 // Since a menu item's ID is optional, we'll use the position as an
333 // ID for the item in the AdapterView
334 return position;
335 }
336
337 public View getView(int position, View convertView, ViewGroup parent) {
338 if (convertView == null) {
339 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
340 }
341
342 MenuView.ItemView itemView = (MenuView.ItemView) convertView;
Adam Powell91511032011-07-13 10:24:06 -0700343 if (mForceShowIcon) {
344 ((ListMenuItemView) convertView).setForceShowIcon(true);
345 }
Adam Powell696cba52011-03-29 10:38:16 -0700346 itemView.initialize(getItem(position), 0);
347 return convertView;
348 }
Adam Powell275702c2011-09-23 17:34:04 -0700349
350 void findExpandedIndex() {
351 final MenuItemImpl expandedItem = mMenu.getExpandedItem();
352 if (expandedItem != null) {
353 final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
354 final int count = items.size();
355 for (int i = 0; i < count; i++) {
356 final MenuItemImpl item = items.get(i);
357 if (item == expandedItem) {
358 mExpandedIndex = i;
359 return;
360 }
361 }
362 }
363 mExpandedIndex = -1;
364 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700365
Adam Powell275702c2011-09-23 17:34:04 -0700366 @Override
Adam Powell76889f32012-05-08 22:22:52 -0700367 public void notifyDataSetChanged() {
368 findExpandedIndex();
369 super.notifyDataSetChanged();
Adam Powell275702c2011-09-23 17:34:04 -0700370 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700371 }
Adam Powell42675342010-07-09 18:02:59 -0700372}