package org.holoeverywhere.widget;
import java.util.ArrayList;
import java.util.List;
import org.holoeverywhere.HoloEverywhere;
import org.holoeverywhere.IHoloActivity.OnWindowFocusChangeListener;
import org.holoeverywhere.app.Activity;
import org.holoeverywhere.util.LongSparseArray;
import org.holoeverywhere.widget.HeaderViewListAdapter.ViewInfo;
import org.holoeverywhere.widget.ListAdapterWrapper.ListAdapterCallback;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug.ExportedProperty;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Checkable;
import android.widget.ListAdapter;
import com.actionbarsherlock.internal.view.menu.ContextMenuBuilder.ContextMenuInfoGetter;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
public class GridView extends android.widget.GridView implements OnWindowFocusChangeListener,
ContextMenuInfoGetter, ListAdapterCallback {
private final class MultiChoiceModeWrapper implements
org.holoeverywhere.widget.ListView.MultiChoiceModeListener {
private org.holoeverywhere.widget.ListView.MultiChoiceModeListener mWrapped;
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return mWrapped.onActionItemClicked(mode, item);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (mWrapped.onCreateActionMode(mode, menu)) {
setLongClickable(false);
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
mChoiceActionMode = null;
clearChoices();
invalidateViews();
setLongClickable(true);
}
@Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
if (getCheckedItemCount() == 0) {
mode.finish();
}
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return mWrapped.onPrepareActionMode(mode, menu);
}
public void setWrapped(org.holoeverywhere.widget.ListView.MultiChoiceModeListener wrapped) {
mWrapped = wrapped;
}
}
private final class OnItemLongClickListenerWrapper implements OnItemLongClickListener {
private OnItemLongClickListener wrapped;
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
return performItemLongClick(view, position, id);
}
public void setWrapped(OnItemLongClickListener wrapped) {
this.wrapped = wrapped;
if (wrapped != null) {
setLongClickable(true);
}
}
}
public static final int CHOICE_MODE_MULTIPLE_MODAL = AbsListView.CHOICE_MODE_MULTIPLE_MODAL;
private static final boolean USE_ACTIVATED = VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
private Activity mActivity;
private ListAdapterWrapper mAdapter;
private boolean mAdapterHasStableIds;
private LongSparseArray<Integer> mCheckedIdStates;
private int mCheckedItemCount;
private SparseBooleanArray mCheckStates;
private ActionMode mChoiceActionMode;
private int mChoiceMode;
private ContextMenuInfo mContextMenuInfo;
private boolean mEnableModalBackgroundWrapper;
private boolean mFastScrollEnabled;
private final List<ViewInfo> mFooterViewInfos = new ArrayList<ViewInfo>(),
mHeaderViewInfos = new ArrayList<ViewInfo>();
private boolean mForceHeaderListAdapter = false;
private boolean mIsAttached;
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
private MultiChoiceModeWrapper mMultiChoiceModeCallback;
private final OnItemLongClickListenerWrapper mOnItemLongClickListenerWrapper;
private OnScrollListener mOnScrollListener;
public GridView(Context context) {
this(context, null);
}
public GridView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.gridViewStyle);
}
@SuppressLint("NewApi")
public GridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (context instanceof Activity) {
setActivity((Activity) context);
}
if (HoloEverywhere.DISABLE_OVERSCROLL_EFFECT && VERSION.SDK_INT >= 9) {
setOverScrollMode(OVER_SCROLL_NEVER);
}
mOnItemLongClickListenerWrapper = new OnItemLongClickListenerWrapper();
super.setOnItemLongClickListener(mOnItemLongClickListenerWrapper);
setLongClickable(false);
}
public void addFooterView(View v) {
addFooterView(v, null, true);
}
public void addFooterView(View v, Object data, boolean isSelectable) {
if (mAdapter != null && !(mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add footer view to list -- setAdapter has already been called.");
}
ViewInfo info = new ViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);
if (mAdapter != null) {
invalidateViews();
}
}
public void addHeaderView(View v) {
addHeaderView(v, null, true);
}
public void addHeaderView(View v, Object data, boolean isSelectable) {
if (mAdapter != null && !(mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
ViewInfo info = new ViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
if (mAdapter != null) {
invalidateViews();
}
}
@Override
public void clearChoices() {
if (mCheckStates != null) {
mCheckStates.clear();
}
if (mCheckedIdStates != null) {
mCheckedIdStates.clear();
}
mCheckedItemCount = 0;
}
protected ContextMenuInfo createContextMenuInfo(View view, int position,
long id) {
return new AdapterContextMenuInfo(view, position, id);
}
public Activity getActivity() {
return mActivity;
}
@Override
public int getCheckedItemCount() {
return mCheckedItemCount;
}
@Override
public long[] getCheckedItemIds() {
if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
return new long[0];
}
final LongSparseArray<Integer> idStates = mCheckedIdStates;
final int count = idStates.size();
final long[] ids = new long[count];
for (int i = 0; i < count; i++) {
ids[i] = idStates.keyAt(i);
}
return ids;
}
@Override
public int getCheckedItemPosition() {
if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
return mCheckStates.keyAt(0);
}
return INVALID_POSITION;
}
@Override
public SparseBooleanArray getCheckedItemPositions() {
if (mChoiceMode != CHOICE_MODE_NONE) {
return mCheckStates;
}
return null;
}
@Deprecated
public long[] getCheckItemIds() {
return getCheckedItemIds();
}
@Override
public int getChoiceMode() {
return mChoiceMode;
}
@Override
public ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
public int getFooterViewsCount() {
return mFooterViewInfos.size();
}
public int getHeaderViewsCount() {
return mHeaderViewInfos.size();
}
public boolean isAttached() {
return mIsAttached;
}
@Override
@ExportedProperty
public boolean isFastScrollEnabled() {
return mFastScrollEnabled;
}
public boolean isForceHeaderListAdapter() {
return mForceHeaderListAdapter;
}
public boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}
@Override
public boolean isItemChecked(int position) {
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
return mCheckStates.get(position);
}
return false;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsAttached = true;
}
@Override
public void onChanged() {
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIsAttached = false;
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (gainFocus && getSelectedItemPosition() < 0 && !isInTouchMode()) {
if (!mIsAttached && mAdapter != null) {
invalidateViews();
}
}
}
@Override
public void onInvalidated() {
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (!isEnabled()) {
return true;
}
if (isClickable() && isPressed() &&
getSelectedItemPosition() >= 0 && mAdapter != null &&
getSelectedItemPosition() < mAdapter.getCount()) {
final View view = getChildAt(getSelectedItemPosition()
- getFirstVisiblePosition());
if (view != null) {
performItemClick(view, getSelectedItemPosition(), getSelectedItemId());
view.setPressed(false);
}
setPressed(false);
return true;
}
break;
}
return super.onKeyUp(keyCode, event);
}
@Override
public View onPrepareView(View view, int position) {
if (mEnableModalBackgroundWrapper && !(view instanceof ModalBackgroundWrapper)) {
if (view.getParent() != null) {
((ViewGroup) view.getParent()).removeView(view);
}
ModalBackgroundWrapper wrapper = new ModalBackgroundWrapper(getContext());
wrapper.addView(view);
view = wrapper;
} else if (!mEnableModalBackgroundWrapper && view instanceof ModalBackgroundWrapper) {
view = ((ModalBackgroundWrapper) view).getChildAt(0);
if (view.getParent() != null) {
((ViewGroup) view.getParent()).removeView(view);
}
}
if (mCheckStates != null) {
setStateOnView(view, mCheckStates.get(position));
} else {
setStateOnView(view, false);
}
return view;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
invalidate();
invalidateViews();
}
}
@Override
public boolean performItemClick(View view, int position, long id) {
boolean handled = false;
boolean dispatchItemClick = true;
if (mChoiceMode != CHOICE_MODE_NONE) {
handled = true;
boolean checkedStateChanged = false;
if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null) {
boolean newValue = !mCheckStates.get(position, false);
mCheckStates.put(position, newValue);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
if (newValue) {
mCheckedIdStates.put(mAdapter.getItemId(position), position);
} else {
mCheckedIdStates.delete(mAdapter.getItemId(position));
}
}
if (newValue) {
mCheckedItemCount++;
} else {
mCheckedItemCount--;
}
if (mChoiceActionMode != null) {
mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
position, id, newValue);
dispatchItemClick = false;
}
checkedStateChanged = true;
} else if (mChoiceMode == CHOICE_MODE_SINGLE) {
boolean newValue = !mCheckStates.get(position, false);
if (newValue) {
mCheckStates.clear();
mCheckStates.put(position, true);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
mCheckedIdStates.clear();
mCheckedIdStates.put(mAdapter.getItemId(position), position);
}
mCheckedItemCount = 1;
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
mCheckedItemCount = 0;
}
checkedStateChanged = true;
}
if (checkedStateChanged) {
updateOnScreenCheckedViews();
}
}
if (dispatchItemClick) {
handled |= super.performItemClick(view, position, id);
}
return handled;
}
public boolean performItemLongClick(final View child,
final int longPressPosition, final long longPressId) {
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
if (mChoiceActionMode == null &&
(mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
setItemChecked(longPressPosition, true);
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return true;
}
boolean handled = false;
if (mOnItemLongClickListenerWrapper.wrapped != null) {
handled = mOnItemLongClickListenerWrapper.wrapped.onItemLongClick(GridView.this, child,
longPressPosition, longPressId);
}
if (!handled) {
mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
handled = super.showContextMenuForChild(GridView.this);
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
public boolean removeFooterView(View v) {
if (mFooterViewInfos.size() > 0) {
boolean result = false;
if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
invalidateViews();
result = true;
}
removeViewInfo(v, mFooterViewInfos);
return result;
}
return false;
}
public boolean removeHeaderView(View v) {
if (mHeaderViewInfos.size() > 0) {
boolean result = false;
if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
invalidateViews();
result = true;
}
removeViewInfo(v, mHeaderViewInfos);
return result;
}
return false;
}
private void removeViewInfo(View v, List<ViewInfo> where) {
int len = where.size();
for (int i = 0; i < len; ++i) {
ViewInfo info = where.get(i);
if (info.view == v) {
where.remove(i);
break;
}
}
}
protected void reportScrollStateChange(int newState) {
if (newState != mLastScrollState) {
if (mOnScrollListener != null) {
mLastScrollState = newState;
mOnScrollListener.onScrollStateChanged(this, newState);
}
}
}
public final void setActivity(Activity activity) {
mActivity = activity;
if (mActivity != null) {
mActivity.addOnWindowFocusChangeListener(this);
}
}
@Override
public void setAdapter(ListAdapter adapter) {
if (adapter == null) {
mAdapter = null;
} else if (mForceHeaderListAdapter || mHeaderViewInfos.size() > 0
|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter,
this);
} else {
mAdapter = new ListAdapterWrapper(adapter, this);
}
if (mAdapter != null) {
mAdapterHasStableIds = mAdapter.hasStableIds();
if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
mCheckedIdStates == null) {
mCheckedIdStates = new LongSparseArray<Integer>();
}
}
if (mCheckStates != null) {
mCheckStates.clear();
}
if (mCheckedIdStates != null) {
mCheckedIdStates.clear();
}
super.setAdapter(mAdapter);
}
@Override
public void setChoiceMode(int choiceMode) {
mChoiceMode = choiceMode;
if (mChoiceActionMode != null) {
mChoiceActionMode.finish();
mChoiceActionMode = null;
}
if (mChoiceMode != CHOICE_MODE_NONE) {
if (mCheckStates == null) {
mCheckStates = new SparseBooleanArray();
}
if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
mCheckedIdStates = new LongSparseArray<Integer>();
}
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
clearChoices();
setLongClickable(true);
setEnableModalBackgroundWrapper(true);
}
}
}
public void setEnableModalBackgroundWrapper(boolean enableModalBackgroundWrapper) {
if (enableModalBackgroundWrapper == mEnableModalBackgroundWrapper) {
return;
}
mEnableModalBackgroundWrapper = enableModalBackgroundWrapper;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
public void setForceHeaderListAdapter(boolean forceHeaderListAdapter) {
mForceHeaderListAdapter = forceHeaderListAdapter;
}
@Override
public void setItemChecked(int position, boolean value) {
if (mChoiceMode == CHOICE_MODE_NONE) {
return;
}
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
boolean oldValue = mCheckStates.get(position);
mCheckStates.put(position, value);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
if (value) {
mCheckedIdStates.put(mAdapter.getItemId(position), position);
} else {
mCheckedIdStates.delete(mAdapter.getItemId(position));
}
}
if (oldValue != value) {
if (value) {
mCheckedItemCount++;
} else {
mCheckedItemCount--;
}
}
if (mChoiceActionMode != null) {
final long id = mAdapter.getItemId(position);
mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
position, id, value);
}
} else {
boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
if (value || isItemChecked(position)) {
mCheckStates.clear();
if (updateIds) {
mCheckedIdStates.clear();
}
}
if (value) {
mCheckStates.put(position, true);
if (updateIds) {
mCheckedIdStates.put(mAdapter.getItemId(position), position);
}
mCheckedItemCount = 1;
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
mCheckedItemCount = 0;
}
}
updateOnScreenCheckedViews();
invalidateViews();
}
public void setMultiChoiceModeListener(
org.holoeverywhere.widget.ListView.MultiChoiceModeListener listener) {
if (mMultiChoiceModeCallback == null) {
mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
}
mMultiChoiceModeCallback.setWrapped(listener);
}
@Override
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListenerWrapper.setWrapped(listener);
}
@Override
public void setOnScrollListener(OnScrollListener l) {
super.setOnScrollListener(mOnScrollListener = l);
}
protected final void setStateOnView(View child, boolean value) {
if (child instanceof Checkable) {
((Checkable) child).setChecked(value);
} else if (USE_ACTIVATED) {
child.setActivated(value);
}
}
@Override
public boolean showContextMenuForChild(View originalView) {
final int longPressPosition = getPositionForView(originalView);
if (longPressPosition >= 0) {
final long longPressId = mAdapter.getItemId(longPressPosition);
boolean handled = false;
if (mOnItemLongClickListenerWrapper.wrapped != null) {
handled = mOnItemLongClickListenerWrapper.wrapped.onItemLongClick(GridView.this,
originalView, longPressPosition, longPressId);
}
if (!handled) {
mContextMenuInfo = createContextMenuInfo(getChildAt(longPressPosition
- getFirstVisiblePosition()), longPressPosition, longPressId);
handled = super.showContextMenuForChild(originalView);
}
return handled;
}
return false;
}
public ActionMode startActionMode(ActionMode.Callback callback) {
if (mActivity != null) {
return mActivity.startActionMode(callback);
}
throw new RuntimeException("HoloEverywhere.ListView (" + this
+ ") don't have reference on Activity");
}
private void updateOnScreenCheckedViews() {
if (mCheckStates == null) {
return;
}
final int firstPos = getFirstVisiblePosition();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final int position = firstPos + i;
final boolean value = mCheckStates.get(position);
setStateOnView(child, value);
}
}
}