1/*
2 * Copyright (C) 2011 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 com.android.internal.view.menu;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25
26import java.util.ArrayList;
27
28/**
29 * Base class for MenuPresenters that have a consistent container view and item
30 * views. Behaves similarly to an AdapterView in that existing item views will
31 * be reused if possible when items change.
32 */
33public abstract class BaseMenuPresenter implements MenuPresenter {
34    protected Context mSystemContext;
35    protected Context mContext;
36    protected MenuBuilder mMenu;
37    protected LayoutInflater mSystemInflater;
38    protected LayoutInflater mInflater;
39    private Callback mCallback;
40
41    private int mMenuLayoutRes;
42    private int mItemLayoutRes;
43
44    protected MenuView mMenuView;
45
46    private int mId;
47
48    /**
49     * Construct a new BaseMenuPresenter.
50     *
51     * @param context Context for generating system-supplied views
52     * @param menuLayoutRes Layout resource ID for the menu container view
53     * @param itemLayoutRes Layout resource ID for a single item view
54     */
55    public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
56        mSystemContext = context;
57        mSystemInflater = LayoutInflater.from(context);
58        mMenuLayoutRes = menuLayoutRes;
59        mItemLayoutRes = itemLayoutRes;
60    }
61
62    @Override
63    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
64        mContext = context;
65        mInflater = LayoutInflater.from(mContext);
66        mMenu = menu;
67    }
68
69    @Override
70    public MenuView getMenuView(ViewGroup root) {
71        if (mMenuView == null) {
72            mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
73            mMenuView.initialize(mMenu);
74            updateMenuView(true);
75        }
76
77        return mMenuView;
78    }
79
80    /**
81     * Reuses item views when it can
82     */
83    public void updateMenuView(boolean cleared) {
84        final ViewGroup parent = (ViewGroup) mMenuView;
85        if (parent == null) return;
86
87        int childIndex = 0;
88        if (mMenu != null) {
89            mMenu.flagActionItems();
90            ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
91            final int itemCount = visibleItems.size();
92            for (int i = 0; i < itemCount; i++) {
93                MenuItemImpl item = visibleItems.get(i);
94                if (shouldIncludeItem(childIndex, item)) {
95                    final View convertView = parent.getChildAt(childIndex);
96                    final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
97                            ((MenuView.ItemView) convertView).getItemData() : null;
98                    final View itemView = getItemView(item, convertView, parent);
99                    if (item != oldItem) {
100                        // Don't let old states linger with new data.
101                        itemView.setPressed(false);
102                        itemView.jumpDrawablesToCurrentState();
103                    }
104                    if (itemView != convertView) {
105                        addItemView(itemView, childIndex);
106                    }
107                    childIndex++;
108                }
109            }
110        }
111
112        // Remove leftover views.
113        while (childIndex < parent.getChildCount()) {
114            if (!filterLeftoverView(parent, childIndex)) {
115                childIndex++;
116            }
117        }
118    }
119
120    /**
121     * Add an item view at the given index.
122     *
123     * @param itemView View to add
124     * @param childIndex Index within the parent to insert at
125     */
126    protected void addItemView(View itemView, int childIndex) {
127        final ViewGroup currentParent = (ViewGroup) itemView.getParent();
128        if (currentParent != null) {
129            currentParent.removeView(itemView);
130        }
131        ((ViewGroup) mMenuView).addView(itemView, childIndex);
132    }
133
134    /**
135     * Filter the child view at index and remove it if appropriate.
136     * @param parent Parent to filter from
137     * @param childIndex Index to filter
138     * @return true if the child view at index was removed
139     */
140    protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
141        parent.removeViewAt(childIndex);
142        return true;
143    }
144
145    public void setCallback(Callback cb) {
146        mCallback = cb;
147    }
148
149    public Callback getCallback() {
150        return mCallback;
151    }
152
153    /**
154     * Create a new item view that can be re-bound to other item data later.
155     *
156     * @return The new item view
157     */
158    public MenuView.ItemView createItemView(ViewGroup parent) {
159        return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false);
160    }
161
162    /**
163     * Prepare an item view for use. See AdapterView for the basic idea at work here.
164     * This may require creating a new item view, but well-behaved implementations will
165     * re-use the view passed as convertView if present. The returned view will be populated
166     * with data from the item parameter.
167     *
168     * @param item Item to present
169     * @param convertView Existing view to reuse
170     * @param parent Intended parent view - use for inflation.
171     * @return View that presents the requested menu item
172     */
173    public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
174        MenuView.ItemView itemView;
175        if (convertView instanceof MenuView.ItemView) {
176            itemView = (MenuView.ItemView) convertView;
177        } else {
178            itemView = createItemView(parent);
179        }
180        bindItemView(item, itemView);
181        return (View) itemView;
182    }
183
184    /**
185     * Bind item data to an existing item view.
186     *
187     * @param item Item to bind
188     * @param itemView View to populate with item data
189     */
190    public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
191
192    /**
193     * Filter item by child index and item data.
194     *
195     * @param childIndex Indended presentation index of this item
196     * @param item Item to present
197     * @return true if this item should be included in this menu presentation; false otherwise
198     */
199    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
200        return true;
201    }
202
203    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
204        if (mCallback != null) {
205            mCallback.onCloseMenu(menu, allMenusAreClosing);
206        }
207    }
208
209    public boolean onSubMenuSelected(SubMenuBuilder menu) {
210        if (mCallback != null) {
211            return mCallback.onOpenSubMenu(menu);
212        }
213        return false;
214    }
215
216    public boolean flagActionItems() {
217        return false;
218    }
219
220    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
221        return false;
222    }
223
224    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
225        return false;
226    }
227
228    public int getId() {
229        return mId;
230    }
231
232    public void setId(int id) {
233        mId = id;
234    }
235}
236