Skip to content

Commit

Permalink
Merge branch 'paste_multiple_undo'
Browse files Browse the repository at this point in the history
  • Loading branch information
simonpoole committed Feb 23, 2025
2 parents bd9cda9 + 485303e commit af2fe9e
Show file tree
Hide file tree
Showing 26 changed files with 672 additions and 128 deletions.
8 changes: 6 additions & 2 deletions documentation/docs/help/en/Simple actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ Create a new untagged OSM Node at the tapped position, merging with nearby objec

### Paste object

If an OSM object has been copied or cut to the clipboard paste it at the tapped position and then select it. This menu item will only be shown if there is something in the clipboard.
If an OSM object has been copied or cut to the clipboard paste it at the tapped position and then select it. This menu item will only be shown if there is something in the clipboard.

There are a total of 5 clipboards that can be used, cutting or copying will create a new clipboard that will be used as the default. If the limit of 5 clipboards is reached, the "oldest" one will be deleted. If you want to use a previously created clipboard instead of the most recent one, clicking on the button in the menu will move it to the "top". If a preset can be determined for the items in the clipboard, an icon will be displayed instead of the clipboard number.

### Paste multiple times

If an OSM object has been copied to the clipboard paste it at the tapped position and remain in the same mode allowing to be pasted repeatedly. The mode can then be exited by pressing the back button or the back arrow in the title bar. This menu item will only be shown if there is something in the clipboard. This item is not shown for cut items as they can only be pasted once.
If an OSM object has been copied to the clipboard paste it at the tapped position and remain in the same mode allowing to be pasted repeatedly. The mode can then be exited by pressing the back button or the back arrow in the title bar. This menu item will only be shown if there is something in the clipboard.

See above for clipboard selection.

## In Address mode

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
import de.blau.android.TestUtils;
import de.blau.android.dialogs.Tip;
import de.blau.android.layer.LayerType;
import de.blau.android.osm.ClipboardStorage;
import de.blau.android.osm.Node;
import de.blau.android.osm.StorageDelegator;
import de.blau.android.osm.Tags;
import de.blau.android.osm.Way;
import de.blau.android.prefs.AdvancedPrefDatabase;
import de.blau.android.prefs.Preferences;
Expand Down Expand Up @@ -447,4 +449,70 @@ public void longClickTip() {
assertTrue(TestUtils.findText(device, false, main.getString(R.string.tip_title)));
TestUtils.clickAwayTip(device, main);
}

/**
* Copy node and then paste it
*/
@Test
public void copyPaste() {
copyNode();
TestUtils.clickSimpleButton(device);
assertTrue(TestUtils.clickText(device, false, context.getString(R.string.menu_paste_object), true, false));
assertTrue(TestUtils.findText(device, false, context.getString(R.string.simple_paste)));
TestUtils.clickAtCoordinates(device, map, 8.3893454, 47.3901898, true);
assertTrue(TestUtils.findText(device, false, context.getString(R.string.actionmode_nodeselect)));

Node pastedNode = App.getLogic().getSelectedNode();
assertNotNull(pastedNode);
assertTrue(pastedNode.getOsmId() < 0);
assertTrue(pastedNode.hasTagWithValue(Tags.KEY_AMENITY, "toilets"));

TestUtils.clickUp(device);
}

/**
* Copy node then paste and undo the paste
*/
@Test
public void copyPasteMultiple() {
copyNode();
StorageDelegator d = App.getDelegator();
int count = d.getApiElementCount();
TestUtils.clickSimpleButton(device);
assertTrue(TestUtils.clickText(device, false, context.getString(R.string.menu_paste_multiple), true, false));
assertTrue(TestUtils.findText(device, false, context.getString(R.string.simple_paste_multiple)));
assertFalse(TestUtils.clickMenuButton(device, context.getString(R.string.undo), false, false));
TestUtils.clickAtCoordinates(device, map, 8.3893454, 47.3901898, true);
assertTrue(TestUtils.findText(device, false, context.getString(R.string.simple_paste_multiple)));
assertEquals(count + 1, d.getApiElementCount());
assertTrue(TestUtils.clickMenuButton(device, context.getString(R.string.undo), false, false));
assertTrue(TestUtils.findText(device, false, context.getString(R.string.simple_paste_multiple)));
assertEquals(count, d.getApiElementCount());
TestUtils.clickUp(device);
}

