1/*
2 * Copyright (C) 2013 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.database.DataSetObserver;
21import android.os.Bundle;
22import android.os.Parcelable;
23import android.support.v7.appcompat.R;
24import android.util.SparseArray;
25import android.view.ContextThemeWrapper;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.AdapterView;
30import android.widget.BaseAdapter;
31import android.widget.ListAdapter;
32
33import java.util.ArrayList;
34
35/**
36 * MenuPresenter for list-style menus.
37 *
38 * @hide
39 */
40public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
41    private static final String TAG = "ListMenuPresenter";
42
43    Context mContext;
44    LayoutInflater mInflater;
45    MenuBuilder mMenu;
46
47    ExpandedMenuView mMenuView;
48
49    private int mItemIndexOffset;
50    int mThemeRes;
51    int mItemLayoutRes;
52
53    private Callback mCallback;
54    MenuAdapter mAdapter;
55
56    private int mId;
57
58    public static final String VIEWS_TAG = "android:menu:list";
59
60    /**
61     * Construct a new ListMenuPresenter.
62     * @param context Context to use for theming. This will supersede the context provided
63     *                to initForMenu when this presenter is added.
64     * @param itemLayoutRes Layout resource for individual item views.
65     */
66    public ListMenuPresenter(Context context, int itemLayoutRes) {
67        this(itemLayoutRes, 0);
68        mContext = context;
69        mInflater = LayoutInflater.from(mContext);
70    }
71
72    /**
73     * Construct a new ListMenuPresenter.
74     * @param itemLayoutRes Layout resource for individual item views.
75     * @param themeRes Resource ID of a theme to use for views.
76     */
77    public ListMenuPresenter(int itemLayoutRes, int themeRes) {
78        mItemLayoutRes = itemLayoutRes;
79        mThemeRes = themeRes;
80    }
81
82    @Override
83    public void initForMenu(Context context, MenuBuilder menu) {
84        if (mThemeRes != 0) {
85            mContext = new ContextThemeWrapper(context, mThemeRes);
86            mInflater = LayoutInflater.from(mContext);
87        } else if (mContext != null) {
88            mContext = context;
89            if (mInflater == null) {
90                mInflater = LayoutInflater.from(mContext);
91            }
92        }
93        mMenu = menu;
94        if (mAdapter != null) {
95            mAdapter.notifyDataSetChanged();
96        }
97    }
98
99    @Override
100    public MenuView getMenuView(ViewGroup root) {
101        if (mAdapter == null) {
102            mAdapter = new MenuAdapter();
103        }
104
105        if (!mAdapter.isEmpty()) {
106            if (mMenuView == null) {
107                mMenuView = (ExpandedMenuView) mInflater.inflate(
108                        R.layout.abc_expanded_menu_layout, root, false);
109                mMenuView.setAdapter(mAdapter);
110                mMenuView.setOnItemClickListener(this);
111            }
112            return mMenuView;
113        }
114
115        // If we reach here, the Menu is empty so we have nothing to display
116        return null;
117    }
118
119    /**
120     * Call this instead of getMenuView if you want to manage your own ListView.
121     * For proper operation, the ListView hosting this adapter should add
122     * this presenter as an OnItemClickListener.
123     *
124     * @return A ListAdapter containing the items in the menu.
125     */
126    public ListAdapter getAdapter() {
127        if (mAdapter == null) {
128            mAdapter = new MenuAdapter();
129        }
130        return mAdapter;
131    }
132
133    @Override
134    public void updateMenuView(boolean cleared) {
135        if (mAdapter != null) mAdapter.notifyDataSetChanged();
136    }
137
138    @Override
139    public void setCallback(Callback cb) {
140        mCallback = cb;
141    }
142
143    @Override
144    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
145        if (!subMenu.hasVisibleItems()) return false;
146
147        // The window manager will give us a token.
148        new MenuDialogHelper(subMenu).show(null);
149        if (mCallback != null) {
150            mCallback.onOpenSubMenu(subMenu);
151        }
152        return true;
153    }
154
155    @Override
156    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
157        if (mCallback != null) {
158            mCallback.onCloseMenu(menu, allMenusAreClosing);
159        }
160    }
161
162    int getItemIndexOffset() {
163        return mItemIndexOffset;
164    }
165
166    public void setItemIndexOffset(int offset) {
167        mItemIndexOffset = offset;
168        if (mMenuView != null) {
169            updateMenuView(false);
170        }
171    }
172
173    @Override
174    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
175        mMenu.performItemAction(mAdapter.getItem(position), 0);
176    }
177
178    @Override
179    public boolean flagActionItems() {
180        return false;
181    }
182
183    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
184        return false;
185    }
186
187    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
188        return false;
189    }
190
191    public void saveHierarchyState(Bundle outState) {
192        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
193        if (mMenuView != null) {
194            ((View) mMenuView).saveHierarchyState(viewStates);
195        }
196        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
197    }
198
199    public void restoreHierarchyState(Bundle inState) {
200        SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
201        if (viewStates != null) {
202            ((View) mMenuView).restoreHierarchyState(viewStates);
203        }
204    }
205
206    public void setId(int id) {
207        mId = id;
208    }
209
210    @Override
211    public int getId() {
212        return mId;
213    }
214
215    @Override
216    public Parcelable onSaveInstanceState() {
217        if (mMenuView == null) {
218            return null;
219        }
220
221        Bundle state = new Bundle();
222        saveHierarchyState(state);
223        return state;
224    }
225
226    @Override
227    public void onRestoreInstanceState(Parcelable state) {
228        restoreHierarchyState((Bundle) state);
229    }
230
231    private class MenuAdapter extends BaseAdapter {
232        private int mExpandedIndex = -1;
233
234        public MenuAdapter() {
235            findExpandedIndex();
236        }
237
238        public int getCount() {
239            ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
240            int count = items.size() - mItemIndexOffset;
241            if (mExpandedIndex < 0) {
242                return count;
243            }
244            return count - 1;
245        }
246
247        public MenuItemImpl getItem(int position) {
248            ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
249            position += mItemIndexOffset;
250            if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
251                position++;
252            }
253            return items.get(position);
254        }
255
256        public long getItemId(int position) {
257            // Since a menu item's ID is optional, we'll use the position as an
258            // ID for the item in the AdapterView
259            return position;
260        }
261
262        public View getView(int position, View convertView, ViewGroup parent) {
263            if (convertView == null) {
264                convertView = mInflater.inflate(mItemLayoutRes, parent, false);
265            }
266
267            MenuView.ItemView itemView = (MenuView.ItemView) convertView;
268            itemView.initialize(getItem(position), 0);
269            return convertView;
270        }
271
272        void findExpandedIndex() {
273            final MenuItemImpl expandedItem = mMenu.getExpandedItem();
274            if (expandedItem != null) {
275                final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
276                final int count = items.size();
277                for (int i = 0; i < count; i++) {
278                    final MenuItemImpl item = items.get(i);
279                    if (item == expandedItem) {
280                        mExpandedIndex = i;
281                        return;
282                    }
283                }
284            }
285            mExpandedIndex = -1;
286        }
287
288        @Override
289        public void notifyDataSetChanged() {
290            findExpandedIndex();
291            super.notifyDataSetChanged();
292        }
293    }
294}
295