/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2016 HydroloGIS (www.hydrologis.com) * * This program 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 * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.geopaparazzi.library.forms; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.app.TimePickerDialog; import android.content.Context; import android.support.v4.app.Fragment; import android.widget.Button; import android.widget.CheckBox; import android.widget.DatePicker; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import eu.geopaparazzi.library.forms.constraints.Constraints; import eu.geopaparazzi.library.forms.constraints.MandatoryConstraint; import eu.geopaparazzi.library.forms.constraints.RangeConstraint; import eu.geopaparazzi.library.forms.views.GBooleanView; import eu.geopaparazzi.library.forms.views.GComboView; import eu.geopaparazzi.library.forms.views.GDateView; import eu.geopaparazzi.library.forms.views.GDynamicEditTextView; import eu.geopaparazzi.library.forms.views.GEditTextView; import eu.geopaparazzi.library.forms.views.GMapView; import eu.geopaparazzi.library.forms.views.GMultiComboView; import eu.geopaparazzi.library.forms.views.GNfcUidView; import eu.geopaparazzi.library.forms.views.GPictureView; import eu.geopaparazzi.library.forms.views.GSketchView; import eu.geopaparazzi.library.forms.views.GTextView; import eu.geopaparazzi.library.forms.views.GTimeView; import eu.geopaparazzi.library.forms.views.GTwoConnectedComboView; import eu.geopaparazzi.library.forms.views.GView; import eu.geopaparazzi.library.util.MultipleChoiceDialog; import eu.geopaparazzi.library.util.Utilities; /** * Utilities methods for form stuff. * * @author Andrea Antonello (www.hydrologis.com) * @since 2.6 */ @SuppressWarnings("nls") public class FormUtilities { /** * */ public static final String COLON = ":"; /** * */ public static final String UNDERSCORE = "_"; /** * Type for a {@link TextView}. */ public static final String TYPE_LABEL = "label"; /** * Type for a {@link TextView} with line below. */ public static final String TYPE_LABELWITHLINE = "labelwithline"; /** * Type for a {@link EditText} containing generic text. */ public static final String TYPE_STRING = "string"; /** * Type for a dynamic (multiple) {@link EditText} containing generic text. */ public static final String TYPE_DYNAMICSTRING = "dynamicstring"; /** * Type for a {@link EditText} area containing generic text. */ public static final String TYPE_STRINGAREA = "stringarea"; /** * Type for a {@link EditText} containing double numbers. */ public static final String TYPE_DOUBLE = "double"; /** * Type for a {@link EditText} containing integer numbers. */ public static final String TYPE_INTEGER = "integer"; /** * Type for a {@link Button} containing date. */ public static final String TYPE_DATE = "date"; /** * Type for a {@link Button} containing time. */ public static final String TYPE_TIME = "time"; /** * Type for a {@link CheckBox}. */ public static final String TYPE_BOOLEAN = "boolean"; /** * Type for a {@link Spinner}. */ public static final String TYPE_STRINGCOMBO = "stringcombo"; /** * Type for two connected {@link Spinner}. */ public static final String TYPE_CONNECTEDSTRINGCOMBO = "connectedstringcombo"; /** * Type for a multi combo. */ public static final String TYPE_STRINGMULTIPLECHOICE = "multistringcombo"; /** * Type for a the NFC UID reader. */ public static final String TYPE_NFCUID = "nfcuid"; /** * Type for a hidden widget, which just needs to be kept as it is but not displayed. */ public static final String TYPE_HIDDEN = "hidden"; /** * Type for latitude, which can be substituted by the engine if necessary. */ public static final String TYPE_LATITUDE = "LATITUDE"; /** * Type for longitude, which can be substituted by the engine if necessary. */ public static final String TYPE_LONGITUDE = "LONGITUDE"; /** * Type for a hidden item, the value of which needs to get the name of the element. * <p/> * <p>This is needed in case of abstraction of forms.</p> */ public static final String TYPE_PRIMARYKEY = "primary_key"; /** * Type for pictures element. */ public static final String TYPE_PICTURES = "pictures"; /** * Type for pictures element. */ public static final String TYPE_SKETCH = "sketch"; /** * Type for map element. */ public static final String TYPE_MAP = "map"; /** * Type for barcode element. * <p> * <b>Not in use yet.</b> */ public static final String TYPE_BARCODE = "barcode"; /** * A constraint that defines the item as mandatory. */ public static final String CONSTRAINT_MANDATORY = "mandatory"; /** * A constraint that defines a range for the value. */ public static final String CONSTRAINT_RANGE = "range"; /** * */ public static final String ATTR_SECTIONNAME = "sectionname"; /** * */ public static final String ATTR_FORMS = "forms"; /** * */ public static final String ATTR_FORMNAME = "formname"; /** * */ public static final String TAG_LONGNAME = "longname"; /** * */ public static final String TAG_SHORTNAME = "shortname"; /** * */ public static final String TAG_FORMS = "forms"; /** * */ public static final String TAG_FORMITEMS = "formitems"; /** * */ public static final String TAG_KEY = "key"; /** * */ public static final String TAG_LABEL = "label"; /** * */ public static final String TAG_VALUE = "value"; /** * */ public static final String TAG_IS_RENDER_LABEL = "islabel"; /** * */ public static final String TAG_VALUES = "values"; /** * */ public static final String TAG_ITEMS = "items"; /** * */ public static final String TAG_ITEM = "item"; /** * */ public static final String TAG_TYPE = "type"; /** * */ public static final String TAG_READONLY = "readonly"; /** * */ public static final String TAG_SIZE = "size"; /** * */ public static final String TAG_URL = "url"; /** * Checks if the type is a special one. * * @param type the type string from the form. * @return <code>true</code> if the type is special. */ public static boolean isTypeSpecial(String type) { if (type.equals(TYPE_PRIMARYKEY)) { return true; } else if (type.equals(TYPE_HIDDEN)) { return true; } return false; } /** * Adds a {@link TextView} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label identifying the widget. * @param value the value to put in the widget. * @param type the text type: * <ul> * <li>0: text</li> * <li>1: double number</li> * <li>2: phone</li> * <li>3: date</li> * <li>4: integer number</li> * </ul> * @param lines lines or 0 * @param constraintDescription constraint * @param readonly if <code>true</code>, it is disabled for editing. * @return the added view. */ public static GView addEditText(Context context, LinearLayout mainView, String label, String value, int type, int lines, String constraintDescription, boolean readonly) { return new GEditTextView(context, null, mainView, label, value, type, lines, constraintDescription, readonly); } /** * Adds a dynamic {@link TextView} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label identifying the widget. * @param value the value to put in the widget. * @param type the text type: * <ul> * <li>0: text</li> * <li>1: double number</li> * <li>2: phone</li> * <li>3: date</li> * <li>4: integer number</li> * </ul> * @param constraintDescription constraint * @param readonly if <code>true</code>, it is disabled for editing. * @return the added view. */ public static GView addDynamicEditText(Context context, LinearLayout mainView, String label, String value, int type, String constraintDescription, boolean readonly) { return new GDynamicEditTextView(context, null, mainView, label, value, type, constraintDescription, readonly); } /** * Adds a {@link GTextView} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param value the value to put in the widget. * @param size the size. * @param withLine if it should have a line. * @param url the url. * @return the added view. */ public static GView addTextView(final Context context, LinearLayout mainView, String value, String size, boolean withLine, final String url) { return new GTextView(context, null, mainView, value, size, withLine, url); } /** * Adds a {@link CheckBox} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @param readonly if <code>true</code>, it is disabled for editing. * @return the added view. */ public static GView addBooleanView(Context context, LinearLayout mainView, String label, String value, String constraintDescription, boolean readonly) { return new GBooleanView(context, null, mainView, label, value, constraintDescription, readonly); } /** * Adds a {@link Spinner} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param itemsArray the items to put in the spinner. * @param constraintDescription constraint * @return the added view. */ public static GView addComboView(Context context, LinearLayout mainView, String label, String value, String[] itemsArray, String constraintDescription) { return new GComboView(context, null, mainView, label, value, itemsArray, constraintDescription); } /** * Adds two connected {@link Spinner} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param valuesMap the map of connected strings to put in the spinners. * @param constraintDescription constraint * @return the added view. */ public static GView addConnectedComboView(Context context, LinearLayout mainView, String label, String value, LinkedHashMap<String, List<String>> valuesMap, String constraintDescription) { return new GTwoConnectedComboView(context, null, mainView, label, value, valuesMap, constraintDescription); } /** * Adds a {@link MultipleChoiceDialog} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param itemsArray the items to put in the spinner. * @param constraintDescription constraint * @return the added view. */ public static GView addMultiSelectionView(final Context context, LinearLayout mainView, String label, String value, final String[] itemsArray, String constraintDescription) { return new GMultiComboView(context, null, mainView, label, value, itemsArray, constraintDescription); } /** * Adds a {@link GPictureView} to the supplied mainView. * * @param noteId the note id this form belogs to. * @param fragmentDetail the fragmentDetail. * @param requestCode the code to use for activity return. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @return the added view. */ public static GView addPictureView(long noteId, FormDetailFragment fragmentDetail, int requestCode, LinearLayout mainView, String label, String value, String constraintDescription) { return new GPictureView(noteId, fragmentDetail, null, requestCode, mainView, label, value, constraintDescription); } /** * Adds a {@link GSketchView} to the supplied mainView. * * @param noteId the note id this form belogs to. * @param fragmentDetail the fragmentDetail. * @param requestCode the code to use for activity return. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @return the added view. */ public static GView addSketchView(long noteId, FormDetailFragment fragmentDetail, int requestCode, LinearLayout mainView, String label, String value, String constraintDescription) { return new GSketchView(noteId, fragmentDetail, null, requestCode, mainView, label, value, constraintDescription); } /** * Adds a {@link GMapView} to the supplied mainView. * * @param context the context. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @return the added view. */ public static GView addMapView(final Context context, LinearLayout mainView, String label, String value, String constraintDescription) { return new GMapView(context, null, mainView, label, value, constraintDescription); } /** * Adds a {@link DatePicker} to the supplied mainView. * * @param fragment the parent {@link Fragment}. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @param readonly if <code>true</code>, it is disabled for editing. * @return the added view. */ public static GView addDateView(final Fragment fragment, LinearLayout mainView, String label, String value, String constraintDescription, boolean readonly) { return new GDateView(fragment, null, mainView, label, value, constraintDescription, readonly); } /** * Adds a {@link TimePickerDialog} to the supplied mainView. * * @param fragment the parent {@link Fragment}. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @param readonly if <code>true</code>, it is disabled for editing. * @return the added view. */ public static GView addTimeView(final Fragment fragment, LinearLayout mainView, String label, String value, String constraintDescription, boolean readonly) { return new GTimeView(fragment, null, mainView, label, value, constraintDescription, readonly); } /** * Adds a {@link GNfcUidView} to the supplied mainView. * * @param activity the activity * @param requestCode teh requestcode for activity return. * @param mainView the main view to which to add the new widget to. * @param label the label of the widget. * @param value the value to put in the widget. * @param constraintDescription constraint * @return the added view. */ public static GView addNfcUIDView(Activity activity, int requestCode, LinearLayout mainView, String label, String value, String constraintDescription) { return new GNfcUidView(activity, null, requestCode, mainView, label, value, constraintDescription); } /** * Check an {@link JSONObject object} for constraints and collect them. * * @param jsonObject the object to check. * @param constraints the {@link Constraints} object to use or <code>null</code>. * @return the original {@link Constraints} object or a new created. * @throws Exception if something goes wrong. */ public static Constraints handleConstraints(JSONObject jsonObject, Constraints constraints) throws Exception { if (constraints == null) constraints = new Constraints(); if (jsonObject.has(CONSTRAINT_MANDATORY)) { String mandatory = jsonObject.getString(CONSTRAINT_MANDATORY).trim(); if (mandatory.trim().equals("yes")) { constraints.addConstraint(new MandatoryConstraint()); } } if (jsonObject.has(CONSTRAINT_RANGE)) { String range = jsonObject.getString(CONSTRAINT_RANGE).trim(); String[] rangeSplit = range.split(","); if (rangeSplit.length == 2) { boolean lowIncluded = rangeSplit[0].startsWith("["); String lowStr = rangeSplit[0].substring(1); Double low = Utilities.adapt(lowStr, Double.class); boolean highIncluded = rangeSplit[1].endsWith("]"); String highStr = rangeSplit[1].substring(0, rangeSplit[1].length() - 1); Double high = Utilities.adapt(highStr, Double.class); constraints.addConstraint(new RangeConstraint(low, lowIncluded, high, highIncluded)); } } return constraints; } /** * Updates a form items array with the given kay/value pair. * * @param formItemsArray the array to update. * @param key the key of the item to update. * @param value the new value to use. * @throws JSONException if something goes wrong. */ public static void update(JSONArray formItemsArray, String key, String value) throws JSONException { int length = formItemsArray.length(); for (int i = 0; i < length; i++) { JSONObject itemObject = formItemsArray.getJSONObject(i); if (itemObject.has(TAG_KEY)) { String objKey = itemObject.getString(TAG_KEY).trim(); if (objKey.equals(key)) { itemObject.put(TAG_VALUE, value); } } } } /** * Update those fields that do not generate widgets. * * @param formItemsArray the items array. * @param latitude the lat value. * @param longitude the long value. * @param pkValue an optional value to set the PRIMARYKEY to. * @throws JSONException if something goes wrong. */ public static void updateExtras(JSONArray formItemsArray, double latitude, double longitude, String pkValue) throws JSONException { int length = formItemsArray.length(); // TODO check back if it would be good to check also on labels for (int i = 0; i < length; i++) { JSONObject itemObject = formItemsArray.getJSONObject(i); if (itemObject.has(TAG_KEY)) { String objKey = itemObject.getString(TAG_KEY).trim(); if (objKey.contains(TYPE_LATITUDE)) { itemObject.put(TAG_VALUE, latitude); } else if (objKey.contains(TYPE_LONGITUDE)) { itemObject.put(TAG_VALUE, longitude); } if (pkValue != null && itemObject.has(TAG_TYPE)) if (itemObject.getString(TAG_TYPE).trim().equals(TYPE_PRIMARYKEY)) { itemObject.put(TAG_VALUE, pkValue); } } } } /** * Transforms a form content to its plain text representation. * <p/> * <p>Media are inserted as the file name.</p> * * @param section the json form. * @param withTitles if <code>true</code>, all the section titles are added. * @return the plain text representation of the form. * @throws Exception if something goes wrong. */ public static String formToPlainText(String section, boolean withTitles) throws Exception { StringBuilder sB = new StringBuilder(); JSONObject sectionObject = new JSONObject(section); if (withTitles) { if (sectionObject.has(FormUtilities.ATTR_SECTIONNAME)) { String sectionName = sectionObject.getString(FormUtilities.ATTR_SECTIONNAME); sB.append(sectionName).append("\n"); for (int i = 0; i < sectionName.length(); i++) { sB.append("="); } sB.append("\n"); } } List<String> formsNames = TagsManager.getFormNames4Section(sectionObject); for (String formName : formsNames) { if (withTitles) { sB.append(formName).append("\n"); for (int i = 0; i < formName.length(); i++) { sB.append("-").append("-"); } sB.append("\n"); } JSONObject form4Name = TagsManager.getForm4Name(formName, sectionObject); JSONArray formItems = TagsManager.getFormItems(form4Name); for (int i = 0; i < formItems.length(); i++) { JSONObject formItem = formItems.getJSONObject(i); if (!formItem.has(FormUtilities.TAG_KEY)) { continue; } String type = formItem.getString(FormUtilities.TAG_TYPE); String key = formItem.getString(FormUtilities.TAG_KEY); String value = formItem.getString(FormUtilities.TAG_VALUE); String label = key; if (formItem.has(FormUtilities.TAG_LABEL)) { label = formItem.getString(FormUtilities.TAG_LABEL); } if (type.equals(FormUtilities.TYPE_PICTURES) || type.equals(FormUtilities.TYPE_MAP) || type.equals(FormUtilities.TYPE_SKETCH)) { if (value.trim().length() == 0) { continue; } String[] imageSplit = value.split(";"); for (String image : imageSplit) { File imgFile = new File(image); String imgName = imgFile.getName(); sB.append(label).append(": "); sB.append(imgName); sB.append("\n"); } } else { sB.append(label).append(": "); sB.append(value); sB.append("\n"); } } } return sB.toString(); } /** * Get the images paths out of a form string. * * @param formString the form. * @return the list of images paths. * @throws Exception if something goes wrong. */ public static List<String> getImageIds(String formString) throws Exception { List<String> imageIds = new ArrayList<String>(); if (formString != null && formString.length() > 0) { JSONObject sectionObject = new JSONObject(formString); List<String> formsNames = TagsManager.getFormNames4Section(sectionObject); for (String formName : formsNames) { JSONObject form4Name = TagsManager.getForm4Name(formName, sectionObject); JSONArray formItems = TagsManager.getFormItems(form4Name); for (int i = 0; i < formItems.length(); i++) { JSONObject formItem = formItems.getJSONObject(i); if (!formItem.has(FormUtilities.TAG_KEY)) { continue; } String type = formItem.getString(FormUtilities.TAG_TYPE); String value = formItem.getString(FormUtilities.TAG_VALUE); if (type.equals(FormUtilities.TYPE_PICTURES)) { if (value.trim().length() == 0) { continue; } String[] imageSplit = value.split(";"); Collections.addAll(imageIds, imageSplit); } else if (type.equals(FormUtilities.TYPE_MAP)) { if (value.trim().length() == 0) { continue; } String image = value.trim(); imageIds.add(image); } else if (type.equals(FormUtilities.TYPE_SKETCH)) { if (value.trim().length() == 0) { continue; } String[] imageSplit = value.split(";"); Collections.addAll(imageIds, imageSplit); } } } } return imageIds; } /** * Make the given string json safe. * * @param text the srting to check. * @return the modified string. */ public static String makeTextJsonSafe(String text) { text = text.replaceAll("\"", "'"); return text; } }