/**
* copy an existing node
*/
private void copyNode() {
StorageDelegator d = App.getDelegator();
List<ClipboardStorage> clipboards = d.getClipboards();
assertTrue(clipboards.isEmpty());
TestUtils.zoomToLevel(device, main, 21);
TestUtils.unlock(device);
TestUtils.clickAtCoordinates(device, map, 8.38782, 47.390339, true);
TestUtils.clickAwayTip(device, context);
TestUtils.clickTextContains(device, "Toilets", true, 1000);
Node node = App.getLogic().getSelectedNode();
assertNotNull(node);
assertEquals(3465444349L, node.getOsmId());
assertTrue(TestUtils.findText(device, false, context.getString(R.string.actionmode_nodeselect)));
if (!TestUtils.clickMenuButton(device, context.getString(R.string.menu_copy), false, false)) {
TestUtils.clickOverflowButton(device);
TestUtils.scrollTo(context.getString(R.string.menu_copy), true);
assertTrue(TestUtils.clickText(device, false, context.getString(R.string.menu_copy), false));
}
assertEquals(1, clipboards.size());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -657,15 +657,15 @@ public void cutPasteWay() {
logic.setSelectedNode(null);
logic.cutToClipboard(null, w1);
assertEquals(OsmElement.STATE_DELETED, w1.getState());
logic.pasteFromClipboard(null, 500.0f, 500.0f);
logic.pasteFromClipboard(null, 0, 500.0f, 500.0f);
assertEquals(OsmElement.STATE_CREATED, w1.getState());

// w1 should now have both nodes in the same place
App.getDelegator().moveNode(w1.getLastNode(), w1.getFirstNode().getLat(), w1.getFirstNode().getLon());

logic.cutToClipboard(null, w1);
assertEquals(OsmElement.STATE_DELETED, w1.getState());
logic.pasteFromClipboard(null, 0.0f, 0.0f);
logic.pasteFromClipboard(null, 0, 0.0f, 0.0f);
assertEquals(OsmElement.STATE_CREATED, w1.getState());

} catch (Exception igit) {
Expand Down Expand Up @@ -694,7 +694,7 @@ public void cutPasteClosedWay() {
logic.setSelectedNode(null);
logic.cutToClipboard(null, w1);
assertEquals(OsmElement.STATE_DELETED, w1.getState());
logic.pasteFromClipboard(null, 500.0f, 500.0f);
logic.pasteFromClipboard(null, 0, 500.0f, 500.0f);
assertEquals(OsmElement.STATE_CREATED, w1.getState());

// collapse the area
Expand All @@ -706,7 +706,7 @@ public void cutPasteClosedWay() {

logic.cutToClipboard(null, w1);
assertEquals(OsmElement.STATE_DELETED, w1.getState());
logic.pasteFromClipboard(null, 0.0f, 0.0f);
logic.pasteFromClipboard(null, 0, 0.0f, 0.0f);
assertEquals(OsmElement.STATE_CREATED, w1.getState());

} catch (Exception igit) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/assets/help/en/Simple actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ <h3>Add node</h3>
<p>Create a new untagged OSM Node at the tapped position, merging with nearby objects. This is for example useful if you simply want to add a Node to an existing Way.</p>
<h3>Paste object</h3>
<p>If an OSM object has been copied or cut to the clipboard paste it at the tapped position and then select it. This menu item will only be shown if there is something in the clipboard.</p>
<p>There are a total of 5 clipboards that can be used, cutting or copying will create a new clipboard that will be used as the default. If the limit of 5 clipboards is reached, the &quot;oldest&quot; one will be deleted. If you want to use a previously created clipboard instead of the most recent one, clicking on the button in the menu will move it to the &quot;top&quot;. If a preset can be determined for the items in the clipboard, an icon will be displayed instead of the clipboard number.</p>
<h3>Paste multiple times</h3>
<p>If an OSM object has been copied to the clipboard paste it at the tapped position and remain in the same mode allowing to be pasted repeatedly. The mode can then be exited by pressing the back button or the back arrow in the title bar. This menu item will only be shown if there is something in the clipboard. This item is not shown for cut items as they can only be pasted once.</p>
<p>If an OSM object has been copied to the clipboard paste it at the tapped position and remain in the same mode allowing to be pasted repeatedly. The mode can then be exited by pressing the back button or the back arrow in the title bar. This menu item will only be shown if there is something in the clipboard.</p>
<p>See above for clipboard selection.</p>
<h2>In Address mode</h2>
<h3>Add address node</h3>
<p>Adds a node at the clicked location and adds address tags with prediction. If the node is part of a building way it will further add &quot;entrance=yes&quot; if not present.</p>
Expand Down
53 changes: 39 additions & 14 deletions src/main/java/de/blau/android/Logic.java
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,18 @@ public UndoStorage getUndo() {
*/
@Nullable
public String undo() {
return postUndo(getDelegator(), getDelegator().getUndo().undo());
return undo(true);
}

/**
* Undo the last checkpoint
*
* @param updateUI update selection etc
* @return checkpoint name or null if none available
*/
@Nullable
public String undo(boolean updateUI) {
return postUndo(getDelegator(), getDelegator().getUndo().undo(), updateUI);
}

/**
Expand All @@ -490,19 +501,20 @@ public String undo() {
*/
@Nullable
public String undo(int checkpoint) {
return postUndo(getDelegator(), getDelegator().getUndo().undo(checkpoint));
return postUndo(getDelegator(), getDelegator().getUndo().undo(checkpoint), true);
}

/**
* Post undo tasks
*
* @param delegator the current StorageDelegator instance
* @param undone the undone Checkpoint
* @param updateUI update selection etc
* @return checkpoint name or null if none available
*/
private String postUndo(@NonNull final StorageDelegator delegator, @Nullable Checkpoint undone) {
private String postUndo(@NonNull final StorageDelegator delegator, @Nullable Checkpoint undone, boolean updateUI) {
Selection.Ids ids = undone != null ? undone.getSelection() : null;
if (ids != null && map != null && map.getContext() instanceof Main) {
if (updateUI && ids != null && map != null && map.getContext() instanceof Main) {
Main main = (Main) map.getContext();
final EasyEditManager easyEditManager = main.getEasyEditManager();
easyEditManager.finish();
Expand Down Expand Up @@ -555,7 +567,7 @@ public String redo(int checkpoint) {
* Undo without creating a redo checkpoint
*/
public void rollback() {
postUndo(getDelegator(), getDelegator().getUndo().undo(false));
postUndo(getDelegator(), getDelegator().getUndo().undo(false), true);
}

/**
Expand Down Expand Up @@ -5796,26 +5808,33 @@ public void hideCrosshairs() {
/**
* Copy element to clipboard
*
* @param activity optional activity we were called from
* @param element element to copy
*/
public void copyToClipboard(@NonNull OsmElement element) {
public void copyToClipboard(@Nullable FragmentActivity activity, @NonNull OsmElement element) {
List<OsmElement> list = new ArrayList<>();
list.add(element);
copyToClipboard(list);
copyToClipboard(activity, list);
}

/**
* Copy elements to clipboard
*
* @param activity optional activity we were called from
* @param elements elements to copy
*/
public void copyToClipboard(@NonNull List<OsmElement> elements) {
public void copyToClipboard(@Nullable FragmentActivity activity, @NonNull List<OsmElement> elements) {
int[] centroid = calcCentroid(elements);
if (centroid.length != 2) {
Log.e(DEBUG_TAG, "Unable to determine centroid");
return;
}
getDelegator().copyToClipboard(elements, centroid[0], centroid[1]);
try {
getDelegator().copyToClipboard(elements, centroid[0], centroid[1]);
} catch (OsmIllegalOperationException | StorageException ex) {
handleDelegatorException(activity, ex);
// don't rethrow
}
}

/**
Expand All @@ -5824,7 +5843,7 @@ public void copyToClipboard(@NonNull List<OsmElement> elements) {
* @param activity the activity we were called from
* @param element element to cut
*/
public void cutToClipboard(@Nullable Activity activity, @NonNull OsmElement element) {
public void cutToClipboard(@Nullable FragmentActivity activity, @NonNull OsmElement element) {
List<OsmElement> list = new ArrayList<>();
list.add(element);
cutToClipboard(activity, list);
Expand All @@ -5836,14 +5855,19 @@ public void cutToClipboard(@Nullable Activity activity, @NonNull OsmElement elem
* @param activity the activity we were called from
* @param elements the elements to cut
*/
public void cutToClipboard(@Nullable Activity activity, @NonNull List<OsmElement> elements) {
public void cutToClipboard(@Nullable FragmentActivity activity, @NonNull List<OsmElement> elements) {
createCheckpoint(activity, R.string.undo_action_cut);
int[] centroid = calcCentroid(elements);
if (centroid.length != 2) {
Log.e(DEBUG_TAG, "Unable to determine centroid");
return;
}
getDelegator().cutToClipboard(elements, centroid[0], centroid[1]);
try {
getDelegator().cutToClipboard(elements, centroid[0], centroid[1]);
} catch (OsmIllegalOperationException | StorageException ex) {
handleDelegatorException(activity, ex);
// don't rethrow
}
invalidateMap();
}

Expand Down Expand Up @@ -5900,16 +5924,17 @@ private int[] calcCentroid(@NonNull List<OsmElement> elements) {
* Paste current contents of the clipboard
*
* @param activity the activity we were called from
* @param index index of the clipboard to use
* @param x screen x to position the object at
* @param y screen y to position the object at
* @return the pasted objects or null if the clipboard was empty
*/
@Nullable
public List<OsmElement> pasteFromClipboard(@Nullable Activity activity, float x, float y) {
public List<OsmElement> pasteFromClipboard(@Nullable Activity activity, int index, float x, float y) {
createCheckpoint(activity, R.string.undo_action_paste);
int lat = yToLatE7(y);
int lon = xToLonE7(x);
return getDelegator().pasteFromClipboard(lat, lon);
return getDelegator().pasteFromClipboard(index, lat, lon);
}

/**
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/de/blau/android/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3516,7 +3516,11 @@ public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}

public class UndoListener implements OnClickListener, OnLongClickListener {
public interface UndoInterface {
void undo(@NonNull final Logic logic);
}

public class UndoListener implements UndoInterface, OnClickListener, OnLongClickListener {

private static final String DEBUG_TAG = "UndoListener";

Expand All @@ -3543,10 +3547,11 @@ public void onClick(View view) {
*
* @param logic the current Logic instance
*/
void undo(@NonNull final Logic logic) {
@Override
public void undo(@NonNull final Logic logic) {
String name = logic.undo();
if (name != null) {
ScreenMessage.toastTopInfo(Main.this, getResources().getString(R.string.undo) + ": " + name);
ScreenMessage.toastTopInfo(Main.this, getString(R.string.undo_message, name));
} else {
ScreenMessage.toastTopInfo(Main.this, R.string.undo_nothing);
}
Expand Down
Loading

0 comments on commit af2fe9e

Please sign in to comment.