blob: 38cec2983529796a983ee799f47ad0e5c76686e4 [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;
20import android.util.DisplayMetrics;
Adam Powell8028dd32010-07-15 10:16:33 -070021import android.view.KeyEvent;
Adam Powell696cba52011-03-29 10:38:16 -070022import android.view.LayoutInflater;
Adam Powell42675342010-07-09 18:02:59 -070023import android.view.View;
24import android.view.View.MeasureSpec;
Adam Powell696cba52011-03-29 10:38:16 -070025import android.view.ViewGroup;
Adam Powell04587962010-11-11 22:15:07 -080026import android.view.ViewTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070027import android.widget.AdapterView;
Adam Powell696cba52011-03-29 10:38:16 -070028import android.widget.BaseAdapter;
29import android.widget.ListAdapter;
Adam Powell42675342010-07-09 18:02:59 -070030import android.widget.ListPopupWindow;
Adam Powell6c6f5752010-08-20 18:34:46 -070031import android.widget.PopupWindow;
Adam Powell42675342010-07-09 18:02:59 -070032
Adam Powell696cba52011-03-29 10:38:16 -070033import java.util.ArrayList;
Adam Powell8028dd32010-07-15 10:16:33 -070034
Adam Powell42675342010-07-09 18:02:59 -070035/**
Adam Powell696cba52011-03-29 10:38:16 -070036 * Presents a menu as a small, simple popup anchored to another view.
Adam Powell42675342010-07-09 18:02:59 -070037 * @hide
38 */
Adam Powell04587962010-11-11 22:15:07 -080039public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
Adam Powell4afd62b2011-02-18 15:02:18 -080040 ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
Adam Powell696cba52011-03-29 10:38:16 -070041 View.OnAttachStateChangeListener, MenuPresenter {
Adam Powell42675342010-07-09 18:02:59 -070042 private static final String TAG = "MenuPopupHelper";
43
Adam Powell696cba52011-03-29 10:38:16 -070044 static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
45
Adam Powell42675342010-07-09 18:02:59 -070046 private Context mContext;
Adam Powell696cba52011-03-29 10:38:16 -070047 private LayoutInflater mInflater;
Adam Powell42675342010-07-09 18:02:59 -070048 private ListPopupWindow mPopup;
Adam Powell8028dd32010-07-15 10:16:33 -070049 private MenuBuilder mMenu;
Adam Powell42675342010-07-09 18:02:59 -070050 private int mPopupMaxWidth;
Adam Powell4afd62b2011-02-18 15:02:18 -080051 private View mAnchorView;
Adam Powell8028dd32010-07-15 10:16:33 -070052 private boolean mOverflowOnly;
Adam Powell04587962010-11-11 22:15:07 -080053 private ViewTreeObserver mTreeObserver;
Adam Powell42675342010-07-09 18:02:59 -070054
Adam Powell696cba52011-03-29 10:38:16 -070055 private MenuAdapter mAdapter;
56
57 private Callback mPresenterCallback;
Adam Powelle2b03a62011-01-25 14:39:02 -080058
Adam Powell8028dd32010-07-15 10:16:33 -070059 public MenuPopupHelper(Context context, MenuBuilder menu) {
60 this(context, menu, null, false);
61 }
62
63 public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
64 this(context, menu, anchorView, false);
65 }
66
67 public MenuPopupHelper(Context context, MenuBuilder menu,
68 View anchorView, boolean overflowOnly) {
Adam Powell42675342010-07-09 18:02:59 -070069 mContext = context;
Adam Powell696cba52011-03-29 10:38:16 -070070 mInflater = LayoutInflater.from(context);
Adam Powell8028dd32010-07-15 10:16:33 -070071 mMenu = menu;
72 mOverflowOnly = overflowOnly;
Adam Powell42675342010-07-09 18:02:59 -070073
74 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
75 mPopupMaxWidth = metrics.widthPixels / 2;
Adam Powell8028dd32010-07-15 10:16:33 -070076
Adam Powell4afd62b2011-02-18 15:02:18 -080077 mAnchorView = anchorView;
Adam Powell696cba52011-03-29 10:38:16 -070078
79 menu.addMenuPresenter(this);
Adam Powell42675342010-07-09 18:02:59 -070080 }
81
Adam Powellf0ad6e62011-01-10 17:14:06 -080082 public void setAnchorView(View anchor) {
Adam Powell4afd62b2011-02-18 15:02:18 -080083 mAnchorView = anchor;
Adam Powellf0ad6e62011-01-10 17:14:06 -080084 }
85
Adam Powell42675342010-07-09 18:02:59 -070086 public void show() {
Adam Powell5e3f2842011-01-07 17:16:56 -080087 if (!tryShow()) {
88 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
89 }
90 }
91
92 public boolean tryShow() {
Adam Powell0b2d3062010-09-14 16:15:02 -070093 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
Adam Powell8515ee82010-11-30 14:09:55 -080094 mPopup.setOnDismissListener(this);
Adam Powell696cba52011-03-29 10:38:16 -070095 mPopup.setOnItemClickListener(this);
Adam Powell42675342010-07-09 18:02:59 -070096
Adam Powell696cba52011-03-29 10:38:16 -070097 mAdapter = new MenuAdapter(mMenu);
98 mPopup.setAdapter(mAdapter);
Adam Powell42675342010-07-09 18:02:59 -070099 mPopup.setModal(true);
100
Adam Powell4afd62b2011-02-18 15:02:18 -0800101 View anchor = mAnchorView;
Adam Powell04587962010-11-11 22:15:07 -0800102 if (anchor != null) {
Adam Powell4afd62b2011-02-18 15:02:18 -0800103 final boolean addGlobalListener = mTreeObserver == null;
104 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
105 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
106 anchor.addOnAttachStateChangeListener(this);
Adam Powell04587962010-11-11 22:15:07 -0800107 mPopup.setAnchorView(anchor);
Adam Powell4be0d522010-08-03 17:53:14 -0700108 } else {
Adam Powell5e3f2842011-01-07 17:16:56 -0800109 return false;
Adam Powell8028dd32010-07-15 10:16:33 -0700110 }
Adam Powell42675342010-07-09 18:02:59 -0700111
Adam Powell696cba52011-03-29 10:38:16 -0700112 mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
Adam Powellaa0b92c2010-12-13 22:38:53 -0800113 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powell42675342010-07-09 18:02:59 -0700114 mPopup.show();
Adam Powell8028dd32010-07-15 10:16:33 -0700115 mPopup.getListView().setOnKeyListener(this);
Adam Powell5e3f2842011-01-07 17:16:56 -0800116 return true;
Adam Powell42675342010-07-09 18:02:59 -0700117 }
118
119 public void dismiss() {
Adam Powell3d3da272010-08-11 18:06:17 -0700120 if (isShowing()) {
121 mPopup.dismiss();
122 }
Adam Powell8515ee82010-11-30 14:09:55 -0800123 }
124
125 public void onDismiss() {
126 mPopup = null;
Adam Powell4afd62b2011-02-18 15:02:18 -0800127 if (mTreeObserver != null) {
128 if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
Adam Powellca51e872011-02-14 19:54:29 -0800129 mTreeObserver.removeGlobalOnLayoutListener(this);
Adam Powell4afd62b2011-02-18 15:02:18 -0800130 mTreeObserver = null;
Adam Powelled8b4032010-11-16 10:22:35 -0800131 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800132 mAnchorView.removeOnAttachStateChangeListener(this);
Adam Powell42675342010-07-09 18:02:59 -0700133 }
134
Adam Powell8028dd32010-07-15 10:16:33 -0700135 public boolean isShowing() {
136 return mPopup != null && mPopup.isShowing();
137 }
138
Adam Powell696cba52011-03-29 10:38:16 -0700139 @Override
Adam Powell42675342010-07-09 18:02:59 -0700140 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Adam Powell696cba52011-03-29 10:38:16 -0700141 MenuAdapter adapter = mAdapter;
142 adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
Adam Powell42675342010-07-09 18:02:59 -0700143 }
144
Adam Powell8028dd32010-07-15 10:16:33 -0700145 public boolean onKey(View v, int keyCode, KeyEvent event) {
146 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
147 dismiss();
148 return true;
149 }
150 return false;
151 }
152
Adam Powell696cba52011-03-29 10:38:16 -0700153 private int measureContentWidth(ListAdapter adapter) {
Adam Powell42675342010-07-09 18:02:59 -0700154 // Menus don't tend to be long, so this is more sane than it looks.
155 int width = 0;
156 View itemView = null;
Adam Powell50f784c2010-12-19 16:12:19 -0800157 int itemType = 0;
Adam Powell42675342010-07-09 18:02:59 -0700158 final int widthMeasureSpec =
159 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
160 final int heightMeasureSpec =
161 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
162 final int count = adapter.getCount();
163 for (int i = 0; i < count; i++) {
Adam Powell50f784c2010-12-19 16:12:19 -0800164 final int positionType = adapter.getItemViewType(i);
165 if (positionType != itemType) {
166 itemType = positionType;
167 itemView = null;
168 }
Adam Powell42675342010-07-09 18:02:59 -0700169 itemView = adapter.getView(i, itemView, null);
170 itemView.measure(widthMeasureSpec, heightMeasureSpec);
171 width = Math.max(width, itemView.getMeasuredWidth());
172 }
173 return width;
174 }
Adam Powell04587962010-11-11 22:15:07 -0800175
176 @Override
177 public void onGlobalLayout() {
Adam Powell4afd62b2011-02-18 15:02:18 -0800178 if (isShowing()) {
179 final View anchor = mAnchorView;
Adam Powellca51e872011-02-14 19:54:29 -0800180 if (anchor == null || !anchor.isShown()) {
Adam Powell04587962010-11-11 22:15:07 -0800181 dismiss();
Adam Powellca51e872011-02-14 19:54:29 -0800182 } else if (isShowing()) {
Adam Powellaa0b92c2010-12-13 22:38:53 -0800183 // Recompute window size and position
184 mPopup.show();
Adam Powell04587962010-11-11 22:15:07 -0800185 }
186 }
187 }
Adam Powell4afd62b2011-02-18 15:02:18 -0800188
189 @Override
190 public void onViewAttachedToWindow(View v) {
191 }
192
193 @Override
194 public void onViewDetachedFromWindow(View v) {
195 if (mTreeObserver != null) {
196 if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
197 mTreeObserver.removeGlobalOnLayoutListener(this);
198 }
199 v.removeOnAttachStateChangeListener(this);
200 }
Adam Powell696cba52011-03-29 10:38:16 -0700201
202 @Override
203 public void initForMenu(Context context, MenuBuilder menu) {
204 // Don't need to do anything; we added as a presenter in the constructor.
205 }
206
207 @Override
208 public MenuView getMenuView(ViewGroup root) {
209 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
210 }
211
212 @Override
213 public void updateMenuView(boolean cleared) {
214 if (mAdapter != null) mAdapter.notifyDataSetChanged();
215 }
216
217 @Override
218 public void setCallback(Callback cb) {
219 mPresenterCallback = cb;
220 }
221
222 @Override
223 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
224 if (subMenu.hasVisibleItems()) {
225 MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
226 subPopup.setCallback(mPresenterCallback);
227 if (subPopup.tryShow()) {
228 if (mPresenterCallback != null) {
229 mPresenterCallback.onOpenSubMenu(subMenu);
230 }
231 return true;
232 }
233 }
234 return false;
235 }
236
237 @Override
238 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
239 // Only care about the (sub)menu we're presenting.
240 if (menu != mMenu) return;
241
242 dismiss();
243 if (mPresenterCallback != null) {
244 mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
245 }
246 }
247
248 @Override
249 public boolean flagActionItems() {
250 return false;
251 }
252
253 private class MenuAdapter extends BaseAdapter {
254 private MenuBuilder mAdapterMenu;
255
256 public MenuAdapter(MenuBuilder menu) {
257 mAdapterMenu = menu;
258 }
259
260 public int getCount() {
261 ArrayList<MenuItemImpl> items = mOverflowOnly ?
262 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
263 return items.size();
264 }
265
266 public MenuItemImpl getItem(int position) {
267 ArrayList<MenuItemImpl> items = mOverflowOnly ?
268 mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
269 return items.get(position);
270 }
271
272 public long getItemId(int position) {
273 // Since a menu item's ID is optional, we'll use the position as an
274 // ID for the item in the AdapterView
275 return position;
276 }
277
278 public View getView(int position, View convertView, ViewGroup parent) {
279 if (convertView == null) {
280 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
281 }
282
283 MenuView.ItemView itemView = (MenuView.ItemView) convertView;
284 itemView.initialize(getItem(position), 0);
285 return convertView;
286 }
287 }
Adam Powell42675342010-07-09 18:02:59 -0700288}