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         Integer groupPosition = (Integer) originalView.getTag(R.id.group_position);
    187         Integer childPosition = (Integer) originalView.getTag(R.id.child_position);
    188 
    189         if (groupPosition == null || childPosition == null) {
    190             return false;
    191         }
    192 
    193         mContextMenuInfo = new BookmarkContextMenuInfo(childPosition, groupPosition);
    194         if (getParent() != null) {
    195             getParent().showContextMenuForChild(this);
    196         }
    197 
    198         return true;
    199     }
    200 
    201     @Override
    202     public void onTop(BreadCrumbView view, int level, Object data) {
    203         if (mBreadcrumbController != null) {
    204             mBreadcrumbController.onTop(view, level, data);
    205         }
    206     }
    207 
    208     public void setBreadcrumbController(BreadCrumbView.Controller controller) {
    209         mBreadcrumbController = controller;
    210     }
    211 
    212     @Override
    213     protected ContextMenuInfo getContextMenuInfo() {
    214         return mContextMenuInfo;
    215     }
    216 
    217     public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
    218         return mAdapter.mChildren.get(groupPosition);
    219     }
    220 
    221     private OnClickListener mChildClickListener = new OnClickListener() {
    222 
    223         @Override
    224         public void onClick(View v) {
    225             if (v.getVisibility() != View.VISIBLE) {
    226                 return;
    227             }
    228             int groupPosition = (Integer) v.getTag(R.id.group_position);
    229             int childPosition = (Integer) v.getTag(R.id.child_position);
    230             if (mAdapter.getGroupCount() <= groupPosition
    231                     || mAdapter.mChildren.get(groupPosition).getCount() <= childPosition) {
    232                 return;
    233             }
    234             long id = mAdapter.mChildren.get(groupPosition).getItemId(childPosition);
    235             if (mOnChildClickListener != null) {
    236                 mOnChildClickListener.onChildClick(BookmarkExpandableView.this,
    237                         v, groupPosition, childPosition, id);
    238             }
    239         }
    240     };
    241 
    242     private OnClickListener mGroupOnClickListener = new OnClickListener() {
    243 
    244         @Override
    245         public void onClick(View v) {
    246             int groupPosition = (Integer) v.getTag(R.id.group_position);
    247             if (isGroupExpanded(groupPosition)) {
    248                 collapseGroup(groupPosition);
    249             } else {
    250                 expandGroup(groupPosition, true);
    251             }
    252         }
    253     };
    254 
    255     public BreadCrumbView getBreadCrumbs(int groupPosition) {
    256         return mAdapter.getBreadCrumbView(groupPosition);
    257     }
    258 
    259     public JSONObject saveGroupState() throws JSONException {
    260         JSONObject obj = new JSONObject();
    261         int count = mAdapter.getGroupCount();
    262         for (int i = 0; i < count; i++) {
    263             String acctName = mAdapter.mGroups.get(i);
    264             if (!isGroupExpanded(i)) {
    265                 obj.put(acctName != null ? acctName : LOCAL_ACCOUNT_NAME, false);
    266             }
    267         }
    268         return obj;
    269     }
    270 
    271     class BookmarkAccountAdapter extends BaseExpandableListAdapter {
    272         ArrayList<BrowserBookmarksAdapter> mChildren;
    273         ArrayList<String> mGroups;
    274         HashMap<Integer, BreadCrumbView> mBreadcrumbs =
    275                 new HashMap<Integer, BreadCrumbView>();
    276         LayoutInflater mInflater;
    277         int mRowCount = 1; // assume at least 1 child fits in a row
    278         int mLastViewWidth = -1;
    279         int mRowPadding = -1;
    280         DataSetObserver mObserver = new DataSetObserver() {
    281             @Override
    282             public void onChanged() {
    283                 notifyDataSetChanged();
    284             }
    285 
    286             @Override
    287             public void onInvalidated() {
    288                 notifyDataSetInvalidated();
    289             }
    290         };
    291 
    292         public BookmarkAccountAdapter(Context context) {
    293             mContext = context;
    294             mInflater = LayoutInflater.from(mContext);
    295             mChildren = new ArrayList<BrowserBookmarksAdapter>();
    296             mGroups = new ArrayList<String>();
    297         }
    298 
    299         public void clear() {
    300             mGroups.clear();
    301             mChildren.clear();
    302             notifyDataSetChanged();
    303         }
    304 
    305         @Override
    306         public Object getChild(int groupPosition, int childPosition) {
    307             return mChildren.get(groupPosition).getItem(childPosition);
    308         }
    309 
    310         @Override
    311         public long getChildId(int groupPosition, int childPosition) {
    312             return childPosition;
    313         }
    314 
    315         @Override
    316         public View getChildView(int groupPosition, int childPosition,
    317                 boolean isLastChild, View convertView, ViewGroup parent) {
    318             if (convertView == null) {
    319                 convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
    320             }
    321             BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
    322             int rowCount = mRowCount;
    323             LinearLayout row = (LinearLayout) convertView;
    324             if (row.getChildCount() > rowCount) {
    325                 row.removeViews(rowCount, row.getChildCount() - rowCount);
    326             }
    327             for (int i = 0; i < rowCount; i++) {
    328                 View cv = null;
    329                 if (row.getChildCount() > i) {
    330                     cv = row.getChildAt(i);
    331                 }
    332                 int realChildPosition = (childPosition * rowCount) + i;
    333                 if (realChildPosition < childAdapter.getCount()) {
    334                     View v = childAdapter.getView(realChildPosition, cv, row);
    335                     v.setTag(R.id.group_position, groupPosition);
    336                     v.setTag(R.id.child_position, realChildPosition);
    337                     v.setOnClickListener(mChildClickListener);
    338                     v.setLongClickable(mLongClickable);
    339                     if (cv == null) {
    340                         row.addView(v);
    341                     } else if (cv != v) {
    342                         row.removeViewAt(i);
    343                         row.addView(v, i);
    344                     } else {
    345                         cv.setVisibility(View.VISIBLE);
    346                     }
    347                 } else if (cv != null) {
    348                     cv.setVisibility(View.GONE);
    349                 }
    350             }
    351             return row;
    352         }
    353 
    354         @Override
    355         public int getChildrenCount(int groupPosition) {
    356             BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
    357             return (int) Math.ceil(adapter.getCount() / (float)mRowCount);
    358         }
    359 
    360         @Override
    361         public Object getGroup(int groupPosition) {
    362             return mChildren.get(groupPosition);
    363         }
    364 
    365         @Override
    366         public int getGroupCount() {
    367             return mGroups.size();
    368         }
    369 
    370         public void measureChildren(int viewWidth) {
    371             if (mLastViewWidth == viewWidth) return;
    372 
    373             int rowCount = viewWidth / mColumnWidth;
    374             if (mMaxColumnCount > 0) {
    375                 rowCount = Math.min(rowCount, mMaxColumnCount);
    376             }
    377             int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
    378             boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
    379             mRowCount = rowCount;
    380             mRowPadding = rowPadding;
    381             mLastViewWidth = viewWidth;
    382             if (notify) {
    383                 notifyDataSetChanged();
    384             }
    385         }
    386 
    387         @Override
    388         public long getGroupId(int groupPosition) {
    389             return groupPosition;
    390         }
    391 
    392         @Override
    393         public View getGroupView(int groupPosition, boolean isExpanded,
    394                 View view, ViewGroup parent) {
    395             if (view == null) {
    396                 view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
    397                 view.setOnClickListener(mGroupOnClickListener);
    398             }
    399             view.setTag(R.id.group_position, groupPosition);
    400             FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
    401             crumbHolder.removeAllViews();
    402             BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
    403             if (crumbs.getParent() != null) {
    404                 ((ViewGroup)crumbs.getParent()).removeView(crumbs);
    405             }
    406             crumbHolder.addView(crumbs);
    407             TextView name = (TextView) view.findViewById(R.id.group_name);
    408             String groupName = mGroups.get(groupPosition);
    409             if (groupName == null) {
    410                 groupName = mContext.getString(R.string.local_bookmarks);
    411             }
    412             name.setText(groupName);
    413             return view;
    414         }
    415 
    416         public BreadCrumbView getBreadCrumbView(int groupPosition) {
    417             BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
    418             if (crumbs == null) {
    419                 crumbs = (BreadCrumbView)
    420                         mInflater.inflate(R.layout.bookmarks_header, null);
    421                 crumbs.setController(BookmarkExpandableView.this);
    422                 crumbs.setUseBackButton(true);
    423                 crumbs.setMaxVisible(2);
    424                 String bookmarks = mContext.getString(R.string.bookmarks);
    425                 crumbs.pushView(bookmarks, false,
    426                         BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
    427                 crumbs.setTag(R.id.group_position, groupPosition);
    428                 crumbs.setVisibility(View.GONE);
    429                 mBreadcrumbs.put(groupPosition, crumbs);
    430             }
    431             return crumbs;
    432         }
    433 
    434         @Override
    435         public boolean hasStableIds() {
    436             return false;
    437         }
    438 
    439         @Override
    440         public boolean isChildSelectable(int groupPosition, int childPosition) {
    441             return true;
    442         }
    443     }
    444 
    445     public static class BookmarkContextMenuInfo implements ContextMenuInfo {
    446 
    447         private BookmarkContextMenuInfo(int childPosition, int groupPosition) {
    448             this.childPosition = childPosition;
    449             this.groupPosition = groupPosition;
    450         }
    451 
    452         public int childPosition;
    453         public int groupPosition;
    454     }
    455 
    456 }
    457