package de.blau.android.easyedit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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 org.acra.ACRA;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Resources.NotFoundException;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.speech.RecognizerIntent;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.app.AppCompatDialog;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.ActionMenuView;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewStub;
import android.widget.EditText;
import android.widget.TextView;
import de.blau.android.App;
import de.blau.android.HelpViewer;
import de.blau.android.Logic;
import de.blau.android.Main;
import de.blau.android.Main.UndoListener;
import de.blau.android.R;
import de.blau.android.dialogs.ElementInfo;
import de.blau.android.exception.OsmIllegalOperationException;
import de.blau.android.names.Names.NameAndTags;
import de.blau.android.osm.BoundingBox;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.OsmElement.ElementType;
import de.blau.android.osm.Relation;
import de.blau.android.osm.RelationMember;
import de.blau.android.osm.StorageDelegator;
import de.blau.android.osm.Tags;
import de.blau.android.osm.Way;
import de.blau.android.prefs.PrefEditor;
import de.blau.android.prefs.Preferences;
import de.blau.android.presets.Preset;
import de.blau.android.presets.Preset.PresetItem;
import de.blau.android.propertyeditor.Address;
import de.blau.android.tasks.TaskFragment;
import de.blau.android.util.ElementSearch;
import de.blau.android.util.GeoMath;
import de.blau.android.util.MenuUtil;
import de.blau.android.util.NetworkStatus;
import de.blau.android.util.SearchIndexUtils;
import de.blau.android.util.Snack;
import de.blau.android.util.StringWithDescription;
import de.blau.android.util.ThemeUtils;
import de.blau.android.util.Util;
/**
* This class handles most of the EasyEdit mode actions, to keep it separate from the main class.
* @author Jan
*
*/
public class EasyEditManager {
private static final String DEBUG_TAG = EasyEditManager.class.getSimpleName();
private final Main main;
private final Logic logic;
/** the touch listener from Main */
private ActionMode currentActionMode = null;
private EasyEditActionModeCallback currentActionModeCallback = null;
private final Object actionModeCallbackLock = new Object();
private ActionMenuView cabBottomBar;
public final static int GROUP_MODE = 0;
public final static int GROUP_BASE = 1;
private static final int MENUITEM_HELP = 0;
public EasyEditManager(Main main) {
this.main = main;
this.logic = App.getLogic();
}
/**
* Returns true if a actionmode is currently active
* @return
*/
public boolean isProcessingAction() {
synchronized (actionModeCallbackLock) {
return (currentActionModeCallback != null);
}
}
public boolean inElementSelectedMode() {
synchronized (actionModeCallbackLock) {
return (currentActionModeCallback != null) && (currentActionModeCallback instanceof ElementSelectionActionModeCallback || currentActionModeCallback instanceof ExtendSelectionActionModeCallback);
}
}
/**
* Check if the actionmode ants its own context menu
* @return
*/
public boolean needsCustomContextMenu() {
return isProcessingAction() && currentActionModeCallback.needsCustomContextMenu();
}
/**
* call if you need to abort the current action mode
*/
public void finish() {
if (currentActionMode != null) {
currentActionMode.finish();
}
}
/**
* Call to let the action mode (if any) have a first go at the click.
* @param x the x coordinate (screen coordinate?) of the click
* @param y the y coordinate (screen coordinate?) of the click
* @return true if the click was handled
*/
public boolean actionModeHandledClick(float x, float y) {
return (currentActionModeCallback != null && currentActionModeCallback.handleClick(x, y));
}
/**
* Handle case where nothing is touched.
* @param doubleTap TODO
*/
public void nothingTouched(boolean doubleTap) {
// User clicked an empty area. If something is selected, deselect it.
if (!doubleTap && currentActionModeCallback instanceof ExtendSelectionActionModeCallback) {
return; // don't deselect all just because we didn't hit anything TODO display a toast
}
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback instanceof ElementSelectionActionModeCallback
|| currentActionModeCallback instanceof ExtendSelectionActionModeCallback
|| currentActionModeCallback instanceof WayRotationActionModeCallback
|| currentActionModeCallback instanceof WayAppendingActionModeCallback) {
currentActionMode.finish();
}
}
logic.setSelectedNode(null);
logic.setSelectedWay(null);
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
}
/**
* Handle editing the given element.
* @param element The OSM element to edit.
*/
public void editElement(OsmElement element) {
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback == null || !currentActionModeCallback.handleElementClick(element)) {
// No callback or didn't handle the click, perform default (select element)
ActionMode.Callback cb = null;
if (element instanceof Node) cb = new NodeSelectionActionModeCallback((Node)element);
if (element instanceof Way ) cb = new WaySelectionActionModeCallback((Way )element);
if (element instanceof Relation ) cb = new RelationSelectionActionModeCallback((Relation )element);
if (cb != null) {
main.startSupportActionMode(cb);
String toast = element.getDescription(main);
if (element.hasProblem(main)) {
String problem = element.describeProblem();
toast = !problem.equals("") ? toast + "\n" + problem : toast;
}
Snack.toastTopInfo(main, toast);
}
}
}
}
/**
* Edit currently selected elements.
*/
public void editElements() {
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback == null) {
// No callback or didn't handle the click, perform default (select element)
ActionMode.Callback cb = null;
OsmElement e = null;
if (logic.getSelectedNodes() != null && logic.getSelectedNodes().size() == 1 && logic.getSelectedWays() == null && logic.getSelectedRelations() == null) {
e = logic.getSelectedNode();
cb = new NodeSelectionActionModeCallback((Node) e);
} else if (logic.getSelectedNodes() == null && logic.getSelectedWays() != null && logic.getSelectedWays().size() == 1 && logic.getSelectedRelations() == null) {
e = logic.getSelectedWay();
cb = new WaySelectionActionModeCallback((Way) e);
} else if (logic.getSelectedNodes() == null && logic.getSelectedWays() == null && logic.getSelectedRelations() != null && logic.getSelectedRelations().size() == 1) {
e = logic.getSelectedRelations().get(0);
cb = new RelationSelectionActionModeCallback((Relation )e);
} else if (logic.getSelectedNodes() != null || logic.getSelectedWays() != null || logic.getSelectedRelations() != null) {
ArrayList<OsmElement> selection = new ArrayList<OsmElement>();
if (logic.getSelectedNodes() != null) {
selection.addAll(logic.getSelectedNodes());
}
if (logic.getSelectedWays() != null) {
selection.addAll(logic.getSelectedWays());
}
if (logic.getSelectedRelations() != null) {
selection.addAll(logic.getSelectedRelations());
}
cb = new ExtendSelectionActionModeCallback(selection);
}
if (cb != null) {
main.startSupportActionMode(cb);
if (e != null) {
String toast = e.getDescription(main);
if (e.hasProblem(main)) {
String problem = e.describeProblem();
toast = !problem.equals("") ? toast + "\n" + problem : toast;
}
Snack.toastTopInfo(main, toast);
}
}
}
}
}
/** This gets called when the map is long-pressed in easy-edit mode */
public boolean handleLongClick(View v, float x, float y) {
synchronized (actionModeCallbackLock) {
if ((currentActionModeCallback instanceof PathCreationActionModeCallback))
{
// we don't do long clicks in the above modes
Log.d("EasyEditManager", "handleLongClick ignoring long click");
return false; // this probably should really return true aka click handled
}
}
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (main.startSupportActionMode(new LongClickActionModeCallback(x, y)) == null) {
main.startSupportActionMode(new PathCreationActionModeCallback(x, y));
}
return true;
}
public void startExtendedSelection(OsmElement osmElement) {
synchronized (actionModeCallbackLock) {
if ((currentActionModeCallback instanceof WaySelectionActionModeCallback)
|| (currentActionModeCallback instanceof NodeSelectionActionModeCallback)
|| (currentActionModeCallback instanceof RelationSelectionActionModeCallback))
{
// one element already selected
((ElementSelectionActionModeCallback)currentActionModeCallback).deselect = false; // keep the element visually selected
main.startSupportActionMode(new ExtendSelectionActionModeCallback(((ElementSelectionActionModeCallback)currentActionModeCallback).element));
// add 2nd element FIXME may need some checks
((ExtendSelectionActionModeCallback)currentActionModeCallback).handleElementClick(osmElement);
} else if (currentActionModeCallback instanceof ExtendSelectionActionModeCallback) {
// ignore for now
} else if (currentActionModeCallback != null) {
// ignore for now
} else {
// nothing selected
main.startSupportActionMode(new ExtendSelectionActionModeCallback(osmElement));
}
}
}
public void invalidate() {
synchronized (actionModeCallbackLock) {
if (currentActionMode != null) {
currentActionMode.invalidate();
}
}
}
/**
* call the onBackPressed method for the currently active action mode
* @return true if the press was consumed
*/
public boolean handleBackPressed() {
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback != null) {
Log.d(DEBUG_TAG, "handleBackPressed for " + currentActionModeCallback.getClass().getSimpleName());
return currentActionModeCallback.onBackPressed();
}
return false;
}
}
/**
* Takes a parameter for a node and one for a way.
* If the way is not null, opens a tag editor for the way.
* Otherwise, opens a tag editor for the node
* (unless the node is also null, then nothing happens).
* @param possibleNode a node that was edited, or null
* @param possibleWay a way that was edited, or null
* @param select TODO
* @param askForName TODO
*/
private void tagApplicable(final Node possibleNode, final Way possibleWay, final boolean select, final boolean askForName) {
if (possibleWay == null) {
// Single node was added
if (possibleNode != null) { // null-check to be sure
if (select) {
main.startSupportActionMode(new NodeSelectionActionModeCallback(possibleNode));
}
main.performTagEdit(possibleNode, null, false, false, askForName);
}
} else { // way was added
if (select) {
main.startSupportActionMode(new WaySelectionActionModeCallback(possibleWay));
}
main.performTagEdit(possibleWay, null, false, false, askForName);
}
}
/**
* Finds which ways can be merged with a way.
* For this, the ways must not be equal, need to share at least one end node,
* and either at least one of them must not have tags, or the tags on both ways must be equal.
*
* @param way the way into which other ways may be merged
* @return a list of all ways which can be merged into the given way
*/
private Set<OsmElement> findMergeableWays(Way way) {
Set<Way> candidates = new HashSet<Way>();
Set<OsmElement> result = new HashSet<OsmElement>();
candidates.addAll(logic.getWaysForNode(way.getFirstNode()));
candidates.addAll(logic.getWaysForNode(way.getLastNode()));
for (Way candidate : candidates) {
if ((way != candidate)
&& (candidate.isEndNode(way.getFirstNode()) || candidate.isEndNode(way.getLastNode()))
&& (candidate.getTags().isEmpty() || way.getTags().isEmpty() ||
way.getTags().entrySet().equals(candidate.getTags().entrySet()) )
//TODO check for relations too
) {
result.add(candidate);
}
}
return result;
}
/**
* Finds which nodes can be append targets.
* @param way The way that will be appended to.
* @return The set of nodes suitable for appending.
*/
private Set<OsmElement> findAppendableNodes(Way way) {
Set<OsmElement> result = new HashSet<OsmElement>();
for (Node node : way.getNodes()) {
if (way.isEndNode(node)) result.add(node);
}
// don't allow appending to circular ways
if (result.size() == 1) result.clear();
return result;
}
/**
* Finds which ways or nodes can be used as a via element in a restriction relation
*
* @param way the from way
* @return a list of all applicable objects
*/
private Set<OsmElement> findViaElements(Way way) {
Set<OsmElement> result = new HashSet<OsmElement>();
for (Node n:way.getNodes()) {
for (Way w:logic.getWaysForNode(n)) {
if (w.getTagWithKey(Tags.KEY_HIGHWAY) != null) {
result.add(w);
result.add(n); // result is a set so we wont get dups
}
}
}
return result;
}
/**
* Find possible elements for the "to" role of a restriction relation
* @param viaElement the current via OSM element
* @return a set of the candidate to OSM elements
*/
private Set<OsmElement> findToElements(OsmElement viaElement) {
Set<OsmElement> result = new HashSet<OsmElement>();
Set<Node> nodes = new HashSet<Node>();
if (Node.NAME.equals(viaElement.getName())) {
nodes.add((Node) viaElement);
} else if (Way.NAME.equals(viaElement.getName())) {
nodes.addAll(((Way)viaElement).getNodes());
} else {
Log.e(DEBUG_TAG, "Unknown element type for via element " + viaElement.getName() + " " + viaElement.getDescription());
}
for (Node n:nodes) {
for (Way w:logic.getWaysForNode(n)) {
if (w.getTagWithKey(Tags.KEY_HIGHWAY) != null) {
result.add(w);
}
}
}
return result;
}
public void handleActivityResult(final int requestCode, final int resultCode, final Intent data) {
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback instanceof LongClickActionModeCallback) {
((LongClickActionModeCallback)currentActionModeCallback).handleActivityResult(requestCode, resultCode, data);
}
}
}
public boolean processShortcut(Character c) {
synchronized (actionModeCallbackLock) {
return currentActionModeCallback != null && currentActionModeCallback.processShortcut(c);
}
}
/**
* Replace the menu used by the action mode by our toolbar if necessary
* @param menu original menu
* @param actionMode the current action mode
* @param callback the callback we are currently in
* @return
*/
private Menu replaceMenu(Menu menu, final ActionMode actionMode, final ActionMode.Callback callback) {
if (cabBottomBar!=null) {
menu = cabBottomBar.getMenu();
android.support.v7.widget.ActionMenuView.OnMenuItemClickListener listener = new android.support.v7.widget.ActionMenuView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return callback.onActionItemClicked(actionMode,item);
}
};
cabBottomBar.setOnMenuItemClickListener(listener);
}
return menu;
}
/**
* Call the per actionmode onCreateContextMenu
* @param menu
*/
public void createContextMenu(ContextMenu menu) {
synchronized (actionModeCallbackLock) {
if (currentActionModeCallback != null) {
currentActionModeCallback.onCreateContextMenu(menu);
}
}
}
/**
* Base class for ActionMode callbacks inside {@link EasyEditManager}.
* Derived classes should call {@link #onCreateActionMode(ActionMode, Menu)} and {@link #onDestroyActionMode(ActionMode)}.
* It will handle registering and de-registering the action mode callback with the {@link EasyEditManager}.
* When the {@link EasyEditManager} receives a click on a node or way, it may pass it to the current action mode callback.
* The callback can then swallow it by returning true or allow the default handling to happen by returning false
* in the {@link #handleElementClick(OsmElement)} method.
*
* @author Jan
*
*/
public abstract class EasyEditActionModeCallback implements ActionMode.Callback {
private static final String DEBUG_TAG = "EasyEditActionModeCa...";
int helpTopic = 0;
MenuUtil menuUtil = new MenuUtil(main);
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Log.d(DEBUG_TAG, "onCreateActionMode");
synchronized (actionModeCallbackLock) {
currentActionMode = mode;
currentActionModeCallback = this;
}
main.hideLock();
if (main.getBottomBar() != null) {
View v = main.findViewById(R.id.cab_stub);
if (v instanceof ViewStub) { // only need to inflate once
ViewStub stub = (ViewStub) v;
stub.setLayoutResource(R.layout.toolbar);
stub.setInflatedId(R.id.cab_stub);
cabBottomBar = (ActionMenuView) stub.inflate();
Preferences prefs = new Preferences(main);
MenuUtil.setupBottomBar(main, cabBottomBar, main.isFullScreen(), prefs.lightThemeEnabled());
} else if (v instanceof ActionMenuView) {
cabBottomBar = (ActionMenuView) v;
cabBottomBar.setVisibility(View.VISIBLE);
cabBottomBar.getMenu().clear();
}
main.hideBottomBar();
}
return false;
}
/**
* Override this is you want to create a custom context menu in onCreateContextMenu
* @return
*/
public boolean needsCustomContextMenu() {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
Log.d(DEBUG_TAG, "onDestroyActionMode");
currentActionMode = null;
currentActionModeCallback = null;
logic.hideCrosshairs();
main.invalidateMap();
main.triggerMenuInvalidation();
if (cabBottomBar != null) {
cabBottomBar.setVisibility(View.GONE);
main.showBottomBar();
}
main.showLock();
}
/**
* This method gets called when the map is clicked, before checking for clicked nodes/ways.
* The ActionModeCallback can then either return true to indicate that the click was handled (or should be ignored),
* or return false to indicate default handling should apply
* (which includes checking for node/way clicks and calling the corresponding methods).
*
* @param x the x screen coordinate of the click
* @param y the y screen coordinate of the click
* @return true if the click has been handled, false if default handling should apply
*/
public boolean handleClick(float x, float y) {
return false;
}
/**
* This method gets called when an OsmElement click has to be handled.
* The ActionModeCallback can then either return true to indicate that the click was handled (or should be ignored),
* or return false to indicate default handling should apply.
* @param element the OsmElement that was clicked
* @return true if the click has been handled, false if default handling should apply
*/
public boolean handleElementClick(OsmElement element) {
return false;
}
/** {@inheritDoc} */ // placed here for convenience, allows to avoid unnecessary methods in subclasses
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menuUtil.reset();
return false;
}
/** {@inheritDoc} */ // placed here for convenience, allows to avoid unnecessary methods in subclasses
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Log.d(DEBUG_TAG, "onActionItemClicked");
if (item.getItemId() == MENUITEM_HELP) {
if (helpTopic != 0) {
HelpViewer.start(main, helpTopic);
} else {
Snack.barWarning(main, R.string.toast_nohelp); // this is essentially just an error message
}
}
return false;
}
/**
* modify behavior of back button in action mode
* @return
*/
public boolean onBackPressed() {
finish();
return true;
}
public boolean processShortcut(Character c) {
return false;
}
void arrangeMenu(Menu menu) {
menuUtil.setShowAlways(menu);
}
public void onCreateContextMenu(ContextMenu menu) {
}
}
private class LongClickActionModeCallback extends EasyEditActionModeCallback implements android.view.MenuItem.OnMenuItemClickListener {
private static final String DEBUG2_TAG = "LongClickActionMode...";
private static final int MENUITEM_OSB = 1;
private static final int MENUITEM_NEWNODEWAY = 2;
private static final int MENUITEM_SPLITWAY = 3;
private static final int MENUITEM_PASTE = 4;
private static final int MENUITEM_NEWNODE_GPS = 5;
private static final int MENUITEM_NEWNODE_ADDRESS = 6;
private static final int MENUITEM_NEWNODE_PRESET = 7;
private static final int MENUITEM_NEWNODE_NAME = 8;
private static final int MENUITEM_NEWNODE_VOICE = 9;
private float startX;
private float startY;
private int startLon;
private int startLat;
private float x;
private float y;
LocationManager locationManager = null;
private List<OsmElement> clickedNodes;
private List<Way>clickedNonClosedWays;
public LongClickActionModeCallback(float x, float y) {
super();
this.x = x;
this.y = y;
clickedNodes = logic.getClickedNodes(x, y);
clickedNonClosedWays = logic.getClickedWays(false, x, y); //
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_longclick;
super.onCreateActionMode(mode, menu);
mode.setTitle(R.string.menu_add);
mode.setSubtitle(null);
// mode.setTitleOptionalHint(true);
// show crosshairs
logic.showCrosshairs(x, y);
startX = x;
startY = y;
startLon = logic.xToLonE7(x);
startLat = logic.yToLatE7(y);
// return isNeeded();
// always required for paste
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
menu.clear();
menuUtil.reset();
Preferences prefs = new Preferences(main);
if (prefs.voiceCommandsEnabled()) {
menu.add(Menu.NONE, MENUITEM_NEWNODE_VOICE, Menu.NONE, R.string.menu_voice_commands).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.mic)).setEnabled(NetworkStatus.isConnected(main));
}
menu.add(Menu.NONE, MENUITEM_NEWNODE_ADDRESS, Menu.NONE, R.string.tag_menu_address).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_address));
menu.add(Menu.NONE, MENUITEM_NEWNODE_PRESET, Menu.NONE, R.string.tag_menu_preset).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_preset));
menu.add(Menu.NONE, MENUITEM_OSB, Menu.NONE, R.string.openstreetbug_new_bug).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_bug));
if ((clickedNonClosedWays != null && clickedNonClosedWays.size() > 0) && (clickedNodes == null || clickedNodes.size()==0) ) {
menu.add(Menu.NONE, MENUITEM_SPLITWAY, Menu.NONE, R.string.menu_split).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_split));
}
if (prefs.tagFormEnabled()) {
menu.add(Menu.NONE, MENUITEM_NEWNODE_NAME, Menu.NONE, R.string.menu_set_name).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.name));
}
menu.add(Menu.NONE, MENUITEM_NEWNODEWAY, Menu.NONE, R.string.openstreetbug_new_nodeway).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_append));
if (!logic.clipboardIsEmpty()) {
menu.add(Menu.NONE, MENUITEM_PASTE, Menu.NONE, R.string.menu_paste).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_paste)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_paste));
}
// check if GPS is enabled
locationManager = (LocationManager)main.getSystemService(android.content.Context.LOCATION_SERVICE);
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
menu.add(Menu.NONE, MENUITEM_NEWNODE_GPS, Menu.NONE, R.string.menu_newnode_gps).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_gps));
}
menu.add(GROUP_BASE, MENUITEM_HELP, Menu.CATEGORY_SYSTEM|10, R.string.menu_help).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_help)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_help));
arrangeMenu(menu);
return true;
}
/**
* if we get a short click go to path creation mode
*/
@Override
public boolean handleClick(float x, float y) {
PathCreationActionModeCallback pcamc = new PathCreationActionModeCallback(logic.lonE7ToX(startLon), logic.latE7ToY(startLat));
main.startSupportActionMode(pcamc);
pcamc.handleClick(x, y);
logic.hideCrosshairs();
return true;
}
@Override
public boolean needsCustomContextMenu() {
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu) {
if (clickedNonClosedWays != null && clickedNonClosedWays.size() > 0) {
int id = 0;
menu.add(Menu.NONE, id++, Menu.NONE, R.string.split_all_ways).setOnMenuItemClickListener(this);
for (Way w:clickedNonClosedWays) {
menu.add(Menu.NONE, id++, Menu.NONE, w.getDescription(main)).setOnMenuItemClickListener(this);
}
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
List<Way>ways = new ArrayList<Way>();
if (itemId==0) {
ways = clickedNonClosedWays;
} else {
ways.add(clickedNonClosedWays.get(itemId -1));
}
try {
Node splitPosition = logic.performAddOnWay(main, ways,startX, startY, false);
if (splitPosition != null) {
for (Way way:ways) {
if (way.hasNode(splitPosition)) {
logic.performSplit(main, way,logic.getSelectedNode());
}
}
}
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
Log.d(DEBUG2_TAG,"Caught exception " + e);
}
currentActionMode.finish();
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
super.onActionItemClicked(mode, item);
switch (item.getItemId()) {
case MENUITEM_OSB:
//
mode.finish();
logic.setSelectedBug(logic.makeNewBug(x, y));
FragmentManager fm = main.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag("fragment_bug");
if (prev != null) {
ft.remove(prev);
}
ft.commit();
TaskFragment bugDialog = TaskFragment.newInstance(logic.getSelectedBug());
bugDialog.show(fm, "fragment_bug");
logic.hideCrosshairs();
return true;
case MENUITEM_NEWNODEWAY:
main.startSupportActionMode(new PathCreationActionModeCallback(x, y));
logic.hideCrosshairs();
return true;
case MENUITEM_SPLITWAY:
if (clickedNonClosedWays.size() > 1) {
main.getMap().showContextMenu();
} else {
Way way = clickedNonClosedWays.get(0);
ArrayList<Way>ways = new ArrayList<Way>();
ways.add(way);
try {
Node node = logic.performAddOnWay(main, ways,startX, startY, false);
if (node != null) {
logic.performSplit(main, way,node);
}
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
Log.d(DEBUG2_TAG,"Caught exception " + e);
}
currentActionMode.finish();
}
return true;
case MENUITEM_NEWNODE_ADDRESS:
case MENUITEM_NEWNODE_PRESET:
case MENUITEM_NEWNODE_NAME:
logic.hideCrosshairs();
try {
logic.setSelectedNode(null);
logic.performAdd(main, x, y);
} catch (OsmIllegalOperationException e1) {
Snack.barError(main, e1.getLocalizedMessage());
Log.d(DEBUG2_TAG,"Caught exception " + e1);
}
Node lastSelectedNode = logic.getSelectedNode();
if (lastSelectedNode != null) {
main.startSupportActionMode(new NodeSelectionActionModeCallback(lastSelectedNode));
main.performTagEdit(lastSelectedNode, null,
item.getItemId() == MENUITEM_NEWNODE_ADDRESS, item.getItemId() == MENUITEM_NEWNODE_PRESET, item.getItemId() == MENUITEM_NEWNODE_NAME); // show preset screen or add addresses
}
return true;
case MENUITEM_PASTE:
logic.pasteFromClipboard(main, startX, startY);
logic.hideCrosshairs();
mode.finish();
return true;
case MENUITEM_NEWNODE_GPS:
logic.hideCrosshairs();
try {
logic.setSelectedNode(null);
logic.performAdd(main, x, y);
Node node = logic.getSelectedNode();
if (locationManager != null && node != null) {
Location location = null;
try {
location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
} catch (SecurityException sex) {
// can be safely ignored, this is only called when GPS is enabled
}
if (location != null) {
double lon = location.getLongitude();
double lat = location.getLatitude();
if (lon >= -180 && lon <= 180 && lat >= -GeoMath.MAX_LAT && lat <= GeoMath.MAX_LAT) {
logic.performSetPosition(main, node,lon,lat);
TreeMap<String, String> tags = new TreeMap<String, String>(node.getTags());
if (location.hasAltitude()) {
tags.put(Tags.KEY_ELE, String.format(Locale.US,"%.1f",location.getAltitude()));
tags.put(Tags.KEY_ELE_MSL, String.format(Locale.US,"%.1f",location.getAltitude()));
tags.put(Tags.KEY_SOURCE_ELE, Tags.VALUE_GPS);
}
tags.put(Tags.KEY_SOURCE, Tags.VALUE_GPS);
logic.setTags(main, node, tags);
}
}
}
currentActionMode.finish();
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
Log.d(DEBUG2_TAG,"Caught exception " + e);
}
return true;
case MENUITEM_NEWNODE_VOICE:
logic.hideCrosshairs();
logic.setSelectedNode(null);
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
try {
main.startActivityForResult(intent, Main.VOICE_RECOGNITION_REQUEST_CODE);
} catch (Exception ex) {
Log.d(DEBUG2_TAG,"Caught exception " + ex);
Snack.barError(main, R.string.toast_no_voice_recognition);
logic.showCrosshairs(startX, startY);
}
return true;
default:
Log.e(DEBUG2_TAG, "Unknown menu item");
break;
}
return false;
}
/**
* Path creation action mode is ending
*/
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setSelectedNode(null);
super.onDestroyActionMode(mode);
}
/**
* FIXME This is still very hackish with lots of code duplication
* @param requestCode
* @param resultCode
* @param data
*/
void handleActivityResult(final int requestCode, final int resultCode, final Intent data) {
ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
//
StorageDelegator storageDelegator = App.getDelegator();
for (String v:matches) {
String[] words = v.split("\\s+", 2);
if (words.length > 0) {
//
String first = words[0];
try {
int number = Integer.parseInt(first);
// worked if there is a further word(s) simply add it/them
Snack.barInfoShort(main, + number + (words.length == 2?words[1]:""));
Node node = logic.performAddNode(main, startLon/1E7D, startLat/1E7D);
if (node != null) {
TreeMap<String, String> tags = new TreeMap<String, String>(node.getTags());
tags.put(Tags.KEY_ADDR_HOUSENUMBER, "" + number + (words.length == 3?words[2]:""));
tags.put("source:original_text", v);
LinkedHashMap<String, ArrayList<String>> map = Address.predictAddressTags(main, Node.NAME, node.getOsmId(),
new ElementSearch(new int[]{node.getLon(),node.getLat()}, true),
Util.getArrayListMap(tags), Address.NO_HYSTERESIS);
tags = new TreeMap<String, String>();
for (String key:map.keySet()) {
tags.put(key, map.get(key).get(0));
}
logic.setTags(main, node, tags);
main.startSupportActionMode(new NodeSelectionActionModeCallback(node));
return;
}
} catch (NumberFormatException ex) {
// ok wasn't a number, just ignore
} catch (OsmIllegalOperationException e) {
// FIXME something went seriously wrong
Log.e(DEBUG_TAG,e.getMessage());
}
List<PresetItem> presetItems = SearchIndexUtils.searchInPresets(main, first,ElementType.NODE,2,1);
if (presetItems != null && presetItems.size()==1) {
Node node = addNode(logic.performAddNode(main, startLon/1E7D, startLat/1E7D), words.length == 2? words[1]:null, presetItems.get(0), logic, v);
if (node != null) {
main.startSupportActionMode(new NodeSelectionActionModeCallback(node));
return;
}
}
Map<String, NameAndTags> namesSearchIndex = App.getNameSearchIndex(main);
if (namesSearchIndex == null) {
return;
}
// search in names
NameAndTags nt = SearchIndexUtils.searchInNames(main, v, 2);
if (nt != null) {
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(nt.getTags());
PresetItem pi = Preset.findBestMatch(App.getCurrentPresets(main), map);
if (pi != null) {
Node node = addNode(logic.performAddNode(main, startLon/1E7D, startLat/1E7D), nt.getName(), pi, logic, v);
if (node != null) {
// set tags from name suggestions
Map<String,String> tags = new TreeMap<String, String>(node.getTags());
for (String k:map.keySet()) {
tags.put(k, map.get(k));
}
storageDelegator.setTags(node,tags); // note doesn't create a new undo checkpoint, performAddNode has already done that
main.startSupportActionMode(new NodeSelectionActionModeCallback(node));
return;
}
}
}
}
}
logic.showCrosshairs(startX, startY); // re-show the cross hairs nothing found/something went wrong
}
Node addNode(Node node, String name, PresetItem pi, Logic logic, String original) {
if (node != null) {
try {
Snack.barInfo(main,pi.getName() + (name != null? " name: " + name:""));
TreeMap<String, String> tags = new TreeMap<String, String>(node.getTags());
for (Entry<String, StringWithDescription> tag : pi.getFixedTags().entrySet()) {
tags.put(tag.getKey(), tag.getValue().getValue());
}
if (name != null) {
tags.put(Tags.KEY_NAME, name);
}
tags.put("source:original_text", original);
logic.setTags(main, node, tags);
logic.setSelectedNode(node);
return node;
} catch (OsmIllegalOperationException e) {
Log.e(DEBUG_TAG,e.getMessage());
Snack.barError(main, e.getLocalizedMessage());
return null;
}
}
return null;
}
public boolean processShortcut(Character c) {
if (c == Util.getShortCut(main, R.string.shortcut_paste)) {
logic.pasteFromClipboard(main, startX, startY);
logic.hideCrosshairs();
if (currentActionMode != null) {
currentActionMode.finish();
}
return true;
}
return false;
}
}
/**
* This callback handles path creation. It is started after a long-press.
* During this action mode, clicks are handled by custom code.
* The node and way click handlers are thus never called.
*/
private class PathCreationActionModeCallback extends EasyEditActionModeCallback {
private static final String DEBUG3_TAG = "PathCreationAction...";
private static final int MENUITEM_UNDO = 1;
private static final int MENUITEM_NEWWAY_PRESET = 2;
/** x coordinate of first node */
private float x;
/** y coordinate of first node */
private float y;
/** Node to append to */
private Node appendTargetNode;
/** Way to append to */
private Way appendTargetWay;
/** flag if we don't want to start the property editor in onDestroy **/
private boolean dontTag = false;
/** contains a pointer to the created way if one was created. used to fix selection after undo. */
private Way createdWay = null;
/** contains a list of created nodes. used to fix selection after undo. */
private ArrayList<Node> createdNodes = new ArrayList<Node>();
public PathCreationActionModeCallback(float x, float y) {
super();
this.x = x;
this.y = y;
appendTargetNode = null;
appendTargetWay = null;
}
public PathCreationActionModeCallback(Node node) {
super();
appendTargetNode = node;
appendTargetWay = null;
}
public PathCreationActionModeCallback(Way way, Node node) {
super();
appendTargetNode = node;
appendTargetWay = way;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_pathcreation;
super.onCreateActionMode(mode, menu);
mode.setSubtitle(R.string.actionmode_createpath);
logic.setSelectedWay(null);
logic.setSelectedNode(appendTargetNode);
if (appendTargetNode != null) {
if (appendTargetWay != null) {
logic.performAppendStart(appendTargetWay, appendTargetNode);
} else {
logic.performAppendStart(appendTargetNode);
}
} else {
try {
pathCreateNode(x, y);
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
}
}
logic.hideCrosshairs();
return true;
}
@Override
public boolean handleClick(float x, float y) {
super.handleClick(x, y);
try {
pathCreateNode(x, y);
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
}
return true;
}
/**
* Creates/adds a node into a path during path creation
* @param x x screen coordinate
* @param y y screen coordinate
* @throws OsmIllegalOperationException
*/
private void pathCreateNode(float x, float y) throws OsmIllegalOperationException {
Node lastSelectedNode = logic.getSelectedNode();
Way lastSelectedWay = logic.getSelectedWay();
if (appendTargetNode != null) {
logic.performAppendAppend(main, x, y);
} else {
logic.performAdd(main, x, y);
}
if (logic.getSelectedNode() == null) {
// user clicked last node again -> finish adding
if (currentActionMode != null) // TODO for unknown reasons this now and then seems to be null
currentActionMode.finish();
tagApplicable(lastSelectedNode, lastSelectedWay, true, false);
} else { // update cache for undo
createdWay = logic.getSelectedWay();
if (createdWay == null) {
createdNodes = new ArrayList<Node>();
}
createdNodes.add(logic.getSelectedNode());
}
main.invalidateMap();
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
menu.clear();
menuUtil.reset();
menu.add(Menu.NONE, MENUITEM_UNDO, Menu.NONE, R.string.undo).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_undo)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_undo));
menu.add(Menu.NONE, MENUITEM_NEWWAY_PRESET, Menu.NONE, R.string.tag_menu_preset).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_preset));
menu.add(GROUP_BASE, MENUITEM_HELP, Menu.CATEGORY_SYSTEM|10, R.string.menu_help).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_help)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_help));
arrangeMenu(menu);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
super.onActionItemClicked(mode, item);
switch (item.getItemId()) {
case MENUITEM_UNDO:
handleUndo();
break;
case MENUITEM_NEWWAY_PRESET:
Way lastSelectedWay = logic.getSelectedWay();
if (lastSelectedWay != null) {
dontTag = true;
main.startSupportActionMode(new WaySelectionActionModeCallback(lastSelectedWay));
main.performTagEdit(lastSelectedWay, null, false, item.getItemId() == MENUITEM_NEWWAY_PRESET, false); // show preset screen
}
return true;
default:
Log.e(DEBUG3_TAG, "Unknown menu item");
break;
}
return false;
}
private void handleUndo() {
logic.undo();
if (logic.getSelectedNode() == null) { // should always happen when we added a new node and removed it
Iterator<Node> nodeIterator = createdNodes.iterator();
while (nodeIterator.hasNext()) { // remove nodes that do not exist anymore
if (!logic.exists(nodeIterator.next())) nodeIterator.remove();
}
} else {
// remove existing node from list
createdNodes.remove(logic.getSelectedNode());
}
// exit or select the previous node
if (createdNodes.isEmpty()) {
logic.setSelectedNode(null);
// all nodes have been deleted, cancel action mode
if (currentActionMode != null) { //TODO shouldn't happen but does
currentActionMode.finish();
}
} else {
// select last node
logic.setSelectedNode(createdNodes.get(createdNodes.size()-1));
}
createdWay = logic.getSelectedWay(); // will be null if way was deleted by undo
main.invalidateMap();
}
/**
* Path creation action mode is ending
*/
@Override
public void onDestroyActionMode(ActionMode mode) {
Node lastSelectedNode = logic.getSelectedNode();
Way lastSelectedWay = logic.getSelectedWay();
logic.setSelectedWay(null);
logic.setSelectedNode(null);
super.onDestroyActionMode(mode);
if (appendTargetNode == null && !dontTag) { // doesn't work as intended element selected modes get zapped, don't try to select because of this
tagApplicable(lastSelectedNode, lastSelectedWay, false, false);
}
}
}
/**
* This action mode handles element selection. When a node or way should be selected, just start this mode.
* The element will be automatically selected, and a second click on the same element will open the tag editor.
* @author Jan
*
*/
private abstract class ElementSelectionActionModeCallback extends EasyEditActionModeCallback {
private static final String DEBUG4_TAG = "ElementSelectionActi...";
private static final int MENUITEM_UNDO = 0;
private static final int MENUITEM_TAG = 1;
private static final int MENUITEM_DELETE = 2;
private static final int MENUITEM_HISTORY = 3;
private static final int MENUITEM_COPY = 4;
private static final int MENUITEM_CUT = 5;
private static final int MENUITEM_RELATION = 6;
private static final int MENUITEM_EXTEND_SELECTION = 7;
private static final int MENUITEM_ELEMENT_INFO = 8;
protected static final int MENUITEM_SHARE_POSITION = 21;
private static final int MENUITEM_TAG_LAST = 22;
private static final int MENUITEM_ZOOM_TO_SELECTION = 23;
private static final int MENUITEM_PREFERENCES = 24;
private static final int MENUITEM_JS_CONSOLE = 25;
OsmElement element = null;
boolean deselect = true;
UndoListener undoListener;
public ElementSelectionActionModeCallback(OsmElement element) {
super();
this.element = element;
undoListener = main.new UndoListener();
}
/**
* Internal helper to avoid duplicate code in {@link #handleElementClick(OsmElement)}}.
* @param element clicked element
* @return true if handled, false if default handling should apply
*/
@Override
public boolean handleElementClick(OsmElement element) {
super.handleElementClick(element);
if (element == this.element) {
main.performTagEdit(element, null, false, false, false);
return true;
}
return false;
}
@SuppressLint("InflateParams")
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
menu.clear();
menuUtil.reset();
main.getMenuInflater().inflate(R.menu.undo_action, menu);
MenuItem undo = menu.findItem(R.id.undo_action);
if (logic.getUndo().canUndo() || logic.getUndo().canRedo()) {
undo.setVisible(true);
undo.setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_undo));
} else {
undo.setVisible(false);
}
View undoView = MenuItemCompat.getActionView(undo);
if (undoView == null) { // FIXME this is a temp workaround for pre-11 Android, we could probably simply always do the following
Log.d(DEBUG4_TAG,"undoView null");
Preferences prefs = new Preferences(main);
Context context = new ContextThemeWrapper(main, prefs.lightThemeEnabled() ? R.style.Theme_customMain_Light : R.style.Theme_customMain);
undoView = ((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.undo_action_view, null);
}
undoView.setOnClickListener(undoListener);
undoView.setOnLongClickListener(undoListener);
menu.add(Menu.NONE, MENUITEM_TAG, Menu.NONE, R.string.menu_tags).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_tagedit)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_tags));
menu.add(Menu.NONE, MENUITEM_DELETE, Menu.CATEGORY_SYSTEM, R.string.delete).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_delete));
// disabled for now menu.add(Menu.NONE, MENUITEM_TAG_LAST, Menu.NONE, R.string.tag_menu_repeat).setIcon(R.drawable.tag_menu_repeat);
if (!(element instanceof Relation)) {
menu.add(Menu.NONE, MENUITEM_COPY, Menu.CATEGORY_SECONDARY, R.string.menu_copy).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_copy)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_copy));
menu.add(Menu.NONE, MENUITEM_CUT, Menu.CATEGORY_SECONDARY, R.string.menu_cut).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_cut)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_cut));
}
menu.add(GROUP_BASE, MENUITEM_EXTEND_SELECTION, Menu.CATEGORY_SYSTEM, R.string.menu_extend_selection).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_multi_select));
menu.add(Menu.NONE, MENUITEM_RELATION, Menu.CATEGORY_SYSTEM, R.string.menu_relation).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_relation));
if (element.getOsmId() > 0) {
menu.add(GROUP_BASE, MENUITEM_HISTORY, Menu.CATEGORY_SYSTEM, R.string.menu_history).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_history)).setEnabled(NetworkStatus.isConnected(main));
}
menu.add(GROUP_BASE, MENUITEM_ELEMENT_INFO, Menu.CATEGORY_SYSTEM, R.string.menu_information).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_info)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_information));
menu.add(GROUP_BASE, MENUITEM_ZOOM_TO_SELECTION, Menu.CATEGORY_SYSTEM|10, R.string.menu_zoom_to_selection);
menu.add(GROUP_BASE, MENUITEM_SHARE_POSITION, Menu.CATEGORY_SYSTEM|10, R.string.share_position);
menu.add(GROUP_BASE, MENUITEM_PREFERENCES, Menu.CATEGORY_SYSTEM|10, R.string.menu_config).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_config));
Preferences prefs = new Preferences(main);
menu.add(GROUP_BASE, MENUITEM_JS_CONSOLE, Menu.CATEGORY_SYSTEM|10, R.string.tag_menu_js_console).setEnabled(prefs.isJsConsoleEnabled());
menu.add(GROUP_BASE, MENUITEM_HELP, Menu.CATEGORY_SYSTEM|10, R.string.menu_help).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_help)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_help));
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
super.onActionItemClicked(mode, item);
switch (item.getItemId()) {
case MENUITEM_TAG: main.performTagEdit(element, null, false, false, false); break;
case MENUITEM_TAG_LAST: main.performTagEdit(element, null, true, false, false); break;
case MENUITEM_DELETE: menuDelete(mode); break;
case MENUITEM_HISTORY: showHistory(); break;
case MENUITEM_COPY: logic.copyToClipboard(element); mode.finish(); break;
case MENUITEM_CUT: logic.cutToClipboard(main, element); mode.finish(); break;
case MENUITEM_RELATION:
deselect = false;
logic.setSelectedNode(null);
logic.setSelectedWay(null);
logic.setSelectedRelation(null);
main.startSupportActionMode(new AddRelationMemberActionModeCallback(element));
break;
case MENUITEM_EXTEND_SELECTION: deselect = false; main.startSupportActionMode(new ExtendSelectionActionModeCallback(element)); break;
case MENUITEM_ELEMENT_INFO: ElementInfo.showDialog(main,element); break;
case MENUITEM_PREFERENCES: PrefEditor.start(main, main.getMap().getViewBox()); break;
case MENUITEM_ZOOM_TO_SELECTION: main.zoomTo(element); main.invalidateMap(); break;
case MENUITEM_JS_CONSOLE:
Main.showJsConsole(main);
break;
case R.id.undo_action:
// should not happen
Log.d(DEBUG4_TAG,"menu undo clicked");
undoListener.onClick(null);
break;
default: return false;
}
return true;
}
protected abstract void menuDelete(ActionMode mode);
/**
* Opens the history page of the selected element in a browser
*/
private void showHistory() {
Intent intent = new Intent(Intent.ACTION_VIEW);
Preferences prefs = new Preferences(main);
intent.setData(Uri.parse(prefs.getServer().getWebsiteBaseUrl()+element.getName()+"/"+element.getOsmId()+"/history"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
main.startActivity(intent);
}
/**
* Element selection action mode is ending
*/
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
if (deselect) {
Log.d(DEBUG4_TAG,"deselecting");
logic.deselectAll();
}
super.onDestroyActionMode(mode);
}
public boolean processShortcut(Character c) {
if (c == Util.getShortCut(main, R.string.shortcut_copy)) {
logic.copyToClipboard(element); currentActionMode.finish();
return true;
} else if (c == Util.getShortCut(main, R.string.shortcut_cut)) {
logic.cutToClipboard(main, element); currentActionMode.finish();
return true;
} else if (c == Util.getShortCut(main, R.string.shortcut_info)) {
ElementInfo.showDialog(main,element);
return true;
} else if (c == Util.getShortCut(main, R.string.shortcut_tagedit)) {
main.performTagEdit(element, null, false, false, false);
return true;
}
return false;
}
}
private class NodeSelectionActionModeCallback extends ElementSelectionActionModeCallback {
private static final int MENUITEM_APPEND = 9;
private static final int MENUITEM_JOIN = 10;
private static final int MENUITEM_UNJOIN = 11;
private static final int MENUITEM_EXTRACT = 12;
private static final int MENUITEM_SET_POSITION = 15;
private static final int MENUITEM_ADDRESS = 16;
private OsmElement joinableElement = null;
private NodeSelectionActionModeCallback(Node node) {
super(node);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_nodeselection;
super.onCreateActionMode(mode, menu);
logic.setSelectedNode((Node)element);
logic.setSelectedWay(null);
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
main.invalidateMap();
mode.setTitle(R.string.actionmode_nodeselect);
mode.setSubtitle(null);
// mode.setTitleOptionalHint(true); // no need to display the title, only available in 4.1 up
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
if (((Node)element).getTags().containsKey(Tags.KEY_ENTRANCE) && !((Node)element).getTags().containsKey(Tags.KEY_ADDR_HOUSENUMBER)) {
menu.add(Menu.NONE, MENUITEM_ADDRESS, Menu.NONE, R.string.tag_menu_address).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_address));
}
if (logic.isEndNode((Node)element)) {
menu.add(Menu.NONE, MENUITEM_APPEND, Menu.NONE, R.string.menu_append).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_append));
}
joinableElement = logic.findJoinableElement((Node)element);
if (joinableElement != null) {
menu.add(Menu.NONE, MENUITEM_JOIN, Menu.NONE, R.string.menu_join).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_merge)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_merge));
}
int wayMembershipCount = logic.getFilteredWaysForNode((Node)element).size();
if (wayMembershipCount > 1) {
menu.add(Menu.NONE, MENUITEM_UNJOIN, Menu.NONE, R.string.menu_unjoin).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_split));
}
if (wayMembershipCount > 0) {
menu.add(Menu.NONE, MENUITEM_EXTRACT, Menu.NONE, R.string.menu_extract).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_extract_node));
}
menu.add(Menu.NONE, MENUITEM_SET_POSITION, Menu.CATEGORY_SYSTEM, R.string.menu_set_position).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_gps));
arrangeMenu(menu);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case MENUITEM_APPEND:
main.startSupportActionMode(new PathCreationActionModeCallback((Node)element));
break;
case MENUITEM_JOIN:
try {
if (!logic.performJoin(main, joinableElement, (Node) element)) {
Snack.barWarning(main, R.string.toast_merge_tag_conflict);
main.performTagEdit(element, null, false, false, false);
} else {
mode.finish();
}
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
}
break;
case MENUITEM_UNJOIN:
logic.performUnjoin(main, (Node)element);
mode.finish();
break;
case MENUITEM_EXTRACT:
logic.performExtract(main, (Node)element);
invalidate();
break;
case MENUITEM_SET_POSITION:
setPosition();
break;
case MENUITEM_ADDRESS: main.performTagEdit(element, null, true, false, false); break;
case MENUITEM_SHARE_POSITION:
double[] lonLat = new double[2];
lonLat[0] = ((Node)element).getLon()/1E7;
lonLat[1] = ((Node)element).getLat()/1E7;
Util.sharePosition(main, lonLat);
break;
default: return false;
}
}
return true;
}
@Override
protected void menuDelete(final ActionMode mode) {
if (element.hasParentRelations()) {
new AlertDialog.Builder(main)
.setTitle(R.string.delete)
.setMessage(R.string.deletenode_relation_description)
.setPositiveButton(R.string.deletenode,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
logic.performEraseNode(main, (Node)element, true);
if (mode != null) {
mode.finish();
}
}
})
.show();
} else {
logic.performEraseNode(main, (Node)element, true);
mode.finish();
}
}
private void setPosition() {
if (element instanceof Node) {
// show dialog to set lon/lat
createSetPositionDialog(((Node)element).getLon(), ((Node)element).getLat()).show();
}
}
@SuppressLint("InflateParams")
AppCompatDialog createSetPositionDialog(int lonE7, int latE7) {
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(main);
Builder dialog = new AlertDialog.Builder(main);
dialog.setTitle(R.string.menu_set_position);
View layout = inflater.inflate(R.layout.set_position, null);
dialog.setView(layout);
TextView datum = (TextView) layout.findViewById(R.id.set_position_datum); // TODO add conversion to/from other datums
datum.setText("WGS84");
EditText lon = (EditText) layout.findViewById(R.id.set_position_lon);
lon.setText(String.format(Locale.US,"%.7f", lonE7/1E7d));
EditText lat = (EditText) layout.findViewById(R.id.set_position_lat);
lat.setText(String.format(Locale.US,"%.7f", latE7/1E7d));
dialog.setPositiveButton(R.string.set, createSetButtonListener(lon, lat, (Node)element));
dialog.setNegativeButton(R.string.cancel, null);
return dialog.create();
}
/**
* Create an onClick listener that sets the coordnaties in the node
* @return the OnClickListnener
*/
private OnClickListener createSetButtonListener(final EditText lonField, final EditText latField, final Node node) {
return new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
double lon = Double.valueOf(lonField.getText().toString());
double lat = Double.valueOf(latField.getText().toString());
if (lon >= -180 && lon <= 180 && lat >= -GeoMath.MAX_LAT && lat <= GeoMath.MAX_LAT) {
logic.performSetPosition(main, node,lon,lat);
invalidate();
} else {
createSetPositionDialog((int)(lon*1E7), (int)(lat*1E7)).show();
Snack.barWarning(main, R.string.coordinates_out_of_range);
}
}
};
}
}
private class WaySelectionActionModeCallback extends ElementSelectionActionModeCallback {
private static final String DEBUG_TAG = "WaySelectionAction...";
private static final int MENUITEM_SPLIT = 9;
private static final int MENUITEM_MERGE = 10;
private static final int MENUITEM_REVERSE = 11;
private static final int MENUITEM_APPEND = 12;
private static final int MENUITEM_RESTRICTION = 13;
private static final int MENUITEM_ROTATE = 14;
private static final int MENUITEM_ORTHOGONALIZE = 15;
private static final int MENUITEM_CIRCULIZE = 16;
private static final int MENUITEM_SPLIT_POLYGON = 17;
private static final int MENUITEM_ADDRESS = 18;
private Set<OsmElement> cachedMergeableWays;
private Set<OsmElement> cachedAppendableNodes;
private Set<OsmElement> cachedViaElements;
private WaySelectionActionModeCallback(Way way) {
super(way);
Log.d(DEBUG_TAG, "constructor");
cachedMergeableWays = findMergeableWays(way);
cachedAppendableNodes = findAppendableNodes(way);
cachedViaElements = findViaElements(way);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_wayselection;
super.onCreateActionMode(mode, menu);
Log.d(DEBUG_TAG, "onCreateActionMode");
logic.setSelectedNode(null);
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
logic.setSelectedWay((Way)element);
main.invalidateMap();
mode.setTitle(R.string.actionmode_wayselect);
mode.setSubtitle(null);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
Log.d(DEBUG_TAG, "onPrepareActionMode");
if (((Way)element).getTags().containsKey(Tags.KEY_BUILDING) && !((Way)element).getTags().containsKey(Tags.KEY_ADDR_HOUSENUMBER)) {
menu.add(Menu.NONE, MENUITEM_ADDRESS, Menu.NONE, R.string.tag_menu_address).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_address));
}
menu.add(Menu.NONE, MENUITEM_REVERSE, Menu.NONE, R.string.menu_reverse).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_reverse));
if (((Way)element).getNodes().size() > 2) {
menu.add(Menu.NONE, MENUITEM_SPLIT, Menu.NONE, R.string.menu_split).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_split));
}
if (cachedMergeableWays.size() > 0) {
menu.add(Menu.NONE, MENUITEM_MERGE, Menu.NONE, R.string.menu_merge).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_merge));
}
if (cachedAppendableNodes.size() > 0) {
menu.add(Menu.NONE, MENUITEM_APPEND, Menu.NONE, R.string.menu_append).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_append));
}
if (((Way)element).getTagWithKey(Tags.KEY_HIGHWAY) != null && (cachedViaElements.size() > 0)) {
menu.add(Menu.NONE, MENUITEM_RESTRICTION, Menu.NONE, R.string.actionmode_restriction).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_add_restriction));
}
if (((Way)element).getNodes().size() > 2) {
menu.add(Menu.NONE, MENUITEM_ORTHOGONALIZE, Menu.NONE, R.string.menu_orthogonalize).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_ortho));
}
menu.add(Menu.NONE, MENUITEM_ROTATE, Menu.NONE, R.string.menu_rotate).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_rotate));
if (((Way)element).getNodes().size() > 3 && ((Way)element).isClosed()) {
menu.add(Menu.NONE, MENUITEM_CIRCULIZE, Menu.NONE, R.string.menu_circulize);
if (((Way)element).getNodes().size() > 4) { // 5 nodes is the minimum required to be able to split in to two polygons
menu.add(Menu.NONE, MENUITEM_SPLIT_POLYGON, Menu.NONE, R.string.menu_split_polygon);
}
}
arrangeMenu(menu);
return true;
}
private void reverseWay() {
final Way way = (Way) element;
if (way.notReversable()) {
new AlertDialog.Builder(main)
.setTitle(R.string.menu_reverse)
.setMessage(R.string.notreversable_description)
.setPositiveButton(R.string.reverse_anyway,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (logic.performReverse(main, way)) { // true if it had oneway tag
Snack.barWarning(main, R.string.toast_oneway_reversed);
main.performTagEdit(way, null, false, false, false);
}
}
})
.show();
} else if (logic.performReverse(main, way)) { // true if it had oneway tag
Snack.barWarning(main, R.string.toast_oneway_reversed);
main.performTagEdit(way, null, false, false, false);
} else {
invalidate(); // sucessful reverseal update menubar
}
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case MENUITEM_SPLIT: main.startSupportActionMode(new WaySplittingActionModeCallback((Way)element, false)); break;
case MENUITEM_MERGE: main.startSupportActionMode(new WayMergingActionModeCallback((Way)element, cachedMergeableWays)); break;
case MENUITEM_REVERSE: reverseWay(); break;
case MENUITEM_APPEND: main.startSupportActionMode(new WayAppendingActionModeCallback((Way)element, cachedAppendableNodes)); break;
case MENUITEM_RESTRICTION: main.startSupportActionMode(new RestrictionFromElementActionModeCallback((Way)element, cachedViaElements)); break;
case MENUITEM_ROTATE: deselect=false; main.startSupportActionMode(new WayRotationActionModeCallback((Way)element)); break;
case MENUITEM_ORTHOGONALIZE: logic.performOrthogonalize(main, (Way)element); invalidate(); break; // FIXME move to asynctask
case MENUITEM_CIRCULIZE: logic.performCirculize(main, (Way)element); invalidate(); break;
case MENUITEM_SPLIT_POLYGON: main.startSupportActionMode(new WaySplittingActionModeCallback((Way)element, true)); break;
case MENUITEM_ADDRESS: main.performTagEdit(element, null, true, false, false); break;
case MENUITEM_SHARE_POSITION:
Util.sharePosition(main, Logic.centroidLonLat((Way) element));
break;
default: return false;
}
}
return true;
}
@Override
protected void menuDelete(final ActionMode mode) {
boolean isRelationMember = element.hasParentRelations();
boolean allNodesDownloaded = logic.isInDownload((Way)element);
if ( allNodesDownloaded) {
new AlertDialog.Builder(main)
.setTitle(R.string.delete)
.setMessage(isRelationMember ? R.string.deleteway_relation_description : R.string.deleteway_description)
.setPositiveButton(R.string.deleteway_wayonly,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
logic.performEraseWay(main, (Way)element, false, true);
if (mode != null) {
mode.finish();
}
}
})
.setNeutralButton(R.string.deleteway_wayandnodes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
logic.performEraseWay(main, (Way)element, true, true);
if (mode != null) {
mode.finish();
}
}
})
.show();
} else {
new AlertDialog.Builder(main)
.setTitle(R.string.delete)
.setMessage(R.string.deleteway_nodesnotdownloaded_description)
.setPositiveButton(R.string.okay, null)
.show();
}
}
}
private class WaySplittingActionModeCallback extends EasyEditActionModeCallback {
private Way way;
private List<OsmElement> nodes = new ArrayList<OsmElement>();
private boolean createPolygons = false;
public WaySplittingActionModeCallback(Way way, boolean createPolygons) {
super();
this.way = way;
nodes.addAll(way.getNodes());
if (!way.isClosed()) {
// remove first and last node
nodes.remove(0);
nodes.remove(nodes.size()-1);
} else {
this.createPolygons = createPolygons;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_waysplitting;
super.onCreateActionMode(mode, menu);
if (way.isClosed())
mode.setSubtitle(R.string.actionmode_closed_way_split_1);
else
mode.setSubtitle(R.string.menu_split);
logic.setClickableElements(new HashSet<OsmElement>(nodes));
logic.setReturnRelations(false);
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid nodes can be clicked
super.handleElementClick(element);
// protect against race conditions
if (!(element instanceof Node)) {
// TODO fix properly
return false;
}
if (way.isClosed())
main.startSupportActionMode(new ClosedWaySplittingActionModeCallback(way, (Node) element, createPolygons));
else {
logic.performSplit(main, way, (Node)element);
currentActionMode.finish();
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
super.onDestroyActionMode(mode);
}
}
private class ClosedWaySplittingActionModeCallback extends EasyEditActionModeCallback {
private Way way;
private Node node;
private Set<OsmElement> nodes = new HashSet<OsmElement>();
private boolean createPolygons = false;
public ClosedWaySplittingActionModeCallback(Way way, Node node, boolean createPolygons) {
super();
this.way = way;
this.node = node;
this.createPolygons = createPolygons;
List<Node> allNodes = way.getNodes();
nodes.addAll(allNodes);
if (createPolygons) { // remove neighbouring nodes
if (way.isEndNode(node)) { // we have at least 4 nodes so this will not cause problems
nodes.remove(allNodes.get(1)); // remove 2nd element
nodes.remove(allNodes.get(allNodes.size()-2)); // remove 2nd last element
} else {
int nodeIndex = allNodes.indexOf(node);
nodes.remove(allNodes.get(nodeIndex-1));
nodes.remove(allNodes.get(nodeIndex+1));
}
}
nodes.remove(node);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_closedwaysplitting;
super.onCreateActionMode(mode, menu);
mode.setSubtitle(R.string.actionmode_closed_way_split_2);
logic.setClickableElements(nodes);
logic.setReturnRelations(false);
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid nodes can be clicked
super.handleElementClick(element);
Way[] result = logic.performClosedWaySplit(main, way, node, (Node)element, createPolygons);
if (result!= null && result.length == 2) {
logic.setSelectedNode(null);
logic.setSelectedRelation(null);
logic.setSelectedWay(result[0]);
logic.addSelectedWay(result[1]);
ArrayList<OsmElement> selection = new ArrayList<OsmElement>();
selection.addAll(logic.getSelectedWays());
main.startSupportActionMode(new ExtendSelectionActionModeCallback(selection));
} else { //FIXME toast here?
Log.d(DEBUG_TAG,"split failed");
currentActionMode.finish();
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
super.onDestroyActionMode(mode);
}
}
private class WayMergingActionModeCallback extends EasyEditActionModeCallback {
private Way way;
private Set<OsmElement> ways;
public WayMergingActionModeCallback(Way way, Set<OsmElement> mergeableWays) {
super();
this.way = way;
ways = mergeableWays;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_mergingways;
mode.setSubtitle(R.string.menu_merge);
logic.setClickableElements(ways);
logic.setReturnRelations(false);
super.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid ways can be clicked
super.handleElementClick(element);
// race conditions with touch events seem to make the impossible possible
//TODO fix properly
if (!(element instanceof Way)) {
return false;
}
if (!findMergeableWays(way).contains((Way)element)) {
return false;
}
try {
if (!logic.performMerge(main, way, (Way)element)) {
Snack.barWarning(main, R.string.toast_merge_tag_conflict);
if (way.getState() != OsmElement.STATE_DELETED)
main.performTagEdit(way, null, false, false, false);
else
main.performTagEdit(element, null, false, false, false);
} else {
if (way.getState() != OsmElement.STATE_DELETED)
main.startSupportActionMode(new WaySelectionActionModeCallback(way));
else
main.startSupportActionMode(new WaySelectionActionModeCallback((Way)element));
}
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
super.onDestroyActionMode(mode);
}
}
private class WayAppendingActionModeCallback extends EasyEditActionModeCallback {
private Way way;
private Set<OsmElement> nodes;
public WayAppendingActionModeCallback(Way way, Set<OsmElement> appendNodes) {
super();
this.way = way;
nodes = appendNodes;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_appendtoway;
mode.setSubtitle(R.string.menu_append);
logic.setClickableElements(nodes);
logic.setReturnRelations(false);
super.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid nodes can be clicked
super.handleElementClick(element);
main.startSupportActionMode(new PathCreationActionModeCallback(way, (Node)element));
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
super.onDestroyActionMode(mode);
}
}
private class WayRotationActionModeCallback extends EasyEditActionModeCallback {
private static final String DEBUG_TAG = "WayRotationAction...";
public WayRotationActionModeCallback(Way way) {
super();
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_wayselection;
super.onCreateActionMode(mode, menu);
Log.d(DEBUG_TAG, "onCreateActionMode");
logic.setRotationMode(true);
logic.showCrosshairsForCentroid();
mode.setTitle(R.string.actionmode_rotateway);
mode.setSubtitle(null);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.deselectAll();
logic.setRotationMode(false);
logic.hideCrosshairs();
super.onDestroyActionMode(mode);
}
}
private class RelationSelectionActionModeCallback extends ElementSelectionActionModeCallback {
private static final String DEBUG7_TAG = "RelationSelectionAct...";
private static final int MENUITEM_ADD_RELATION_MEMBERS = 9;
private static final int MENUITEM_SELECT_RELATION_MEMBERS = 10;
private RelationSelectionActionModeCallback(Relation relation) {
super(relation);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_relationselection;
super.onCreateActionMode(mode, menu);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
if (element != null && (((Relation)element).getMembers()==null || ((Relation)element).getMembers().size()==0)) {
// we can only select an empty relation if there is a reference from another object, this is always a bug
String message = "relation " + element.getOsmId() + " is empty";
Log.e(DEBUG7_TAG, message);
Snack.barWarning(main, R.string.toast_rmpty_relation);
ACRA.getErrorReporter().putCustomData("CAUSE", message);
ACRA.getErrorReporter().putCustomData("STATUS", "NOCRASH");
ACRA.getErrorReporter().handleException(null);
super.onDestroyActionMode(mode);
return false;
}
logic.setSelectedRelation((Relation) element);
Log.d(DEBUG7_TAG,"selected relations " + logic.selectedRelationsCount());
mode.setTitle(R.string.actionmode_relationselect);
mode.setSubtitle(null);
main.invalidateMap();
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
super.onPrepareActionMode(mode, menu);
menu.add(Menu.NONE, MENUITEM_ADD_RELATION_MEMBERS, Menu.NONE, R.string.menu_add_relation_member).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_relation_add_member));
if (((Relation)element).getMembers() != null) {
menu.add(Menu.NONE, MENUITEM_SELECT_RELATION_MEMBERS, Menu.NONE, R.string.menu_select_relation_members).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_relation_members));
}
arrangeMenu(menu);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case MENUITEM_ADD_RELATION_MEMBERS: main.startSupportActionMode(new AddRelationMemberActionModeCallback((Relation)element, null)); break;
case MENUITEM_SELECT_RELATION_MEMBERS:
ArrayList<OsmElement> selection = new ArrayList<OsmElement>();
if (((Relation)element).getMembers() != null) {
for (RelationMember rm : ((Relation)element).getMembers()) {
selection.add(rm.getElement());
}
}
if (selection.size() > 0) {
deselect = false;
main.startSupportActionMode(new ExtendSelectionActionModeCallback(selection));
}
break;
case MENUITEM_SHARE_POSITION:
BoundingBox box = element.getBounds();
double[] lonLat = new double[2];
lonLat[0] = ((box.getRight() - box.getLeft())/2 + box.getLeft())/1E7;
lonLat[1] = ((box.getTop() - box.getBottom())/2 + box.getBottom())/1E7; // rough
Util.sharePosition(main, lonLat);
break;
default: return false;
}
}
return true;
}
@Override
protected void menuDelete(final ActionMode mode) {
if (element.hasParentRelations()) {
new AlertDialog.Builder(main)
.setTitle(R.string.delete)
.setMessage(R.string.deleterelation_relation_description)
.setPositiveButton(R.string.deleterelation,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
logic.performEraseRelation(main, (Relation)element, true);
if (mode != null) {
mode.finish();
}
}
})
.show();
} else {
logic.performEraseRelation(main, (Relation)element, true);
mode.finish();
}
}
}
private class RestartRestrictionFromElementActionModeCallback extends EasyEditActionModeCallback {
private final static String DEBUG8_TAG = "RestartRestrictionFr...";
private Set<OsmElement> fromElements;
private Set<OsmElement> viaElements;
private boolean fromSelected = false;
public RestartRestrictionFromElementActionModeCallback(Set<OsmElement> froms, Set<OsmElement> vias) {
super();
fromElements = froms;
viaElements = vias;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_addingrestriction;
mode.setTitle(R.string.actionmode_restriction_restart_from);
logic.setClickableElements(fromElements);
logic.setReturnRelations(false);
logic.setSelectedRelationWays(null); // just to be safe
logic.addSelectedRelationWay(null);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
super.onCreateActionMode(mode, menu);
return true;
}
@Override
/**
*/
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid nodes can be clicked
super.handleElementClick(element);
if (viaElements.size() > 1) {
fromSelected = true;
// redo via selection, this time with pre-split way
main.startSupportActionMode(new RestrictionFromElementActionModeCallback(R.string.actionmode_restriction_restart_via,(Way)element, viaElements));
return true;
} else if (viaElements.size() == 1) {
fromSelected = true;
main.startSupportActionMode(new RestrictionViaElementActionModeCallback((Way)element, viaElements.iterator().next()));
return true;
}
Log.e(DEBUG8_TAG, "viaElements size " + viaElements.size());
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
if (!fromSelected) {
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
}
super.onDestroyActionMode(mode);
}
}
private class RestrictionFromElementActionModeCallback extends EasyEditActionModeCallback {
private Way fromWay;
private Set<OsmElement> viaElements;
private boolean viaSelected = false;
private int titleId = R.string.actionmode_restriction_via;
public RestrictionFromElementActionModeCallback(Way way, Set<OsmElement> vias) {
super();
this.fromWay = way;
viaElements = vias;
}
public RestrictionFromElementActionModeCallback(int titleId, Way way, Set<OsmElement> vias) {
this(way,vias);
this.titleId = titleId;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_addingrestriction;
mode.setTitle(titleId);
logic.setClickableElements(viaElements);
logic.setReturnRelations(false);
logic.setSelectedRelationWays(null); // just to be safe
logic.addSelectedRelationWay(fromWay);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
super.onCreateActionMode(mode, menu);
return true;
}
@Override
/**
* In the simplest case this selects the next step in creating the restriction, in the worst it splits both the via and from way and
* restarts the process.
*/
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid nodes can be clicked
super.handleElementClick(element);
// check if we have to split from or via
Node viaNode = null;
Way viaWay = null;
if (Node.NAME.equals(element.getName())) {
viaNode = (Node) element;
} else if (Way.NAME.equals(element.getName())) {
viaWay = (Way) element;
viaNode = fromWay.getCommonNode(viaWay);
} else {
// ABORT
}
Way newFromWay = null;
if (!fromWay.getFirstNode().equals(viaNode) && !fromWay.getLastNode().equals(viaNode)) {
// split from at node
newFromWay = logic.performSplit(main, fromWay,viaNode);
}
Way newViaWay = null;
if (viaWay != null && !viaWay.getFirstNode().equals(viaNode) && !viaWay.getLastNode().equals(viaNode)) {
newViaWay = logic.performSplit(main, viaWay,viaNode);
}
Set<OsmElement> viaElements = new HashSet<OsmElement>();
viaElements.add(element);
if (newViaWay != null) {
viaElements.add(newViaWay);
}
if (newFromWay != null) {
Set<OsmElement> fromElements = new HashSet<OsmElement>();
fromElements.add(fromWay);
fromElements.add(newFromWay);
Snack.barInfo(main, newViaWay == null ? R.string.toast_split_from:R.string.toast_split_from_and_via);
main.startSupportActionMode(new RestartRestrictionFromElementActionModeCallback(fromElements, viaElements));
return true;
}
if (newViaWay != null) {
// restart via selection
Snack.barInfo(main, R.string.toast_split_via);
main.startSupportActionMode(new RestrictionFromElementActionModeCallback(R.string.actionmode_restriction_restart_via,fromWay, viaElements));
return true;
}
viaSelected = true;
main.startSupportActionMode(new RestrictionViaElementActionModeCallback(fromWay, element));
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
if (!viaSelected) { // back button or done pressed early
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
}
super.onDestroyActionMode(mode);
}
}
private class RestrictionViaElementActionModeCallback extends EasyEditActionModeCallback {
private Way fromWay;
private OsmElement viaElement;
private Set<OsmElement> cachedToElements;
private boolean toSelected = false;
private int titleId = R.string.actionmode_restriction_to;
public RestrictionViaElementActionModeCallback(Way from, OsmElement via) {
super();
fromWay = from;
viaElement = via;
cachedToElements = findToElements(viaElement);
}
public RestrictionViaElementActionModeCallback(Way from, OsmElement via, Set<OsmElement>toElements) {
super();
fromWay = from;
viaElement = via;
cachedToElements = toElements;
this.titleId = R.string.actionmode_restriction_restart_to;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_addingrestriction;
mode.setTitle(titleId);
logic.setClickableElements(cachedToElements);
logic.setReturnRelations(false);
if (Node.NAME.equals(viaElement.getName())) {
logic.addSelectedRelationNode((Node) viaElement);
} else {
logic.addSelectedRelationWay((Way) viaElement);
}
super.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid elements can be clicked
super.handleElementClick(element);
Node viaNode = null;
Way toWay = (Way) element;
if (Node.NAME.equals(viaElement.getName())) {
viaNode = (Node) viaElement;
} else if (Way.NAME.equals(viaElement.getName())) {
Way viaWay = (Way) viaElement;
viaNode = ((Way)viaElement).getCommonNode(toWay);
if (!viaWay.getFirstNode().equals(viaNode) && !viaWay.getLastNode().equals(viaNode)) {
// split via way and use appropriate segment
Way newViaWay = logic.performSplit(main, viaWay, viaNode);
Snack.barInfo(main, R.string.toast_split_via);
if (fromWay.hasNode(newViaWay.getFirstNode()) || fromWay.hasNode(newViaWay.getLastNode())) {
viaElement = newViaWay;
}
}
}
// now check if we need to split the toWay
if (!toWay.getFirstNode().equals(viaNode) && !toWay.getLastNode().equals(viaNode)) {
Way newToWay = logic.performSplit(main, toWay, viaNode);
Snack.barInfo(main, R.string.toast_split_to);
Set<OsmElement> toCandidates = new HashSet<OsmElement>();
toCandidates.add(toWay);
toCandidates.add(newToWay);
main.startSupportActionMode(new RestrictionViaElementActionModeCallback(fromWay, viaElement, toCandidates));
return true;
}
toSelected = true;
main.startSupportActionMode(new RestrictionToElementActionModeCallback(fromWay, viaElement, (Way) element));
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
logic.setClickableElements(null);
logic.setReturnRelations(true);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
if (!toSelected) {
// back button or done pressed early
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
}
super.onDestroyActionMode(mode);
}
}
private class RestrictionToElementActionModeCallback extends EasyEditActionModeCallback {
private final static String DEBUG9_TAG = "RestrictionToElement...";
private Way fromWay;
private OsmElement viaElement;
private Way toWay;
public RestrictionToElementActionModeCallback(Way from, OsmElement via, Way to) {
super();
fromWay = from;
viaElement = via;
toWay = to;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_addingrestriction;
mode.setTitle(R.string.actionmode_restriction);
super.onCreateActionMode(mode, menu);
logic.addSelectedRelationWay(toWay);
boolean uTurn = fromWay == toWay;
Relation restriction = logic.createRestriction(main, fromWay, viaElement, toWay, uTurn ? Tags.VALUE_NO_U_TURN : null);
Log.i(DEBUG9_TAG, "Created restriction");
main.performTagEdit(restriction, !uTurn ? Tags.VALUE_RESTRICTION : null, false, false, false);
main.startSupportActionMode(new RelationSelectionActionModeCallback(restriction));
return false; // we are actually already finished
}
@Override
public void onDestroyActionMode(ActionMode mode) { // note never called
logic.setClickableElements(null);
logic.setReturnRelations(true);
logic.setSelectedRelationWays(null);
logic.setSelectedRelationNodes(null);
logic.setSelectedNode(null);
logic.setSelectedWay(null);
super.onDestroyActionMode(mode);
}
}
private class AddRelationMemberActionModeCallback extends EasyEditActionModeCallback {
private static final int MENUITEM_REVERT = 1;
private ArrayList<OsmElement> members;
private Relation relation = null;
private MenuItem revert = null;
private boolean backPressed = false;
public AddRelationMemberActionModeCallback(ArrayList<OsmElement> selection) {
super();
members = new ArrayList<OsmElement>(selection);
}
public AddRelationMemberActionModeCallback(OsmElement element) {
super();
members = new ArrayList<OsmElement>();
addElement(element);
}
public AddRelationMemberActionModeCallback(Relation relation, OsmElement element) {
super();
members = new ArrayList<OsmElement>();
if (element != null)
addElement(element);
this.relation = relation;
}
private void addElement(OsmElement element) {
members.add(element);
if (element.getName().equals(Way.NAME)) {
logic.addSelectedRelationWay((Way)element);
} else if (element.getName().equals(Node.NAME)) {
logic.addSelectedRelationNode((Node)element);
} else if (element.getName().equals(Relation.NAME)) {
logic.addSelectedRelationRelation((Relation)element);
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_addrelationmember;
mode.setTitle(R.string.menu_relation);
mode.setSubtitle(R.string.menu_add_relation_member);
super.onCreateActionMode(mode, menu);
logic.setReturnRelations(true); // can add relations
menu.add(Menu.NONE, MENUITEM_REVERT, Menu.NONE, R.string.tag_menu_revert).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_undo)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_undo));
revert = menu.findItem(MENUITEM_REVERT);
revert.setVisible(false);
setClickableElements();
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
if (members.size() > 0)
revert.setVisible(true);
arrangeMenu(menu);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case MENUITEM_REVERT: // remove last item in list
if(members.size() > 0) {
OsmElement element = members.get(members.size()-1);
if (element.getName().equals(Way.NAME))
logic.removeSelectedRelationWay((Way)element);
else if (element.getName().equals(Node.NAME))
logic.removeSelectedRelationNode((Node)element);
members.remove(members.size()-1);
setClickableElements();
main.invalidateMap();
if (members.size() == 0)
item.setVisible(false);
}
break;
default: return false;
}
}
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid elements can be clicked
super.handleElementClick(element);
addElement(element);
setClickableElements();
if (members.size() > 0)
revert.setVisible(true);
main.invalidateMap();
return true;
}
private void setClickableElements() {
ArrayList<OsmElement> excludes = new ArrayList<OsmElement>(members);
if (relation != null) {
logic.selectRelation(relation);
excludes.addAll(relation.getMemberElements());
}
logic.setClickableElements(logic.findClickableElements(excludes));
}
@Override
public void onDestroyActionMode(ActionMode mode) {
super.onDestroyActionMode(mode);
logic.setClickableElements(null);
logic.setReturnRelations(true);
logic.deselectAll();
if (!backPressed) {
if (members.size() > 0) { // something was actually added
if (relation == null) {
relation = logic.createRelation(main, null, members);
main.performTagEdit(relation,"type", false, false, false);
} else {
logic.addMembers(main, relation, members);
main.performTagEdit(relation, null, false, false, false);
}
// starting action mode here doesn't seem to work ... main.startSupportActionMode(new RelationSelectionActionModeCallback(relation));
}
}
}
/**
* back button should abort relation creation
*/
@Override
public boolean onBackPressed() {
backPressed = true;
return super.onBackPressed(); // call the normal stuff
}
}
private class ExtendSelectionActionModeCallback extends EasyEditActionModeCallback {
private final static String DEBUG10_TAG = "ExtendSelectionAct...";
private static final int MENUITEM_TAG = 2;
private static final int MENUITEM_DELETE = 3;
private static final int MENUITEM_COPY = 4;
private static final int MENUITEM_CUT = 5;
private static final int MENUITEM_MERGE = 6;
private static final int MENUITEM_RELATION = 7;
private static final int MENUITEM_ORTHOGONALIZE = 8;
private static final int MENUITEM_MERGE_POLYGONS = 9;
private static final int MENUITEM_PREFERENCES = 10;
private static final int MENUITEM_JS_CONSOLE = 11;
private ArrayList<OsmElement> selection;
private List<OsmElement> sortedWays;
private ActionMode mode;
UndoListener undoListener;
private boolean deselect = true;
public ExtendSelectionActionModeCallback(ArrayList<OsmElement> elements) {
super();
selection = new ArrayList<OsmElement>();
for (OsmElement e: elements) {
if (e != null) {
addOrRemoveElement(e);
}
}
undoListener = main.new UndoListener();
}
public ExtendSelectionActionModeCallback(OsmElement element) {
super();
Log.d(DEBUG10_TAG,"Multi-Select create mode with " + element);
selection = new ArrayList<OsmElement>();
if (element != null) {
addOrRemoveElement(element);
}
undoListener = main.new UndoListener();
}
/**
* Add or remove objects from the selection
* @param element object to add or remove
*/
private void addOrRemoveElement(OsmElement element) {
if (!selection.contains(element)) {
selection.add(element);
if (element.getName().equals(Way.NAME)) {
logic.addSelectedWay((Way)element);
} else if (element.getName().equals(Node.NAME)) {
logic.addSelectedNode((Node)element);
} else if (element.getName().equals(Relation.NAME)) {
logic.addSelectedRelation((Relation)element);
}
} else {
selection.remove(element);
if (element.getName().equals(Way.NAME)) {
logic.removeSelectedWay((Way)element);
} else if (element.getName().equals(Node.NAME)) {
logic.removeSelectedNode((Node)element);
} else if (element.getName().equals(Relation.NAME)) {
logic.removeSelectedRelation((Relation)element);
}
}
if (selection.size() == 0) {
// nothing slected more .... stop
currentActionMode.finish();
} else {
sortedWays = Util.sortWays(selection);
invalidate();
}
setSubTitle(mode);
main.invalidateMap();
}
/**
* Set aselected object count in the action mode subtitle
* @param mode the ActionMode
*/
private void setSubTitle(ActionMode mode) {
if (mode != null) {
int count = selection.size();
if (count > 1) {
mode.setSubtitle(main.getString(R.string.actionmode_object_count,count));
} else {
mode.setSubtitle(R.string.actionmode_one_object);
}
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helpTopic = R.string.help_multiselect;
this.mode = mode;
mode.setTitle(R.string.actionmode_multiselect);
setSubTitle(mode);
super.onCreateActionMode(mode, menu);
logic.setReturnRelations(true); // can add relations
setClickableElements();
return true;
}
@SuppressLint("InflateParams")
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu = replaceMenu(menu, mode, this);
menu.clear();
menuUtil.reset();
main.getMenuInflater().inflate(R.menu.undo_action, menu);
MenuItem undo = menu.findItem(R.id.undo_action);
if (logic.getUndo().canUndo() || logic.getUndo().canRedo()) {
undo.setVisible(true);
undo.setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_undo));
}
View undoView = MenuItemCompat.getActionView(undo);
if (undoView == null) { // FIXME this is a temp workaround for pre-11 Android
Preferences prefs = new Preferences(main);
Context context = new ContextThemeWrapper(main, prefs.lightThemeEnabled() ? R.style.Theme_customMain_Light : R.style.Theme_customMain);
undoView = ((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.undo_action_view, null);
}
undoView.setOnClickListener(undoListener);
undoView.setOnLongClickListener(undoListener);
menu.add(Menu.NONE, MENUITEM_TAG, Menu.NONE, R.string.menu_tags).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_tags));
menu.add(Menu.NONE, MENUITEM_DELETE, Menu.CATEGORY_SYSTEM, R.string.delete).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_delete));
// disabled for now menu.add(Menu.NONE, MENUITEM_TAG_LAST, Menu.NONE, R.string.tag_menu_repeat).setIcon(R.drawable.tag_menu_repeat);
// if (!(element instanceof Relation)) {
// menu.add(Menu.NONE, MENUITEM_COPY, Menu.CATEGORY_SECONDARY, R.string.menu_copy).setIcon(ThemeUtils.getResIdFromAttribute(caller.getActivity(),R.attr.menu_copy)).setShowAsAction(menuSize.showAlways());
// menu.add(Menu.NONE, MENUITEM_CUT, Menu.CATEGORY_SECONDARY, R.string.menu_cut).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_cut)).setShowAsAction(menuSize.showAlways());
//}
if (sortedWays != null) {
menu.add(Menu.NONE, MENUITEM_MERGE, Menu.NONE, R.string.menu_merge).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_merge));
}
menu.add(Menu.NONE, MENUITEM_RELATION, Menu.CATEGORY_SYSTEM, R.string.menu_relation).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_relation));
List<Way> selectedWays = logic.getSelectedWays();
if (selectedWays != null && selectedWays.size() >0) {
menu.add(Menu.NONE, MENUITEM_ORTHOGONALIZE, Menu.NONE, R.string.menu_orthogonalize).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_ortho));
}
// // for now just two
// if (selection.size() == 2 && canMerge(selection)) {
// menu.add(Menu.NONE,MENUITEM_MERGE_POLYGONS, Menu.NONE, "Merge polygons");
// }
menu.add(GROUP_BASE, MENUITEM_PREFERENCES, Menu.CATEGORY_SYSTEM|10, R.string.menu_config).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_config));
Preferences prefs = new Preferences(main);
menu.add(GROUP_BASE, MENUITEM_JS_CONSOLE, Menu.CATEGORY_SYSTEM|10, R.string.tag_menu_js_console).setEnabled(prefs.isJsConsoleEnabled());
menu.add(GROUP_BASE, MENUITEM_HELP, Menu.CATEGORY_SYSTEM|10, R.string.menu_help).setAlphabeticShortcut(Util.getShortCut(main, R.string.shortcut_help)).setIcon(ThemeUtils.getResIdFromAttribute(main,R.attr.menu_help));
arrangeMenu(menu);
return true;
}
// private boolean canMerge(ArrayList<OsmElement> selection) {
// for (OsmElement e:selection) {
// if (!(e.getName().equals(Way.NAME) && ((Way)e).isClosed())) {
// return false;
// }
// }
//
// return true;
// }
//
// private ArrayList<OsmElement> merge(ArrayList<OsmElement> selection) {
// if (selection.size() > 1) {
// Way first = (Way) selection.get(0);
// ArrayList<OsmElement> rest = (ArrayList<OsmElement>) selection.subList(1,selection.size());
// ArrayList<OsmElement> newSelection = new ArrayList<OsmElement>();
// for (OsmElement w:rest) {
// Way n = logic.mergeSimplePolygons(first, (Way)w);
// if (n!=null) {
// first = n;
// } else {
// newSelection.add(first);
// first = (Way)w;
// }
// }
// newSelection.add(first);
// return
// }
// }
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case MENUITEM_TAG: main.performTagEdit(selection, false, false); break;
// case MENUITEM_TAG_LAST: main.performTagEdit(element, null, true); break;
case MENUITEM_DELETE: menuDelete(false); break;
// case MENUITEM_COPY: logic.copyToClipboard(element); currentActionMode.finish(); break;
// case MENUITEM_CUT: logic.cutToClipboard(element); currentActionMode.finish(); break;
case MENUITEM_RELATION: main.startSupportActionMode(new AddRelationMemberActionModeCallback(selection)); break;
case MENUITEM_ORTHOGONALIZE:
List<Way> selectedWays = logic.getSelectedWays();
if (selectedWays != null && selectedWays.size() >0) {
logic.performOrthogonalize(main, selectedWays);
}
break;
case MENUITEM_MERGE:
// check if the tags are the same for all ways first ... ignores direction dependent stuff
Map<String,String> firstTags = selection.get(0).getTags();
boolean ok = true;
for (int i=1;i<selection.size();i++) {
if ((firstTags.isEmpty() && !selection.get(i).getTags().isEmpty())
|| !firstTags.entrySet().equals(selection.get(i).getTags().entrySet())) {
ok = false;
}
}
if (!ok) {
Snack.barWarning(main, R.string.toast_potential_merge_tag_conflict);
main.performTagEdit(selection, false, false);
} else {
try {
boolean result = logic.performMerge(main,sortedWays);
// find the remaing way
Way remaining = null;
for (OsmElement w:selection) {
if (!(w.getState()==OsmElement.STATE_DELETED)) {
remaining = (Way) w;
}
}
if (remaining != null) {
main.startSupportActionMode(new WaySelectionActionModeCallback(remaining));
if (!result) { // merge conflict
Snack.barWarning(main, R.string.toast_merge_tag_conflict);
main.performTagEdit(remaining, null, false, false, false);
} else {
invalidate(); // update menubar
}
} else {
Log.e(DEBUG10_TAG,"no merged way");
}
} catch (OsmIllegalOperationException e) {
Snack.barError(main, e.getLocalizedMessage());
}
}
break;
case MENUITEM_PREFERENCES:
PrefEditor.start(main, main.getMap().getViewBox());
break;
case MENUITEM_JS_CONSOLE:
Main.showJsConsole(main);
break;
case R.id.undo_action:
// should not happen
Log.d(DEBUG10_TAG,"menu undo clicked");
undoListener.onClick(null);
break;
default: return false;
}
}
return true;
}
@Override
public boolean handleElementClick(OsmElement element) { // due to clickableElements, only valid elements can be clicked
Log.d(DEBUG10_TAG,"Multi-Select add/remove " + element);
addOrRemoveElement(element);
setClickableElements();
main.invalidateMap();
return true;
}
private void setClickableElements() {
// ArrayList<OsmElement> excludes = new ArrayList<OsmElement>(selection);
// logic.setClickableElements(logic.findClickableElements(excludes));
}
@Override
public void onDestroyActionMode(ActionMode mode) {
Log.d(DEBUG10_TAG,"onDestroyActionMode deselect " + deselect);
super.onDestroyActionMode(mode);
logic.setClickableElements(null);
logic.setReturnRelations(true);
if (deselect) {
logic.deselectAll();
main.invalidateMap();
}
}
private void menuDelete(boolean deleteFromRelations) {
Log.d(DEBUG10_TAG,"menuDelete " + deleteFromRelations + " " + selection);
// check for relation membership
if (!deleteFromRelations) {
for (OsmElement e:selection) {
if (e.hasParentRelations()) {
new AlertDialog.Builder(main)
.setTitle(R.string.delete)
.setMessage(R.string.delete_from_relation_description)
.setPositiveButton(R.string.delete,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
menuDelete(true);
}
})
.show();
return;
}
}
}
logic.performEraseMultipleObjects(main, selection);
currentActionMode.finish();
}
@Override
public boolean onBackPressed() {
Log.d(DEBUG10_TAG,"onBackPressed");
deselect = true;
return super.onBackPressed(); // call the normal stuff
}
}
}