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