16142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/* 26142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Copyright (C) 2016 The Android Open Source Project 36142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 46142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 56142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * you may not use this file except in compliance with the License. 66142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * You may obtain a copy of the License at 76142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 86142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 96142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Unless required by applicable law or agreed to in writing, software 116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * See the License for the specific language governing permissions and 146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * limitations under the License. 156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.appcompat.view.menu; 186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 196142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.content.Context; 206142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.graphics.Rect; 216142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.MenuItem; 226142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.View; 236142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.View.MeasureSpec; 246142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.ViewGroup; 256142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.AdapterView; 266142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.FrameLayout; 276142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.HeaderViewListAdapter; 286142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.ListAdapter; 296142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.PopupWindow; 306142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 313de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.annotation.NonNull; 323de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.annotation.Nullable; 333de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.core.internal.view.SupportMenu; 343de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikas 356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/** 366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window 376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * environment. 386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 396142a54baae3289f734947c6b5375b12eb0fb722Chris Banesabstract class MenuPopup implements ShowableListMenu, MenuPresenter, 406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes AdapterView.OnItemClickListener { 416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes private Rect mEpicenterBounds; 426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setForceShowIcon(boolean forceShow); 446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Adds the given menu to the popup, if it is capable of displaying submenus within itself. 476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * If menu is the first menu shown, it won't be displayed until show() is called. 486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * If the popup was already showing, adding a submenu via this method will cause that new 496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of 506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * showing its own submenus). 516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param menu 536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void addMenu(MenuBuilder menu); 556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setGravity(int dropDownGravity); 576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setAnchorView(View anchor); 596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setHorizontalOffset(int x); 616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setVerticalOffset(int y); 636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Specifies the anchor-relative bounds of the popup's transition 666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * epicenter. 676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param bounds anchor-relative bounds 696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void setEpicenterBounds(Rect bounds) { 716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes mEpicenterBounds = bounds; 726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return anchor-relative bounds of the popup's transition epicenter 766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public Rect getEpicenterBounds() { 786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return mEpicenterBounds; 796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Set whether a title entry should be shown in the popup menu (if a title exists for the 836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * menu). 846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param showTitle 866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setShowTitle(boolean showTitle); 886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Set a listener to receive a callback when the popup is dismissed. 916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param listener Listener that will be notified when the popup is dismissed. 936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener); 956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { 986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Don't need to do anything; we added as a presenter in the constructor. 996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public MenuView getMenuView(ViewGroup root) { 1036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes throw new UnsupportedOperationException("MenuPopups manage their own views"); 1046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 1086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return false; 1096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 1136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return false; 1146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public int getId() { 1186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return 0; 1196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1216142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes ListAdapter outerAdapter = (ListAdapter) parent.getAdapter(); 1246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter); 1256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Use the position from the outer adapter so that if a header view was added, we don't get 1276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // an off-by-1 error in position. 128c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes wrappedAdapter.mAdapterMenu.performItemAction( 129c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes (MenuItem) outerAdapter.getItem(position), 130c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes this, // always make sure that we show the sub-menu 131c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes closeMenuOnSubMenuOpened() ? 0 : SupportMenu.FLAG_KEEP_OPEN_ON_SUBMENU_OPENED); 1326142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 1356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Measures the width of the given menu view. 1366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 1376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param view The view to measure. 1386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return The width. 1396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 1406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent, 1416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes Context context, int maxAllowedWidth) { 1426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Menus don't tend to be long, so this is more sane than it looks. 1436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes int maxWidth = 0; 1446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes View itemView = null; 1456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes int itemType = 0; 1466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int count = adapter.getCount(); 1506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes for (int i = 0; i < count; i++) { 1516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int positionType = adapter.getItemViewType(i); 1526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (positionType != itemType) { 1536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemType = positionType; 1546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView = null; 1556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (parent == null) { 1586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes parent = new FrameLayout(context); 1596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView = adapter.getView(i, itemView, parent); 1626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView.measure(widthMeasureSpec, heightMeasureSpec); 1636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int itemWidth = itemView.getMeasuredWidth(); 1656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (itemWidth >= maxAllowedWidth) { 1666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return maxAllowedWidth; 1676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } else if (itemWidth > maxWidth) { 1686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes maxWidth = itemWidth; 1696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return maxWidth; 1736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 1766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for 1776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could 1786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * happen if a header view was added on the menu.) 1796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 1806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param adapter 1816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return 1826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 1836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes protected static MenuAdapter toMenuAdapter(ListAdapter adapter) { 1846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (adapter instanceof HeaderViewListAdapter) { 1856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 1866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return (MenuAdapter) adapter; 1886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 189d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes 190d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes /** 191d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * Returns whether icon spacing needs to be preserved for the given menu, based on whether any 192d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * of its items contains an icon. 193d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * 194d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * NOTE: This should only be used for non-overflow-only menus, because this method does not 195d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * take into account whether the menu items are being shown as part of the popup or or being 196d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * shown as actions in the action bar. 197d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * 198d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * @param menu 199d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * @return Whether to preserve icon spacing. 200d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes */ 201d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes protected static boolean shouldPreserveIconSpacing(MenuBuilder menu) { 202c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes boolean preserveIconSpacing = false; 203c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes final int count = menu.size(); 204c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes 205c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes for (int i = 0; i < count; i++) { 206c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes MenuItem childItem = menu.getItem(i); 207c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes if (childItem.isVisible() && childItem.getIcon() != null) { 208c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes preserveIconSpacing = true; 209c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes break; 210c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes } 211c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes } 212c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes 213c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes return preserveIconSpacing; 214c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes } 215c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes 216c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes protected boolean closeMenuOnSubMenuOpened() { 217c9b31694a631e135b85b2cf36a8e455e196a68e2Chris Banes return true; 218d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes } 219d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes} 220