package de.blau.android.propertyeditor; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.Spinner; import de.blau.android.App; import de.blau.android.HelpViewer; import de.blau.android.R; import de.blau.android.osm.Relation; import de.blau.android.osm.StorageDelegator; import de.blau.android.presets.Preset; import de.blau.android.presets.Preset.PresetItem; import de.blau.android.util.BaseFragment; import de.blau.android.util.StringWithDescription; public class RelationMembershipFragment extends BaseFragment implements PropertyRows, OnItemSelectedListener { private static final String DEBUG_TAG = RelationMembershipFragment.class.getSimpleName(); private LayoutInflater inflater = null; private HashMap<Long, String> savedParents = null; private EditorUpdate tagListener = null; private static SelectedRowsActionModeCallback parentSelectedActionModeCallback = null; private static final Object actionModeCallbackLock = new Object(); /** */ static public RelationMembershipFragment newInstance(HashMap<Long,String> parents) { RelationMembershipFragment f = new RelationMembershipFragment(); Bundle args = new Bundle(); args.putSerializable("parents", parents); f.setArguments(args); // f.setShowsDialog(true); return f; } @Override public void onAttachToContext(Context context) { Log.d(DEBUG_TAG, "onAttachToContext"); try { tagListener = (EditorUpdate) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement OnPresetSelectedListener"); } setHasOptionsMenu(true); getActivity().supportInvalidateOptionsMenu(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(DEBUG_TAG, "onCreate"); setHasOptionsMenu(true); getActivity().supportInvalidateOptionsMenu(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ScrollView parentRelationsLayout = null; LinearLayout membershipVerticalLayout = null; // Inflate the layout for this fragment this.inflater = inflater; parentRelationsLayout = (ScrollView) inflater.inflate(R.layout.membership_view, container, false); membershipVerticalLayout = (LinearLayout) parentRelationsLayout.findViewById(R.id.membership_vertical_layout); // membershipVerticalLayout.setSaveFromParentEnabled(false); membershipVerticalLayout.setSaveEnabled(false); HashMap<Long,String> parents; if (savedInstanceState != null) { Log.d(DEBUG_TAG,"Restoring from saved state"); parents = (HashMap<Long, String>) savedInstanceState.getSerializable("PARENTS"); } else if (savedParents != null ) { Log.d(DEBUG_TAG,"Restoring from instance variable"); parents = savedParents; } else { parents = (HashMap<Long,String>) getArguments().getSerializable("parents"); } loadParents(membershipVerticalLayout, parents); CheckBox headerCheckBox = (CheckBox) parentRelationsLayout.findViewById(R.id.header_membership_selected); headerCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { selectAllParents(); } else { deselectAllParents(); } } }); return parentRelationsLayout; } /** * Creates edits from a SortedMap containing tags (as sequential key-value pairs) */ private void loadParents(final Map<Long, String> parents) { LinearLayout membershipVerticalLayout = (LinearLayout) getOurView(); loadParents(membershipVerticalLayout, parents); } /** * Creates edits from a SortedMap containing tags (as sequential key-value pairs) */ private void loadParents(LinearLayout membershipVerticalLayout, final Map<Long, String> parents) { membershipVerticalLayout.removeAllViews(); if (parents != null && parents.size() > 0) { StorageDelegator storageDelegator = App.getDelegator(); for (Long id : parents.keySet()) { Relation r = (Relation) storageDelegator.getOsmElement(Relation.NAME, id.longValue()); insertNewMembership(membershipVerticalLayout, parents.get(id),r,0, false); } } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d(DEBUG_TAG, "onSaveInstanceState"); outState.putSerializable("PARENTS", savedParents); } @Override public void onPause() { super.onPause(); Log.d(DEBUG_TAG, "onPause"); savedParents = getParentRelationMap(); } @Override public void onStop() { super.onStop(); Log.d(DEBUG_TAG, "onStop"); } @Override public void onDestroy() { super.onDestroy(); Log.d(DEBUG_TAG, "onDestroy"); } /** * Insert a new row with a parent relation * * @param role role of this element in the relation * @param r the relation * @param position the position where this should be inserted. set to -1 to insert at end, or 0 to insert at beginning. * @param showSpinner TODO * @return the new RelationMembershipRow */ private RelationMembershipRow insertNewMembership(LinearLayout membershipVerticalLayout, final String role, final Relation r, final int position, boolean showSpinner) { RelationMembershipRow row = (RelationMembershipRow) inflater.inflate(R.layout.relation_membership_row, membershipVerticalLayout, false); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // stop Hint from wrapping row.roleEdit.setEllipsize(TruncateAt.END); } if (r != null) { row.setValues(role, r); } membershipVerticalLayout.addView(row, (position == -1) ? membershipVerticalLayout.getChildCount() : position); row.showSpinner = showSpinner; row.selected.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { parentSelected(); } else { deselectRow(); } } }); return row; } /** * A row representing a parent relation with an edit for role and further values and a delete button. */ public static class RelationMembershipRow extends LinearLayout implements SelectedRowsActionModeCallback.Row { private PropertyEditor owner; private long relationId =-1; // flag value for new relation memberships private CheckBox selected; private AutoCompleteTextView roleEdit; private Spinner parentEdit; public boolean showSpinner = false; public RelationMembershipRow(Context context) { super(context); owner = (PropertyEditor) (isInEditMode() ? null : context); // Can only be instantiated inside TagEditor or in Eclipse } public RelationMembershipRow(Context context, AttributeSet attrs) { super(context, attrs); owner = (PropertyEditor) (isInEditMode() ? null : context); // Can only be instantiated inside TagEditor or in Eclipse } // public RelationMembershipRow(Context context, AttributeSet attrs, int defStyle) { // super(context, attrs, defStyle); // owner = (TagEditor) (isInEditMode() ? null : context); // Can only be instantiated inside TagEditor or in Eclipse // } @Override protected void onFinishInflate() { super.onFinishInflate(); if (isInEditMode()) return; // allow visual editor to work selected = (CheckBox) findViewById(R.id.parent_selected); roleEdit = (AutoCompleteTextView)findViewById(R.id.editRole); roleEdit.setOnKeyListener(owner.myKeyListener); parentEdit = (Spinner)findViewById(R.id.editParent); ArrayAdapter<Relation> a = getRelationSpinnerAdapter(); // a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); parentEdit.setAdapter(a); parentEdit.setOnItemSelectedListener(owner.relationMembershipFragment); roleEdit.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { roleEdit.setAdapter(getMembershipRoleAutocompleteAdapter()); if (/*running && */roleEdit.getText().length() == 0) roleEdit.showDropDown(); } } }); OnClickListener autocompleteOnClick = new OnClickListener() { @Override public void onClick(View v) { if (v.hasFocus()) { ((AutoCompleteTextView)v).showDropDown(); } } }; roleEdit.setOnClickListener(autocompleteOnClick); roleEdit.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 StringWithDescription) { roleEdit.setText(((StringWithDescription)o).getValue()); } else if (o instanceof String) { roleEdit.setText((String)o); } } }); } ArrayAdapter<StringWithDescription> getMembershipRoleAutocompleteAdapter() { // Use a set to prevent duplicate keys appearing Set<StringWithDescription> roles = new HashSet<StringWithDescription>(); Relation r = (Relation) App.getDelegator().getOsmElement(Relation.NAME, relationId); if ( r!= null) { if ( owner.presets != null) { PresetItem relationPreset = Preset.findBestMatch(owner.presets,r.getTags()); if (relationPreset != null) { List<StringWithDescription> presetRoles = relationPreset.getRoles(); if (presetRoles != null) { roles.addAll(presetRoles); } } } } List<StringWithDescription> result = new ArrayList<StringWithDescription>(roles); Collections.sort(result); return new ArrayAdapter<StringWithDescription>(owner, R.layout.autocomplete_row, result); } ArrayAdapter<Relation> getRelationSpinnerAdapter() { // List<Relation> result = App.getDelegator().getCurrentStorage().getRelations(); // Collections.sort(result); return new ArrayAdapter<Relation>(owner, R.layout.autocomplete_row, result); } /** * Sets key and value values * @param aTagKey the key value to set * @param aTagValue the value value to set * @return the TagEditRow object for convenience */ public RelationMembershipRow setValues(String role, Relation r) { relationId = r.getOsmId(); roleEdit.setText(role); parentEdit.setSelection(App.getDelegator().getCurrentStorage().getRelations().indexOf(r)); return this; } /** * Sets key and value values * @param aTagKey the key value to set * @param aTagValue the value value to set * @return the TagEditRow object for convenience */ public RelationMembershipRow setRelation(Relation r) { relationId = r.getOsmId(); parentEdit.setSelection(App.getDelegator().getCurrentStorage().getRelations().indexOf(r)); Log.d(DEBUG_TAG, "Set parent relation to " + relationId + " " + r.getDescription()); roleEdit.setAdapter(getMembershipRoleAutocompleteAdapter()); // update return this; } public long getOsmId() { return relationId; } public String getRole() { return roleEdit.getText().toString(); } /** * Deletes this row */ @Override public void delete() { if (owner != null) { View cf = owner.getCurrentFocus(); if (cf == roleEdit) { // owner.focusRow(0); // FIXME focus is on this fragement } LinearLayout membershipVerticalLayout = (LinearLayout) owner.relationMembershipFragment.getOurView(); membershipVerticalLayout.removeView(this); membershipVerticalLayout.invalidate(); } else { Log.d("PropertyEditor", "deleteRow owner null"); } } /** * awlful hack to show spinner after insert */ @Override public void onWindowFocusChanged (boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (showSpinner) { parentEdit.performClick(); showSpinner = false; } } // return the status of the checkbox @Override public boolean isSelected() { return selected.isChecked(); } @Override public void deselect() { selected.setChecked(false); } public void disableCheckBox() { selected.setEnabled(false); } protected void enableCheckBox() { selected.setEnabled(true); } } // RelationMembershipRow private void parentSelected() { synchronized (actionModeCallbackLock) { LinearLayout rowLayout = (LinearLayout) getOurView(); if (parentSelectedActionModeCallback == null) { parentSelectedActionModeCallback = new SelectedRowsActionModeCallback(this, rowLayout); ((AppCompatActivity)getActivity()).startSupportActionMode(parentSelectedActionModeCallback); } } } @Override public void deselectRow() { synchronized (actionModeCallbackLock) { if (parentSelectedActionModeCallback != null) { if (parentSelectedActionModeCallback.rowsDeselected(true)) { parentSelectedActionModeCallback = null; } } } } private void selectAllParents() { LinearLayout rowLayout = (LinearLayout) getOurView(); int i = rowLayout.getChildCount(); while (--i >= 0) { RelationMembershipRow row = (RelationMembershipRow)rowLayout.getChildAt(i); if (row.selected.isEnabled()) { row.selected.setChecked(true); } } } private void deselectAllParents() { LinearLayout rowLayout = (LinearLayout) getOurView(); int i = rowLayout.getChildCount(); while (--i >= 0) { RelationMembershipRow row = (RelationMembershipRow)rowLayout.getChildAt(i); if (row.selected.isEnabled()) { row.selected.setChecked(false); } } } /** */ private interface ParentRelationHandler { void handleParentRelation(final EditText roleEdit, final long relationId); } /** * Perform some processing for each row in the parent relation view. * @param handler The handler that will be called for each row. */ private void processParentRelations(final ParentRelationHandler handler) { LinearLayout membershipVerticalLayout = (LinearLayout) getOurView(); if (membershipVerticalLayout == null) { Log.e(DEBUG_TAG,"unable to process parent relations"); return; } final int size = membershipVerticalLayout.getChildCount(); for (int i = 0; i < size; ++i) { View view = membershipVerticalLayout.getChildAt(i); RelationMembershipRow row = (RelationMembershipRow)view; handler.handleParentRelation(row.roleEdit, row.relationId); } } /** * Collect all interesting values from the parent relation view HashMap<String,String>, currently only the role value * * @return The HashMap<Long,String> of relation and role in that relation pairs. */ HashMap<Long,String> getParentRelationMap() { final HashMap<Long,String> parents = new HashMap<Long,String>(); processParentRelations(new ParentRelationHandler() { @Override public void handleParentRelation(final EditText roleEdit, final long relationId) { String role = roleEdit.getText().toString().trim(); parents.put(Long.valueOf(relationId), role); } }); return parents; } @Override public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { // An item was selected. You can retrieve the selected item using // parent.getItemAtPosition(pos) Log.d(DEBUG_TAG, ((Relation)parent.getItemAtPosition(pos)).getDescription()); if (view != null) { ViewParent pv = view.getParent(); while (!(pv instanceof RelationMembershipRow)) { pv = pv.getParent(); } ((RelationMembershipRow)pv).setRelation((Relation)parent.getItemAtPosition(pos)); } else { Log.d(DEBUG_TAG, "onItemselected view is null"); } } @Override public void onNothingSelected(AdapterView<?> parent) { // Another interface callback } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { // final MenuInflater inflater = getSupportMenuInflater(); super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.membership_menu, menu); } @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: ((PropertyEditor)getActivity()).sendResultAndFinish(); return true; case R.id.tag_menu_revert: doRevert(); return true; case R.id.tag_menu_addtorelation: addToRelation(); return true; case R.id.tag_menu_help: HelpViewer.start(getActivity(), R.string.help_propertyeditor); return true; } return false; } /** * reload original arguments */ void doRevert() { loadParents((HashMap<Long,String>) getArguments().getSerializable("parents")); } /** * Add this object to an existing relation */ private void addToRelation() { insertNewMembership((LinearLayout) getOurView(), null,null,-1, true).roleEdit.requestFocus(); } @Override public void deselectHeaderCheckBox() { CheckBox headerCheckBox = (CheckBox) getView().findViewById(R.id.header_membership_selected); headerCheckBox.setChecked(false); } /** * Return the view we have our rows in and work around some android craziness * @return */ private View getOurView() { // android.support.v4.app.NoSaveStateFrameLayout View v = getView(); if (v != null) { if ( v.getId() == R.id.membership_vertical_layout) { Log.d(DEBUG_TAG,"got correct view in getView"); return v; } else { v = v.findViewById(R.id.membership_vertical_layout); if (v == null) { Log.d(DEBUG_TAG,"didn't find R.id.membership_vertical_layout"); } else { Log.d(DEBUG_TAG,"Found R.id.membership_vertical_layoutt"); } return v; } } else { Log.d(DEBUG_TAG,"got null view in getView"); } return null; } }