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.KeyEvent;
11 import android.view.LayoutInflater;
12 import android.view.Menu;
13 import android.view.MenuItem;
14 import android.view.Surface;
15 import android.view.View;
16 import android.view.View.OnKeyListener;
17 import android.view.ViewGroup;
18 import android.widget.AdapterView;
19 import android.widget.AdapterView.OnItemClickListener;
20 import android.widget.ImageButton;
21 import android.widget.ListPopupWindow;
22 import android.widget.PopupWindow;
23 import android.widget.PopupWindow.OnDismissListener;
25 import org.chromium.chrome.R;
27 import java.util.ArrayList;
28 import java.util.List;
31 * Shows a popup of menuitems anchored to a host view. When a item is selected we call
32 * Activity.onOptionsItemSelected with the appropriate MenuItem.
33 * - Only visible MenuItems are shown.
34 * - Disabled items are grayed out.
36 public class AppMenu implements OnItemClickListener, OnKeyListener {
37 private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
39 private final Menu mMenu;
40 private final int mItemRowHeight;
41 private final int mItemDividerHeight;
42 private final int mVerticalFadeDistance;
43 private final int mAdditionalVerticalOffset;
44 private ListPopupWindow mPopup;
45 private AppMenuAdapter mAdapter;
46 private AppMenuHandler mHandler;
47 private int mCurrentScreenRotation = -1;
48 private boolean mIsByHardwareButton;
51 * Creates and sets up the App Menu.
52 * @param menu Original menu created by the framework.
53 * @param itemRowHeight Desired height for each app menu row.
54 * @param itemDividerHeight Desired height for the divider between app menu items.
55 * @param handler AppMenuHandler receives callbacks from AppMenu.
56 * @param res Resources object used to get dimensions and style attributes.
58 AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
62 mItemRowHeight = itemRowHeight;
63 assert mItemRowHeight > 0;
67 mItemDividerHeight = itemDividerHeight;
68 assert mItemDividerHeight >= 0;
70 mAdditionalVerticalOffset = res.getDimensionPixelSize(R.dimen.menu_vertical_offset);
71 mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
75 * Creates and shows the app menu anchored to the specified view.
77 * @param context The context of the AppMenu (ensure the proper theme is set on
79 * @param anchorView The anchor {@link View} of the {@link ListPopupWindow}.
80 * @param isByHardwareButton Whether or not hardware button triggered it. (oppose to software
82 * @param screenRotation Current device screen rotation.
83 * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
85 void show(Context context, View anchorView, boolean isByHardwareButton, int screenRotation,
86 Rect visibleDisplayFrame) {
87 mPopup = new ListPopupWindow(context, null, android.R.attr.popupMenuStyle);
88 mPopup.setModal(true);
89 mPopup.setAnchorView(anchorView);
90 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
91 mPopup.setOnDismissListener(new OnDismissListener() {
93 public void onDismiss() {
94 if (mPopup.getAnchorView() instanceof ImageButton) {
95 ((ImageButton) mPopup.getAnchorView()).setSelected(false);
97 mHandler.onMenuVisibilityChanged(false);
100 mPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.menu_width));
102 // Need to explicitly set the background here. Relying on it being set in the style caused
103 // an incorrectly drawn background.
104 mPopup.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.menu_bg));
106 mCurrentScreenRotation = screenRotation;
107 mIsByHardwareButton = isByHardwareButton;
109 // Extract visible items from the Menu.
110 int numItems = mMenu.size();
111 List<MenuItem> menuItems = new ArrayList<MenuItem>();
112 for (int i = 0; i < numItems; ++i) {
113 MenuItem item = mMenu.getItem(i);
114 if (item.isVisible()) {
119 // A List adapter for visible items in the Menu. The first row is added as a header to the
121 mAdapter = new AppMenuAdapter(this, menuItems, LayoutInflater.from(context));
122 mPopup.setAdapter(mAdapter);
124 setMenuHeight(menuItems.size(), visibleDisplayFrame);
125 setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame);
126 mPopup.setOnItemClickListener(this);
128 mPopup.getListView().setItemsCanFocus(true);
129 mPopup.getListView().setOnKeyListener(this);
131 mHandler.onMenuVisibilityChanged(true);
133 if (mVerticalFadeDistance > 0) {
134 mPopup.getListView().setVerticalFadingEdgeEnabled(true);
135 mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
139 private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) {
140 Rect paddingRect = new Rect();
141 popup.getBackground().getPadding(paddingRect);
142 int[] anchorLocation = new int[2];
143 popup.getAnchorView().getLocationInWindow(anchorLocation);
145 // If we have a hardware menu button, locate the app menu closer to the estimated
146 // hardware menu button location.
147 if (mIsByHardwareButton) {
148 int horizontalOffset = -anchorLocation[0];
149 switch (screenRotation) {
150 case Surface.ROTATION_0:
151 case Surface.ROTATION_180:
152 horizontalOffset += (appRect.width() - mPopup.getWidth()) / 2;
154 case Surface.ROTATION_90:
155 horizontalOffset += appRect.width() - mPopup.getWidth();
157 case Surface.ROTATION_270:
163 popup.setHorizontalOffset(horizontalOffset);
164 // The menu is displayed above the anchored view, so shift the menu up by the top
165 // padding of the background.
166 popup.setVerticalOffset(mAdditionalVerticalOffset - paddingRect.bottom);
168 // The menu is displayed below the anchored view, so shift the menu up by the top
169 // padding of the background.
170 popup.setVerticalOffset(mAdditionalVerticalOffset - paddingRect.top);
175 * Handles clicks on the AppMenu popup.
176 * @param menuItem The menu item in the popup that was clicked.
178 void onItemClick(MenuItem menuItem) {
179 if (menuItem.isEnabled()) {
181 mHandler.onOptionsItemSelected(menuItem);
186 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
187 onItemClick(mAdapter.getItem(position));
191 public boolean onKey(View v, int keyCode, KeyEvent event) {
192 if (mPopup == null || mPopup.getListView() == null) return false;
194 if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
195 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
196 event.startTracking();
197 v.getKeyDispatcherState().startTracking(event, this);
199 } else if (event.getAction() == KeyEvent.ACTION_UP) {
200 v.getKeyDispatcherState().handleUpEvent(event);
201 if (event.isTracking() && !event.isCanceled()) {
211 * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
214 mHandler.appMenuDismissed();
215 if (isShowing()) mPopup.dismiss();
219 * @return Whether the app menu is currently showing.
221 boolean isShowing() {
222 if (mPopup == null) {
225 return mPopup.isShowing();
229 * @return ListPopupWindow that displays all the menu options.
231 ListPopupWindow getPopup() {
235 private void setMenuHeight(int numMenuItems, Rect appDimensions) {
236 assert mPopup.getAnchorView() != null;
237 View anchorView = mPopup.getAnchorView();
238 int[] anchorViewLocation = new int[2];
239 anchorView.getLocationOnScreen(anchorViewLocation);
240 anchorViewLocation[1] -= appDimensions.top;
242 int availableScreenSpace = Math.max(anchorViewLocation[1],
243 appDimensions.height() - anchorViewLocation[1] - anchorView.getHeight());
245 Rect padding = new Rect();
246 mPopup.getBackground().getPadding(padding);
247 availableScreenSpace -= mIsByHardwareButton ? padding.top : padding.bottom;
249 int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
251 // Fade out the last item if we cannot fit all items.
252 if (numCanFit < numMenuItems) {
253 int spaceForFullItems = numCanFit * (mItemRowHeight + mItemDividerHeight);
254 int spaceForPartialItem = (int) (LAST_ITEM_SHOW_FRACTION * mItemRowHeight);
255 // Determine which item needs hiding.
256 if (spaceForFullItems + spaceForPartialItem < availableScreenSpace) {
257 mPopup.setHeight(spaceForFullItems + spaceForPartialItem +
258 padding.top + padding.bottom);
260 mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
261 padding.top + padding.bottom);
264 mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);