2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.actionbarsherlock
.internal
.view
.menu
;
19 import java
.util
.ArrayList
;
20 import android
.content
.Context
;
21 import android
.content
.res
.Resources
;
22 import android
.database
.DataSetObserver
;
23 import android
.os
.Parcelable
;
24 import android
.view
.KeyEvent
;
25 import android
.view
.LayoutInflater
;
26 import android
.view
.View
;
27 import android
.view
.View
.MeasureSpec
;
28 import android
.view
.ViewGroup
;
29 import android
.view
.ViewTreeObserver
;
30 import android
.widget
.AdapterView
;
31 import android
.widget
.BaseAdapter
;
32 import android
.widget
.FrameLayout
;
33 import android
.widget
.ListAdapter
;
34 import android
.widget
.PopupWindow
;
35 import com
.actionbarsherlock
.R
;
36 import com
.actionbarsherlock
.internal
.view
.View_HasStateListenerSupport
;
37 import com
.actionbarsherlock
.internal
.view
.View_OnAttachStateChangeListener
;
38 import com
.actionbarsherlock
.internal
.widget
.IcsListPopupWindow
;
39 import com
.actionbarsherlock
.view
.MenuItem
;
42 * Presents a menu as a small, simple popup anchored to another view.
45 public class MenuPopupHelper
implements AdapterView
.OnItemClickListener
, View
.OnKeyListener
,
46 ViewTreeObserver
.OnGlobalLayoutListener
, PopupWindow
.OnDismissListener
,
47 View_OnAttachStateChangeListener
, MenuPresenter
{
48 //UNUSED private static final String TAG = "MenuPopupHelper";
50 static final int ITEM_LAYOUT
= R
.layout
.abs__popup_menu_item_layout
;
52 private Context mContext
;
53 private LayoutInflater mInflater
;
54 private IcsListPopupWindow mPopup
;
55 private MenuBuilder mMenu
;
56 private int mPopupMaxWidth
;
57 private View mAnchorView
;
58 private boolean mOverflowOnly
;
59 private ViewTreeObserver mTreeObserver
;
61 private MenuAdapter mAdapter
;
63 private Callback mPresenterCallback
;
65 boolean mForceShowIcon
;
67 private ViewGroup mMeasureParent
;
69 public MenuPopupHelper(Context context
, MenuBuilder menu
) {
70 this(context
, menu
, null
, false
);
73 public MenuPopupHelper(Context context
, MenuBuilder menu
, View anchorView
) {
74 this(context
, menu
, anchorView
, false
);
77 public MenuPopupHelper(Context context
, MenuBuilder menu
,
78 View anchorView
, boolean overflowOnly
) {
80 mInflater
= LayoutInflater
.from(context
);
82 mOverflowOnly
= overflowOnly
;
84 final Resources res
= context
.getResources();
85 mPopupMaxWidth
= Math
.max(res
.getDisplayMetrics().widthPixels
/ 2,
86 res
.getDimensionPixelSize(R
.dimen
.abs__config_prefDialogWidth
));
88 mAnchorView
= anchorView
;
90 menu
.addMenuPresenter(this);
93 public void setAnchorView(View anchor
) {
97 public void setForceShowIcon(boolean forceShow
) {
98 mForceShowIcon
= forceShow
;
103 throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
107 public boolean tryShow() {
108 mPopup
= new IcsListPopupWindow(mContext
, null
, R
.attr
.popupMenuStyle
);
109 mPopup
.setOnDismissListener(this);
110 mPopup
.setOnItemClickListener(this);
112 mAdapter
= new MenuAdapter(mMenu
);
113 mPopup
.setAdapter(mAdapter
);
114 mPopup
.setModal(true
);
116 View anchor
= mAnchorView
;
117 if (anchor
!= null
) {
118 final boolean addGlobalListener
= mTreeObserver
== null
;
119 mTreeObserver
= anchor
.getViewTreeObserver(); // Refresh to latest
120 if (addGlobalListener
) mTreeObserver
.addOnGlobalLayoutListener(this);
121 ((View_HasStateListenerSupport
)anchor
).addOnAttachStateChangeListener(this);
122 mPopup
.setAnchorView(anchor
);
127 mPopup
.setContentWidth(Math
.min(measureContentWidth(mAdapter
), mPopupMaxWidth
));
128 mPopup
.setInputMethodMode(PopupWindow
.INPUT_METHOD_NOT_NEEDED
);
130 mPopup
.getListView().setOnKeyListener(this);
134 public void dismiss() {
140 public void onDismiss() {
143 if (mTreeObserver
!= null
) {
144 if (!mTreeObserver
.isAlive()) mTreeObserver
= mAnchorView
.getViewTreeObserver();
145 mTreeObserver
.removeGlobalOnLayoutListener(this);
146 mTreeObserver
= null
;
148 ((View_HasStateListenerSupport
)mAnchorView
).removeOnAttachStateChangeListener(this);
151 public boolean isShowing() {
152 return mPopup
!= null
&& mPopup
.isShowing();
156 public void onItemClick(AdapterView
<?
> parent
, View view
, int position
, long id
) {
157 MenuAdapter adapter
= mAdapter
;
158 adapter
.mAdapterMenu
.performItemAction(adapter
.getItem(position
), 0);
161 public boolean onKey(View v
, int keyCode
, KeyEvent event
) {
162 if (event
.getAction() == KeyEvent
.ACTION_UP
&& keyCode
== KeyEvent
.KEYCODE_MENU
) {
169 private int measureContentWidth(ListAdapter adapter
) {
170 // Menus don't tend to be long, so this is more sane than it looks.
172 View itemView
= null
;
174 final int widthMeasureSpec
=
175 MeasureSpec
.makeMeasureSpec(0, MeasureSpec
.UNSPECIFIED
);
176 final int heightMeasureSpec
=
177 MeasureSpec
.makeMeasureSpec(0, MeasureSpec
.UNSPECIFIED
);
178 final int count
= adapter
.getCount();
179 for (int i
= 0; i
< count
; i
++) {
180 final int positionType
= adapter
.getItemViewType(i
);
181 if (positionType
!= itemType
) {
182 itemType
= positionType
;
185 if (mMeasureParent
== null
) {
186 mMeasureParent
= new FrameLayout(mContext
);
188 itemView
= adapter
.getView(i
, itemView
, mMeasureParent
);
189 itemView
.measure(widthMeasureSpec
, heightMeasureSpec
);
190 width
= Math
.max(width
, itemView
.getMeasuredWidth());
196 public void onGlobalLayout() {
198 final View anchor
= mAnchorView
;
199 if (anchor
== null
|| !anchor
.isShown()) {
201 } else if (isShowing()) {
202 // Recompute window size and position
209 public void onViewAttachedToWindow(View v
) {
213 public void onViewDetachedFromWindow(View v
) {
214 if (mTreeObserver
!= null
) {
215 if (!mTreeObserver
.isAlive()) mTreeObserver
= v
.getViewTreeObserver();
216 mTreeObserver
.removeGlobalOnLayoutListener(this);
218 ((View_HasStateListenerSupport
)v
).removeOnAttachStateChangeListener(this);
222 public void initForMenu(Context context
, MenuBuilder menu
) {
223 // Don't need to do anything; we added as a presenter in the constructor.
227 public MenuView
getMenuView(ViewGroup root
) {
228 throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
232 public void updateMenuView(boolean cleared
) {
233 if (mAdapter
!= null
) mAdapter
.notifyDataSetChanged();
237 public void setCallback(Callback cb
) {
238 mPresenterCallback
= cb
;
242 public boolean onSubMenuSelected(SubMenuBuilder subMenu
) {
243 if (subMenu
.hasVisibleItems()) {
244 MenuPopupHelper subPopup
= new MenuPopupHelper(mContext
, subMenu
, mAnchorView
, false
);
245 subPopup
.setCallback(mPresenterCallback
);
247 boolean preserveIconSpacing
= false
;
248 final int count
= subMenu
.size();
249 for (int i
= 0; i
< count
; i
++) {
250 MenuItem childItem
= subMenu
.getItem(i
);
251 if (childItem
.isVisible() && childItem
.getIcon() != null
) {
252 preserveIconSpacing
= true
;
256 subPopup
.setForceShowIcon(preserveIconSpacing
);
258 if (subPopup
.tryShow()) {
259 if (mPresenterCallback
!= null
) {
260 mPresenterCallback
.onOpenSubMenu(subMenu
);
269 public void onCloseMenu(MenuBuilder menu
, boolean allMenusAreClosing
) {
270 // Only care about the (sub)menu we're presenting.
271 if (menu
!= mMenu
) return;
274 if (mPresenterCallback
!= null
) {
275 mPresenterCallback
.onCloseMenu(menu
, allMenusAreClosing
);
280 public boolean flagActionItems() {
284 public boolean expandItemActionView(MenuBuilder menu
, MenuItemImpl item
) {
288 public boolean collapseItemActionView(MenuBuilder menu
, MenuItemImpl item
) {
298 public Parcelable
onSaveInstanceState() {
303 public void onRestoreInstanceState(Parcelable state
) {
306 private class MenuAdapter
extends BaseAdapter
{
307 private MenuBuilder mAdapterMenu
;
308 private int mExpandedIndex
= -1;
310 public MenuAdapter(MenuBuilder menu
) {
312 registerDataSetObserver(new ExpandedIndexObserver());
316 public int getCount() {
317 ArrayList
<MenuItemImpl
> items
= mOverflowOnly ?
318 mAdapterMenu
.getNonActionItems() : mAdapterMenu
.getVisibleItems();
319 if (mExpandedIndex
< 0) {
322 return items
.size() - 1;
325 public MenuItemImpl
getItem(int position
) {
326 ArrayList
<MenuItemImpl
> items
= mOverflowOnly ?
327 mAdapterMenu
.getNonActionItems() : mAdapterMenu
.getVisibleItems();
328 if (mExpandedIndex
>= 0 && position
>= mExpandedIndex
) {
331 return items
.get(position
);
334 public long getItemId(int position
) {
335 // Since a menu item's ID is optional, we'll use the position as an
336 // ID for the item in the AdapterView
340 public View
getView(int position
, View convertView
, ViewGroup parent
) {
341 if (convertView
== null
) {
342 convertView
= mInflater
.inflate(ITEM_LAYOUT
, parent
, false
);
345 MenuView
.ItemView itemView
= (MenuView
.ItemView
) convertView
;
346 if (mForceShowIcon
) {
347 ((ListMenuItemView
) convertView
).setForceShowIcon(true
);
349 itemView
.initialize(getItem(position
), 0);
353 void findExpandedIndex() {
354 final MenuItemImpl expandedItem
= mMenu
.getExpandedItem();
355 if (expandedItem
!= null
) {
356 final ArrayList
<MenuItemImpl
> items
= mMenu
.getNonActionItems();
357 final int count
= items
.size();
358 for (int i
= 0; i
< count
; i
++) {
359 final MenuItemImpl item
= items
.get(i
);
360 if (item
== expandedItem
) {
370 private class ExpandedIndexObserver
extends DataSetObserver
{
372 public void onChanged() {
373 mAdapter
.findExpandedIndex();