package org.odk.collect.android.widgets; import java.io.File; import java.util.Vector; import org.javarosa.core.model.SelectChoice; import org.javarosa.core.model.data.IAnswerData; import org.javarosa.core.model.data.SelectMultiData; import org.javarosa.core.model.data.helper.Selection; import org.javarosa.core.reference.InvalidReferenceException; import org.javarosa.core.reference.ReferenceManager; import org.javarosa.form.api.FormEntryCaption; import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.R; import org.odk.collect.android.utilities.FileUtils; import org.odk.collect.android.utilities.StringUtils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Typeface; import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /** * ListMultiWidget handles multiple selection fields using check boxes. The check boxes are aligned * horizontally. They are typically meant to be used in a field list, where multiple questions with * the same multiple choice answers can sit on top of each other and make a grid of buttons that is * easy to navigate quickly. Optionally, you can turn off the labels. This would be done if a label * widget was at the top of your field list to provide the labels. If audio or video are specified * in the select answers they are ignored. This class is almost identical to ListWidget, except it * uses checkboxes. It also did not require a custom clickListener class. * * @author Jeff Beorse (jeff@beorse.net) */ public class ListMultiWidget extends QuestionWidget { int buttonIdBase; private final static int CHECKBOX_ID = 100; protected final static int TEXTSIZE = 21; private static final String t = "ListMultiWidget"; // Layout holds the horizontal list of buttons LinearLayout buttonLayout; // Holds the entire question and answers. It is a horizontally aligned linear layout LinearLayout questionLayout; // Option to keep labels blank boolean displayLabel; private boolean mCheckboxInit = true; Vector<SelectChoice> mItems; private Vector<CheckBox> mCheckboxes; private TextView questionText; @SuppressWarnings("unchecked") public ListMultiWidget(Context context, FormEntryPrompt prompt, boolean displayLabel) { super(context, prompt); mItems = prompt.getSelectChoices(); mCheckboxes = new Vector<CheckBox>(); mPrompt = prompt; this.displayLabel = displayLabel; buttonLayout = new LinearLayout(context); Vector<Selection> ve = new Vector<Selection>(); if (prompt.getAnswerValue() != null) { ve = (Vector<Selection>) getCurrentAnswer().getValue(); } //Is this safe enough from collisions? buttonIdBase = Math.abs(prompt.getIndex().toString().hashCode()); if (prompt.getSelectChoices() != null) { for (int i = 0; i < mItems.size(); i++) { CheckBox c = new CheckBox(getContext()); // when clicked, check for readonly before toggling c.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { /* * (non-Javadoc) * @see android.widget.CompoundButton.OnCheckedChangeListener#onCheckedChanged(android.widget.CompoundButton, boolean) */ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (!mCheckboxInit && mPrompt.isReadOnly()) { if (buttonView.isChecked()) { buttonView.setChecked(false); } else { buttonView.setChecked(true); } } widgetEntryChanged(); } }); c.setId(CHECKBOX_ID + i); c.setFocusable(!prompt.isReadOnly()); c.setEnabled(!prompt.isReadOnly()); for (int vi = 0; vi < ve.size(); vi++) { // match based on value, not key if (mItems.get(i).getValue().equals(ve.elementAt(vi).getValue())) { c.setChecked(true); break; } } mCheckboxes.add(c); String imageURI = null; imageURI = prompt.getSpecialFormSelectChoiceText(mItems.get(i), FormEntryCaption.TEXT_FORM_IMAGE); // build image view (if an image is provided) ImageView mImageView = null; TextView mMissingImage = null; // Now set up the image view String errorMsg = null; if (imageURI != null) { try { String imageFilename = ReferenceManager._().DeriveReference(imageURI).getLocalURI(); final File imageFile = new File(imageFilename); if (imageFile.exists()) { Bitmap b = null; try { Display display = ((WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); int screenWidth = display.getWidth(); int screenHeight = display.getHeight(); b = FileUtils.getBitmapScaledToDisplay(imageFile, screenHeight, screenWidth); } catch (OutOfMemoryError e) { errorMsg = "ERROR: " + e.getMessage(); } if (b != null) { mImageView = new ImageView(getContext()); mImageView.setPadding(2, 2, 2, 2); mImageView.setAdjustViewBounds(true); mImageView.setImageBitmap(b); mImageView.setId(23423534); } else if (errorMsg == null) { // An error hasn't been logged and loading the image failed, so it's // likely // a bad file. errorMsg = StringUtils.getStringRobust(getContext(), R.string.file_invalid, imageFile.toString()); } } else if (errorMsg == null) { // An error hasn't been logged. We should have an image, but the file // doesn't // exist. errorMsg = StringUtils.getStringRobust(getContext(), R.string.file_missing, imageFile.toString()); } if (errorMsg != null) { // errorMsg is only set when an error has occured Log.e(t, errorMsg); mMissingImage = new TextView(getContext()); mMissingImage.setText(errorMsg); mMissingImage.setPadding(2, 2, 2, 2); mMissingImage.setId(234873453); } } catch (InvalidReferenceException e) { Log.e(t, "image invalid reference exception"); e.printStackTrace(); } } else { // There's no imageURI listed, so just ignore it. } // build text label. Don't assign the text to the built in label to he // button because it aligns horizontally, and we want the label on top TextView label = new TextView(getContext()); label.setText(prompt.getSelectChoiceText(mItems.get(i))); label.setTextSize(TypedValue.COMPLEX_UNIT_DIP, TEXTSIZE); if (!displayLabel) { label.setVisibility(View.GONE); } // answer layout holds the label text/image on top and the radio button on bottom LinearLayout answer = new LinearLayout(getContext()); answer.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.TOP; answer.setLayoutParams(params); if (mImageView != null) { if (!displayLabel) { mImageView.setVisibility(View.GONE); } answer.addView(mImageView); } else if (mMissingImage != null) { answer.addView(mMissingImage); } else { if (displayLabel) { answer.addView(label); } } answer.addView(c); answer.setPadding(4, 0, 4, 0); // /Each button gets equal weight LinearLayout.LayoutParams answerParams = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); answerParams.weight = 1; buttonLayout.addView(answer, answerParams); } } // Align the buttons so that they appear horizonally and are right justified // buttonLayout.setGravity(Gravity.RIGHT); buttonLayout.setOrientation(LinearLayout.HORIZONTAL); // LinearLayout.LayoutParams params = new // LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // buttonLayout.setLayoutParams(params); // The buttons take up the right half of the screen LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); buttonParams.weight = 1; questionLayout.addView(buttonLayout, buttonParams); addView(questionLayout); } /* * (non-Javadoc) * @see org.odk.collect.android.widgets.QuestionWidget#clearAnswer() */ @Override public void clearAnswer() { int j = mItems.size(); for (int i = 0; i < j; i++) { // no checkbox group so find by id + offset CheckBox c = ((CheckBox) findViewById(CHECKBOX_ID + i)); if (c.isChecked()) { c.setChecked(false); } } } /* * (non-Javadoc) * @see org.odk.collect.android.widgets.QuestionWidget#getAnswer() */ @Override public IAnswerData getAnswer() { Vector<Selection> vc = new Vector<Selection>(); for (int i = 0; i < mItems.size(); i++) { CheckBox c = ((CheckBox) findViewById(CHECKBOX_ID + i)); if (c.isChecked()) { vc.add(new Selection(mItems.get(i))); } } if (vc.size() == 0) { return null; } else { return new SelectMultiData(vc); } } /* * (non-Javadoc) * @see org.odk.collect.android.widgets.QuestionWidget#setFocus(android.content.Context) */ @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 QuestionWidget's add question text. Build it the same // but add it to the questionLayout protected void addQuestionText(FormEntryPrompt p) { // Add the text view. Textview always exists, regardless of whether there's text. questionText = new TextView(getContext()); questionText.setText(p.getLongText()); questionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, TEXTSIZE); questionText.setTypeface(null, Typeface.BOLD); questionText.setPadding(0, 0, 0, 7); questionText.setId(buttonIdBase); // assign random id // Wrap to the size of the parent view questionText.setHorizontallyScrolling(false); if (p.getLongText() == null) { questionText.setVisibility(GONE); } // Put the question text on the left half of the screen LinearLayout.LayoutParams labelParams = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); labelParams.weight = 1; questionLayout = new LinearLayout(getContext()); questionLayout.setOrientation(LinearLayout.HORIZONTAL); questionLayout.addView(questionText, labelParams); } /* * (non-Javadoc) * @see org.odk.collect.android.widgets.QuestionWidget#setOnLongClickListener(android.view.View.OnLongClickListener) */ @Override public void setOnLongClickListener(OnLongClickListener l) { for (CheckBox c : mCheckboxes) { c.setOnLongClickListener(l); } } /* * (non-Javadoc) * @see org.odk.collect.android.widgets.QuestionWidget#cancelLongPress() */ @Override public void cancelLongPress() { super.cancelLongPress(); for (CheckBox c : mCheckboxes) { c.cancelLongPress(); } } }