package org.commcare.logic; import android.content.Context; import org.commcare.activities.FormEntryActivity; import org.commcare.activities.FormHierarchyActivity; import org.commcare.dalvik.R; import org.javarosa.core.model.Constants; import org.javarosa.core.model.FormIndex; import org.javarosa.form.api.FormEntryCaption; import org.javarosa.form.api.FormEntryController; import org.javarosa.form.api.FormEntryPrompt; import org.javarosa.xpath.XPathTypeMismatchException; import java.util.List; /** * Traverses the form building a hierarchy representation of the form. * * @author Phillip Mates (pmates@dimagi.com). */ public class FormHierarchyBuilder { private final List<HierarchyElement> formList; private final Context context; private String hierarchyPath; private FormIndex enclosingGroupIndex; private FormHierarchyBuilder(Context context, List<HierarchyElement> formList) { this.formList = formList; this.context = context; } /** * Builds the form hierarchy list starting at the form controller's current index. * * @param context Used for drawable resource loading * @param formList hierarchy elements are added to this list * @return Path representing the nesting level of the entries being shown */ public static String populateHierarchyList(Context context, List<HierarchyElement> formList) { FormHierarchyBuilder builder = new FormHierarchyBuilder(context, formList); builder.hierarchyIndexSetup(); builder.buildHierarchyList(); return builder.hierarchyPath; } private void hierarchyIndexSetup() { FormIndex currentIndex = FormEntryActivity.mFormController.getFormIndex(); enclosingGroupIndex = null; // If we're not at the first level, we're inside a repeated group so we // want to only display everything enclosed within that group. // If we're currently at a repeat node, record the name of the node and // step to the next node to display. if (FormEntryActivity.mFormController.getEvent() == FormEntryController.EVENT_REPEAT) { enclosingGroupIndex = FormEntryActivity.mFormController.getFormIndex(); FormEntryActivity.mFormController.stepToNextEvent(FormEntryController.STEP_INTO_GROUP); } else { FormIndex startTest = FormHierarchyActivity.stepIndexOut(currentIndex); // If we have a 'group' tag, we want to step back until we hit a // repeat or the beginning. while (startTest != null && FormEntryActivity.mFormController.getEvent(startTest) == FormEntryController.EVENT_GROUP) { startTest = FormHierarchyActivity.stepIndexOut(startTest); } if (startTest == null) { // check to see if the question is at the first level of the // hierarchy. If it is, display the root level from the // beginning. FormEntryActivity.mFormController.jumpToIndex(FormIndex .createBeginningOfFormIndex()); } else { // otherwise we're at a repeated group FormEntryActivity.mFormController.jumpToIndex(startTest); } // now test again for repeat. This should be true at this point or // we're at the beginning if (FormEntryActivity.mFormController.getEvent() == FormEntryController.EVENT_REPEAT) { enclosingGroupIndex = FormEntryActivity.mFormController.getFormIndex(); FormEntryActivity.mFormController.stepToNextEvent(FormEntryController.STEP_INTO_GROUP); } } int event = FormEntryActivity.mFormController.getEvent(); if (event == FormEntryController.EVENT_BEGINNING_OF_FORM) { FormEntryActivity.mFormController.stepToNextEvent(FormEntryController.STEP_INTO_GROUP); hierarchyPath = ""; } else { hierarchyPath = FormHierarchyActivity.getCurrentPath(); } } private void buildHierarchyList() { // Refresh the current event in case we did step forward. int event = FormEntryActivity.mFormController.getEvent(); while (event != FormEntryController.EVENT_END_OF_FORM && isCurrentIndexSubOf(enclosingGroupIndex)) { switch (event) { case FormEntryController.EVENT_QUESTION: addQuestionEntry(); break; case FormEntryController.EVENT_GROUP: break; case FormEntryController.EVENT_PROMPT_NEW_REPEAT: addNewRepeatHeading(); break; case FormEntryController.EVENT_REPEAT: addRepeatHeading(); event = addRepeatChildren(); continue; } event = FormEntryActivity.mFormController.stepToNextEvent(FormEntryController.STEP_INTO_GROUP); } } private boolean isCurrentIndexSubOf(FormIndex enclosingIndex) { if (enclosingIndex == null) { return true; } FormIndex currentIndex = FormEntryActivity.mFormController.getFormIndex(); return FormIndex.isSubElement(enclosingIndex, currentIndex); } private void addQuestionEntry() { FormEntryPrompt fp = FormEntryActivity.mFormController.getQuestionPrompt(); int fepIcon = getFormEntryPromptIcon(fp); String questionText; boolean isError = false; try { questionText = fp.getLongText(); } catch (XPathTypeMismatchException e) { questionText = e.getMessage(); isError = true; } formList.add(new HierarchyElement(context, questionText, fp.getAnswerText(), fepIcon == -1 ? null : context.getResources().getDrawable(fepIcon), isError, HierarchyEntryType.question, fp.getIndex())); } private void addNewRepeatHeading() { FormEntryCaption fc = FormEntryActivity.mFormController.getCaptionPrompt(); int fepIcon = R.drawable.avatar_vellum_repeat_group; formList.add(new HierarchyElement(context, fc.getLongText(), null, context.getResources().getDrawable(fepIcon), false, HierarchyEntryType.question, fc.getIndex())); } private void addRepeatHeading() { FormEntryCaption fc = FormEntryActivity.mFormController.getCaptionPrompt(); if (fc.getMultiplicity() == 0) { // Only add the heading if it is the repeat group entry, not an element in the group. HierarchyElement group = new HierarchyElement(context, fc.getLongText(), null, context.getResources().getDrawable(R.drawable.expander_ic_minimized), false, HierarchyEntryType.collapsed, fc.getIndex()); formList.add(group); } } private int addRepeatChildren() { int event = FormEntryActivity.mFormController.getEvent(); FormIndex firstRepeatChildIndex = FormEntryActivity.mFormController.getFormIndex(); while (event != FormEntryController.EVENT_END_OF_FORM) { if (event == FormEntryController.EVENT_REPEAT && isCurrentIndexIsSiblingOf(firstRepeatChildIndex)) { addRepeatChild(); } else if (!isCurrentIndexOutsideOfGroup(firstRepeatChildIndex)) { return event; } event = FormEntryActivity.mFormController.stepToNextEvent(FormEntryController.STEP_OVER_GROUP); } return event; } private boolean isCurrentIndexIsSiblingOf(FormIndex enclosingIndex) { FormIndex currentIndex = FormEntryActivity.mFormController.getFormIndex(); return FormIndex.areSiblings(enclosingIndex, currentIndex); } private boolean isCurrentIndexOutsideOfGroup(FormIndex enclosingIndex) { FormIndex currentIndex = FormEntryActivity.mFormController.getFormIndex(); return FormIndex.overlappingLocalIndexesMatch(enclosingIndex, currentIndex); } private void addRepeatChild() { // Add this group name to the drop down list for this repeating group. HierarchyElement h = formList.get(formList.size() - 1); String mIndent = " "; FormEntryCaption fc = FormEntryActivity.mFormController.getCaptionPrompt(); h.addChild(new HierarchyElement(context, mIndent + fc.getLongText() + " " + (fc.getMultiplicity() + 1), null, null, false, HierarchyEntryType.child, fc .getIndex())); } private static int getFormEntryPromptIcon(FormEntryPrompt fep) { switch (fep.getControlType()) { case Constants.CONTROL_SELECT_ONE: return R.drawable.avatar_vellum_single_answer; case Constants.CONTROL_SELECT_MULTI: return R.drawable.avatar_vellum_multi_answer; case Constants.CONTROL_TEXTAREA: return R.drawable.avatar_vellum_text; case Constants.CONTROL_SECRET: return R.drawable.avatar_vellum_password; case Constants.CONTROL_LABEL: return R.drawable.avatar_vellum_label; case Constants.CONTROL_AUDIO_CAPTURE: return R.drawable.avatar_vellum_audio_capture; case Constants.CONTROL_VIDEO_CAPTURE: return R.drawable.avatar_vellum_video; case Constants.CONTROL_TRIGGER: return R.drawable.avatar_vellum_question_list; case Constants.CONTROL_IMAGE_CHOOSE: return R.drawable.avatar_search; case Constants.CONTROL_RANGE: case Constants.CONTROL_UPLOAD: case Constants.CONTROL_SUBMIT: case Constants.CONTROL_INPUT: return getDrawableIDFor(fep); } return -1; } private static int getDrawableIDFor(FormEntryPrompt fep) { switch (fep.getDataType()) { case Constants.DATATYPE_TEXT: return R.drawable.avatar_vellum_text; case Constants.DATATYPE_INTEGER: return R.drawable.avatar_vellum_integer; case Constants.DATATYPE_DECIMAL: return R.drawable.avatar_vellum_decimal; case Constants.DATATYPE_DATE: return R.drawable.avatar_vellum_date; case Constants.DATATYPE_DATE_TIME: return R.drawable.avatar_vellum_datetime; case Constants.DATATYPE_CHOICE: return R.drawable.avatar_vellum_single_answer; case Constants.DATATYPE_CHOICE_LIST: return R.drawable.avatar_vellum_multi_answer; case Constants.DATATYPE_GEOPOINT: return R.drawable.avatar_vellum_gps; case Constants.DATATYPE_BARCODE: return R.drawable.avatar_vellum_barcode; } return -1; } }