From 197357ca61c9cb39b3dd0506552640fee74fe4ce Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Mon, 8 Jul 2024 15:29:25 -0600 Subject: [PATCH] Allow users to download additional tasks from a specific challenge Signed-off-by: Taylor Smock --- .../maproulette/actions/DownloadTasks.java | 52 ++++++ .../MapRouletteDownloadTask.java | 156 ++++++++++++++---- .../layer/MapRouletteClusteredPointLayer.java | 2 + .../gui/task/list/TaskListPanel.java | 2 + .../maproulette/gui/TaskListPanelTest.java | 4 +- 5 files changed, 181 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/DownloadTasks.java diff --git a/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/DownloadTasks.java b/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/DownloadTasks.java new file mode 100644 index 0000000..8d5fa1a --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/DownloadTasks.java @@ -0,0 +1,52 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.maproulette.actions; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayDeque; +import java.util.List; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.actions.downloadtasks.DownloadParams; +import org.openstreetmap.josm.gui.progress.NullProgressMonitor; +import org.openstreetmap.josm.plugins.maproulette.actions.downloadtasks.MapRouletteDownloadTask; +import org.openstreetmap.josm.plugins.maproulette.api.model.TaskClusteredPoint; +import org.openstreetmap.josm.tools.Shortcut; + +/** + * Download additional nearby tasks for that challenge + */ +public class DownloadTasks extends JosmAction { + /** + * Create a new action for downloading tasks + */ + public DownloadTasks() { + super(tr("Download additional tasks"), "download", tr("Download additional tasks from challenge"), + Shortcut.registerShortcut("maproulette:download_additional_tasks_from_challenge", + tr("MapRoulette: Download additional tasks from challenge"), KeyEvent.CHAR_UNDEFINED, + Shortcut.NONE), + false); + } + + @Override + public void actionPerformed(ActionEvent e) { + final var component = ((JPopupMenu) ((JMenuItem) e.getSource()).getParent()).getInvoker(); + final List objects = ActionUtils.getSelectedItems(component); + final var locations = new ArrayDeque(objects.size()); + for (var obj : objects) { + if (obj instanceof TaskClusteredPoint t) { + locations.add(t); + } + } + while (!locations.isEmpty()) { + final var t = locations.pop(); + new MapRouletteDownloadTask(t.parentId(), t.id()).loadUrl(new DownloadParams(), "", + NullProgressMonitor.INSTANCE); + } + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/downloadtasks/MapRouletteDownloadTask.java b/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/downloadtasks/MapRouletteDownloadTask.java index 9781841..f8e8ce2 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/downloadtasks/MapRouletteDownloadTask.java +++ b/src/main/java/org/openstreetmap/josm/plugins/maproulette/actions/downloadtasks/MapRouletteDownloadTask.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -18,23 +19,44 @@ import org.openstreetmap.josm.actions.downloadtasks.DownloadParams; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.gui.progress.ProgressTaskId; -import org.openstreetmap.josm.io.OsmTransferException; +import org.openstreetmap.josm.gui.util.GuiHelper; +import org.openstreetmap.josm.plugins.maproulette.api.ChallengeAPI; import org.openstreetmap.josm.plugins.maproulette.api.TaskAPI; import org.openstreetmap.josm.plugins.maproulette.api.model.Task; import org.openstreetmap.josm.plugins.maproulette.api.model.TaskClusteredPoint; import org.openstreetmap.josm.plugins.maproulette.api_caching.ChallengeCache; import org.openstreetmap.josm.plugins.maproulette.gui.layer.MapRouletteClusteredPointLayer; -import org.xml.sax.SAXException; /** * A download task for downloading specific tasks */ public class MapRouletteDownloadTask extends AbstractDownloadTask> { private static final Pattern PATTERN_CHALLENGE_TASK = Pattern.compile(".*/challenge/(\\d+)/task/(\\d+)"); - private DownloadTask downloadTask; + private static final Pattern PATTERN_CHALLENGE = Pattern.compile(".*/challenges/(\\d+)"); + private final long challenge; + private final long task; + private PleaseWaitRunnable downloadTask; + + /** + * The default constructor + */ + public MapRouletteDownloadTask() { + this(-1, -1); + } + + /** + * Download tasks from a specific challenge and optionally around the specified task + * @param challenge The challenge to download from + * @param task The task to download around + */ + public MapRouletteDownloadTask(long challenge, long task) { + this.challenge = challenge; + this.task = task; + } @Override public Future download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) { @@ -49,22 +71,45 @@ public Future loadUrl(DownloadParams settings, String url, ProgressMonitor pr this.downloadTask = new DownloadTask(progressMonitor, Long.parseLong(taskId)); return MainApplication.worker.submit(this.downloadTask); } + final var challengeMatcher = PATTERN_CHALLENGE.matcher(url); + if (challengeMatcher.matches()) { + final var challengeId = Long.parseLong(challengeMatcher.group(1)); + this.downloadTask = new DownloadChallenge(progressMonitor, challengeId, -1); + return MainApplication.worker.submit(this.downloadTask); + } + if (challenge > 0) { + this.downloadTask = new DownloadChallenge(progressMonitor, challenge, task); + return MainApplication.worker.submit(this.downloadTask); + } return null; } @Override public void cancel() { - this.downloadTask.cancel(); + if (this.downloadTask instanceof DownloadChallenge c) { + c.cancel(); + } else if (this.downloadTask instanceof DownloadTask t) { + t.cancel(); + } else { + throw new UnsupportedOperationException("Cannot cancel " + this.downloadTask); + } } @Override public String getConfirmationMessage(URL url) { - return tr("Download task {0}", url.toExternalForm()); + if (PATTERN_CHALLENGE_TASK.matcher(url.toExternalForm()).matches()) { + return tr("Download task {0}", url.toExternalForm()); + } + if (PATTERN_CHALLENGE.matcher(url.toExternalForm()).matches()) { + return tr("Download challenge {0}", url.toExternalForm()); + } + throw new IllegalArgumentException("Unsupported url: " + url); } @Override public String[] getPatterns() { - return new String[] { "https?://.*" + PATTERN_CHALLENGE_TASK.pattern() }; + return new String[] { "https?://.*" + PATTERN_CHALLENGE_TASK.pattern(), + "https?://.*" + PATTERN_CHALLENGE.pattern() }; } @Override @@ -72,21 +117,12 @@ public String getTitle() { return tr("Download MapRoulette Task"); } - private static class DownloadTask extends PleaseWaitRunnable { - private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download task"); - private final long[] taskIds; - private Collection tasks; - private boolean isCancelled; + private abstract static class DownloadParent extends PleaseWaitRunnable { + protected Collection tasks; + protected boolean isCancelled; - /** - * Create a new download task - * - * @param progressMonitor The progress monitor to use - * @param ids The ids to download - */ - DownloadTask(ProgressMonitor progressMonitor, long... ids) { - super(tr("Downloading MapRoulette Task {0}", ids[0]), progressMonitor, false); - this.taskIds = ids; + protected DownloadParent(String title, ProgressMonitor progressMonitor, boolean ignoreException) { + super(title, progressMonitor, ignoreException); } @Override @@ -94,19 +130,6 @@ protected void cancel() { this.isCancelled = true; } - @Override - protected void realRun() throws SAXException, IOException, OsmTransferException { - this.tasks = new ArrayList<>(); - for (var taskId : this.taskIds) { - if (this.isCancelled) { - return; - } - final var task = TaskAPI.get(taskId); - this.tasks.add(task); - ChallengeCache.challenge(task.parentId()); - } - } - @Override protected void finish() { if (tasks != null && !tasks.isEmpty()) { @@ -130,6 +153,73 @@ protected void finish() { } } } + } + + private static class DownloadChallenge extends DownloadParent { + private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download challenge"); + private final long challengeId; + private final long task; + + /** + * Create a new download task + * + * @param progressMonitor The progress monitor to use + * @param id The id to download + * @param task The optional task to get tasks around + */ + DownloadChallenge(ProgressMonitor progressMonitor, long id, long task) { + super(tr("Downloading MapRoulette Challenge {0}", id), progressMonitor, false); + this.challengeId = id; + this.task = task; + } + + @Override + protected void realRun() throws IOException { + this.tasks = new ArrayList<>(); + final var challenge = ChallengeCache.challenge(this.challengeId); + if (!this.isCancelled) { + if (challenge.tasksRemaining() != null && challenge.tasksRemaining() > 0) { + this.tasks + .addAll(Arrays.asList(ChallengeAPI.randomTask(challenge.id(), null, null, 10, this.task))); + } else { + GuiHelper.runInEDT(() -> new Notification(tr("Challenge may be done")).show()); + } + } + } + + @Override + public ProgressTaskId canRunInBackground() { + return PROGRESS_TASK_ID; + } + } + + private static class DownloadTask extends DownloadParent { + private static final ProgressTaskId PROGRESS_TASK_ID = new ProgressTaskId("maproulette", "download task"); + private final long[] taskIds; + + /** + * Create a new download task + * + * @param progressMonitor The progress monitor to use + * @param ids The ids to download + */ + DownloadTask(ProgressMonitor progressMonitor, long... ids) { + super(tr("Downloading MapRoulette Task {0}", ids[0]), progressMonitor, false); + this.taskIds = ids; + } + + @Override + protected void realRun() throws IOException { + this.tasks = new ArrayList<>(); + for (var taskId : this.taskIds) { + if (this.isCancelled) { + return; + } + final var task = TaskAPI.get(taskId); + this.tasks.add(task); + ChallengeCache.challenge(task.parentId()); + } + } @Override public ProgressTaskId canRunInBackground() { diff --git a/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/layer/MapRouletteClusteredPointLayer.java b/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/layer/MapRouletteClusteredPointLayer.java index 537b875..509755e 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/layer/MapRouletteClusteredPointLayer.java +++ b/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/layer/MapRouletteClusteredPointLayer.java @@ -45,6 +45,7 @@ import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage; import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol; +import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.plugins.maproulette.api.MRColors; import org.openstreetmap.josm.plugins.maproulette.api.enums.TaskStatus; import org.openstreetmap.josm.plugins.maproulette.api.model.Identifier; @@ -157,6 +158,7 @@ public synchronized void refreshTasks(Map tc final Map updateCollection = this.pointBucket.stream() .collect(Collectors.toMap(Identifier::id, c -> c)); this.updatedDataListeners.fireEvent(consumer -> consumer.accept(updateCollection)); + GuiHelper.runInEDT(this::invalidate); } @Override diff --git a/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/task/list/TaskListPanel.java b/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/task/list/TaskListPanel.java index ce8b21d..83ba8fb 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/task/list/TaskListPanel.java +++ b/src/main/java/org/openstreetmap/josm/plugins/maproulette/gui/task/list/TaskListPanel.java @@ -37,6 +37,7 @@ import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.FilterField; +import org.openstreetmap.josm.plugins.maproulette.actions.DownloadTasks; import org.openstreetmap.josm.plugins.maproulette.actions.GoToTaskLocation; import org.openstreetmap.josm.plugins.maproulette.actions.IgnoreAction; import org.openstreetmap.josm.plugins.maproulette.actions.downloadtasks.MapRouletteDownloadTaskBox; @@ -125,6 +126,7 @@ public boolean include(Entry entry) layers.forEach(MapRouletteClusteredPointLayer::invalidate); }); menu.add(new GoToTaskLocation()); + menu.add(new DownloadTasks()); menu.add(new IgnoreAction(IgnoreAction.IgnoreType.IGNORE_TASK)); menu.add(new IgnoreAction(IgnoreAction.IgnoreType.IGNORE_CHALLENGE)); table.setComponentPopupMenu(menu); diff --git a/src/test/unit/org/openstreetmap/josm/plugins/maproulette/gui/TaskListPanelTest.java b/src/test/unit/org/openstreetmap/josm/plugins/maproulette/gui/TaskListPanelTest.java index d94474d..40610bb 100644 --- a/src/test/unit/org/openstreetmap/josm/plugins/maproulette/gui/TaskListPanelTest.java +++ b/src/test/unit/org/openstreetmap/josm/plugins/maproulette/gui/TaskListPanelTest.java @@ -49,8 +49,8 @@ public static void fireIgnoreAction(TaskListPanel panel, IgnoreAction.IgnoreType .getComponent(0 /* A panel */)).getComponent(2 /* The table */)); final var menu = table.getComponentPopupMenu(); final var menuItem = (JMenuItem) switch (ignoreType) { - case IGNORE_TASK -> menu.getComponent(1); - case IGNORE_CHALLENGE -> menu.getComponent(2); + case IGNORE_TASK -> menu.getComponent(2); + case IGNORE_CHALLENGE -> menu.getComponent(3); }; menu.setInvoker(table); final var action = menuItem.getAction();