/*
* Copyright (C) 2009 University of Washington
*
* 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 org.odk.collect.android.widgets;
import android.content.Context;
import android.database.Cursor;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.data.StringData;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.form.api.FormEntryPrompt;
import org.javarosa.xpath.XPathNodeset;
import org.javarosa.xpath.XPathParseTool;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.parser.XPathSyntaxException;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.database.ItemsetDbAdapter;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
/**
* The most basic widget that allows for entry of any text.
*
* @author Carl Hartung (carlhartung@gmail.com)
* @author Yaw Anokwa (yanokwa@gmail.com)
*/
public class ItemsetWidget extends QuestionWidget implements
android.widget.CompoundButton.OnCheckedChangeListener {
private static String tag = "ItemsetWidget";
boolean mReadOnly;
protected RadioGroup mButtons;
private String mAnswer = null;
// Hashmap linking label:value
private HashMap<String, String> mAnswers;
public ItemsetWidget(Context context, FormEntryPrompt prompt, boolean readOnlyOverride) {
this(context, prompt, readOnlyOverride, true);
}
protected ItemsetWidget(Context context, FormEntryPrompt prompt, boolean readOnlyOverride,
boolean derived) {
super(context, prompt);
mButtons = new RadioGroup(context);
mButtons.setId(QuestionWidget.newUniqueId());
mReadOnly = prompt.isReadOnly() || readOnlyOverride;
mAnswers = new HashMap<String, String>();
String currentAnswer = prompt.getAnswerText();
// the format of the query should be something like this:
// query="instance('cities')/root/item[state=/data/state and county=/data/county]"
// "query" is what we're using to notify that this is an
// itemset widget.
String nodesetStr = prompt.getQuestion().getAdditionalAttribute(null, "query");
// parse out the list name, between the ''
String list_name = nodesetStr.substring(nodesetStr.indexOf("'") + 1,
nodesetStr.lastIndexOf("'"));
// isolate the string between between the [ ] characters
String queryString = nodesetStr.substring(nodesetStr.indexOf("[") + 1,
nodesetStr.lastIndexOf("]"));
StringBuilder selection = new StringBuilder();
// add the list name as the first argument, which will always be there
selection.append("list_name=?");
// check to see if there are any arguments
if (queryString.indexOf("=") != -1) {
selection.append(" and ");
}
// can't just split on 'and' or 'or' because they have different
// behavior, so loop through and break them off until we don't have any
// more
// must include the spaces in indexOf so we don't match words like
// "land"
int andIndex = -1;
int orIndex = -1;
ArrayList<String> arguments = new ArrayList<String>();
while ((andIndex = queryString.indexOf(" and ")) != -1
|| (orIndex = queryString.indexOf(" or ")) != -1) {
if (andIndex != -1) {
String subString = queryString.substring(0, andIndex);
String pair[] = subString.split("=");
if (pair.length == 2) {
selection.append(pair[0].trim() + "=? and ");
arguments.add(pair[1].trim());
} else {
// parse error
}
// move string forward to after " and "
queryString = queryString.substring(andIndex + 5, queryString.length());
andIndex = -1;
} else if (orIndex != -1) {
String subString = queryString.substring(0, orIndex);
String pair[] = subString.split("=");
if (pair.length == 2) {
selection.append(pair[0].trim() + "=? or ");
arguments.add(pair[1].trim());
} else {
// parse error
}
// move string forward to after " or "
queryString = queryString.substring(orIndex + 4, queryString.length());
orIndex = -1;
}
}
// parse the last segment (or only segment if there are no 'and' or 'or'
// clauses
String pair[] = queryString.split("=");
if (pair.length == 2) {
selection.append(pair[0].trim() + "=?");
arguments.add(pair[1].trim());
}
if (pair.length == 1) {
// this is probably okay, because then you just list all items in
// the list
} else {
// parse error
}
// +1 is for the list_name
String[] selectionArgs = new String[arguments.size() + 1];
boolean nullArgs = false; // can't have any null arguments
selectionArgs[0] = list_name; // first argument is always listname
// loop through the arguments, evaluate any expressions
// and build the query string for the DB
for (int i = 0; i < arguments.size(); i++) {
XPathExpression xpr = null;
try {
xpr = XPathParseTool.parseXPath(arguments.get(i));
} catch (XPathSyntaxException e) {
e.printStackTrace();
TextView error = new TextView(context);
error.setText("XPathParser Exception: \"" + arguments.get(i) + "\"");
addView(error);
break;
}
if (xpr != null) {
FormDef form = Collect.getInstance().getFormController().getFormDef();
TreeElement mTreeElement = form.getMainInstance().resolveReference(prompt.getIndex().getReference());
EvaluationContext ec = new EvaluationContext(form.exprEvalContext,
mTreeElement.getRef());
Object value = xpr.eval(form.getMainInstance(), ec);
if (value == null) {
nullArgs = true;
} else {
if (value instanceof XPathNodeset) {
XPathNodeset xpn = (XPathNodeset) value;
value = xpn.getValAt(0);
}
selectionArgs[i + 1] = value.toString();
}
}
}
File itemsetFile = new File(Collect.getInstance().getFormController().getMediaFolder().getAbsolutePath() + "/itemsets.csv");
if (nullArgs) {
// we can't try to query with null values else it blows up
// so just leave the screen blank
// TODO: put an error?
} else if (itemsetFile.exists()) {
ItemsetDbAdapter ida = new ItemsetDbAdapter();
ida.open();
// name of the itemset table for this form
String pathHash = ItemsetDbAdapter.getMd5FromString(itemsetFile.getAbsolutePath());
try {
Cursor c = ida.query(pathHash, selection.toString(), selectionArgs);
if (c != null) {
c.move(-1);
while (c.moveToNext()) {
String label = "";
String val = "";
// try to get the value associated with the label:lang
// string if that doen't exist, then just use label
String lang = "";
if (Collect.getInstance().getFormController().getLanguages() != null
&& Collect.getInstance().getFormController().getLanguages().length > 0) {
lang = Collect.getInstance().getFormController().getLanguage();
}
// apparently you only need the double quotes in the
// column name when creating the column with a :
// included
String labelLang = "label" + "::" + lang;
int langCol = c.getColumnIndex(labelLang);
if (langCol == -1) {
label = c.getString(c.getColumnIndex("label"));
} else {
label = c.getString(c.getColumnIndex(labelLang));
}
// the actual value is stored in name
val = c.getString(c.getColumnIndex("name"));
mAnswers.put(label, val);
RadioButton rb = new RadioButton(context);
rb.setOnCheckedChangeListener(this);
rb.setText(label);
rb.setTextSize(mAnswerFontsize);
mButtons.addView(rb);
// have to add it to the radiogroup before checking it,
// else it lets two buttons be checked...
if (currentAnswer != null
&& val.compareTo(currentAnswer) == 0) {
rb.setChecked(true);
}
}
c.close();
}
} finally {
ida.close();
}
addView(mButtons);
} else {
TextView error = new TextView(context);
error.setText(getContext().getString(R.string.file_missing, itemsetFile.getAbsolutePath()));
addView(error);
}
}
@Override
public void clearAnswer() {
mButtons.clearCheck();
mAnswer = null;
}
@Override
public IAnswerData getAnswer() {
if (mAnswer == null) {
return null;
} else {
return new StringData(mAnswer);
}
}
@Override
public void setFocus(Context context) {
// Hide the soft keyboard if it's showing.
InputMethodManager inputManager = (InputMethodManager) context
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(this.getWindowToken(), 0);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.isAltPressed() == true) {
return false;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
mButtons.setOnLongClickListener(l);
for (int i = 0; i < mButtons.getChildCount(); i++) {
mButtons.getChildAt(i).setOnLongClickListener(l);
}
}
@Override
public void cancelLongPress() {
super.cancelLongPress();
mButtons.cancelLongPress();
for (int i = 0; i < mButtons.getChildCount(); i++) {
mButtons.getChildAt(i).cancelLongPress();
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mAnswer = mAnswers.get((String) buttonView.getText());
}
}
}