/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.browser.view;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.browser.BreadCrumbView;
import com.android.browser.BrowserBookmarksAdapter;
import com.android.browser.R;
import com.android.browser.provider.BrowserContract;
import com.android.internal.view.menu.MenuBuilder;
public class BookmarkExpandableView extends ExpandableListView
implements BreadCrumbView.Controller {
public static final String LOCAL_ACCOUNT_NAME = "local";
private BookmarkAccountAdapter mAdapter;
private int mColumnWidth;
private Context mContext;
private OnChildClickListener mOnChildClickListener;
private ContextMenuInfo mContextMenuInfo = null;
private OnCreateContextMenuListener mOnCreateContextMenuListener;
private boolean mLongClickable;
private BreadCrumbView.Controller mBreadcrumbController;
private int mMaxColumnCount;
public BookmarkExpandableView(Context context) {
super(context);
init(context);
}
public BookmarkExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BookmarkExpandableView(
Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
void init(Context context) {
mContext = context;
setItemsCanFocus(true);
setLongClickable(false);
mMaxColumnCount = mContext.getResources()
.getInteger(R.integer.max_bookmark_columns);
setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
mAdapter = new BookmarkAccountAdapter(mContext);
super.setAdapter(mAdapter);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (width > 0) {
mAdapter.measureChildren(width);
setPadding(mAdapter.mRowPadding, 0, mAdapter.mRowPadding, 0);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (width != getMeasuredWidth()) {
mAdapter.measureChildren(getMeasuredWidth());
}
}
@Override
public void setAdapter(ExpandableListAdapter adapter) {
throw new RuntimeException("Not supported");
}
public void setColumnWidthFromLayout(int layout) {
LayoutInflater infalter = LayoutInflater.from(mContext);
View v = infalter.inflate(layout, this, false);
v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mColumnWidth = v.getMeasuredWidth();
}
public void clearAccounts() {
mAdapter.clear();
}
public void addAccount(String accountName, BrowserBookmarksAdapter adapter,
boolean expandGroup) {
// First, check if it already exists
int indexOf = mAdapter.mGroups.indexOf(accountName);
if (indexOf >= 0) {
BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf);
if (existing != adapter) {
existing.unregisterDataSetObserver(mAdapter.mObserver);
// Replace the existing one
mAdapter.mChildren.remove(indexOf);
mAdapter.mChildren.add(indexOf, adapter);
adapter.registerDataSetObserver(mAdapter.mObserver);
}
} else {
mAdapter.mGroups.add(accountName);
mAdapter.mChildren.add(adapter);
adapter.registerDataSetObserver(mAdapter.mObserver);
}
mAdapter.notifyDataSetChanged();
if (expandGroup) {
expandGroup(mAdapter.getGroupCount() - 1);
}
}
@Override
public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
mOnChildClickListener = onChildClickListener;
}
@Override
public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
mOnCreateContextMenuListener = l;
if (!mLongClickable) {
mLongClickable = true;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
}
@Override
public void createContextMenu(ContextMenu menu) {
// The below is copied from View - we want to bypass the override
// in AbsListView
ContextMenuInfo menuInfo = getContextMenuInfo();
// Sets the current menu info so all items added to menu will have
// my extra info set.
((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
onCreateContextMenu(menu);
if (mOnCreateContextMenuListener != null) {
mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
}
// Clear the extra information so subsequent items that aren't mine don't
// have my extra info.
((MenuBuilder)menu).setCurrentMenuInfo(null);
if (mParent != null) {
mParent.createContextMenu(menu);
}
}
@Override
public boolean showContextMenuForChild(View originalView) {
int groupPosition = (Integer) originalView.getTag(R.id.group_position);
int childPosition = (Integer) originalView.getTag(R.id.child_position);
mContextMenuInfo = new BookmarkContextMenuInfo(childPosition,
groupPosition);
if (getParent() != null) {
getParent().showContextMenuForChild(this);
}
return true;
}
@Override
public void onTop(BreadCrumbView view, int level, Object data) {
if (mBreadcrumbController != null) {
mBreadcrumbController.onTop(view, level, data);
}
}
public void setBreadcrumbController(BreadCrumbView.Controller controller) {
mBreadcrumbController = controller;
}
@Override
protected ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
return mAdapter.mChildren.get(groupPosition);
}
private OnClickListener mChildClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (v.getVisibility() != View.VISIBLE) {
return;
}
int groupPosition = (Integer) v.getTag(R.id.group_position);
int childPosition = (Integer) v.getTag(R.id.child_position);
if (mAdapter.getGroupCount() <= groupPosition
|| mAdapter.mChildren.get(groupPosition).getCount() <= childPosition) {
return;
}
long id = mAdapter.mChildren.get(groupPosition).getItemId(childPosition);
if (mOnChildClickListener != null) {
mOnChildClickListener.onChildClick(BookmarkExpandableView.this,
v, groupPosition, childPosition, id);
}
}
};
private OnClickListener mGroupOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
int groupPosition = (Integer) v.getTag(R.id.group_position);
if (isGroupExpanded(groupPosition)) {
collapseGroup(groupPosition);
} else {
expandGroup(groupPosition, true);
}
}
};
public BreadCrumbView getBreadCrumbs(int groupPosition) {
return mAdapter.getBreadCrumbView(groupPosition);
}
public JSONObject saveGroupState() throws JSONException {
JSONObject obj = new JSONObject();
int count = mAdapter.getGroupCount();
for (int i = 0; i < count; i++) {
String acctName = mAdapter.mGroups.get(i);
if (!isGroupExpanded(i)) {
obj.put(acctName != null ? acctName : LOCAL_ACCOUNT_NAME, false);
}
}
return obj;
}
class BookmarkAccountAdapter extends BaseExpandableListAdapter {
ArrayList<BrowserBookmarksAdapter> mChildren;
ArrayList<String> mGroups;
HashMap<Integer, BreadCrumbView> mBreadcrumbs =
new HashMap<Integer, BreadCrumbView>();
LayoutInflater mInflater;
int mRowCount = 1; // assume at least 1 child fits in a row
int mLastViewWidth = -1;
int mRowPadding = -1;
DataSetObserver mObserver = new DataSetObserver() {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
notifyDataSetInvalidated();
}
};
public BookmarkAccountAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mChildren = new ArrayList<BrowserBookmarksAdapter>();
mGroups = new ArrayList<String>();
}
public void clear() {
mGroups.clear();
mChildren.clear();
notifyDataSetChanged();
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return mChildren.get(groupPosition).getItem(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
}
BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
int rowCount = mRowCount;
LinearLayout row = (LinearLayout) convertView;
if (row.getChildCount() > rowCount) {
row.removeViews(rowCount, row.getChildCount() - rowCount);
}
for (int i = 0; i < rowCount; i++) {
View cv = null;
if (row.getChildCount() > i) {
cv = row.getChildAt(i);
}
int realChildPosition = (childPosition * rowCount) + i;
if (realChildPosition < childAdapter.getCount()) {
View v = childAdapter.getView(realChildPosition, cv, row);
v.setTag(R.id.group_position, groupPosition);
v.setTag(R.id.child_position, realChildPosition);
v.setOnClickListener(mChildClickListener);
v.setLongClickable(mLongClickable);
if (cv == null) {
row.addView(v);
} else if (cv != v) {
row.removeViewAt(i);
row.addView(v, i);
} else {
cv.setVisibility(View.VISIBLE);
}
} else if (cv != null) {
cv.setVisibility(View.GONE);
}
}
return row;
}
@Override
public int getChildrenCount(int groupPosition) {
BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
return (int) Math.ceil(adapter.getCount() / (float)mRowCount);
}
@Override
public Object getGroup(int groupPosition) {
return mChildren.get(groupPosition);
}
@Override
public int getGroupCount() {
return mGroups.size();
}
public void measureChildren(int viewWidth) {
if (mLastViewWidth == viewWidth) return;
int rowCount = viewWidth / mColumnWidth;
if (mMaxColumnCount > 0) {
rowCount = Math.min(rowCount, mMaxColumnCount);
}
int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
mRowCount = rowCount;
mRowPadding = rowPadding;
mLastViewWidth = viewWidth;
if (notify) {
notifyDataSetChanged();
}
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View view, ViewGroup parent) {
if (view == null) {
view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
view.setOnClickListener(mGroupOnClickListener);
}
view.setTag(R.id.group_position, groupPosition);
FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
crumbHolder.removeAllViews();
BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
if (crumbs.getParent() != null) {
((ViewGroup)crumbs.getParent()).removeView(crumbs);
}
crumbHolder.addView(crumbs);
TextView name = (TextView) view.findViewById(R.id.group_name);
String groupName = mGroups.get(groupPosition);
if (groupName == null) {
groupName = mContext.getString(R.string.local_bookmarks);
}
name.setText(groupName);
return view;
}
public BreadCrumbView getBreadCrumbView(int groupPosition) {
BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
if (crumbs == null) {
crumbs = (BreadCrumbView)
mInflater.inflate(R.layout.bookmarks_header, null);
crumbs.setController(BookmarkExpandableView.this);
crumbs.setUseBackButton(true);
crumbs.setMaxVisible(2);
String bookmarks = mContext.getString(R.string.bookmarks);
crumbs.pushView(bookmarks, false,
BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
crumbs.setTag(R.id.group_position, groupPosition);
crumbs.setVisibility(View.GONE);
mBreadcrumbs.put(groupPosition, crumbs);
}
return crumbs;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
public static class BookmarkContextMenuInfo implements ContextMenuInfo {
private BookmarkContextMenuInfo(int childPosition, int groupPosition) {
this.childPosition = childPosition;
this.groupPosition = groupPosition;
}
public int childPosition;
public int groupPosition;
}
}