/*
* Copyright © 2016 TIBCO Software,Inc.All rights reserved.
* http://community.jaspersoft.com/project/jaspermobile-android
*
* Unless you have purchased a commercial license agreement from TIBCO Jaspersoft,
* the following license terms apply:
*
* This program is part of TIBCO Jaspersoft Mobile for Android.
*
* TIBCO Jaspersoft Mobile is free software:you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation,either version 3of the License,or
* (at your option)any later version.
*
* TIBCO Jaspersoft Mobile is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY;without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with TIBCO Jaspersoft Mobile for Android.If not,see
* <http://www.gnu.org/licenses/lgpl>.
*/
package com.jaspersoft.android.jaspermobile.widget;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatSpinner;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import com.jaspersoft.android.jaspermobile.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A view that extends Spinner and allows to choose multiple items. Selected values are displayed on the spinner
* divided by comma.
*
* @author Ivan Gadzhega
* @version $Id$
* @since 1.0
*/
public class MultiSelectSpinner<T> extends AppCompatSpinner implements DialogInterface.OnMultiChoiceClickListener {
private static final String TEXT_SEPARATOR = ", ";
private static final String TEXT_ELLIPSIS = "\u2026";
private static final int TEXT_MAX_LENGTH = 100;
private List<T> items;
CharSequence[] stringItems;
private boolean[] checkedItems;
private boolean[] defaultCheckedItems;
/**
* the text that displays on the spinner if there is nothing selected.
*/
private String defaultText;
/**
* The listener that receives notifications when items are selected.
*/
private OnItemsSelectedListener onItemsSelectedListener;
private Button negativeButton;
public MultiSelectSpinner(Context context) {
super(context);
}
public MultiSelectSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MultiSelectSpinner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* This method will be invoked when an item in the dialog is clicked.
*
* @param dialog The dialog where the selection was made.
* @param which The position of the item in the list that was clicked.
* @param isChecked True if the click checked the item, else false.
*/
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
checkedItems[which] = isChecked;
updateState(false);
negativeButton.setText(ifAnyChecked() ? R.string.mss_btn_uncheck_all : R.string.mss_btn_check_all);
}
@Override
public boolean performClick() {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
// The prompt to display when the dialog is shown
if (getPrompt() != null) {
builder.setTitle(getPrompt());
}
defaultCheckedItems = Arrays.copyOf(checkedItems, checkedItems.length);
builder.setMultiChoiceItems(stringItems, checkedItems, this);
builder.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
updateState();
}
});
builder.setNegativeButton(R.string.mss_btn_check_all, null);
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
dialog.getListView().setAdapter(initCustomAdapter(dialog));
negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
negativeButton.setText(ifAnyChecked() ? R.string.mss_btn_uncheck_all : R.string.mss_btn_check_all);
negativeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Button button = (Button) view;
boolean ifAnyChecked = ifAnyChecked();
button.setText(ifAnyChecked ? R.string.mss_btn_check_all : R.string.mss_btn_uncheck_all);
if (ifAnyChecked) {
deselectAll();
} else {
selectAll();
}
dialog.getListView().setAdapter(initCustomAdapter(dialog));
updateState(false);
}
});
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
checkedItems = defaultCheckedItems;
updateState(false);
}
});
dialog.show();
return true;
}
private boolean ifAnyChecked() {
boolean ifAnyCheckedMask = false;
for (boolean checkedItem : checkedItems) {
ifAnyCheckedMask = ifAnyCheckedMask || checkedItem;
}
return ifAnyCheckedMask;
}
private BaseAdapter initCustomAdapter(final AlertDialog dialog) {
return new ArrayAdapter<CharSequence>(
getContext(), R.layout.item_select_dialog_multichoice, android.R.id.text1, stringItems) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if (checkedItems != null) {
boolean isItemChecked = checkedItems[position];
if (isItemChecked) {
dialog.getListView().setItemChecked(position, true);
}
}
return view;
}
};
}
/**
* Gets a list of items displayed in the spinner.
*
* @return list of items
*/
public List<T> getItems() {
return items;
}
/**
* Sets a list of items to be displayed in the spinner.
*
* @param items list of items
*/
public void setItemsList(List<T> items) {
setItemsList(items, null);
}
/**
* Sets a list of items to be displayed in the spinner.
*
* @param items list of items
* @param defaultText the text that displays on the spinner if there is nothing selected.
*/
public void setItemsList(List<T> items, String defaultText) {
this.defaultText = (defaultText != null) ? defaultText : "";
this.items = items;
int size = items.size();
// init the strings list
stringItems = new CharSequence[size];
checkedItems = new boolean[size];
for (int i = 0; i < size; i++) {
stringItems[i] = items.get(i).toString();
checkedItems[i] = false;
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
android.R.layout.simple_spinner_item, new String[]{defaultText});
setAdapter(adapter);
}
/**
* Returns the position of the specified item in the array.
*
* @param item The item to retrieve the position of
* @return The position of the specified item, or -1 if this list does not contain the element
*/
public int getItemPosition(T item) {
return items.indexOf(item);
}
/**
* Returns positions of the currently selected items.
*
* @return List of Integers (starting at 0).
*/
public List<Integer> getSelectedItemsPositions() {
List<Integer> positions = new ArrayList<Integer>();
for (int i = 0; i < items.size(); i++) {
if (checkedItems[i]) {
positions.add(i);
}
}
return positions;
}
/**
* Sets the currently selected items.
*
* @param positions List of indexes (starting at 0) of the data items to be selected.
*/
public void setSelection(List<Integer> positions) {
deselectAll();
for (int position : positions) {
if (position < checkedItems.length) {
checkedItems[position] = true;
}
}
updateState();
}
/**
* Returns the list of the currently selected items.
*
* @return The list that contains all of the currently selected items, or
* empty list if there is nothing selected.
*/
public List<T> getSelectedItems() {
List<T> selectedItems = new ArrayList<T>();
for (Integer position : getSelectedItemsPositions()) {
selectedItems.add(items.get(position));
}
return selectedItems;
}
public void setOnItemsSelectedListener(OnItemsSelectedListener listener) {
this.onItemsSelectedListener = listener;
}
public interface OnItemsSelectedListener {
public void onItemsSelected(List selectedItems);
}
@Override
public void setSelection(int position) {
}
public void setSelection(int position, boolean animate) {
}
//---------------------------------------------------------------------
// Helper methods
//---------------------------------------------------------------------
private void updateState() {
updateState(true);
}
private void updateState(boolean notifyListener) {
// refresh text on spinner
StringBuilder spinnerBuilder = new StringBuilder();
for (int i = 0; i < stringItems.length; i++) {
if (checkedItems[i]) {
if (spinnerBuilder.length() > 0) {
spinnerBuilder.append(TEXT_SEPARATOR);
}
if (spinnerBuilder.length() > TEXT_MAX_LENGTH) {
spinnerBuilder.append(TEXT_ELLIPSIS);
break;
}
spinnerBuilder.append(stringItems[i]);
}
}
String spinnerText = (spinnerBuilder.length() > 0) ? spinnerBuilder.toString() : defaultText;
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
android.R.layout.simple_spinner_item,
new String[]{spinnerText});
setAdapter(adapter);
if (onItemsSelectedListener != null && notifyListener) {
onItemsSelectedListener.onItemsSelected(getSelectedItems());
}
}
private void selectAll() {
setSelection(true);
}
private void deselectAll() {
setSelection(false);
}
private void setSelection(boolean isSelected) {
for (int i = 0; i < checkedItems.length; i++) {
checkedItems[i] = isSelected;
}
}
}