a227ef07873ad69287375c247956d6ec7c9d0600
[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.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;
24
25 import org.chromium.chrome.R;
26
27 import java.util.ArrayList;
28 import java.util.List;
29
30 /**
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.
35  */
36 public class AppMenu implements OnItemClickListener, OnKeyListener {
37     private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
38
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;
49
50     /**
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.
57      */
58     AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
59             Resources res) {
60         mMenu = menu;
61
62         mItemRowHeight = itemRowHeight;
63         assert mItemRowHeight > 0;
64
65         mHandler = handler;
66
67         mItemDividerHeight = itemDividerHeight;
68         assert mItemDividerHeight >= 0;
69
70         mAdditionalVerticalOffset = res.getDimensionPixelSize(R.dimen.menu_vertical_offset);
71         mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
72     }
73
74     /**
75      * Creates and shows the app menu anchored to the specified view.
76      *
77      * @param context             The context of the AppMenu (ensure the proper theme is set on
78      *                            this context).
79      * @param anchorView          The anchor {@link View} of the {@link ListPopupWindow}.
80      * @param isByHardwareButton  Whether or not hardware button triggered it. (oppose to software
81      *                            button)
82      * @param screenRotation      Current device screen rotation.
83      * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
84      */
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() {
92             @Override
93             public void onDismiss() {
94                 if (mPopup.getAnchorView() instanceof ImageButton) {
95                     ((ImageButton) mPopup.getAnchorView()).setSelected(false);
96                 }
97                 mHandler.onMenuVisibilityChanged(false);
98             }
99         });
100         mPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.menu_width));
101
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));
105
106         mCurrentScreenRotation = screenRotation;
107         mIsByHardwareButton = isByHardwareButton;
108
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()) {
115                 menuItems.add(item);
116             }
117         }
118
119         // A List adapter for visible items in the Menu. The first row is added as a header to the
120         // list view.
121         mAdapter = new AppMenuAdapter(this, menuItems, LayoutInflater.from(context));
122         mPopup.setAdapter(mAdapter);
123
124         setMenuHeight(menuItems.size(), visibleDisplayFrame);
125         setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame);
126         mPopup.setOnItemClickListener(this);
127         mPopup.show();
128         mPopup.getListView().setItemsCanFocus(true);
129         mPopup.getListView().setOnKeyListener(this);
130
131         mHandler.onMenuVisibilityChanged(true);
132
133         if (mVerticalFadeDistance > 0) {
134             mPopup.getListView().setVerticalFadingEdgeEnabled(true);
135             mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
136         }
137     }
138
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);
144
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;
153                     break;
154                 case Surface.ROTATION_90:
155                     horizontalOffset += appRect.width() - mPopup.getWidth();
156                     break;
157                 case Surface.ROTATION_270:
158                     break;
159                 default:
160                     assert false;
161                     break;
162             }
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);
167         } else {
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);
171         }
172     }
173
174     /**
175      * Handles clicks on the AppMenu popup.
176      * @param menuItem The menu item in the popup that was clicked.
177      */
178     void onItemClick(MenuItem menuItem) {
179         if (menuItem.isEnabled()) {
180             dismiss();
181             mHandler.onOptionsItemSelected(menuItem);
182         }
183     }
184
185     @Override
186     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
187         onItemClick(mAdapter.getItem(position));
188     }
189
190     @Override
191     public boolean onKey(View v, int keyCode, KeyEvent event) {
192         if (mPopup == null || mPopup.getListView() == null) return false;
193
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);
198                 return true;
199             } else if (event.getAction() == KeyEvent.ACTION_UP) {
200                 v.getKeyDispatcherState().handleUpEvent(event);
201                 if (event.isTracking() && !event.isCanceled()) {
202                     dismiss();
203                     return true;
204                 }
205             }
206         }
207         return false;
208     }
209
210     /**
211      * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
212      */
213     void dismiss() {
214         mHandler.appMenuDismissed();
215         if (isShowing()) mPopup.dismiss();
216     }
217
218     /**
219      * @return Whether the app menu is currently showing.
220      */
221     boolean isShowing() {
222         if (mPopup == null) {
223             return false;
224         }
225         return mPopup.isShowing();
226     }
227
228     /**
229      * @return ListPopupWindow that displays all the menu options.
230      */
231     ListPopupWindow getPopup() {
232         return mPopup;
233     }
234
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;
241
242         int availableScreenSpace = Math.max(anchorViewLocation[1],
243                 appDimensions.height() - anchorViewLocation[1] - anchorView.getHeight());
244
245         Rect padding = new Rect();
246         mPopup.getBackground().getPadding(padding);
247         availableScreenSpace -= mIsByHardwareButton ? padding.top : padding.bottom;
248
249         int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
250
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);
259             } else {
260                 mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
261                         padding.top + padding.bottom);
262             }
263         } else {
264             mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
265         }
266     }
267 }