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