/* * 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.activities; 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.odk.collect.android.R; import org.odk.collect.android.adapters.HierarchyListAdapter; import org.odk.collect.android.logic.FormController; import org.odk.collect.android.logic.HierarchyElement; import android.app.ListActivity; import android.graphics.Color; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class FormHierarchyActivity extends ListActivity { private static final String t = "FormHierarchyActivity"; int state; private static final int CHILD = 1; private static final int EXPANDED = 2; private static final int COLLAPSED = 3; private static final int QUESTION = 4; private final String mIndent = " "; private Button jumpPreviousButton; List<HierarchyElement> formList; TextView mPath; FormIndex mStartIndex; /* * (non-Javadoc) * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.hierarchy_layout); // We use a static FormEntryController to make jumping faster. mStartIndex = FormEntryActivity.mFormController.getFormIndex(); setTitle(getString(R.string.app_name) + " > " + FormEntryActivity.mFormController.getFormTitle()); mPath = (TextView) findViewById(R.id.pathtext); jumpPreviousButton = (Button) findViewById(R.id.jumpPreviousButton); jumpPreviousButton.setOnClickListener(new OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { goUpLevel(); } }); Button jumpBeginningButton = (Button) findViewById(R.id.jumpBeginningButton); jumpBeginningButton.setOnClickListener(new OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { FormEntryActivity.mFormController.jumpToIndex(FormIndex .createBeginningOfFormIndex()); setResult(RESULT_OK); finish(); } }); Button jumpEndButton = (Button) findViewById(R.id.jumpEndButton); jumpEndButton.setOnClickListener(new OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { FormEntryActivity.mFormController.jumpToIndex(FormIndex.createEndOfFormIndex()); setResult(RESULT_OK); finish(); } }); // kinda slow, but works. // this scrolls to the last question the user was looking at getListView().post(new Runnable() { /* * (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { int position = 0; for (int i = 0; i < getListAdapter().getCount(); i++) { HierarchyElement he = (HierarchyElement) getListAdapter().getItem(i); if (mStartIndex.equals(he.getFormIndex())) { position = i; break; } } getListView().setSelection(position); } }); refreshView(); } private void goUpLevel() { FormIndex index = stepIndexOut(FormEntryActivity.mFormController.getFormIndex()); int currentEvent = FormEntryActivity.mFormController.getEvent(); // Step out of any group indexes that are present. while (index != null && FormEntryActivity.mFormController.getEvent(index) == FormEntryController.EVENT_GROUP) { index = stepIndexOut(index); } if (index == null) { FormEntryActivity.mFormController.jumpToIndex(FormIndex.createBeginningOfFormIndex()); } else { if (currentEvent == FormEntryController.EVENT_REPEAT) { // We were at a repeat, so stepping back brought us to then previous level FormEntryActivity.mFormController.jumpToIndex(index); } else { // We were at a question, so stepping back brought us to either: // The beginning. or The start of a repeat. So we need to step // out again to go passed the repeat. index = stepIndexOut(index); if (index == null) { FormEntryActivity.mFormController.jumpToIndex(FormIndex .createBeginningOfFormIndex()); } else { FormEntryActivity.mFormController.jumpToIndex(index); } } } refreshView(); } private String getCurrentPath() { FormIndex index = stepIndexOut(FormEntryActivity.mFormController.getFormIndex()); String path = ""; while (index != null) { path = FormEntryActivity.mFormController.getCaptionPrompt(index).getLongText() + " (" + (FormEntryActivity.mFormController.getCaptionPrompt(index) .getMultiplicity() + 1) + ") > " + path; index = stepIndexOut(index); } // return path? return path.substring(0, path.length() - 2); } public void refreshView() { // Record the current index so we can return to the same place if the user hits 'back'. FormIndex currentIndex = FormEntryActivity.mFormController.getFormIndex(); // 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. String enclosingGroupRef = ""; formList = new ArrayList<HierarchyElement>(); // 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) { enclosingGroupRef = FormEntryActivity.mFormController.getFormIndex().getReference().toString(false); FormEntryActivity.mFormController.stepToNextEvent(FormController.STEP_INTO_GROUP); } else { FormIndex startTest = 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 = 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) { enclosingGroupRef = FormEntryActivity.mFormController.getFormIndex().getReference().toString(false); FormEntryActivity.mFormController.stepToNextEvent(FormController.STEP_INTO_GROUP); } } int event = FormEntryActivity.mFormController.getEvent(); if (event == FormEntryController.EVENT_BEGINNING_OF_FORM) { // The beginning of form has no valid prompt to display. FormEntryActivity.mFormController.stepToNextEvent(FormController.STEP_INTO_GROUP); mPath.setVisibility(View.GONE); jumpPreviousButton.setEnabled(false); } else { mPath.setVisibility(View.VISIBLE); mPath.setText(getCurrentPath()); jumpPreviousButton.setEnabled(true); } // Refresh the current event in case we did step forward. event = FormEntryActivity.mFormController.getEvent(); // There may be repeating Groups at this level of the hierarchy, we use this variable to // keep track of them. String repeatedGroupRef = ""; event_search: while (event != FormEntryController.EVENT_END_OF_FORM) { switch (event) { case FormEntryController.EVENT_QUESTION: if (!repeatedGroupRef.equalsIgnoreCase("")) { // We're in a repeating group, so skip this question and move to the next // index. event = FormEntryActivity.mFormController .stepToNextEvent(FormController.STEP_INTO_GROUP); continue; } FormEntryPrompt fp = FormEntryActivity.mFormController.getQuestionPrompt(); formList.add(new HierarchyElement(fp.getLongText(), fp.getAnswerText(), null, Color.WHITE, QUESTION, fp.getIndex())); break; case FormEntryController.EVENT_GROUP: // ignore group events break; case FormEntryController.EVENT_PROMPT_NEW_REPEAT: if (enclosingGroupRef.compareTo(FormEntryActivity.mFormController .getFormIndex().getReference().toString(false)) == 0) { // We were displaying a set of questions inside of a repeated group. This is // the end of that group. break event_search; } if (repeatedGroupRef.compareTo(FormEntryActivity.mFormController.getFormIndex() .getReference().toString(false)) != 0) { // We're in a repeating group, so skip this repeat prompt and move to the // next event. event = FormEntryActivity.mFormController .stepToNextEvent(FormController.STEP_INTO_GROUP); continue; } if (repeatedGroupRef.compareTo(FormEntryActivity.mFormController.getFormIndex() .getReference().toString(false)) == 0) { // This is the end of the current repeating group, so we reset the // repeatedGroupName variable repeatedGroupRef = ""; } break; case FormEntryController.EVENT_REPEAT: FormEntryCaption fc = FormEntryActivity.mFormController.getCaptionPrompt(); if (enclosingGroupRef.compareTo(FormEntryActivity.mFormController .getFormIndex().getReference().toString(false)) == 0) { // We were displaying a set of questions inside a repeated group. This is // the end of that group. break event_search; } if (repeatedGroupRef.equalsIgnoreCase("") && fc.getMultiplicity() == 0) { // This is the start of a repeating group. We only want to display // "Group #", so we mark this as the beginning and skip all of its children HierarchyElement group = new HierarchyElement(fc.getLongText(), null, getResources() .getDrawable(R.drawable.expander_ic_minimized), Color.WHITE, COLLAPSED, fc.getIndex()); repeatedGroupRef = FormEntryActivity.mFormController.getFormIndex().getReference() .toString(false); formList.add(group); } if (repeatedGroupRef.compareTo(FormEntryActivity.mFormController.getFormIndex() .getReference().toString(false)) == 0) { // Add this group name to the drop down list for this repeating group. HierarchyElement h = formList.get(formList.size() - 1); h.addChild(new HierarchyElement(mIndent + fc.getLongText() + " " + (fc.getMultiplicity() + 1), null, null, Color.WHITE, CHILD, fc .getIndex())); } break; } event = FormEntryActivity.mFormController.stepToNextEvent(FormController.STEP_INTO_GROUP); } HierarchyListAdapter itla = new HierarchyListAdapter(this); itla.setListItems(formList); setListAdapter(itla); // set the controller back to the current index in case the user hits 'back' FormEntryActivity.mFormController.jumpToIndex(currentIndex); } /** * used to go up one level in the formIndex. That is, if you're at 5_0, 1 (the second question * in a repeating group), this method will return a FormInex of 5_0 (the start of the repeating * group). If your at index 16 or 5_0, this will return null; * * @param index * @return index */ public FormIndex stepIndexOut(FormIndex index) { if (index.isTerminal()) { return null; } else { return new FormIndex(stepIndexOut(index.getNextLevel()), index); } } /* * (non-Javadoc) * @see android.app.ListActivity#onListItemClick(android.widget.ListView, android.view.View, int, long) */ @Override protected void onListItemClick(ListView l, View v, int position, long id) { HierarchyElement h = (HierarchyElement) l.getItemAtPosition(position); if (h.getFormIndex() == null) { goUpLevel(); return; } switch (h.getType()) { case EXPANDED: h.setType(COLLAPSED); ArrayList<HierarchyElement> children = h.getChildren(); for (int i = 0; i < children.size(); i++) { formList.remove(position + 1); } h.setIcon(getResources().getDrawable(R.drawable.expander_ic_minimized)); h.setColor(Color.WHITE); break; case COLLAPSED: h.setType(EXPANDED); ArrayList<HierarchyElement> children1 = h.getChildren(); for (int i = 0; i < children1.size(); i++) { Log.i(t, "adding child: " + children1.get(i).getFormIndex()); formList.add(position + 1 + i, children1.get(i)); } h.setIcon(getResources().getDrawable(R.drawable.expander_ic_maximized)); h.setColor(Color.WHITE); break; case QUESTION: FormEntryActivity.mFormController.jumpToIndex(h.getFormIndex()); setResult(RESULT_OK); finish(); return; case CHILD: FormEntryActivity.mFormController.jumpToIndex(h.getFormIndex()); setResult(RESULT_OK); refreshView(); return; } // Should only get here if we've expanded or collapsed a group HierarchyListAdapter itla = new HierarchyListAdapter(this); itla.setListItems(formList); setListAdapter(itla); getListView().setSelection(position); } /* * (non-Javadoc) * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent) */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: FormEntryActivity.mFormController.jumpToIndex(mStartIndex); } return super.onKeyDown(keyCode, event); } }