diff --git a/src/main/java/de/blau/android/easyedit/EasyEditActionModeCallback.java b/src/main/java/de/blau/android/easyedit/EasyEditActionModeCallback.java index e40b6aa1e..fe31d3df1 100644 --- a/src/main/java/de/blau/android/easyedit/EasyEditActionModeCallback.java +++ b/src/main/java/de/blau/android/easyedit/EasyEditActionModeCallback.java @@ -34,6 +34,8 @@ import de.blau.android.R; import de.blau.android.dialogs.ElementIssueDialog; import de.blau.android.dialogs.ErrorAlert; +import de.blau.android.exception.OsmIllegalOperationException; +import de.blau.android.exception.StorageException; import de.blau.android.osm.Node; import de.blau.android.osm.OsmElement; import de.blau.android.osm.RelationUtils; @@ -553,6 +555,45 @@ public void onError(@Nullable AsyncResult result) { } } + /** + * Extract a segment(s) from a List of Way given two nodes on all the Ways + * + * @param ways a List of Ways + * @param n1 1st Node + * @param n2 2nd Node + * @return a Runnable that will actually do the work + */ + @NonNull + protected Runnable extractSegment(@NonNull final List ways, @NonNull final Node n1, @NonNull final Node n2) { + return () -> { + try { + List segments = new ArrayList<>(); + for (Way way : ways) { + List result = logic.performExtractSegment(main, way, n1, n2); + checkSplitResult(way, result); + Way segment = newWayFromSplitResult(result); + if (segment == null) { + throw new OsmIllegalOperationException("null segment"); + } + segments.add(segment); + } + if (segments.size() == 1) { + Way segment = (Way) segments.get(0); + if (segment.hasTagKey(Tags.KEY_HIGHWAY) || segment.hasTagKey(Tags.KEY_WATERWAY)) { + main.startSupportActionMode(new WaySegmentModifyActionModeCallback(manager, segment)); + } else { + main.startSupportActionMode(new WaySelectionActionModeCallback(manager, segment)); + } + } else { + main.startSupportActionMode(new MultiSelectActionModeCallback(manager, segments)); + } + } catch (OsmIllegalOperationException | StorageException ex) { + // toast has already been displayed + manager.finish(); + } + }; + } + /** * De-select elements * diff --git a/src/main/java/de/blau/android/easyedit/MultiSelectActionModeCallback.java b/src/main/java/de/blau/android/easyedit/MultiSelectActionModeCallback.java index 891173d3e..373e5fc52 100644 --- a/src/main/java/de/blau/android/easyedit/MultiSelectActionModeCallback.java +++ b/src/main/java/de/blau/android/easyedit/MultiSelectActionModeCallback.java @@ -222,39 +222,40 @@ public boolean elementsOnly() { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (!super.onActionItemClicked(mode, item)) { - switch (item.getItemId()) { - case ElementSelectionActionModeCallback.MENUITEM_TAG: - main.performTagEdit(selection, false, false); - break; - case MENUITEM_ZOOM_TO_SELECTION: - main.zoomTo(selection); - main.invalidateMap(); - break; - case MENUITEM_SEARCH_OBJECTS: - Search.search(main); - break; - case MENUITEM_ADD_TO_TODO: - ElementSelectionActionModeCallback.addToTodoList(main, manager, selection); - break; - case MENUITEM_UPLOAD: - main.descheduleAutoLock(); - main.confirmUpload(ElementSelectionActionModeCallback.addRequiredElements(main, new ArrayList<>(selection))); - break; - case ElementSelectionActionModeCallback.MENUITEM_PREFERENCES: - PrefEditor.start(main); - break; - case ElementSelectionActionModeCallback.MENUITEM_JS_CONSOLE: - Main.showJsConsole(main); - break; - case R.id.undo_action: - // should not happen - Log.d(DEBUG_TAG, "menu undo clicked"); - undoListener.onClick(null); - break; - default: - return false; - } + if (super.onActionItemClicked(mode, item)) { + return true; + } + switch (item.getItemId()) { + case ElementSelectionActionModeCallback.MENUITEM_TAG: + main.performTagEdit(selection, false, false); + break; + case MENUITEM_ZOOM_TO_SELECTION: + main.zoomTo(selection); + main.invalidateMap(); + break; + case MENUITEM_SEARCH_OBJECTS: + Search.search(main); + break; + case MENUITEM_ADD_TO_TODO: + ElementSelectionActionModeCallback.addToTodoList(main, manager, selection); + break; + case MENUITEM_UPLOAD: + main.descheduleAutoLock(); + main.confirmUpload(ElementSelectionActionModeCallback.addRequiredElements(main, new ArrayList<>(selection))); + break; + case ElementSelectionActionModeCallback.MENUITEM_PREFERENCES: + PrefEditor.start(main); + break; + case ElementSelectionActionModeCallback.MENUITEM_JS_CONSOLE: + Main.showJsConsole(main); + break; + case R.id.undo_action: + // should not happen + Log.d(DEBUG_TAG, "menu undo clicked"); + undoListener.onClick(null); + break; + default: + return false; } return true; } diff --git a/src/main/java/de/blau/android/easyedit/MultiSelectWithGeometryActionModeCallback.java b/src/main/java/de/blau/android/easyedit/MultiSelectWithGeometryActionModeCallback.java index f96ec9aaf..d36eb98d1 100644 --- a/src/main/java/de/blau/android/easyedit/MultiSelectWithGeometryActionModeCallback.java +++ b/src/main/java/de/blau/android/easyedit/MultiSelectWithGeometryActionModeCallback.java @@ -14,8 +14,10 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; import de.blau.android.App; +import de.blau.android.DisambiguationMenu; import de.blau.android.Map; import de.blau.android.R; +import de.blau.android.DisambiguationMenu.Type; import de.blau.android.dialogs.ElementIssueDialog; import de.blau.android.exception.OsmIllegalOperationException; import de.blau.android.osm.Node; @@ -23,6 +25,7 @@ import de.blau.android.osm.OsmElement.ElementType; import de.blau.android.osm.Relation; import de.blau.android.osm.Result; +import de.blau.android.osm.Storage; import de.blau.android.osm.StorageDelegator; import de.blau.android.osm.Tags; import de.blau.android.osm.Way; @@ -46,11 +49,13 @@ public class MultiSelectWithGeometryActionModeCallback extends MultiSelectAction private static final int MENUITEM_INTERSECT = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 5; private static final int MENUITEM_CREATE_CIRCLE = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 6; private static final int MENUITEM_ROTATE = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 7; + private static final int MENUITEM_EXTRACT_SEGMENT = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 8; private MenuItem mergeItem; private MenuItem orthogonalizeItem; private MenuItem intersectItem; private MenuItem createCircleItem; + private MenuItem extractSegmentItem; /** * Construct an Multi-Select actionmode from a List of OsmElements @@ -87,6 +92,8 @@ public boolean onCreateActionMode(@NonNull ActionMode mode, @NonNull Menu menu) mergeItem = menu.add(Menu.NONE, MENUITEM_MERGE, Menu.NONE, R.string.menu_merge).setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_merge)); + extractSegmentItem = menu.add(Menu.NONE, MENUITEM_EXTRACT_SEGMENT, Menu.NONE, R.string.menu_extract_segment); + menu.add(Menu.NONE, MENUITEM_RELATION, Menu.CATEGORY_SYSTEM, R.string.menu_relation) .setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_relation)); @@ -123,6 +130,11 @@ public boolean onPrepareActionMode(@NonNull ActionMode mode, @NonNull Menu menu) updated |= ElementSelectionActionModeCallback.setItemVisibility(countType(ElementType.NODE) >= StorageDelegator.MIN_NODES_CIRCLE, createCircleItem, false); + if (selection.size() == 2 && selection.get(0) instanceof Node && selection.get(1) instanceof Node) { + List commonWays = getWaysForNodes((Node) selection.get(0), (Node) selection.get(1)); + updated |= ElementSelectionActionModeCallback.setItemVisibility(!commonWays.isEmpty(), extractSegmentItem, true); + } + if (updated) { arrangeMenu(menu); } @@ -176,60 +188,96 @@ private boolean canMergePolygons(@NonNull List selection) { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (!super.onActionItemClicked(mode, item)) { - switch (item.getItemId()) { - case ElementSelectionActionModeCallback.MENUITEM_DELETE: - menuDelete(false); - break; - case ElementSelectionActionModeCallback.MENUITEM_COPY: - logic.copyToClipboard(selection); - mode.finish(); - break; - case ElementSelectionActionModeCallback.MENUITEM_CUT: - logic.cutToClipboard(main, selection); - mode.finish(); - break; - case MENUITEM_RELATION: - ElementSelectionActionModeCallback.buildPresetSelectDialog(main, - p -> main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, - p != null ? p.getPath(App.getCurrentRootPreset(main).getRootGroup()) : null, selection)), - ElementType.RELATION, R.string.select_relation_type_title, Tags.KEY_TYPE, null).show(); - break; - case MENUITEM_ADD_RELATION_MEMBERS: - ElementSelectionActionModeCallback.buildRelationSelectDialog(main, r -> { - Relation relation = (Relation) App.getDelegator().getOsmElement(Relation.NAME, r); - if (relation != null) { - main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, relation, selection)); - } - }, -1, R.string.select_relation_title, null, null, selection).show(); - break; - case MENUITEM_ORTHOGONALIZE: - orthogonalizeWays(); - break; - case MENUITEM_MERGE: - if (canMergePolygons(selection)) { - mergePolygons(); - } else { - mergeWays(); + if (super.onActionItemClicked(mode, item)) { + return true; + } + switch (item.getItemId()) { + case ElementSelectionActionModeCallback.MENUITEM_DELETE: + menuDelete(false); + break; + case ElementSelectionActionModeCallback.MENUITEM_COPY: + logic.copyToClipboard(selection); + mode.finish(); + break; + case ElementSelectionActionModeCallback.MENUITEM_CUT: + logic.cutToClipboard(main, selection); + mode.finish(); + break; + case MENUITEM_RELATION: + ElementSelectionActionModeCallback.buildPresetSelectDialog(main, + p -> main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, + p != null ? p.getPath(App.getCurrentRootPreset(main).getRootGroup()) : null, selection)), + ElementType.RELATION, R.string.select_relation_type_title, Tags.KEY_TYPE, null).show(); + break; + case MENUITEM_ADD_RELATION_MEMBERS: + ElementSelectionActionModeCallback.buildRelationSelectDialog(main, r -> { + Relation relation = (Relation) App.getDelegator().getOsmElement(Relation.NAME, r); + if (relation != null) { + main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, relation, selection)); } - break; - case MENUITEM_INTERSECT: - intersectWays(); - break; - case MENUITEM_CREATE_CIRCLE: - createCircle(); - break; - case MENUITEM_ROTATE: - deselectOnExit = false; - main.startSupportActionMode(new RotationActionModeCallback(manager)); - break; - default: - return false; + }, -1, R.string.select_relation_title, null, null, selection).show(); + break; + case MENUITEM_ORTHOGONALIZE: + orthogonalizeWays(); + break; + case MENUITEM_MERGE: + if (canMergePolygons(selection)) { + mergePolygons(); + } else { + mergeWays(); } + break; + case MENUITEM_INTERSECT: + intersectWays(); + break; + case MENUITEM_CREATE_CIRCLE: + createCircle(); + break; + case MENUITEM_ROTATE: + deselectOnExit = false; + main.startSupportActionMode(new RotationActionModeCallback(manager)); + break; + case MENUITEM_EXTRACT_SEGMENT: + extractSegment(); + break; + default: + return false; } return true; } + /** + * Extract a segment from way(s) between two nodes + */ + private void extractSegment() { + if (selection.size() == 2 && selection.get(0) instanceof Node && selection.get(1) instanceof Node) { + final Node node1 = (Node) selection.get(0); + final Node node2 = (Node) selection.get(1); + List commonWays = getWaysForNodes(node1, node2); + if (!commonWays.isEmpty()) { + if (commonWays.size() == 1) { + splitSafe(commonWays, extractSegment(commonWays, node1, node2)); + } else { + DisambiguationMenu menu = new DisambiguationMenu(main.getMap()); + menu.setHeaderTitle(R.string.select_way_to_extract_from); + int id = 0; + menu.add(id, Type.WAY, main.getString(R.string.split_all_ways), + (int position) -> splitSafe(commonWays, extractSegment(commonWays, node1, node2))); + id++; + for (Way w : commonWays) { + menu.add(id, Type.WAY, w.getDescription(main), + (int position) -> splitSafe(Util.wrapInList(w), extractSegment(Util.wrapInList(w), node1, node2))); + id++; + } + menu.show(); + } + return; + } + } + Log.e(DEBUG_TAG, "extractSegment called but selection is invalid"); + + } + /** * Check if the current selection are ways that can be intersected * @@ -338,6 +386,32 @@ private void mergePolygons() { } } + /** + * Get a list of all the Ways common to the two given Nodes. + * + * @param node1 the 1st Node + * @param node2 the 2nd Node + * @return A list of all Ways connected to both Nodes + */ + @NonNull + public List getWaysForNodes(@NonNull final Node node1, @NonNull final Node node2) { + List result = new ArrayList<>(); + final Storage currentStorage = App.getDelegator().getCurrentStorage(); + List ways1 = currentStorage.getWays(node1); + List ways2 = currentStorage.getWays(node2); + if (ways1.size() < ways2.size()) { + List temp = ways2; + ways2 = ways1; + ways1 = temp; + } + for (Way w : ways1) { + if (ways2.contains(w)) { + result.add(w); + } + } + return result; + } + /** * Delete action * diff --git a/src/main/java/de/blau/android/easyedit/WaySegmentActionModeCallback.java b/src/main/java/de/blau/android/easyedit/WaySegmentActionModeCallback.java index d901f6a9f..f32b2fd3f 100644 --- a/src/main/java/de/blau/android/easyedit/WaySegmentActionModeCallback.java +++ b/src/main/java/de/blau/android/easyedit/WaySegmentActionModeCallback.java @@ -9,12 +9,8 @@ import de.blau.android.App; import de.blau.android.Logic; import de.blau.android.R; -import de.blau.android.exception.OsmIllegalOperationException; -import de.blau.android.exception.StorageException; import de.blau.android.osm.Node; import de.blau.android.osm.OsmElement; -import de.blau.android.osm.Result; -import de.blau.android.osm.Tags; import de.blau.android.osm.Way; import de.blau.android.util.Geometry; import de.blau.android.util.SerializableState; @@ -117,21 +113,8 @@ public boolean handleElementClick(OsmElement element) { // due to clickableEleme if (segmentNodes.length == 2) { final Node n1 = segmentNodes[0]; final Node n2 = segmentNodes[1]; - splitSafe(Util.wrapInList(way), () -> { - try { - List result = logic.performExtractSegment(main, way, n1, n2); - checkSplitResult(way, result); - Way segment = newWayFromSplitResult(result); - if (segment.hasTagKey(Tags.KEY_HIGHWAY) || segment.hasTagKey(Tags.KEY_WATERWAY)) { - main.startSupportActionMode(new WaySegmentModifyActionModeCallback(manager, segment)); - } else { - main.startSupportActionMode(new WaySelectionActionModeCallback(manager, segment)); - } - } catch (OsmIllegalOperationException | StorageException ex) { - // toast has already been displayed - manager.finish(); - } - }); + final List wayList = Util.wrapInList(way); + splitSafe(wayList, extractSegment(wayList, n1, n2)); } return true; } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6b6a9ddc3..9074b064f 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1223,6 +1223,7 @@ Snap Follow way Select way to follow + Select way to extract segment from Add node Tap the screen position