BookmarkExpandableView.java revision 9db9529bdb7490ccd72fbe66f7e9c9d7cdd73899
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.browser.view;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.database.DataSetObserver;
22import android.provider.BrowserContract;
23import android.util.AttributeSet;
24import android.view.ContextMenu;
25import android.view.ContextMenu.ContextMenuInfo;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.BaseExpandableListAdapter;
30import android.widget.ExpandableListAdapter;
31import android.widget.ExpandableListView;
32import android.widget.FrameLayout;
33import android.widget.LinearLayout;
34import android.widget.TextView;
35
36import com.android.browser.BookmarkDragHandler;
37import com.android.browser.BookmarkDragHandler.BookmarkDragAdapter;
38import com.android.browser.BookmarkDragHandler.BookmarkDragState;
39import com.android.browser.BreadCrumbView;
40import com.android.browser.BrowserBookmarksAdapter;
41import com.android.browser.BrowserBookmarksPage;
42import com.android.browser.BrowserBookmarksPage.ExtraDragState;
43import com.android.browser.R;
44import com.android.internal.view.menu.MenuBuilder;
45
46import java.util.ArrayList;
47import java.util.HashMap;
48
49public class BookmarkExpandableView extends ExpandableListView
50        implements BreadCrumbView.Controller {
51
52    private BookmarkAccountAdapter mAdapter;
53    private int mColumnWidth;
54    private Context mContext;
55    private OnChildClickListener mOnChildClickListener;
56    private ContextMenuInfo mContextMenuInfo = null;
57    private OnCreateContextMenuListener mOnCreateContextMenuListener;
58    private boolean mLongClickable;
59    private BreadCrumbView.Controller mBreadcrumbController;
60    private BookmarkDragHandler mDragHandler;
61    private int mMaxColumnCount;
62    private int mCurrentView = -1;
63
64    public BookmarkExpandableView(Context context) {
65        super(context);
66        init(context);
67    }
68
69    public BookmarkExpandableView(Context context, AttributeSet attrs) {
70        super(context, attrs);
71        init(context);
72    }
73
74    public BookmarkExpandableView(
75            Context context, AttributeSet attrs, int defStyle) {
76        super(context, attrs, defStyle);
77        init(context);
78    }
79
80    void init(Context context) {
81        mContext = context;
82        setItemsCanFocus(true);
83        setLongClickable(false);
84        mMaxColumnCount = mContext.getResources()
85                .getInteger(R.integer.max_bookmark_columns);
86        mAdapter = new BookmarkAccountAdapter(mContext);
87        super.setAdapter(mAdapter);
88    }
89
90    @Override
91    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
92        int width = MeasureSpec.getSize(widthMeasureSpec);
93        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
94        if (width > 0) {
95            mAdapter.measureChildren(width);
96            if (mAdapter.mRowPadding > 0) {
97                width -= mAdapter.mRowPadding * 2;
98            }
99            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode);
100        }
101        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
102        if (width != getMeasuredWidth()) {
103            mAdapter.measureChildren(getMeasuredWidth());
104        }
105    }
106
107    @Override
108    public void setAdapter(ExpandableListAdapter adapter) {
109        throw new RuntimeException("Not supported");
110    }
111
112    public void setColumnWidthFromLayout(int layout) {
113        LayoutInflater infalter = LayoutInflater.from(mContext);
114        View v = infalter.inflate(layout, this, false);
115        v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
116        mColumnWidth = v.getMeasuredWidth();
117    }
118
119    public void clearAccounts() {
120        mAdapter.clear();
121    }
122
123    public void addAccount(String accountName, BrowserBookmarksAdapter adapter) {
124        // First, check if it already exists
125        int indexOf = mAdapter.mGroups.indexOf(accountName);
126        if (indexOf >= 0) {
127            BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf);
128            if (existing != adapter) {
129                existing.unregisterDataSetObserver(mAdapter.mObserver);
130                // Replace the existing one
131                mAdapter.mChildren.remove(indexOf);
132                mAdapter.mChildren.add(indexOf, adapter);
133                adapter.registerDataSetObserver(mAdapter.mObserver);
134            }
135        } else {
136            if (mCurrentView >= 0) {
137                adapter.selectView(mCurrentView);
138            }
139            mAdapter.mGroups.add(accountName);
140            mAdapter.mChildren.add(adapter);
141            adapter.registerDataSetObserver(mAdapter.mObserver);
142        }
143        mAdapter.notifyDataSetChanged();
144        expandGroup(mAdapter.getGroupCount() - 1);
145    }
146
147    @Override
148    public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
149        mOnChildClickListener = onChildClickListener;
150    }
151
152    @Override
153    public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
154        mOnCreateContextMenuListener = l;
155        if (!mLongClickable) {
156            mLongClickable = true;
157            if (mAdapter != null) {
158                mAdapter.notifyDataSetChanged();
159            }
160        }
161    }
162
163    @Override
164    public void createContextMenu(ContextMenu menu) {
165        // The below is copied from View - we want to bypass the override
166        // in AbsListView
167
168        ContextMenuInfo menuInfo = getContextMenuInfo();
169
170        // Sets the current menu info so all items added to menu will have
171        // my extra info set.
172        ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
173
174        onCreateContextMenu(menu);
175        if (mOnCreateContextMenuListener != null) {
176            mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
177        }
178
179        // Clear the extra information so subsequent items that aren't mine don't
180        // have my extra info.
181        ((MenuBuilder)menu).setCurrentMenuInfo(null);
182
183        if (mParent != null) {
184            mParent.createContextMenu(menu);
185        }
186    }
187
188    @Override
189    public boolean showContextMenuForChild(View originalView) {
190        int groupPosition = (Integer) originalView.getTag(R.id.group_position);
191        int childPosition = (Integer) originalView.getTag(R.id.child_position);
192
193        mContextMenuInfo = new BookmarkContextMenuInfo(childPosition,
194                groupPosition);
195        if (getParent() != null) {
196            getParent().showContextMenuForChild(this);
197        }
198
199        return true;
200    }
201
202    @Override
203    public void onTop(BreadCrumbView view, int level, Object data) {
204        if (mBreadcrumbController != null) {
205            mBreadcrumbController.onTop(view, level, data);
206        }
207    }
208
209    public void setBreadcrumbController(BreadCrumbView.Controller controller) {
210        mBreadcrumbController = controller;
211    }
212
213    @Override
214    protected ContextMenuInfo getContextMenuInfo() {
215        return mContextMenuInfo;
216    }
217
218    public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
219        return mAdapter.mChildren.get(groupPosition);
220    }
221
222    public BookmarkDragAdapter getDragAdapter() {
223        return mDragAdapter;
224    }
225
226    public void showContextMenuForState(BookmarkDragState state) {
227        ExtraDragState extraState = (ExtraDragState) state.extraState;
228        mContextMenuInfo = new BookmarkContextMenuInfo(
229                extraState.childPosition,
230                extraState.groupPosition);
231        if (getParent() != null) {
232            getParent().showContextMenuForChild(BookmarkExpandableView.this);
233        }
234    }
235
236    private BookmarkDragAdapter mDragAdapter = new BookmarkDragAdapter() {
237
238        @Override
239        public void setBookmarkDragHandler(BookmarkDragHandler handler) {
240            mDragHandler = handler;
241        }
242
243        @Override
244        public Cursor getItemForView(View v) {
245            int groupPosition = (Integer) v.getTag(R.id.group_position);
246            int childPosition = (Integer) v.getTag(R.id.child_position);
247            return getChildAdapter(groupPosition).getItem(childPosition);
248        }
249
250    };
251
252    private OnClickListener mChildClickListener = new OnClickListener() {
253
254        @Override
255        public void onClick(View v) {
256            int groupPosition = (Integer) v.getTag(R.id.group_position);
257            int childPosition = (Integer) v.getTag(R.id.child_position);
258            long id = (Long) v.getTag(R.id.child_id);
259            if (mOnChildClickListener != null) {
260                mOnChildClickListener.onChildClick(BookmarkExpandableView.this,
261                        v, groupPosition, childPosition, id);
262            }
263        }
264    };
265
266    private OnClickListener mGroupOnClickListener = new OnClickListener() {
267
268        @Override
269        public void onClick(View v) {
270            int groupPosition = (Integer) v.getTag(R.id.group_position);
271            if (isGroupExpanded(groupPosition)) {
272                collapseGroup(groupPosition);
273            } else {
274                expandGroup(groupPosition, true);
275            }
276        }
277    };
278
279    private OnLongClickListener mChildOnLongClickListener = new OnLongClickListener() {
280
281        @Override
282        public boolean onLongClick(View v) {
283            ExtraDragState state = new ExtraDragState();
284            state.groupPosition = (Integer) v.getTag(R.id.group_position);
285            state.childPosition = (Integer) v.getTag(R.id.child_position);
286            long id = (Long) v.getTag(R.id.child_id);
287            Cursor c = getChildAdapter(state.groupPosition)
288                    .getItem(state.childPosition);
289            return mDragHandler.startDrag(v, c, id, state);
290        }
291    };
292
293    public BreadCrumbView getBreadCrumbs(int groupPosition) {
294        return mAdapter.getBreadCrumbView(groupPosition);
295    }
296
297    public void selectView(int view) {
298        mCurrentView = view;
299        for (BrowserBookmarksAdapter adapter : mAdapter.mChildren) {
300            adapter.selectView(mCurrentView);
301        }
302        mAdapter.notifyDataSetChanged();
303    }
304
305    class BookmarkAccountAdapter extends BaseExpandableListAdapter {
306        ArrayList<BrowserBookmarksAdapter> mChildren;
307        ArrayList<String> mGroups;
308        HashMap<Integer, BreadCrumbView> mBreadcrumbs =
309                new HashMap<Integer, BreadCrumbView>();
310        LayoutInflater mInflater;
311        int mRowCount = 1; // assume at least 1 child fits in a row
312        int mLastViewWidth = -1;
313        int mRowPadding = -1;
314        DataSetObserver mObserver = new DataSetObserver() {
315            @Override
316            public void onChanged() {
317                notifyDataSetChanged();
318            }
319
320            @Override
321            public void onInvalidated() {
322                notifyDataSetChanged();
323            }
324        };
325
326        public BookmarkAccountAdapter(Context context) {
327            mContext = context;
328            mInflater = LayoutInflater.from(mContext);
329            mChildren = new ArrayList<BrowserBookmarksAdapter>();
330            mGroups = new ArrayList<String>();
331        }
332
333        public void clear() {
334            mGroups.clear();
335            mChildren.clear();
336            notifyDataSetChanged();
337        }
338
339        @Override
340        public Object getChild(int groupPosition, int childPosition) {
341            return mChildren.get(groupPosition).getItem(childPosition);
342        }
343
344        @Override
345        public long getChildId(int groupPosition, int childPosition) {
346            return childPosition;
347        }
348
349        @Override
350        public View getChildView(int groupPosition, int childPosition,
351                boolean isLastChild, View convertView, ViewGroup parent) {
352            if (convertView == null) {
353                convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
354            }
355            BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
356            int rowCount = mRowCount;
357            if (childAdapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) {
358                rowCount = 1;
359            }
360            LinearLayout row = (LinearLayout) convertView;
361            if (row.getChildCount() > rowCount) {
362                row.removeViews(rowCount, row.getChildCount() - rowCount);
363            }
364            for (int i = 0; i < rowCount; i++) {
365                View cv = null;
366                if (row.getChildCount() > i) {
367                    cv = row.getChildAt(i);
368                }
369                int realChildPosition = (childPosition * rowCount) + i;
370                if (realChildPosition < childAdapter.getCount()) {
371                    View v = childAdapter.getView(realChildPosition, cv, row);
372                    v.setTag(R.id.group_position, groupPosition);
373                    v.setTag(R.id.child_position, realChildPosition);
374                    v.setTag(R.id.child_id, childAdapter.getItemId(realChildPosition));
375                    v.setOnClickListener(mChildClickListener);
376                    v.setLongClickable(mLongClickable);
377                    if (mDragHandler != null) {
378                        v.setOnLongClickListener(mChildOnLongClickListener);
379                        mDragHandler.registerBookmarkDragHandler(v);
380                    }
381                    if (cv == null) {
382                        row.addView(v);
383                    } else if (cv != v) {
384                        row.removeViewAt(i);
385                        row.addView(v, i);
386                    } else {
387                        cv.setVisibility(View.VISIBLE);
388                    }
389                } else if (cv != null) {
390                    cv.setVisibility(View.GONE);
391                }
392            }
393            return row;
394        }
395
396        @Override
397        public int getChildrenCount(int groupPosition) {
398            BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
399            if (adapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) {
400                return adapter.getCount();
401            }
402            return (int) Math.ceil(adapter.getCount() / (float)mRowCount);
403        }
404
405        @Override
406        public Object getGroup(int groupPosition) {
407            return mChildren.get(groupPosition);
408        }
409
410        @Override
411        public int getGroupCount() {
412            return mGroups.size();
413        }
414
415        public void measureChildren(int viewWidth) {
416            if (mLastViewWidth == viewWidth) return;
417
418            int rowCount = viewWidth / mColumnWidth;
419            if (mMaxColumnCount > 0) {
420                rowCount = Math.min(rowCount, mMaxColumnCount);
421            }
422            int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
423            boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
424            mRowCount = rowCount;
425            mRowPadding = rowPadding;
426            mLastViewWidth = viewWidth;
427            if (notify) {
428                notifyDataSetChanged();
429            }
430        }
431
432        @Override
433        public long getGroupId(int groupPosition) {
434            return groupPosition;
435        }
436
437        @Override
438        public View getGroupView(int groupPosition, boolean isExpanded,
439                View view, ViewGroup parent) {
440            if (view == null) {
441                view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
442                view.setOnClickListener(mGroupOnClickListener);
443            }
444            view.setTag(R.id.group_position, groupPosition);
445            FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
446            crumbHolder.removeAllViews();
447            BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
448            if (crumbs.getParent() != null) {
449                ((ViewGroup)crumbs.getParent()).removeView(crumbs);
450            }
451            crumbHolder.addView(crumbs);
452            TextView name = (TextView) view.findViewById(R.id.group_name);
453            String groupName = mGroups.get(groupPosition);
454            if (groupName == null) {
455                groupName = mContext.getString(R.string.local_bookmarks);
456            }
457            name.setText(groupName);
458            return view;
459        }
460
461        public BreadCrumbView getBreadCrumbView(int groupPosition) {
462            BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
463            if (crumbs == null) {
464                crumbs = (BreadCrumbView)
465                        mInflater.inflate(R.layout.bookmarks_header, null);
466                crumbs.setController(BookmarkExpandableView.this);
467                crumbs.setUseBackButton(true);
468                crumbs.setMaxVisible(2);
469                String bookmarks = mContext.getString(R.string.bookmarks);
470                crumbs.pushView(bookmarks, false,
471                        BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
472                crumbs.setTag(R.id.group_position, groupPosition);
473                mBreadcrumbs.put(groupPosition, crumbs);
474            }
475            return crumbs;
476        }
477
478        @Override
479        public boolean hasStableIds() {
480            return false;
481        }
482
483        @Override
484        public boolean isChildSelectable(int groupPosition, int childPosition) {
485            return true;
486        }
487
488        @Override
489        public int getChildTypeCount() {
490            return 2;
491        }
492
493        @Override
494        public int getChildType(int groupPosition, int childPosition) {
495            BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
496            if (adapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) {
497                return 1;
498            }
499            return 0;
500        }
501    }
502
503    public static class BookmarkContextMenuInfo implements ContextMenuInfo {
504
505        private BookmarkContextMenuInfo(int childPosition, int groupPosition) {
506            this.childPosition = childPosition;
507            this.groupPosition = groupPosition;
508        }
509
510        public int childPosition;
511        public int groupPosition;
512    }
513
514}
515