diff --git a/src/org/openstreetmap/josm/actions/history/DownloadHistorySelectionAction.java b/src/org/openstreetmap/josm/actions/history/DownloadHistorySelectionAction.java new file mode 100644 index 00000000000..e2b24305787 --- /dev/null +++ b/src/org/openstreetmap/josm/actions/history/DownloadHistorySelectionAction.java @@ -0,0 +1,68 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.actions.history; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.Collection; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.history.HistoryLoadTask; +import org.openstreetmap.josm.io.NetworkManager; +import org.openstreetmap.josm.io.OnlineResource; +import org.openstreetmap.josm.tools.Shortcut; +import org.openstreetmap.josm.tools.Utils; + +/** + * This action downloads and caches the history for selected primitives. + * @since 1670 + */ +public class DownloadHistorySelectionAction extends JosmAction { + + /** + * Constructs a new {@code UpdateSelectionAction}. + */ + public DownloadHistorySelectionAction() { + super(tr("Download history for selection"), "download", + tr("Downloads and caches the history for currently selected objects from the server"), + Shortcut.registerShortcut("file:downloadhistoryselection", + tr("File: {0}", tr("Download in current view")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), + true, "downloadhistoryselection", true); + } + + @Override + protected void updateEnabledState() { + updateEnabledStateOnCurrentSelection(); + } + + @Override + protected void updateEnabledState(Collection selection) { + if (Utils.isEmpty(selection)) { + setEnabled(false); + } else { + setEnabled(!NetworkManager.isOffline(OnlineResource.OSM_API) && + selection.stream().anyMatch(p -> !p.isNew())); + } + } + + @Override + public void actionPerformed(ActionEvent arg0) { + if (!isEnabled()) + return; + + HistoryLoadTask task = new HistoryLoadTask(); + task.addOsmPrimitives(getData()); + MainApplication.worker.submit(task); + } + + /** + * Returns the data on which this action operates. Override if needed. + * @return the data on which this action operates + */ + public Collection getData() { + return getLayerManager().getActiveDataSet().getAllSelected(); + } +} diff --git a/src/org/openstreetmap/josm/data/osm/history/History.java b/src/org/openstreetmap/josm/data/osm/history/History.java index 97d7c10ae85..5f0e13bb8bb 100644 --- a/src/org/openstreetmap/josm/data/osm/history/History.java +++ b/src/org/openstreetmap/josm/data/osm/history/History.java @@ -4,6 +4,7 @@ import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -266,6 +267,14 @@ public HistoryOsmPrimitive getLatest() { return sortDescending().versions.get(0); } + /** + * Returns an immutable list of entries of this history. + * @return a list of historic primitive versions in this history + */ + public List getAsList() { + return Collections.unmodifiableList(versions); + } + /** * Replies the number of versions. * @return the number of versions diff --git a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java index 0e6612d0d7a..848b04118e4 100644 --- a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java +++ b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java @@ -37,6 +37,8 @@ import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Tagged; import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.osm.history.History; +import org.openstreetmap.josm.data.osm.history.HistoryDataSet; import org.openstreetmap.josm.data.osm.search.PushbackTokenizer.Range; import org.openstreetmap.josm.data.osm.search.PushbackTokenizer.Token; import org.openstreetmap.josm.data.projection.ProjectionRegistry; @@ -216,7 +218,7 @@ public Collection getKeywords() { } public static class CoreUnaryMatchFactory implements UnaryMatchFactory { - private static final Collection keywords = Arrays.asList("parent", "child"); + private static final Collection keywords = Arrays.asList("parent", "child", "was"); @Override public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) { @@ -224,6 +226,8 @@ public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer toke return new Parent(matchOperand); else if ("child".equals(keyword)) return new Child(matchOperand); + else if ("was".equals(keyword)) + return new Was(matchOperand); return null; } @@ -1733,6 +1737,39 @@ public String toString() { } } + /** + * Matches objects if the expression matches at any point in their history + */ + public static class Was extends UnaryMatch { + + public Was(Match m) { + super(m); + } + + @Override + public boolean match(OsmPrimitive osm) { + return match.match(osm) || matchInHistory(osm); + } + + private boolean matchInHistory(OsmPrimitive osm) { + if (osm.isNew()) { + return false; + } + + History h = HistoryDataSet.getInstance().getHistory(osm); + if (h == null) { + return false; + } + + return h.getAsList().stream().anyMatch(match::match); + } + + @Override + public String toString() { + return "was(" + match + ')'; + } + } + /** * Matches if the size of the area is within the given range * diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java index 707defa2707..0f57b8d8355 100644 --- a/src/org/openstreetmap/josm/gui/MainMenu.java +++ b/src/org/openstreetmap/josm/gui/MainMenu.java @@ -121,6 +121,7 @@ import org.openstreetmap.josm.actions.audio.AudioPlayPauseAction; import org.openstreetmap.josm.actions.audio.AudioPrevAction; import org.openstreetmap.josm.actions.audio.AudioSlowerAction; +import org.openstreetmap.josm.actions.history.DownloadHistorySelectionAction; import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.actions.search.SearchAction; import org.openstreetmap.josm.data.UndoRedoHandler; @@ -200,6 +201,8 @@ public enum WINDOW_MENU_GROUP { public final JosmAction updateSelection = new UpdateSelectionAction(); /** File / Update modified **/ public final JosmAction updateModified = new UpdateModifiedAction(); + /** File / Download history for selection **/ + public final JosmAction downloadHistorySelection = new DownloadHistorySelectionAction(); /** File / Upload data **/ public final JosmAction upload = new UploadAction(); /** File / Upload selection **/ @@ -753,6 +756,7 @@ public void initialize() { add(fileMenu, update); add(fileMenu, updateSelection); add(fileMenu, updateModified); + add(fileMenu, downloadHistorySelection); fileMenu.addSeparator(); add(fileMenu, upload); add(fileMenu, uploadSelection); diff --git a/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java b/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java index 83a27eec288..f0356c72a66 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/SearchDialog.java @@ -414,6 +414,10 @@ private static JPanel buildHintsSection(AutoCompComboBox hcbSearc .addKeyword("allindownloadedarea", "allindownloadedarea ", tr("objects (and all its way nodes / relation members) in downloaded area")), GBC.eol()); + hintPanel.add(new SearchKeywordRow(hcbSearchString) + .addTitle(tr("history")) + .addKeyword("was expr", "was ", tr("match expr in any object version (current and historic)"), "was building=*"), + GBC.eol()); } if (options.overpassQuery) { hintPanel.add(new SearchKeywordRow(hcbSearchString)