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