1 // Copyright 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chrome.browser.appmenu;
7 import android.content.Context;
8 import android.content.res.Resources;
9 import android.graphics.Rect;
10 import android.view.LayoutInflater;
11 import android.view.Menu;
12 import android.view.MenuItem;
13 import android.view.Surface;
14 import android.view.View;
15 import android.view.ViewGroup;
16 import android.widget.AdapterView;
17 import android.widget.AdapterView.OnItemClickListener;
18 import android.widget.ImageButton;
19 import android.widget.ListPopupWindow;
20 import android.widget.PopupWindow;
21 import android.widget.PopupWindow.OnDismissListener;
23 import org.chromium.chrome.R;
25 import java.util.ArrayList;
26 import java.util.List;
29 * Shows a popup of menuitems anchored to a host view. When a item is selected we call
30 * Activity.onOptionsItemSelected with the appropriate MenuItem.
31 * - Only visible MenuItems are shown.
32 * - Disabled items are grayed out.
34 public class AppMenu implements OnItemClickListener {
35 private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
37 private final Menu mMenu;
38 private final int mItemRowHeight;
39 private final int mItemDividerHeight;
40 private final int mVerticalFadeDistance;
41 private final int mAdditionalVerticalOffset;
42 private ListPopupWindow mPopup;
43 private AppMenuAdapter mAdapter;
44 private AppMenuHandler mHandler;
45 private int mCurrentScreenRotation = -1;
46 private boolean mIsByHardwareButton;
49 * Creates and sets up the App Menu.
50 * @param menu Original menu created by the framework.
51 * @param itemRowHeight Desired height for each app menu row.
52 * @param itemDividerHeight Desired height for the divider between app menu items.
53 * @param handler AppMenuHandler receives callbacks from AppMenu.
54 * @param res Resources object used to get dimensions and style attributes.
56 AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
60 mItemRowHeight = itemRowHeight;
61 assert mItemRowHeight > 0;
65 mItemDividerHeight = itemDividerHeight;
66 assert mItemDividerHeight > 0;
68 mAdditionalVerticalOffset = res.getDimensionPixelSize(R.dimen.menu_vertical_offset);
69 mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
73 * Creates and shows the app menu anchored to the specified view.
75 * @param context The context of the AppMenu (ensure the proper theme is set on
77 * @param anchorView The anchor {@link View} of the {@link ListPopupWindow}.
78 * @param isByHardwareButton Whether or not hardware button triggered it. (oppose to software
80 * @param screenRotation Current device screen rotation.
81 * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
83 void show(Context context, View anchorView, boolean isByHardwareButton, int screenRotation,
84 Rect visibleDisplayFrame) {
85 mPopup = new ListPopupWindow(context, null, android.R.attr.popupMenuStyle);
86 mPopup.setModal(true);
87 mPopup.setAnchorView(anchorView);
88 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
89 mPopup.setOnDismissListener(new OnDismissListener() {
91 public void onDismiss() {
92 if (mPopup.getAnchorView() instanceof ImageButton) {
93 ((ImageButton) mPopup.getAnchorView()).setSelected(false);
95 mHandler.onMenuVisibilityChanged(false);
98 mPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.menu_width));
100 mCurrentScreenRotation = screenRotation;
101 mIsByHardwareButton = isByHardwareButton;
103 // Extract visible items from the Menu.
104 int numItems = mMenu.size();
105 List<MenuItem> menuItems = new ArrayList<MenuItem>();
106 for (int i = 0; i < numItems; ++i) {
107 MenuItem item = mMenu.getItem(i);
108 if (item.isVisible()) {
113 // A List adapter for visible items in the Menu. The first row is added as a header to the
115 mAdapter = new AppMenuAdapter(this, menuItems, LayoutInflater.from(context));
116 mPopup.setAdapter(mAdapter);
118 setMenuHeight(menuItems.size(), visibleDisplayFrame);
119 setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame);
120 mPopup.setOnItemClickListener(this);
122 mPopup.getListView().setItemsCanFocus(true);
124 mHandler.onMenuVisibilityChanged(true);
126 if (mVerticalFadeDistance > 0) {
127 mPopup.getListView().setVerticalFadingEdgeEnabled(true);
128 mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
132 private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) {
133 Rect paddingRect = new Rect();
134 popup.getBackground().getPadding(paddingRect);
135 int[] anchorLocation = new int[2];
136 popup.getAnchorView().getLocationInWindow(anchorLocation);
138 // If we have a hardware menu button, locate the app menu closer to the estimated
139 // hardware menu button location.
140 if (mIsByHardwareButton) {
141 int horizontalOffset = -anchorLocation[0];
142 switch (screenRotation) {
143 case Surface.ROTATION_0:
144 case Surface.ROTATION_180:
145 horizontalOffset += (appRect.width() - mPopup.getWidth()) / 2;
147 case Surface.ROTATION_90:
148 horizontalOffset += appRect.width() - mPopup.getWidth();
150 case Surface.ROTATION_270:
156 popup.setHorizontalOffset(horizontalOffset);
157 // The menu is displayed above the anchored view, so shift the menu up by the top
158 // padding of the background.
159 popup.setVerticalOffset(mAdditionalVerticalOffset - paddingRect.bottom);
161 // The menu is displayed below the anchored view, so shift the menu up by the top
162 // padding of the background.
163 popup.setVerticalOffset(mAdditionalVerticalOffset - paddingRect.top);
168 * Handles clicks on the AppMenu popup.
169 * @param menuItem The menu item in the popup that was clicked.
171 void onItemClick(MenuItem menuItem) {
172 if (menuItem.isEnabled()) {
174 mHandler.onOptionsItemSelected(menuItem);
179 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
180 onItemClick(mAdapter.getItem(position));
184 * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
187 mHandler.appMenuDismissed();
188 if (isShowing()) mPopup.dismiss();
192 * @return Whether the app menu is currently showing.
194 boolean isShowing() {
195 if (mPopup == null) {
198 return mPopup.isShowing();
202 * @return ListPopupWindow that displays all the menu options.
204 ListPopupWindow getPopup() {
208 private void setMenuHeight(int numMenuItems, Rect appDimensions) {
209 assert mPopup.getAnchorView() != null;
210 View anchorView = mPopup.getAnchorView();
211 int[] anchorViewLocation = new int[2];
212 anchorView.getLocationOnScreen(anchorViewLocation);
213 anchorViewLocation[1] -= appDimensions.top;
215 int availableScreenSpace = Math.max(anchorViewLocation[1],
216 appDimensions.height() - anchorViewLocation[1] - anchorView.getHeight());
218 Rect padding = new Rect();
219 mPopup.getBackground().getPadding(padding);
220 availableScreenSpace -= mIsByHardwareButton ? padding.top : padding.bottom;
222 int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
224 // Fade out the last item if we cannot fit all items.
225 if (numCanFit < numMenuItems) {
226 int spaceForFullItems = numCanFit * (mItemRowHeight + mItemDividerHeight);
227 int spaceForPartialItem = (int) (LAST_ITEM_SHOW_FRACTION * mItemRowHeight);
228 // Determine which item needs hiding.
229 if (spaceForFullItems + spaceForPartialItem < availableScreenSpace) {
230 mPopup.setHeight(spaceForFullItems + spaceForPartialItem +
231 padding.top + padding.bottom);
233 mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
234 padding.top + padding.bottom);
237 mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);