package de.blau.android.propertyeditor;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.widget.AppCompatCheckBox;
import android.support.v7.widget.AppCompatRadioButton;
import android.text.Editable;
import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.text.style.CharacterStyle;
import android.text.style.MetricAffectingSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import ch.poole.conditionalrestrictionparser.ConditionalRestrictionParser;
import de.blau.android.App;
import de.blau.android.HelpViewer;
import de.blau.android.R;
import de.blau.android.names.Names;
import de.blau.android.names.Names.NameAndTags;
import de.blau.android.names.Names.TagMap;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.Tags;
import de.blau.android.prefs.Preferences;
import de.blau.android.presets.Preset;
import de.blau.android.presets.Preset.PresetItem;
import de.blau.android.presets.Preset.PresetKeyType;
import de.blau.android.presets.ValueWithCount;
import de.blau.android.util.BaseFragment;
import de.blau.android.util.NetworkStatus;
import de.blau.android.util.StringWithDescription;
import de.blau.android.util.ThemeUtils;
import de.blau.android.util.Util;
import de.blau.android.views.CustomAutoCompleteTextView;
@SuppressWarnings("UnnecessaryLocalVariable")
public class TagFormFragment extends BaseFragment implements FormUpdate {
private static final String FOCUS_TAG = "focusTag";
private static final String FOCUS_ON_ADDRESS = "focusOnAddress";
private static final String DISPLAY_MRU_PRESETS = "displayMRUpresets";
private static final String ASK_FOR_NAME = "askForName";
private static final String DEBUG_TAG = TagFormFragment.class.getSimpleName();
private LayoutInflater inflater = null;
private Names names = null;
private Preferences prefs = null;
private EditorUpdate tagListener = null;
private NameAdapters nameAdapters = null;
private boolean focusOnAddress = false;
private String focusTag = null;
private boolean askForName = false;
private int maxInlineValues = 3;
private StringWithDescription.LocaleComparator comparator;
/**
* @param applyLastAddressTags
* @param focusOnKey
* @param displayMRUpresets
*/
static public TagFormFragment newInstance(boolean displayMRUpresets, boolean focusOnAddress, String focusTag, boolean askForName) {
TagFormFragment f = new TagFormFragment();
Bundle args = new Bundle();
args.putSerializable(DISPLAY_MRU_PRESETS, Boolean.valueOf(displayMRUpresets));
args.putSerializable(FOCUS_ON_ADDRESS, Boolean.valueOf(focusOnAddress));
args.putSerializable(FOCUS_TAG, focusTag);
args.putSerializable(ASK_FOR_NAME, askForName);
f.setArguments(args);
// f.setShowsDialog(true);
return f;
}
@Override
public void onAttachToContext(Context context) {
Log.d(DEBUG_TAG, "onAttachToContext");
try {
tagListener = (EditorUpdate) context;
nameAdapters = (NameAdapters) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement OnPresetSelectedListener and NameAdapters");
}
setHasOptionsMenu(true);
getActivity().supportInvalidateOptionsMenu();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(DEBUG_TAG, "onCreate");
comparator = new StringWithDescription.LocaleComparator();
}
/**
* display member elements of the relation if any
* @param members
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ScrollView rowLayout = null;
if (savedInstanceState == null) {
// No previous state to restore - get the state from the intent
Log.d(DEBUG_TAG, "Initializing from original arguments");
} else {
// Restore activity from saved state
Log.d(DEBUG_TAG, "Restoring from savedInstanceState");
}
this.inflater = inflater;
rowLayout = (ScrollView) inflater.inflate(R.layout.tag_form_view, container, false);
boolean displayMRUpresets = ((Boolean) getArguments().getSerializable(DISPLAY_MRU_PRESETS)).booleanValue();
focusOnAddress = ((Boolean) getArguments().getSerializable(FOCUS_ON_ADDRESS)).booleanValue();
focusTag = getArguments().getString(FOCUS_TAG);
askForName = ((Boolean) getArguments().getSerializable(ASK_FOR_NAME)).booleanValue();
// Log.d(DEBUG_TAG,"element " + element + " tags " + tags);
if (getUserVisibleHint()) { // don't request focus if we are not visible
Log.d(DEBUG_TAG,"is visible");
}
//
prefs = new Preferences(getActivity());
if (prefs.getEnableNameSuggestions()) {
names = App.getNames(getActivity());
}
maxInlineValues = prefs.getMaxInlineValues();
if (displayMRUpresets) {
Log.d(DEBUG_TAG,"Adding MRU prests");
FragmentManager fm = getChildFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment recentPresetsFragment = fm.findFragmentByTag("recentpresets_fragment");
if (recentPresetsFragment != null) {
ft.remove(recentPresetsFragment);
}
recentPresetsFragment = RecentPresetsFragment.newInstance(((PropertyEditor)getActivity()).getElement()); // FIXME
ft.add(R.id.form_mru_layout,recentPresetsFragment,"recentpresets_fragment");
ft.commit();
}
Log.d(DEBUG_TAG,"onCreateView returning");
return rowLayout;
}
@Override
public void onStart() {
super.onStart();
Log.d(DEBUG_TAG, "onStart");
}
@Override
public void onResume() {
super.onResume();
Log.d(DEBUG_TAG, "onResume");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(DEBUG_TAG, "onSaveInstanceState");
}
@Override
public void onPause() {
super.onPause();
Log.d(DEBUG_TAG, "onPause");
}
@Override
public void onStop() {
super.onStop();
Log.d(DEBUG_TAG, "onStop");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(DEBUG_TAG, "onDestroy");
}
@Override
public void onDestroyView() {
// remove onFocusChangeListeners or else bad things might happen (at least with API 23)
ViewGroup v = (ViewGroup) getView();
if (v != null) {
loopViews(v);
}
super.onDestroyView();
Log.d(DEBUG_TAG, "onDestroyView");
}
/**
* Recursively loop over views and remove onFocusChangeListeners, might be worth it
* to make this more generic
* @param view
*/
private void loopViews(ViewGroup view) {
for (int i = 0; i < view.getChildCount(); i++) {
View v = view.getChildAt(i);
if (v instanceof ViewGroup) {
this.loopViews((ViewGroup) v);
} else {
view.setOnFocusChangeListener(null);
}
}
}
/**
* Simplified version for non-multi-select and preset only situation
* @param key
* @param value
* @param allTags
* @return
*/
private ArrayAdapter<?> getValueAutocompleteAdapter(String key, ArrayList<String> values, PresetItem preset, LinkedHashMap<String, String> allTags) {
ArrayAdapter<?> adapter = null;
if (key != null && key.length() > 0) {
Set<String> usedKeys = allTags.keySet();
if (TagEditorFragment.isStreetName(key, usedKeys)) {
adapter = nameAdapters.getStreetNameAdapter(values);
} else if (TagEditorFragment.isPlaceName(key, usedKeys)) {
adapter = nameAdapters.getPlaceNameAdapter(values);
} else if (key.equals(Tags.KEY_NAME) && (names != null) && TagEditorFragment.useNameSuggestions(usedKeys)) {
Log.d(DEBUG_TAG,"generate suggestions for name from name suggestion index");
ArrayList<NameAndTags> suggestions = (ArrayList<NameAndTags>) names.getNames(new TreeMap<String,String>(allTags));
if (suggestions != null && !suggestions.isEmpty()) {
ArrayList<NameAndTags> result = suggestions;
Collections.sort(result);
adapter = new ArrayAdapter<NameAndTags>(getActivity(), R.layout.autocomplete_row, result);
}
} else {
HashMap<String, Integer> counter = new HashMap<String, Integer>();
ArrayAdapter<ValueWithCount> adapter2 = new ArrayAdapter<ValueWithCount>(getActivity(), R.layout.autocomplete_row);
if (preset != null) {
Collection<StringWithDescription> presetValues = preset.getAutocompleteValues(key);
Log.d(DEBUG_TAG,"setting autocomplete adapter for values " + presetValues);
if (values != null && !values.isEmpty()) {
ArrayList<StringWithDescription> result = new ArrayList<StringWithDescription>(presetValues);
if (preset.sortIt(key)) {
Collections.sort(result, comparator);
}
for (StringWithDescription s:result) {
if (counter != null && counter.containsKey(s.getValue())) {
continue; // skip stuff that is already listed
}
counter.put(s.getValue(),Integer.valueOf(1));
adapter2.add(new ValueWithCount(s.getValue(), s.getDescription(), true));
}
Log.d(DEBUG_TAG,"key " + key + " type " + preset.getKeyType(key));
}
} else {
OsmElement element = ((PropertyEditor)getActivity()).getElement();
if (((PropertyEditor)getActivity()).presets != null && element != null) {
Log.d(DEBUG_TAG,"generate suggestions for >" + key + "< from presets"); // only do this if there is no other source of suggestions
for (StringWithDescription s:Preset.getAutocompleteValues(((PropertyEditor)getActivity()).presets,element.getType(), key)) {
adapter2.add(new ValueWithCount(s.getValue(), s.getDescription()));
}
}
}
if (!counter.containsKey("") && !counter.containsKey(null)) { // add empty value so that we can remove tag
adapter2.insert(new ValueWithCount("", getString(R.string.tag_not_set), true),0); // FIXME allow unset value depending on preset
}
if (values != null) { // add in any non-standard non-empty values
for (String value:values) {
if (!"".equals(value) && !counter.containsKey(value)) {
ValueWithCount v = new ValueWithCount(value,1); // FIXME determine description in some way
adapter2.insert(v,0);
}
}
}
Log.d(DEBUG_TAG,adapter2==null ? "adapter2 is null": "adapter2 has " + adapter2.getCount() + " elements");
if (adapter2.getCount() > 0) {
return adapter2;
}
}
}
Log.d(DEBUG_TAG,adapter==null ? "adapter is null": "adapter has " + adapter.getCount() + " elements");
return adapter;
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
// final MenuInflater inflater = getSupportMenuInflater();
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.tag_form_menu, menu);
FragmentActivity activity = getActivity();
menu.findItem(R.id.tag_menu_mapfeatures).setEnabled(NetworkStatus.isConnected(activity));
menu.findItem(R.id.tag_menu_paste).setVisible(tagListener.pasteIsPossible());
menu.findItem(R.id.tag_menu_paste_from_clipboard).setVisible(tagListener.pasteFromClipboardIsPossible());
Locale locale = Locale.getDefault();
if (activity != null && !(locale.equals(Locale.US) || locale.equals(Locale.UK))) {
menu.findItem(R.id.tag_menu_locale).setVisible(true).setTitle(activity.getString(R.string.tag_menu_i8n, locale.toString()));
} else {
menu.findItem(R.id.tag_menu_locale).setVisible(false);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// disable address tagging for stuff that won't have an address
// menu.findItem(R.id.tag_menu_address).setVisible(!type.equals(Way.NAME) || element.hasTagKey(Tags.KEY_BUILDING));
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Log.d(DEBUG_TAG,"home pressed");
updateEditorFromText();
((PropertyEditor)getActivity()).sendResultAndFinish();
return true;
case R.id.tag_menu_address:
updateEditorFromText();
tagListener.predictAddressTags(true);
update();
if (!focusOnTag(Tags.KEY_ADDR_HOUSENUMBER)) {
focusOnTag(Tags.KEY_ADDR_STREET);
}
return true;
case R.id.tag_menu_apply_preset:
PresetItem pi = tagListener.getBestPreset();
if (pi!=null) {
tagListener.applyPreset(pi, true);
tagsUpdated();
}
return true;
case R.id.tag_menu_revert:
doRevert();
return true;
case R.id.tag_menu_paste:
if (tagListener.paste(true)) {
update();
}
return true;
case R.id.tag_menu_paste_from_clipboard:
if (tagListener.pasteFromClipboard(true)) {
update();
}
return true;
case R.id.tag_menu_mapfeatures:
startActivity(Preset.getMapFeaturesIntent(getActivity(),tagListener.getBestPreset()));
return true;
case R.id.tag_menu_resetMRU:
for (Preset p:((PropertyEditor)getActivity()).presets)
p.resetRecentlyUsed();
((PropertyEditor)getActivity()).recreateRecentPresetView();
return true;
case R.id.tag_menu_reset_address_prediction:
// simply overwrite with an empty file
Address.resetLastAddresses(getActivity());
return true;
case R.id.tag_menu_locale:
// add locale to any name keys present
LinkedHashMap<String, String> allTags = tagListener.getKeyValueMapSingle(true);
LinkedHashMap<String, String> result = new LinkedHashMap<String, String>();
Locale locale = Locale.getDefault();
for (Entry<String,String> e:allTags.entrySet()) {
String key = e.getKey();
result.put(key, e.getValue());
if (Tags.I18N_NAME_KEYS.contains(key)) {
String languageKey = key + ":" + locale.getLanguage();
String variantKey = key + ":" + locale.toString();
if (!allTags.containsKey(languageKey)) {
result.put(languageKey,"");
}
if (!allTags.containsKey(variantKey)) {
result.put(variantKey,"");
}
}
}
if (result.size() != allTags.size()) {
tagListener.updateTags(result, true);
update();
}
return true;
case R.id.tag_menu_help:
HelpViewer.start(getActivity(), R.string.help_propertyeditor);
return true;
}
return false;
}
/**
* reload original arguments
*/
private void doRevert() {
tagListener.revertTags();
update();
}
/**
* update editor with any potential text changes that haven't been saved yet
*/
boolean updateEditorFromText() {
Log.d(DEBUG_TAG,"updating data from last text field");
// check for focus on text field
View fragementView = getView();
if (fragementView == null) {
return false; // already destroyed?
}
LinearLayout l = (LinearLayout) fragementView.findViewById(R.id.form_container_layout);
if (l != null) { // FIXME this might need an alert
View v = l.findFocus();
Log.d(DEBUG_TAG,"focus is on " + v);
if (v != null && v instanceof CustomAutoCompleteTextView){
View row = v;
do {
row = (View) row.getParent();
} while (row != null && !(row instanceof TagTextRow));
if (row != null) {
tagListener.updateSingleValue(((TagTextRow) row).getKey(), ((TagTextRow) row).getValue());
if (row.getParent() instanceof EditableLayout) {
(((EditableLayout)row.getParent())).putTag(((TagTextRow) row).getKey(), ((TagTextRow) row).getValue());
}
}
}
}
return true;
}
/**
* Return the view we have our rows in and work around some android craziness
* @return
*/
private View getImmutableView() {
// android.support.v4.app.NoSaveStateFrameLayout
View v = getView();
if (v != null) {
if ( v.getId() == R.id.form_immutable_row_layout) {
Log.d(DEBUG_TAG,"got correct view in getView");
return v;
} else {
v = v.findViewById(R.id.form_immutable_row_layout);
if (v == null) {
Log.d(DEBUG_TAG,"didn't find R.id.form_immutable_row_layout");
} else {
Log.d(DEBUG_TAG,"Found R.id.form_immutable_row_layout");
}
return v;
}
} else {
Log.d(DEBUG_TAG,"got null view in getView");
}
return null;
}
public void enableRecentPresets() {
FragmentManager fm = getChildFragmentManager();
Fragment recentPresetsFragment = fm.findFragmentByTag(PropertyEditor.RECENTPRESETS_FRAGMENT);
if (recentPresetsFragment != null) {
((RecentPresetsFragment)recentPresetsFragment).enable();
}
}
public void disableRecentPresets() {
FragmentManager fm = getChildFragmentManager();
Fragment recentPresetsFragment = fm.findFragmentByTag(PropertyEditor.RECENTPRESETS_FRAGMENT);
if (recentPresetsFragment != null) {
((RecentPresetsFragment)recentPresetsFragment).disable();
}
}
void recreateRecentPresetView() {
Log.d(DEBUG_TAG,"Updating MRU prests");
FragmentManager fm = getChildFragmentManager();
Fragment recentPresetsFragment = fm.findFragmentByTag(PropertyEditor.RECENTPRESETS_FRAGMENT);
if (recentPresetsFragment != null) {
((RecentPresetsFragment)recentPresetsFragment).recreateRecentPresetView();
}
}
public void update() {
Log.d(DEBUG_TAG,"update");
// remove all editable stuff
View sv = getView();
LinearLayout ll = (LinearLayout) sv.findViewById(R.id.form_container_layout);
if (ll != null) {
while (ll.getChildAt(0) instanceof EditableLayout) {
ll.removeViewAt(0);
}
} else {
Log.d(DEBUG_TAG,"update container layout null");
return;
}
final EditableLayout editableView = (EditableLayout)inflater.inflate(R.layout.tag_form_editable, ll, false);
editableView.setSaveEnabled(false);
int pos = 0;
ll.addView(editableView, pos++);
LinearLayout nonEditableView = (LinearLayout) getImmutableView();
if (nonEditableView != null && nonEditableView.getChildCount() > 0) {
nonEditableView.removeAllViews();
}
PresetItem mainPreset = tagListener.getBestPreset();
editableView.setTitle(mainPreset);
editableView.setListeners(tagListener,this);
editableView.applyPresetButton.setVisibility(View.GONE);
LinkedHashMap<String, String> allTags = tagListener.getKeyValueMapSingle(true);
Map<String, String> nonEditable;
if (mainPreset != null) {
nonEditable = addTagsToViews(editableView, mainPreset, allTags);
for (PresetItem preset:tagListener.getSecondaryPresets()) {
final EditableLayout editableView1 = (EditableLayout)inflater.inflate(R.layout.tag_form_editable, ll, false);
editableView1.setSaveEnabled(false);
editableView1.setTitle(preset);
editableView1.setListeners(tagListener,this);
ll.addView(editableView1, pos++);
nonEditable = addTagsToViews(editableView1, preset, (LinkedHashMap<String, String>) nonEditable);
}
} else {
nonEditable = allTags;
}
LinearLayout nel = (LinearLayout) getView().findViewById(R.id.form_immutable_header_layout);
if (nel != null) {
nel.setVisibility(View.GONE);
}
if (nonEditable.size() > 0) {
nel.setVisibility(View.VISIBLE);
for (String key:nonEditable.keySet()) {
addRow(nonEditableView,key, nonEditable.get(key),null, allTags);
}
}
// some final UI stuff
if (focusOnAddress) {
focusOnAddress = false; // only do it once
if (!focusOnTag(Tags.KEY_ADDR_HOUSENUMBER)) {
if (!focusOnTag(Tags.KEY_ADDR_STREET)) {
focusOnEmpty();
}
}
} else if (focusTag != null){
if (!focusOnTag(focusTag)) {
focusOnEmpty();
}
focusTag = null;
} else {
focusOnEmpty();
}
// display dialog for name selection for chains
if (askForName) {
askForName = false; // only do this once
AlertDialog d = buildNameDialog(getActivity());
d.show();
// force dropdown and keyboard to appear
final View v = d.findViewById(R.id.textValue);
if (v != null && v instanceof AutoCompleteTextView) {
v.post(new Runnable() {
@Override
public void run() {
((AutoCompleteTextView)v).showDropDown();
InputMethodManager mgr = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT);
}});
}
}
}
private Map<String,String> addTagsToViews(EditableLayout editableView, PresetItem preset, LinkedHashMap<String, String> tags) {
LinkedHashMap<String,String> recommendedEditable = new LinkedHashMap<String,String>();
LinkedHashMap<String,String> optionalEditable = new LinkedHashMap<String,String>();
LinkedHashMap<String,String> linkedTags = new LinkedHashMap<String,String>();
LinkedHashMap<String,String> nonEditable = new LinkedHashMap<String,String>();
HashMap<String,PresetItem> keyToLinkedPreset = new HashMap<String,PresetItem>();
boolean groupingRequired = false;
if (preset != null) {
// iterate over preset entries so that we maintain ordering
LinkedHashMap<String,String> tagList = new LinkedHashMap<String,String>(tags);
for (Entry<String,StringWithDescription>e:preset.getFixedTags().entrySet()) {
String key = e.getKey();
String value = tagList.get(key);
if (value != null && value.equals(e.getValue().getValue())) {
tagList.remove(key);
editableView.putTag(key, value);
}
}
for (String key:preset.getRecommendedTags().keySet()) {
String value = tagList.get(key);
if (value != null) {
if (preset.hasKeyValue(key, value)) {
recommendedEditable.put(key, value);
tagList.remove(key);
editableView.putTag(key, value);
}
}
}
for (String key:preset.getOptionalTags().keySet()) {
String value = tagList.get(key);
if (value != null) {
if (preset.hasKeyValue(key, value)) {
optionalEditable.put(key, value);
tagList.remove(key);
editableView.putTag(key, value);
}
}
}
// process any remaining tags
List<PresetItem> linkedPresets = preset.getLinkedPresets(true);
// loop over the tags assigning them to the remaining linked presets
for (Entry<String,String>e:tagList.entrySet()) {
String key = e.getKey();
String value = e.getValue();
// check if i18n version of a name tag
boolean found = addI18nKeyToPreset(key, value, preset, recommendedEditable, editableView);
if (found) {
groupingRequired = true;
}
if (!found && linkedPresets != null) { // check if tag is in a linked preset
for (PresetItem l:linkedPresets) {
if (l.hasKeyValue(key, value)) {
linkedTags.put(key, value);
editableView.putTag(key, value);
keyToLinkedPreset.put(key, l);
found = true;
break;
}
// check if i18n version of a name tag
if (found = addI18nKeyToPreset(key, value, preset, linkedTags, editableView)) {
keyToLinkedPreset.put(key, l);
groupingRequired = true;
break;
}
}
}
if (!found) {
nonEditable.put(key, tags.get(key));
}
}
} else {
Log.e(DEBUG_TAG,"addTagsToViews called with null preset");
}
if (groupingRequired) {
Log.d(DEBUG_TAG,"grouping i18n keys");
preset.groupI18nKeys();
Util.groupI18nKeys(recommendedEditable);
Util.groupI18nKeys(optionalEditable);
Util.groupI18nKeys(linkedTags);
}
for (String key:recommendedEditable.keySet()) {
addRow(editableView,key, recommendedEditable.get(key),preset, tags);
}
for (String key:optionalEditable.keySet()) {
addRow(editableView,key, optionalEditable.get(key),preset, tags);
}
for (String key: linkedTags.keySet()) {
addRow(editableView,key, linkedTags.get(key), keyToLinkedPreset.get(key), tags);
}
return nonEditable;
}
/**
* Add international name keys to preset
* @param key
* @param value
* @param preset
* @param map
* @param editableView
* @return
*/
private boolean addI18nKeyToPreset(String key, String value, PresetItem preset, Map<String, String> map, EditableLayout editableView) {
for (String tag:Tags.I18N_NAME_KEYS) {
if (key.startsWith(tag + ":")) {
String[] s = key.split("\\Q:\\E");
if (preset.hasKey(tag) && s != null && s.length == 2) {
preset.addTag(preset.isOptionalTag(tag), key, PresetKeyType.TEXT, null);
String hint = preset.getHint(tag);
if (hint != null) {
preset.addHint(key, getActivity().getString(R.string.internationalized_hint, hint, s[1])); // FIXME RTL
}
map.put(key, value);
editableView.putTag(key, value);
return true;
}
}
}
return false;
}
private void addRow(final LinearLayout rowLayout, final String key, final String value, PresetItem preset, LinkedHashMap<String, String> allTags) {
if (rowLayout != null) {
if (preset != null) {
if (!preset.isFixedTag(key)) {
ArrayAdapter<?> adapter = null;
ArrayList<String> values = null;
if (preset != null && preset.getKeyType(key) == PresetKeyType.MULTISELECT) {
values = Preset.splitValues(Util.getArrayList(value), preset, key);
adapter = getValueAutocompleteAdapter(key, values, preset, allTags);
} else {
adapter = getValueAutocompleteAdapter(key, Util.getArrayList(value), preset, allTags);
}
int count = 0;
if (adapter!=null) {
count = adapter.getCount();
} else {
Log.d(DEBUG_TAG,"adapter null " + key + " " + value + " " + preset);
}
String hint = preset.getHint(key);
//
PresetKeyType keyType = preset.getKeyType(key);
String defaultValue = preset.getDefault(key);
if (keyType == PresetKeyType.TEXT
|| key.startsWith(Tags.KEY_ADDR_BASE)
|| preset.isEditable(key)
|| key.endsWith(Tags.KEY_CONDITIONAL_SUFFIX)) {
if (key.endsWith(Tags.KEY_CONDITIONAL_SUFFIX)) {
rowLayout.addView(addConditionalRestrictionDialogRow(rowLayout, preset, hint, key, value, adapter));
} else {
// special handling for international names
rowLayout.addView(addTextRow(rowLayout, preset, keyType, hint, key, value, defaultValue, adapter));
}
} else if (preset.getKeyType(key) == PresetKeyType.COMBO || (keyType == PresetKeyType.CHECK && count > 2)) {
if (count <= maxInlineValues) {
rowLayout.addView(addComboRow(rowLayout, preset, hint, key, value, defaultValue, adapter));
} else {
rowLayout.addView(addComboDialogRow(rowLayout, preset, hint, key, value, defaultValue, adapter, allTags));
}
} else if (preset.getKeyType(key) == PresetKeyType.MULTISELECT) {
if (count <= maxInlineValues) {
rowLayout.addView(addMultiselectRow(rowLayout, preset, hint, key, values, defaultValue, adapter, allTags));
} else {
rowLayout.addView(addMultiselectDialogRow(rowLayout, preset, hint, key, value, defaultValue, adapter, allTags));
}
} else if (preset.getKeyType(key) == PresetKeyType.CHECK) {
final TagCheckRow row = (TagCheckRow)inflater.inflate(R.layout.tag_form_check_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
String v = "";
String description = "";
final String valueOn = preset.getOnValue(key);
String tempValueOff = "";
// this is a bit of a roundabout way of determining the non-checked value;
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
v = swd.getValue();
description = swd.getDescription();
if (!v.equals(valueOn)) {
tempValueOff = v;
}
}
final String valueOff = tempValueOff;
Log.d(DEBUG_TAG,"adapter size " + adapter.getCount() + " checked value >" + valueOn + "< not checked value >" + valueOff + "<");
if (description==null) {
description=v;
}
row.getCheckBox().setChecked(valueOn != null && valueOn.equals(value));
rowLayout.addView(row);
row.getCheckBox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(
CompoundButton buttonView, boolean isChecked) {
tagListener.updateSingleValue(key, isChecked?valueOn:valueOff);
if (rowLayout instanceof EditableLayout) {
((EditableLayout)rowLayout).putTag(key, isChecked?valueOn:valueOff);
}
}
});
} else {
Log.e(DEBUG_TAG,"unknown preset element type " + key + " " + value + " " + preset.getName());
}
}
// } else if (key.startsWith(Tags.KEY_ADDR_BASE)) { // make address tags always editable
// Set<String> usedKeys = allTags.keySet();
// ArrayAdapter<?> adapter = null;
// if (TagEditorFragment.isStreetName(key, usedKeys)) {
// adapter = nameAdapters.getStreetNameAutocompleteAdapter(Util.getArrayList(value));
// } else if (TagEditorFragment.isPlaceName(key, usedKeys)) {
// adapter = nameAdapters.getPlaceNameAutocompleteAdapter(Util.getArrayList(value));
// }
// // String hint = preset.getHint(key);
// rowLayout.addView(addTextRow(null, null, key, value, adapter));
} else {
if (false) { // make tags not associated with a preset un-editable, disabled for now, may end up in a preference
final TagStaticTextRow row = (TagStaticTextRow)inflater.inflate(R.layout.tag_form_static_text_row, rowLayout, false);
row.keyView.setText(key);
row.valueView.setText(value);
rowLayout.addView(row);
} else {
ArrayAdapter<?> adapter = getValueAutocompleteAdapter(key, Util.getArrayList(value), null, allTags);
rowLayout.addView(addTextRow(rowLayout, null, PresetKeyType.TEXT, null, key, value, null, adapter));
}
}
} else {
Log.d(DEBUG_TAG, "addRow rowLayout null");
}
}
private TagTextRow addTextRow(final LinearLayout rowLayout, final PresetItem preset, final PresetKeyType keyType, final String hint, final String key, final String value, final String defaultValue, final ArrayAdapter<?> adapter) {
final TagTextRow row = (TagTextRow)inflater.inflate(R.layout.tag_form_text_row, rowLayout, false);
final boolean isWebsite = Tags.isWebsiteKey(key);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // stop Hint from wrapping
row.valueView.setEllipsize(TruncateAt.END);
}
if ((value == null || "".equals(value)) && (defaultValue != null && !"".equals(defaultValue))) {
row.valueView.setText(defaultValue);
} else {
row.valueView.setText(value);
}
if (adapter != null) {
row.valueView.setAdapter(adapter);
} else {
Log.e(DEBUG_TAG,"adapter null");
row.valueView.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.autocomplete_row, new String[0]));
}
if (keyType==PresetKeyType.MULTISELECT) {
// FIXME this should be somewhere better since it creates a non obvious side effect
row.valueView.setTokenizer(new CustomAutoCompleteTextView.SingleCharTokenizer(preset.getDelimiter(key)));
}
if (keyType==PresetKeyType.TEXT && (adapter==null || adapter.getCount() < 2)) {
row.valueView.setHint(R.string.tag_value_hint);
} else {
row.valueView.setHint(R.string.tag_autocomplete_value_hint);
}
row.valueView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
String rowValue = row.getValue();
if (!hasFocus && !rowValue.equals(value)) {
Log.d(DEBUG_TAG,"onFocusChange");
tagListener.updateSingleValue(key, rowValue);
if (rowLayout instanceof EditableLayout) {
((EditableLayout)rowLayout).putTag(key, rowValue);
}
} else if (hasFocus && isWebsite) {
TagEditorFragment.initWebsite(row.valueView);
}
}
});
row.valueView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(DEBUG_TAG,"onItemClicked value");
Object o = parent.getItemAtPosition(position);
if (o instanceof Names.NameAndTags) {
row.valueView.setOrReplaceText(((NameAndTags)o).getName());
tagListener.applyTagSuggestions(((NameAndTags)o).getTags());
update();
return;
} else if (o instanceof ValueWithCount) {
row.valueView.setOrReplaceText(((ValueWithCount)o).getValue());
} else if (o instanceof StringWithDescription) {
row.valueView.setOrReplaceText(((StringWithDescription)o).getValue());
} else if (o instanceof String) {
row.valueView.setOrReplaceText((String)o);
}
tagListener.updateSingleValue(key, row.getValue());
if (rowLayout instanceof EditableLayout) {
((EditableLayout)rowLayout).putTag(key, row.getValue());
}
}
});
row.valueView.addTextChangedListener(new RemoveFormatingWatcher());
return row;
}
class RemoveFormatingWatcher implements TextWatcher {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
// remove formating from pastes etc
CharacterStyle[] toBeRemovedSpans = s.getSpans(0, s.length(), MetricAffectingSpan.class);
for (int i = 0; i < toBeRemovedSpans.length; i++)
s.removeSpan(toBeRemovedSpans[i]);
}
}
private TagComboRow addComboRow(final LinearLayout rowLayout, final PresetItem preset, final String hint, final String key, final String value, final String defaultValue, final ArrayAdapter<?> adapter) {
final TagComboRow row = (TagComboRow)inflater.inflate(R.layout.tag_form_combo_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
String description = swd.getDescription();
if (v==null || "".equals(v)) {
continue;
}
if (description==null) {
description=v;
}
if ((value == null || "".equals(value)) && (defaultValue != null && !"".equals(defaultValue))) {
row.addButton(description, v, v.equals(defaultValue));
} else {
row.addButton(description, v, v.equals(value));
}
}
row.getRadioGroup().setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Log.d(DEBUG_TAG,"radio group onCheckedChanged");
String value = "";
if (checkedId != -1) {
RadioButton button = (RadioButton) group.findViewById(checkedId);
value = (String)button.getTag();
}
tagListener.updateSingleValue(key, value);
if (rowLayout instanceof EditableLayout) {
((EditableLayout)rowLayout).putTag(key, value);
}
row.setValue(value);
row.setChanged(true);
}
});
return row;
}
private TagMultiselectRow addMultiselectRow(final LinearLayout rowLayout, final PresetItem preset, final String hint, final String key, final ArrayList<String> values, final String defaultValue, ArrayAdapter<?> adapter, LinkedHashMap<String, String> allTags) {
final TagMultiselectRow row = (TagMultiselectRow)inflater.inflate(R.layout.tag_form_multiselect_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
row.setDelimiter(preset.getDelimiter(key));
CompoundButton.OnCheckedChangeListener onCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(
CompoundButton buttonView, boolean isChecked) {
tagListener.updateSingleValue(key, row.getValue());
if (rowLayout instanceof EditableLayout) {
((EditableLayout)rowLayout).putTag(key, row.getValue());
}
}
};
int count = adapter.getCount();
for (int i=0;i<count;i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
String description = swd.getDescription();
if (v==null || "".equals(v)) {
continue;
}
if (description==null) {
description=v;
}
if ((values==null || (values.size()==1 && "".equals(values.get(0)))) && (defaultValue != null && !"".equals(defaultValue))) {
row.addCheck(description, v, v.equals(defaultValue), onCheckedChangeListener);
} else {
row.addCheck(description, v, values != null && values.contains(v), onCheckedChangeListener);
}
}
return row;
}
private TagFormDialogRow addConditionalRestrictionDialogRow(LinearLayout rowLayout, PresetItem preset, final String hint, final String key, final String value, final ArrayAdapter<?> adapter) {
final TagFormDialogRow row = (TagFormDialogRow)inflater.inflate(R.layout.tag_form_combo_dialog_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
row.setPreset(preset);
final ArrayList<String> templates = new ArrayList<String>();
Log.d(DEBUG_TAG, "adapter size " + adapter.getCount());
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
Log.d(DEBUG_TAG, "adding " + swd);
String v = swd.getValue();
if (v==null || "".equals(v)) {
continue;
}
if (v.equals(value)){
ConditionalRestrictionParser parser = new ConditionalRestrictionParser(new ByteArrayInputStream(v.getBytes()));
try {
row.setValue(ch.poole.conditionalrestrictionparser.Util.prettyPrint(parser.restrictions()));
} catch (Exception ex) {
row.setValue(v);
}
}
Log.d(DEBUG_TAG, "adding " + v + " to templates");
templates.add(v);
}
final ArrayList<String> ohTemplates = new ArrayList<String>();
for (StringWithDescription s:Preset.getAutocompleteValues(((PropertyEditor)getActivity()).presets,((PropertyEditor)getActivity()).getElement().getType(), Tags.KEY_OPENING_HOURS)) {
ohTemplates.add(s.getValue());
}
row.valueView.setHint(R.string.tag_dialog_value_hint);
row.setOnClickListener(new OnClickListener() {
@SuppressLint("NewApi")
@Override
public void onClick(View v) {
final View finalView = v;
// finalView.setEnabled(false); // FIXME debounce
FragmentManager fm = getChildFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag("fragment_conditional_restriction");
if (prev != null) {
ft.remove(prev);
}
ft.commit();
ConditionalRestrictionFragment conditionalRestrictionDialog = ConditionalRestrictionFragment.newInstance(key,value,templates,ohTemplates);
conditionalRestrictionDialog.show(fm, "fragment_conditional_restriction");
}
});
return row;
}
private TagFormDialogRow addComboDialogRow(LinearLayout rowLayout, PresetItem preset, final String hint, final String key, final String value, final String defaultValue, final ArrayAdapter<?> adapter, final LinkedHashMap<String, String> allTags) {
final TagFormDialogRow row = (TagFormDialogRow)inflater.inflate(R.layout.tag_form_combo_dialog_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
row.setPreset(preset);
String selectedValue=null;
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
String description = swd.getDescription();
if (v==null || "".equals(v)) {
continue;
}
if (description==null) {
description=v;
}
if ((value == null || "".equals(value)) && (defaultValue != null && !"".equals(defaultValue)) && v.equals(defaultValue)) {
row.setValue(description,v);
selectedValue = v;
break;
} else if (v.equals(value)){
row.setValue(swd);
selectedValue = v;
break;
}
}
row.valueView.setHint(R.string.tag_dialog_value_hint);
final String finalSelectedValue;
if (selectedValue != null) {
finalSelectedValue = selectedValue;
} else {
finalSelectedValue = null;
}
row.setOnClickListener(new OnClickListener() {
@SuppressLint("NewApi")
@Override
public void onClick(View v) {
final View finalView = v;
finalView.setEnabled(false); // debounce
final AlertDialog dialog = buildComboDialog(hint != null?hint:key,key,defaultValue,adapter,row);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
dialog.setOnShowListener(new OnShowListener(){
@Override
public void onShow(DialogInterface d) {
if (finalSelectedValue != null) {
scrollDialogToValue(finalSelectedValue, dialog, R.id.valueGroup);
}
}});
}
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finalView.setEnabled(true);
}
});
dialog.show();
}
});
return row;
}
private TagFormMultiselectDialogRow addMultiselectDialogRow(LinearLayout rowLayout, PresetItem preset, final String hint, final String key, final String value, final String defaultValue, final ArrayAdapter<?> adapter, final LinkedHashMap<String, String> allTags) {
final TagFormMultiselectDialogRow row = (TagFormMultiselectDialogRow)inflater.inflate(R.layout.tag_form_multiselect_dialog_row, rowLayout, false);
row.keyView.setText(hint != null?hint:key);
row.keyView.setTag(key);
row.setPreset(preset);
Log.d(DEBUG_TAG, "addMultiselectDialogRow value " + value);
ArrayList<String> multiselectValues = Preset.splitValues(Util.getArrayList(value),preset,key);
ArrayList<StringWithDescription> selectedValues= new ArrayList<StringWithDescription>();
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
if (v==null || "".equals(v)) {
continue;
}
if ((value == null || "".equals(value)) && (defaultValue != null && !"".equals(defaultValue)) && v.equals(defaultValue)) {
selectedValues.add(swd);
break;
} else if (multiselectValues != null) {
for (String m:multiselectValues) {
if (v.equals(m)) {
selectedValues.add(swd);
break;
}
}
}
}
row.setValue(selectedValues);
row.valueView.setHint(R.string.tag_dialog_value_hint);
row.setOnClickListener(new OnClickListener() {
@SuppressLint("NewApi")
@Override
public void onClick(View v) {
final View finalView = v;
finalView.setEnabled(false); // debounce
final AlertDialog dialog = buildMultiselectDialog(hint != null?hint:key,key,defaultValue,adapter,row, allTags);
final Object tag = finalView.getTag();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
dialog.setOnShowListener(new OnShowListener(){
@Override
public void onShow(DialogInterface d) {
if (tag != null && tag instanceof String) {
scrollDialogToValue((String)tag, dialog, R.id.valueGroup);
}
}});
}
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finalView.setEnabled(true);
}
});
dialog.show();
// the following is one big awful hack to stop the button dismissing the dialog
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
LinearLayout valueGroup = (LinearLayout) dialog.findViewById(R.id.valueGroup);
for (int pos=0;pos < valueGroup.getChildCount();pos++) {
View c = valueGroup.getChildAt(pos);
if (c != null && c instanceof AppCompatCheckBox) {
((AppCompatCheckBox)c).setChecked(false);
}
}
}
});
}
});
return row;
}
/**
* Build a dialog for selecting a single value of none via a scrollable list of radio buttons
* @param hint
* @param key
* @param defaultValue
* @param adapter
* @param row
* @return
*/
private AlertDialog buildComboDialog(String hint, String key, String defaultValue, final ArrayAdapter<?> adapter, final TagFormDialogRow row) {
String value = row.getValue();
Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(hint);
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(getActivity());
final View layout = inflater.inflate(R.layout.form_combo_dialog, null);
RadioGroup valueGroup = (RadioGroup) layout.findViewById(R.id.valueGroup);
builder.setView(layout);
View.OnClickListener listener = new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.d(DEBUG_TAG,"radio button clicked " + row.getValue() + " " + v.getTag());
if (!row.hasChanged()) {
RadioGroup g = (RadioGroup) v.getParent();
g.clearCheck();
} else {
row.setChanged(false);
}
}
};
LayoutParams buttonLayoutParams = valueGroup.getLayoutParams();
buttonLayoutParams.width = LayoutParams.FILL_PARENT;
for (int i=0;i< adapter.getCount();i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
if (v==null || "".equals(v)) {
continue;
}
if ((value == null || "".equals(value)) && (defaultValue != null && !"".equals(defaultValue))) {
addButton(getActivity(), valueGroup, i, swd, v.equals(defaultValue), listener, buttonLayoutParams);
} else {
addButton(getActivity(), valueGroup, i, swd, v.equals(value), listener, buttonLayoutParams);
}
}
final Handler handler = new Handler();
builder.setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
tagListener.updateSingleValue((String) layout.getTag(), "");
row.setValue("","");
row.setChanged(true);
final DialogInterface finalDialog = dialog;
// allow a tiny bit of time to see that the action actually worked
handler.postDelayed(new Runnable(){@Override public void run() {finalDialog.dismiss();}}, 100);
}
});
builder.setNegativeButton(R.string.cancel, null);
final AlertDialog dialog = builder.create();
layout.setTag(key);
valueGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Log.d(DEBUG_TAG,"radio group onCheckedChanged");
StringWithDescription value = null;
if (checkedId != -1) {
RadioButton button = (RadioButton) group.findViewById(checkedId);
value = (StringWithDescription)button.getTag();
tagListener.updateSingleValue((String) layout.getTag(), value.getValue());
row.setValue(value);
row.setChanged(true);
}
// allow a tiny bit of time to see that the action actually worked
handler.postDelayed(new Runnable(){@Override public void run() {dialog.dismiss();}}, 100);
}
});
return dialog;
}
private AlertDialog buildMultiselectDialog(String hint, String key, String defaultValue, ArrayAdapter<?> adapter, final TagFormMultiselectDialogRow row, LinkedHashMap<String, String> allTags) {
Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(hint);
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(getActivity());
final View layout = inflater.inflate(R.layout.form_multiselect_dialog, null);
final LinearLayout valueGroup = (LinearLayout) layout.findViewById(R.id.valueGroup);
builder.setView(layout);
LayoutParams buttonLayoutParams = valueGroup.getLayoutParams();
buttonLayoutParams.width = LayoutParams.FILL_PARENT;
layout.setTag(key);
ArrayList<String> values = Preset.splitValues(Util.getArrayList(row.getValue()), row.getPreset(), key);
int count = adapter.getCount();
for (int i=0;i<count;i++) {
Object o = adapter.getItem(i);
StringWithDescription swd = new StringWithDescription(o);
String v = swd.getValue();
String description = swd.getDescription();
if (v==null || "".equals(v)) {
continue;
}
if (description==null) {
description=v;
}
if ((values==null || (values.size()==1 && "".equals(values.get(0)))) && (defaultValue != null && !"".equals(defaultValue))) {
addCheck(getActivity(), valueGroup, swd, v.equals(defaultValue), buttonLayoutParams);
} else {
addCheck(getActivity(), valueGroup, swd, values.contains(v), buttonLayoutParams);
}
}
builder.setNeutralButton(R.string.clear, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ArrayList<StringWithDescription> values = new ArrayList<StringWithDescription>();
for (int pos=0;pos<valueGroup.getChildCount();pos++) {
View c = valueGroup.getChildAt(pos);
if (c != null && c instanceof AppCompatCheckBox) {
AppCompatCheckBox checkBox = (AppCompatCheckBox)c;
if (checkBox.isChecked()) {
values.add((StringWithDescription) checkBox.getTag());
}
}
}
row.setValue(values);
tagListener.updateSingleValue((String) layout.getTag(), row.getValue());
row.setChanged(true);
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
}
/**
* Scroll the view in the dialog to show the value, assumes the ScrollView has id R.is.scrollView
* @param key
* @return
*/
private void scrollDialogToValue(String value, AlertDialog dialog, int containerId) {
Log.d(DEBUG_TAG,"scrollDialogToValue scrolling to " + value);
final View sv = (View) dialog.findViewById(R.id.myScrollView);
if (sv != null) {
ViewGroup container = (ViewGroup) dialog.findViewById(containerId);
if (container != null) {
for (int pos = 0;pos < container.getChildCount();pos++) {
View child = container.getChildAt(pos);
Object tag = child.getTag();
if (tag != null && tag instanceof StringWithDescription && ((StringWithDescription)tag).equals(value)) {
Util.scrollToRow(sv, child, true, true);
return;
}
}
} else {
Log.d(DEBUG_TAG,"scrollDialogToValue container view null");
}
} else {
Log.d(DEBUG_TAG,"scrollDialogToValue scroll view null");
}
}
/**
* Focus on the value field of a tag with key "key"
* @param key
* @return
*/
private boolean focusOnTag(String key) {
boolean found = false;
View sv = getView();
LinearLayout ll = (LinearLayout) sv.findViewById(R.id.form_container_layout);
if (ll != null) {
int pos = 0;
while (ll.getChildAt(pos) instanceof EditableLayout && pos < ll.getChildCount() && !found) {
EditableLayout ll2 = (EditableLayout) ll.getChildAt(pos);
Log.d(DEBUG_TAG,"focusOnTag key " + key);
for (int i = ll2.getChildCount() - 1; i >= 0; --i) {
View v = ll2.getChildAt(i);
if (v instanceof TagTextRow && ((TagTextRow)v).getKey().equals(key)) {
((TagTextRow)v).getValueView().requestFocus();
Util.scrollToRow(sv, v, true, true);
found = true;
break;
} else if (v instanceof TagFormDialogRow && ((TagFormDialogRow)v).getKey().equals(key)) {
Util.scrollToRow(sv, v, true, true);
((TagFormDialogRow)v).click();
found = true;
}
}
pos++;
}
} else {
Log.d(DEBUG_TAG,"focusOnTag container layout null");
return false;
}
return found;
}
/**
* Focus on the first empty value field
* @return
*/
private boolean focusOnEmpty() {
boolean found = false;
View sv = getView();
LinearLayout ll = (LinearLayout) sv.findViewById(R.id.form_container_layout);
if (ll != null) {
int pos = 0;
while (ll.getChildAt(pos) instanceof EditableLayout && pos < ll.getChildCount() && !found) {
EditableLayout ll2 = (EditableLayout) ll.getChildAt(pos);
for (int i = 0 ; i < ll2.getChildCount(); i++) {
View v = ll2.getChildAt(i);
if (v instanceof TagTextRow && "".equals(((TagTextRow)v).getValue())) {
((TagTextRow)v).getValueView().requestFocus();
found = true;
break;
}
}
pos++;
}
} else {
Log.d(DEBUG_TAG,"update container layout null");
return false;
}
return found;
}
public static class TagTextRow extends LinearLayout {
private TextView keyView;
private CustomAutoCompleteTextView valueView;
public TagTextRow(Context context) {
super(context);
}
public TagTextRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueView = (CustomAutoCompleteTextView)findViewById(R.id.textValue);
}
/**
* Set the text via id of the key view
* @param k
*/
public void setKeyText(int k) {
keyView.setText(k);
}
/**
* Set the text via id of the key view
* @param k
*/
public void setValueAdapter(ArrayAdapter<?> a) {
valueView.setAdapter(a);
}
/**
* Return the OSM key value
* @return
*/
public String getKey() {
return (String) keyView.getTag();
}
public String getValue() {
return valueView.getText().toString();
}
public CustomAutoCompleteTextView getValueView() {
return valueView;
}
}
public static class TagStaticTextRow extends LinearLayout {
private TextView keyView;
private TextView valueView;
public TagStaticTextRow(Context context) {
super(context);
}
public TagStaticTextRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueView = (TextView)findViewById(R.id.textValue);
}
}
public static class TagComboRow extends LinearLayout {
private TextView keyView;
private RadioGroup valueGroup;
private String value;
private Context context;
private int idCounter = 0;
private boolean changed = false;
public TagComboRow(Context context) {
super(context);
this.context = context;
}
public TagComboRow(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueGroup = (RadioGroup)findViewById(R.id.valueGroup);
}
/**
* Return the OSM key value
* @return
*/
public String getKey() {
return (String) keyView.getTag();
}
public RadioGroup getRadioGroup() {
return valueGroup;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setChanged(boolean changed) {
this.changed = changed;
}
public boolean hasChanged() {
return changed;
}
public void addButton(String description, String value, boolean selected) {
final AppCompatRadioButton button = new AppCompatRadioButton(context);
button.setText(description);
button.setTag(value);
button.setChecked(selected);
button.setId(idCounter++);
valueGroup.addView(button);
if (selected) {
setValue(value);
}
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d(DEBUG_TAG,"radio button clicked " + getValue() + " " + button.getTag());
if (!changed) {
RadioGroup g = (RadioGroup) v.getParent();
g.clearCheck();
} else {
changed = false;
}
}
});
}
}
private void addButton(Context context, RadioGroup group, int id, StringWithDescription swd, boolean selected, View.OnClickListener listener, LayoutParams layoutParams) {
final AppCompatRadioButton button = new AppCompatRadioButton(context);
String description = swd.getDescription();
button.setText(description != null && !"".equals(description)?description:swd.getValue());
button.setTag(swd);
button.setChecked(selected);
button.setId(id);
button.setLayoutParams(layoutParams);
group.addView(button);
button.setOnClickListener(listener);
}
/**
* Display a single value and allow editing via a dialog
*/
public static class TagFormDialogRow extends LinearLayout {
TextView keyView;
TextView valueView;
private String value;
private boolean changed = false;
PresetItem preset;
public TagFormDialogRow(Context context) {
super(context);
}
public TagFormDialogRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueView = (TextView)findViewById(R.id.textValue);
}
public void setOnClickListener(final OnClickListener listener) {
valueView.setOnClickListener(listener);
}
/**
* Return the OSM key value
* @return
*/
public String getKey() {
return (String) keyView.getTag();
}
public void setValue(String value, String description) {
this.value = value;
valueView.setText(description);
valueView.setTag(value);
if (getParent() instanceof EditableLayout) {
((EditableLayout)getParent()).putTag(getKey(), getValue());
}
}
public void setValue(StringWithDescription swd) {
String description = swd.getDescription();
setValue(swd.getValue(),description != null && !"".equals(description)?description:swd.getValue());
}
public void setValue(String s) {
setValue(s,s);
}
public String getValue() {
return value;
}
public void setChanged(boolean changed) {
this.changed = changed;
}
public boolean hasChanged() {
return changed;
}
public void setPreset(PresetItem preset) {
this.preset = preset;
}
public PresetItem getPreset() {
return preset;
}
public void click() {
valueView.performClick();
}
}
/**
* Row that displays multiselect values and allows changing them via a dialog
*/
public static class TagFormMultiselectDialogRow extends TagFormDialogRow {
OnClickListener listener;
LinearLayout valueList;
final LayoutInflater inflater;
public TagFormMultiselectDialogRow(Context context) {
super(context);
inflater = LayoutInflater.from(context);
}
public TagFormMultiselectDialogRow(Context context, AttributeSet attrs) {
super(context, attrs);
inflater = LayoutInflater.from(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
valueList = (LinearLayout)findViewById(R.id.valueList);
}
/**
* Set the onclicklistener for every value
*/
public void setOnClickListener(final OnClickListener listener) {
this.listener = listener;
for (int pos=0;pos<valueList.getChildCount();pos++) {
View v = valueList.getChildAt(pos);
if (v instanceof TextView) {
((TextView)v).setOnClickListener(listener);
}
}
}
/**
* Add additional description values as individual TextViews
* @param values
*/
public void setValue(ArrayList<StringWithDescription> values) {
String value = "";
char delimiter = preset.getDelimiter(getKey());
int childCount = valueList.getChildCount();
for (int pos = 0;pos < childCount ;pos++) { // don^t delete first child, just clear
if (pos == 0) {
setValue("","");
} else {
valueList.removeViewAt(1);
}
}
boolean first=true;
for (StringWithDescription swd:values) {
String d = swd.getDescription();
if (first) {
setValue(swd.getValue(),d != null && !"".equals(d)?d:swd.getValue());
first = false;
} else {
TextView extraValue = (TextView)inflater.inflate(R.layout.form_dialog_multiselect_value, valueList, false);
extraValue.setText(d != null && !"".equals(d)?d:swd.getValue());
extraValue.setTag(swd.getValue());
valueList.addView(extraValue);
}
// collect the individual values for what we actually store
if ("".equals(value)) {
value = swd.getValue();
} else {
value = value + delimiter + swd.getValue();
}
}
super.value = value;
setOnClickListener(listener);
}
}
/**
* Inline multiselect value display with checkboxes
*/
public static class TagMultiselectRow extends LinearLayout {
private TextView keyView;
private LinearLayout valueLayout;
private Context context;
private char delimiter;
public TagMultiselectRow(Context context) {
super(context);
this.context = context;
}
public TagMultiselectRow(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueLayout = (LinearLayout)findViewById(R.id.valueGroup);
}
/**
* Return the OSM key value
* @return
*/
public String getKey() {
return (String) keyView.getTag();
}
public LinearLayout getValueGroup() {
return valueLayout;
}
/**
* Return all checked values concatenated with the required delimiter
* @return
*/
public String getValue() {
StringBuilder result = new StringBuilder();
for (int i=0;i<valueLayout.getChildCount();i++) {
AppCompatCheckBox check = (AppCompatCheckBox) valueLayout.getChildAt(i);
if (check.isChecked()) {
if (result.length() > 0) { // not the first entry
result.append(delimiter);
}
result.append(valueLayout.getChildAt(i).getTag());
}
}
return result.toString();
}
public void setDelimiter(char delimiter) {
this.delimiter = delimiter;
}
public void addCheck(String description, String value, boolean selected, CompoundButton.OnCheckedChangeListener listener) {
final AppCompatCheckBox check = new AppCompatCheckBox(context);
check.setText(description);
check.setTag(value);
check.setChecked(selected);
valueLayout.addView(check);
check.setOnCheckedChangeListener(listener);
}
}
private void addCheck(Context context, LinearLayout layout, StringWithDescription swd, boolean selected, LayoutParams layoutParams) {
final AppCompatCheckBox check = new AppCompatCheckBox(context);
String description = swd.getDescription();
check.setText(description != null && !"".equals(description)?description:swd.getValue());
check.setTag(swd);
check.setLayoutParams(layoutParams);
check.setChecked(selected);
layout.addView(check);
}
public static class TagCheckRow extends LinearLayout {
private TextView keyView;
private AppCompatCheckBox valueCheck;
public TagCheckRow(Context context) {
super(context);
}
public TagCheckRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
keyView = (TextView)findViewById(R.id.textKey);
valueCheck = (AppCompatCheckBox)findViewById(R.id.valueSelected);
}
/**
* Return the OSM key value
* @return
*/
public String getKey() {
return (String) keyView.getTag();
}
public AppCompatCheckBox getCheckBox() {
return valueCheck;
}
public boolean isChecked() {
return valueCheck.isChecked();
}
}
public static class EditableLayout extends LinearLayout {
private ImageView headerIconView;
private TextView headerTitleView;
private LinearLayout rowLayout;
private ImageButton applyPresetButton;
private ImageButton copyButton;
private ImageButton cutButton;
private ImageButton deleteButton;
private PresetItem preset;
private LinkedHashMap<String,String> tags = new LinkedHashMap<String,String>();
public EditableLayout(Context context) {
super(context);
}
public EditableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) return; // allow visual editor to work
headerIconView = (ImageView)findViewById(R.id.form_header_icon_view);
headerTitleView = (TextView)findViewById(R.id.form_header_title);
rowLayout = (LinearLayout) findViewById(R.id.form_editable_row_layout);
applyPresetButton = (ImageButton) findViewById(R.id.tag_menu_apply_preset);
copyButton = (ImageButton) findViewById(R.id.form_header_copy);
cutButton = (ImageButton) findViewById(R.id.form_header_cut);
deleteButton = (ImageButton) findViewById(R.id.form_header_delete);
}
/**
* As side effect this sets the onClickListeners for the buttons
* @param listener
*/
public void setListeners(final EditorUpdate editorListener, final FormUpdate formListener) {
Log.d(DEBUG_TAG, "setting listeners");
applyPresetButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
editorListener.applyPreset(preset, true);
formListener.tagsUpdated();
}});
copyButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
editorListener.copyTags(tags);
}});
cutButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
editorListener.copyTags(tags);
for (String key:tags.keySet()) {
editorListener.deleteTag(key);
}
editorListener.updatePresets();
formListener.tagsUpdated();
}});
deleteButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Builder builder = new AlertDialog.Builder(v.getContext());
builder.setMessage(v.getContext().getString(R.string.delete_tags, headerTitleView.getText()));
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
for (String key:tags.keySet()) {
editorListener.deleteTag(key);
}
editorListener.updatePresets();
formListener.tagsUpdated();
}});
builder.show();
}});
}
private void setMyVisibility(int visibility) {
rowLayout.setVisibility(visibility);
for (int i=0;i < rowLayout.getChildCount();i++) {
rowLayout.getChildAt(i).setVisibility(visibility);
}
}
public void close() {
Log.d(DEBUG_TAG,"close");
setMyVisibility(View.GONE);
}
public void open() {
Log.d(DEBUG_TAG,"open");
setMyVisibility(View.VISIBLE);
}
public void setTitle(PresetItem preset) {
if (preset != null) {
Drawable icon = preset.getIcon();
this.preset = preset;
if (icon != null) {
headerIconView.setVisibility(View.VISIBLE);
//NOTE directly using the icon seems to trash it, so make a copy
headerIconView.setImageDrawable(icon.getConstantState().newDrawable());
} else {
headerIconView.setVisibility(View.GONE);
}
headerTitleView.setText(preset.getTranslatedName());
applyPresetButton.setVisibility(View.VISIBLE);
copyButton.setVisibility(View.VISIBLE);
cutButton.setVisibility(View.VISIBLE);
deleteButton.setVisibility(View.VISIBLE);
} else {
headerTitleView.setText(R.string.tag_form_unknown_element);
applyPresetButton.setVisibility(View.GONE);
copyButton.setVisibility(View.GONE);
cutButton.setVisibility(View.GONE);
deleteButton.setVisibility(View.GONE);
}
}
public void putTag(String key, String value) {
tags.put(key, value);
}
}
@Override
public void tagsUpdated() {
update();
}
/**
* Show a dialog to select a name
* @param ctx
* @return
*/
private AlertDialog buildNameDialog(Context ctx) {
Names names = App.getNames(ctx);
ArrayList<NameAndTags> suggestions = (ArrayList<NameAndTags>) names.getNames(new TreeMap<String,String>(new TreeMap<String, String>()));
ArrayAdapter<NameAndTags> adapter = null;
if (suggestions != null && !suggestions.isEmpty()) {
Collections.sort(suggestions);
adapter = new ArrayAdapter<NameAndTags>(ctx, R.layout.autocomplete_row, suggestions);
}
Builder builder = new AlertDialog.Builder(ctx);
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(ctx);
final CustomAutoCompleteTextView autoComplete = (CustomAutoCompleteTextView) inflater.inflate(R.layout.customautocomplete, null);
builder.setView(autoComplete);
autoComplete.setHint(R.string.tag_autocomplete_name_hint);
autoComplete.setAdapter(adapter);
builder.setNegativeButton(R.string.cancel, null);
final AlertDialog dialog = builder.create();
autoComplete.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(DEBUG_TAG,"onItemClicked value");
Object o = parent.getItemAtPosition(position);
if (o instanceof Names.NameAndTags) {
TagMap tags = ((NameAndTags)o).getTags();
tags.put(Tags.KEY_NAME, ((NameAndTags)o).getName());
tagListener.applyTagSuggestions(tags);
} else if (o instanceof String) {
tagListener.updateSingleValue(Tags.KEY_NAME, (String)o);
} else {
Log.e(DEBUG_TAG, "got a " + o.getClass().getName() + " instead of NameAndTags");
}
// allow a tiny bit of time to see that the action actually worked
(new Handler()).postDelayed(new Runnable(){@Override public void run() {dialog.dismiss();update();}}, 100);
}
});
autoComplete.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP || event.getAction() == KeyEvent.ACTION_MULTIPLE) {
if (v instanceof EditText) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
tagListener.updateSingleValue(Tags.KEY_NAME, ((EditText) v).getText().toString());
dialog.dismiss();
update();
return true;
}
}
}
return false;
}
});
return dialog;
}
}