/*
* Copyright (C) 2013 Manuel Peinado
*
* 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.manuelpeinado.multichoiceadapter;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.Checkable;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import com.actionbarsherlock.view.ActionMode;
import com.manuelpeinado.multichoicelistadapter.R;
class MultiChoiceAdapterHelper implements OnItemLongClickListener, OnItemClickListener, OnCheckedChangeListener {
protected static final String TAG = MultiChoiceAdapterHelper.class.getSimpleName();
private static final String BUNDLE_KEY = "mca__selection";
private Set<Long> checkedItems = new HashSet<Long>();
private AdapterView<? super MultiChoiceBaseAdapter> adapterView;
private BaseAdapter owner;
private OnItemClickListener itemClickListener;
private ActionMode actionMode;
private Boolean itemIncludesCheckBox;
/*
* Defines what happens when an item is clicked and the action mode was already active
*/
private ItemClickInActionModePolicy itemClickInActionModePolicy = null;
private boolean ignoreCheckedListener;
MultiChoiceAdapterHelper(BaseAdapter owner) {
this.owner = owner;
}
void restoreSelectionFromSavedInstanceState(Bundle savedInstanceState) {
if (savedInstanceState == null) {
return;
}
long[] array = savedInstanceState.getLongArray(BUNDLE_KEY);
checkedItems.clear();
for (long id : array) {
checkedItems.add(id);
}
}
void setAdapterView(AdapterView<? super BaseAdapter> adapterView) {
this.adapterView = adapterView;
checkActivity();
adapterView.setOnItemLongClickListener(this);
adapterView.setOnItemClickListener(this);
adapterView.setAdapter(owner);
parseAttrs();
if (!checkedItems.isEmpty()) {
startActionMode();
onItemSelectedStateChanged();
}
}
void checkActivity() {
Context context = adapterView.getContext();
if (context instanceof ListActivity) {
throw new RuntimeException("ListView cannot belong to an activity which subclasses ListActivity");
}
if (context instanceof SherlockActivity || context instanceof SherlockFragmentActivity
|| context instanceof SherlockPreferenceActivity) {
return;
}
throw new RuntimeException("ListView must belong to an activity which subclasses SherlockActivity");
}
void setOnItemClickListener(OnItemClickListener listener) {
this.itemClickListener = listener;
}
void save(Bundle outState) {
long[] array = new long[checkedItems.size()];
int i = 0;
for (Long id : checkedItems) {
array[i++] = id;
}
outState.putLongArray(BUNDLE_KEY, array);
}
void setItemChecked(long handle, boolean checked) {
if (checked) {
checkItem(handle);
} else {
uncheckItem(handle);
}
}
void checkItem(long handle) {
boolean wasSelected = isChecked(handle);
if (wasSelected) {
return;
}
if (actionMode == null) {
startActionMode();
}
checkedItems.add((long) handle);
owner.notifyDataSetChanged();
onItemSelectedStateChanged();
}
void uncheckItem(long handle) {
boolean wasSelected = isChecked(handle);
if (!wasSelected) {
return;
}
checkedItems.remove(handle);
if (getCheckedItemCount() == 0) {
finishActionMode();
return;
}
owner.notifyDataSetChanged();
onItemSelectedStateChanged();
}
Set<Long> getCheckedItems() {
// Return a copy to prevent concurrent modification problems
return new HashSet<Long>(checkedItems);
}
int getCheckedItemCount() {
return checkedItems.size();
}
boolean isChecked(long handle) {
return checkedItems.contains(handle);
}
void finishActionMode() {
if (actionMode != null) {
actionMode.finish();
}
}
Context getContext() {
return adapterView.getContext();
}
void setItemClickInActionModePolicy(ItemClickInActionModePolicy policy) {
itemClickInActionModePolicy = policy;
}
ItemClickInActionModePolicy getItemClickInActionModePolicy() {
return itemClickInActionModePolicy;
}
private void onItemSelectedStateChanged() {
int count = getCheckedItemCount();
if (count == 0) {
finishActionMode();
return;
}
Resources res = adapterView.getResources();
String title = res.getQuantityString(R.plurals.selected_items, count, count);
actionMode.setTitle(title);
}
private void startActionMode() {
try {
Activity activity = (Activity) adapterView.getContext();
Method method = activity.getClass().getMethod("startActionMode", ActionMode.Callback.class);
actionMode = (ActionMode) method.invoke(activity, owner);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private void parseAttrs() {
Context ctx = getContext();
int styleAttr = R.attr.multiChoiceAdapterStyle;
int defStyle = R.style.MultiChoiceAdapter_DefaultSelectedItemStyle;
TypedArray ta = ctx.obtainStyledAttributes(null, R.styleable.MultiChoiceAdapter, styleAttr, defStyle);
// If it's not null it means that it has been set programmatically, which has precedence over the theme attribute
if (itemClickInActionModePolicy == null) {
int ordinal = ta.getInt(R.styleable.MultiChoiceAdapter_itemClickInActionMode,
ItemClickInActionModePolicy.SELECT.ordinal());
itemClickInActionModePolicy = ItemClickInActionModePolicy.values()[ordinal];
}
ta.recycle();
}
//
// OnItemLongClickListener implementation
//
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
int correctedPosition = correctPositionAccountingForHeader(adapterView, position);
long handle = positionToSelectionHandle(correctedPosition);
boolean wasChecked = isChecked(handle);
setItemChecked(handle, !wasChecked);
return true;
}
private int correctPositionAccountingForHeader(AdapterView<?> adapterView, int position) {
ListView listView = (adapterView instanceof ListView) ? (ListView) adapterView : null;
int headersCount = listView == null ? 0 : listView.getHeaderViewsCount();
if (headersCount > 0) {
position -= listView.getHeaderViewsCount();
}
return position;
}
protected long positionToSelectionHandle(int position) {
return position;
}
//
// ActionMode.Callback related methods
//
void onDestroyActionMode(ActionMode mode) {
checkedItems.clear();
actionMode = null;
owner.notifyDataSetChanged();
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (actionMode != null) {
switch (itemClickInActionModePolicy) {
case SELECT:
onItemLongClick(adapterView, view, position, id);
return;
case OPEN:
finishActionMode();
break;
default:
throw new RuntimeException("Invalid \"itemClickInActionMode\" value: " + itemClickInActionModePolicy);
}
}
if (itemClickListener != null) {
itemClickListener.onItemClick(adapterView, view, position, id);
}
}
View getView(int position, View viewWithoutSelection) {
if (viewWithoutSelection instanceof Checkable) {
long handle = positionToSelectionHandle(position);
boolean selected = isChecked(handle);
ignoreCheckedListener = true;
((Checkable) viewWithoutSelection).setChecked(selected);
ignoreCheckedListener = false;
}
if (itemIncludesCheckBox(viewWithoutSelection)) {
initItemCheckbox(position, (ViewGroup) viewWithoutSelection);
}
return viewWithoutSelection;
}
private boolean itemIncludesCheckBox(View v) {
if (itemIncludesCheckBox == null) {
if (!(v instanceof ViewGroup)) {
itemIncludesCheckBox = false;
} else {
ViewGroup root = (ViewGroup) v;
itemIncludesCheckBox = root.findViewById(android.R.id.checkbox) != null;
}
}
return itemIncludesCheckBox;
}
private void initItemCheckbox(int position, ViewGroup view) {
CheckBox checkBox = (CheckBox) view.findViewById(android.R.id.checkbox);
boolean checked = isChecked(position);
checkBox.setTag(position);
checkBox.setChecked(checked);
checkBox.setOnCheckedChangeListener(this);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (ignoreCheckedListener) {
return;
}
int position = (Integer) buttonView.getTag();
setItemChecked(position, isChecked);
}
}