/** * ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab. * * @author Robin Murray <robin5@pdx.edu> (code) * @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app * design and features, report questionnaires and new ORcycle features) * * For more information on the project, go to * http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development * * Updated/modified for Oregon pilot study and app deployment. * * ORcycle is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. * ORcycle 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 General Public License for more details. * You should have received a copy of the GNU General Public License along with * ORcycle. If not, see <http://www.gnu.org/licenses/>. * */ package edu.pdx.cecs.orcycle; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnMultiChoiceClickListener; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; import android.widget.SpinnerAdapter; public class MultiSelectionSpinner extends Spinner implements OnMultiChoiceClickListener { private static final String MODULE_TAG = "MultiSelectionSpinner"; private static final String DEFAULT_OTHER_CAPTION = "Other..."; private String[] _items = null; private boolean[] mSelection = null; private boolean[] prevSelections = null; private String title = null; private int indexOfOther = -1; private int indexOfOverride = -1; private String otherText = ""; private EditText etOther = null; private AlertDialog alertDialog = null; private final ArrayAdapter<String> simple_adapter; public MultiSelectionSpinner(Context context) { super(context); simple_adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item); super.setAdapter(simple_adapter); } public MultiSelectionSpinner(Context context, AttributeSet attrs) { super(context, attrs); simple_adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item); super.setAdapter(simple_adapter); } public void onClick(DialogInterface dialog, int index, boolean isChecked) { if (mSelection != null && index < mSelection.length) { mSelection[index] = isChecked; if (indexOfOverride >= 0) { // then other checkbox was clicked if (isChecked) { if (index == indexOfOverride) { // then show dialog to get other text value performResetAllOthers(); } else { performResetOverride(); } } } if ((indexOfOther >= 0) && (indexOfOther == index)) { // then other checkbox was clicked if (isChecked) { // then show dialog to get other text value performGetOtherText(); } else { // other was deselected so clear value setOtherText(null); } } else { simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } } else { throw new IllegalArgumentException( "Argument 'index' is out of bounds."); } } private void performResetAllOthers() { for (int index = 0; index < mSelection.length; ++index) { if (indexOfOverride != index) { mSelection[index] = false; } } simple_adapter.clear(); String selectedItems = buildSelectedItemString(); simple_adapter.add(selectedItems); if (null != alertDialog) { // get a reference to the ListView widget in the dialog ListView list = alertDialog.getListView(); for (int index = 0; index < mSelection.length; ++index) { list.setItemChecked(index, mSelection[index]); } } } private void performResetOverride() { mSelection[indexOfOverride] = false; simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); if (null != alertDialog) { // get a reference to the ListView widget in the dialog ListView list = alertDialog.getListView(); list.setItemChecked(indexOfOverride, false); } } private void performResetChanges() { for (int index = 0; index < mSelection.length; ++index) { mSelection[index] = prevSelections[index]; } simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } public void setOtherText(String text) { // Set and trim value of other if (null == text) { otherText = ""; } else { otherText = text.trim(); } // change the text in the item array if (otherText.equals("")) { _items[indexOfOther] = DEFAULT_OTHER_CAPTION; } else { _items[indexOfOther] = "Other( \"" + otherText + "\" )"; } // simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); if (null != alertDialog) { // get a reference to the ListView widget in the dialog ListView list = alertDialog.getListView(); // get a reference to the arrayadapter held by the ListView widget ArrayAdapter adapter = (ArrayAdapter)list.getAdapter(); // Notify the adapter that the underlying dataset has changed adapter.notifyDataSetChanged(); } } private void performGetOtherText() { // Create an EditText view to get user input etOther = new EditText(getContext()); AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle("Specify other:"); builder.setView(etOther); // Here the variable alert is the instance of builder.setPositiveButton("OK", new OtherPositiveButton_OnClickListener()); builder.setNegativeButton("Cancel", new OtherNegativeButton_OnClickListener()); builder.show(); } private final class PositiveButton_OnClickListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int id) { // ... } } private final class NegativeButton_OnClickListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int id) { performResetChanges(); } } private final class OtherPositiveButton_OnClickListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int id) { try { setOtherText(etOther.getEditableText().toString()); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } private final class OtherNegativeButton_OnClickListener implements DialogInterface.OnClickListener { @Override public void onClick(DialogInterface dialog, int id) { // ... } } @Override public boolean performClick() { prevSelections = new boolean[mSelection.length]; for (int i = 0; i < mSelection.length; ++i) { prevSelections[i] = mSelection[i]; } AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setMultiChoiceItems(_items, mSelection, this); builder.setTitle(title); builder.setPositiveButton("OK", new PositiveButton_OnClickListener()); builder.setNegativeButton("Cancel", new NegativeButton_OnClickListener()); alertDialog = builder.create(); alertDialog.show(); return true; } public void setTitle(String value) { this.title = value; } public String getTitle() { return this.title; } public void setOtherIndex(int value) { if (value < 0) value = -1; indexOfOther = value; } public void setOverrideIndex(int value) { if (value < 0) value = -1; indexOfOverride = value; } @Override public void setAdapter(SpinnerAdapter adapter) { throw new RuntimeException( "setAdapter is not supported by MultiSelectSpinner."); } public void setItems(String[] items) { _items = items; mSelection = new boolean[_items.length]; simple_adapter.clear(); // simple_adapter.add(_items[0]); Arrays.fill(mSelection, false); } public void setItems(List<String> items) { _items = items.toArray(new String[items.size()]); mSelection = new boolean[_items.length]; simple_adapter.clear(); // simple_adapter.add(_items[0]); Arrays.fill(mSelection, false); } public void setSelection(String[] selection) { for (String cell : selection) { for (int j = 0; j < _items.length; ++j) { if (_items[j].equals(cell)) { mSelection[j] = true; } } } } public void setSelection(List<String> selection) { for (int i = 0; i < mSelection.length; i++) { mSelection[i] = false; } for (String sel : selection) { for (int j = 0; j < _items.length; ++j) { if (_items[j].equals(sel)) { mSelection[j] = true; } } } simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } @Override public void setSelection(int index) { for (int i = 0; i < mSelection.length; i++) { mSelection[i] = false; } if (index >= 0 && index < mSelection.length) { mSelection[index] = true; } else { throw new IllegalArgumentException("Index " + index + " is out of bounds."); } simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } public void setSelection(int[] selectedIndicies) { for (int i = 0; i < mSelection.length; i++) { mSelection[i] = false; } for (int index : selectedIndicies) { if (index >= 0 && index < mSelection.length) { mSelection[index] = true; } else { throw new IllegalArgumentException("Index " + index + " is out of bounds."); } } simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } public void setSelection(String text) { int index; // Initialize all entries to false for (int i = 0; i < mSelection.length; i++) { mSelection[i] = false; } // Set the selections String[] selections = text.split(","); for (String selection : selections) { try { index = Integer.parseInt(selection); if (index >= 0 && index < mSelection.length) { mSelection[index] = true; } else { throw new IllegalArgumentException("Index " + index + " is out of bounds."); } } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } simple_adapter.clear(); simple_adapter.add(buildSelectedItemString()); } public List<String> getSelectedStrings() { List<String> selection = new LinkedList<String>(); for (int i = 0; i < _items.length; ++i) { if (mSelection[i]) { selection.add(_items[i]); } } return selection; } public List<Integer> getSelectedIndicies() { List<Integer> selection = new LinkedList<Integer>(); for (int i = 0; i < _items.length; ++i) { if (mSelection[i]) { selection.add(i); } } return selection; } private String buildSelectedItemString() { StringBuilder sb = new StringBuilder(); boolean foundOne = false; for (int i = 0; i < _items.length; ++i) { if (mSelection[i]) { if (foundOne) { sb.append(", "); } foundOne = true; sb.append(_items[i]); } } return sb.toString(); } public String getSelectedItemsAsString() { StringBuilder sb = new StringBuilder(); boolean foundOne = false; for (int i = 0; i < _items.length; ++i) { if (mSelection[i]) { if (foundOne) { sb.append(", "); } foundOne = true; sb.append(_items[i]); } } return sb.toString(); } public String getSelectedIndicesAsString() { StringBuilder sb = new StringBuilder(); boolean foundOne = false; for (int i = 0; i < _items.length; ++i) { if (mSelection[i]) { if (foundOne) { sb.append(","); } foundOne = true; sb.append(i); } } return sb.toString(); } public String getOtherText() { return otherText; } /** * <p> * Callback method to be invoked when an item in this view has been * selected. This callback is invoked only when the newly selected position * is different from the previously selected position or if there was no * selected item. * </p> * * Impelmenters can call getItemAtPosition(position) if they need to access * the data associated with the selected item. * * @param parent * The AdapterView where the selection happened * @param view * The view within the AdapterView that was clicked * @param position * The position of the view in the adapter * @param id * The row id of the item that is selected */ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Log.v(MODULE_TAG, "_onItemSelected: Item selected(" + position + ") = " + id); } /** * Callback method to be invoked when the selection disappears from this * view. The selection can disappear for instance when touch is activated or * when the adapter becomes empty. * * @param parent * The AdapterView that now contains no selected item. */ public void onNothingSelected(AdapterView<?> parent) { Log.v(MODULE_TAG, "_onNothingSelected()"); } }