Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / appmenu / AppMenu.java
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.
4
5 package org.chromium.chrome.browser.appmenu;
6
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;
22
23 import org.chromium.chrome.R;
24
25 import java.util.ArrayList;
26 import java.util.List;
27
28 /**
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.
33  */
34 public class AppMenu implements OnItemClickListener {
35     private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
36
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;
47
48     /**
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.
55      */
56     AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
57             Resources res) {
58         mMenu = menu;
59
60         mItemRowHeight = itemRowHeight;
61         assert mItemRowHeight > 0;
62
63         mHandler = handler;
64
65         mItemDividerHeight = itemDividerHeight;
66         assert mItemDividerHeight > 0;
67
68         mAdditionalVerticalOffset = res.getDimensionPixelSize(R.dimen.menu_vertical_offset);
69         mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
70     }
71
72     /**
73      * Creates and shows the app menu anchored to the specified view.
74      *
75      * @param context             The context of the AppMenu (ensure the proper theme is set on
76      *                            this context).
77      * @param anchorView          The anchor {@link View} of the {@link ListPopupWindow}.
78      * @param isByHardwareButton  Whether or not hardware button triggered it. (oppose to software
79      *                            button)
80      * @param screenRotation      Current device screen rotation.
81      * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
82      */
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() {
90             @Override
91             public void onDismiss() {
92                 if (mPopup.getAnchorView() instanceof ImageButton) {
93                     ((ImageButton) mPopup.getAnchorView()).setSelected(false);
94                 }
95                 mHandler.onMenuVisibilityChanged(false);
96             }
97         });
98         mPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.menu_width));
99
100         mCurrentScreenRotation = screenRotation;
101         mIsByHardwareButton = isByHardwareButton;
102
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()) {
109                 menuItems.add(item);
110             }
111         }
112
113         // A List adapter for visible items in the Menu. The first row is added as a header to the
114         // list view.
115         mAdapter = new AppMenuAdapter(this, menuItems, LayoutInflater.from(context));
116         mPopup.setAdapter(mAdapter);
117
118         setMenuHeight(menuItems.size(), visibleDisplayFrame);
119         setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame);
120         mPopup.setOnItemClickListener(this);
121         mPopup.show();
122         mPopup.getListView().setItemsCanFocus(true);
123
124         mHandler.onMenuVisibilityChanged(true);
125
126         if (mVerticalFadeDistance > 0) {
127             mPopup.getListView().setVerticalFadingEdgeEnabled(true);
128             mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
129         }
130     }
131
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);
137
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;
146                     break;
147                 case Surface.ROTATION_90:
148                     horizontalOffset += appRect.width() - mPopup.getWidth();
149                     break;
150                 case Surface.ROTATION_270:
151                     break;
152                 default:
153                     assert false;
154                     break;
155             }
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);
160         } else {
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);
164         }
165     }
166
167     /**
168      * Handles clicks on the AppMenu popup.
169      * @param menuItem The menu item in the popup that was clicked.
170      */
171     void onItemClick(MenuItem menuItem) {
172         if (menuItem.isEnabled()) {
173             dismiss();
174             mHandler.onOptionsItemSelected(menuItem);
175         }
176     }
177
178     @Override
179     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
180         onItemClick(mAdapter.getItem(position));
181     }
182
183     /**
184      * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
185      */
186     void dismiss() {
187         mHandler.appMenuDismissed();
188         if (isShowing()) mPopup.dismiss();
189     }
190
191     /**
192      * @return Whether the app menu is currently showing.
193      */
194     boolean isShowing() {
195         if (mPopup == null) {
196             return false;
197         }
198         return mPopup.isShowing();
199     }
200
201     /**
202      * @return ListPopupWindow that displays all the menu options.
203      */
204     ListPopupWindow getPopup() {
205         return mPopup;
206     }
207
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;
214
215         int availableScreenSpace = Math.max(anchorViewLocation[1],
216                 appDimensions.height() - anchorViewLocation[1] - anchorView.getHeight());
217
218         Rect padding = new Rect();
219         mPopup.getBackground().getPadding(padding);
220         availableScreenSpace -= mIsByHardwareButton ? padding.top : padding.bottom;
221
222         int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
223
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);
232             } else {
233                 mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
234                         padding.top + padding.bottom);
235             }
236         } else {
237             mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
238         }
239     }
240 }