1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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
15 */
16
17package android.widget;
18
19import android.annotation.NonNull;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.transition.Transition;
24import android.util.AttributeSet;
25import android.view.KeyEvent;
26import android.view.MenuItem;
27import android.view.MotionEvent;
28import android.view.View;
29
30import com.android.internal.view.menu.ListMenuItemView;
31import com.android.internal.view.menu.MenuAdapter;
32import com.android.internal.view.menu.MenuBuilder;
33
34/**
35 * A MenuPopupWindow represents the popup window for menu.
36 *
37 * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
38 * behaviors specific to menus,
39 *
40 * @hide
41 */
42public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
43    private MenuItemHoverListener mHoverListener;
44
45    public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
46        super(context, attrs, defStyleAttr, defStyleRes);
47    }
48
49    @Override
50    DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
51        MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
52        view.setHoverListener(this);
53        return view;
54    }
55
56    public void setEnterTransition(Transition enterTransition) {
57        mPopup.setEnterTransition(enterTransition);
58    }
59
60    public void setExitTransition(Transition exitTransition) {
61        mPopup.setExitTransition(exitTransition);
62    }
63
64    public void setHoverListener(MenuItemHoverListener hoverListener) {
65        mHoverListener = hoverListener;
66    }
67
68    /**
69     * Set whether this window is touch modal or if outside touches will be sent to
70     * other windows behind it.
71     */
72    public void setTouchModal(boolean touchModal) {
73        mPopup.setTouchModal(touchModal);
74    }
75
76    @Override
77    public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
78        // Forward up the chain
79        if (mHoverListener != null) {
80            mHoverListener.onItemHoverEnter(menu, item);
81        }
82    }
83
84    @Override
85    public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
86        // Forward up the chain
87        if (mHoverListener != null) {
88            mHoverListener.onItemHoverExit(menu, item);
89        }
90    }
91
92    /**
93     * @hide
94     */
95    public static class MenuDropDownListView extends DropDownListView {
96        final int mAdvanceKey;
97        final int mRetreatKey;
98
99        private MenuItemHoverListener mHoverListener;
100        private MenuItem mHoveredMenuItem;
101
102        public MenuDropDownListView(Context context, boolean hijackFocus) {
103            super(context, hijackFocus);
104
105            final Resources res = context.getResources();
106            final Configuration config = res.getConfiguration();
107            if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
108                mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
109                mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
110            } else {
111                mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
112                mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
113            }
114        }
115
116        public void setHoverListener(MenuItemHoverListener hoverListener) {
117            mHoverListener = hoverListener;
118        }
119
120        public void clearSelection() {
121            setSelectedPositionInt(INVALID_POSITION);
122            setNextSelectedPositionInt(INVALID_POSITION);
123        }
124
125        @Override
126        public boolean onKeyDown(int keyCode, KeyEvent event) {
127            ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
128            if (selectedItem != null && keyCode == mAdvanceKey) {
129                if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
130                    performItemClick(
131                            selectedItem,
132                            getSelectedItemPosition(),
133                            getSelectedItemId());
134                }
135                return true;
136            } else if (selectedItem != null && keyCode == mRetreatKey) {
137                setSelectedPositionInt(INVALID_POSITION);
138                setNextSelectedPositionInt(INVALID_POSITION);
139
140                // Close only the top-level menu.
141                ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
142                return true;
143            }
144            return super.onKeyDown(keyCode, event);
145        }
146
147        @Override
148        public boolean onHoverEvent(MotionEvent ev) {
149            // Dispatch any changes in hovered item index to the listener.
150            if (mHoverListener != null) {
151                // The adapter may be wrapped. Adjust the index if necessary.
152                final int headersCount;
153                final MenuAdapter menuAdapter;
154                final ListAdapter adapter = getAdapter();
155                if (adapter instanceof HeaderViewListAdapter) {
156                    final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
157                    headersCount = headerAdapter.getHeadersCount();
158                    menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
159                } else {
160                    headersCount = 0;
161                    menuAdapter = (MenuAdapter) adapter;
162                }
163
164                // Find the menu item for the view at the event coordinates.
165                MenuItem menuItem = null;
166                if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
167                    final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
168                    if (position != INVALID_POSITION) {
169                        final int itemPosition = position - headersCount;
170                        if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
171                            menuItem = menuAdapter.getItem(itemPosition);
172                        }
173                    }
174                }
175
176                final MenuItem oldMenuItem = mHoveredMenuItem;
177                if (oldMenuItem != menuItem) {
178                    final MenuBuilder menu = menuAdapter.getAdapterMenu();
179                    if (oldMenuItem != null) {
180                        mHoverListener.onItemHoverExit(menu, oldMenuItem);
181                    }
182
183                    mHoveredMenuItem = menuItem;
184
185                    if (menuItem != null) {
186                        mHoverListener.onItemHoverEnter(menu, menuItem);
187                    }
188                }
189            }
190
191            return super.onHoverEvent(ev);
192        }
193    }
194}