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 android.support.v7.internal.view.menu;
18
19import android.content.Context;
20import android.view.LayoutInflater;
21import android.view.View;
22import android.view.ViewGroup;
23
24import java.util.ArrayList;
25
26/**
27 * Base class for MenuPresenters that have a consistent container view and item views. Behaves
28 * similarly to an AdapterView in that existing item views will be reused if possible when items
29 * change.
30 *
31 * @hide
32 */
33public abstract class BaseMenuPresenter implements MenuPresenter {
34
35    protected Context mSystemContext;
36    protected Context mContext;
37    protected MenuBuilder mMenu;
38    protected LayoutInflater mSystemInflater;
39    protected LayoutInflater mInflater;
40    private Callback mCallback;
41
42    private int mMenuLayoutRes;
43    private int mItemLayoutRes;
44
45    protected MenuView mMenuView;
46
47    private int mId;
48
49    /**
50     * Construct a new BaseMenuPresenter.
51     *
52     * @param context       Context for generating system-supplied views
53     * @param menuLayoutRes Layout resource ID for the menu container view
54     * @param itemLayoutRes Layout resource ID for a single item view
55     */
56    public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
57        mSystemContext = context;
58        mSystemInflater = LayoutInflater.from(context);
59        mMenuLayoutRes = menuLayoutRes;
60        mItemLayoutRes = itemLayoutRes;
61    }
62
63    @Override
64    public void initForMenu(Context context, MenuBuilder menu) {
65        mContext = context;
66        mInflater = LayoutInflater.from(mContext);
67        mMenu = menu;
68    }
69
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) {
86            return;
87        }
88
89        int childIndex = 0;
90        if (mMenu != null) {
91            mMenu.flagActionItems();
92            ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
93            final int itemCount = visibleItems.size();
94            for (int i = 0; i < itemCount; i++) {
95                MenuItemImpl item = visibleItems.get(i);
96                if (shouldIncludeItem(childIndex, item)) {
97                    final View convertView = parent.getChildAt(childIndex);
98                    final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
99                            ((MenuView.ItemView) convertView).getItemData() : null;
100                    final View itemView = getItemView(item, convertView, parent);
101                    if (item != oldItem) {
102                        // Don't let old states linger with new data.
103                        itemView.setPressed(false);
104                        // itemView.jumpDrawablesToCurrentState();
105                        // Animation API: Not available on API < 11
106                    }
107                    if (itemView != convertView) {
108                        addItemView(itemView, childIndex);
109                    }
110                    childIndex++;
111                }
112            }
113        }
114
115        // Remove leftover views.
116        while (childIndex < parent.getChildCount()) {
117            if (!filterLeftoverView(parent, childIndex)) {
118                childIndex++;
119            }
120        }
121    }
122
123    /**
124     * Add an item view at the given index.
125     *
126     * @param itemView   View to add
127     * @param childIndex Index within the parent to insert at
128     */
129    protected void addItemView(View itemView, int childIndex) {
130        final ViewGroup currentParent = (ViewGroup) itemView.getParent();
131        if (currentParent != null) {
132            currentParent.removeView(itemView);
133        }
134        ((ViewGroup) mMenuView).addView(itemView, childIndex);
135    }
136
137    /**
138     * Filter the child view at index and remove it if appropriate.
139     *
140     * @param parent     Parent to filter from
141     * @param childIndex Index to filter
142     * @return true if the child view at index was removed
143     */
144    protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
145        parent.removeViewAt(childIndex);
146        return true;
147    }
148
149    public void setCallback(Callback cb) {
150        mCallback = cb;
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. This may
164     * require creating a new item view, but well-behaved implementations will re-use the view
165     * passed as convertView if present. The returned view will be populated with data from the item
166     * 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