001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Container;
007import java.awt.Rectangle;
008import java.text.MessageFormat;
009import java.util.Arrays;
010import java.util.List;
011import java.util.Objects;
012
013import javax.swing.Action;
014import javax.swing.JComponent;
015import javax.swing.JPopupMenu;
016
017import org.openstreetmap.josm.actions.OpenBrowserAction;
018import org.openstreetmap.josm.data.StructUtils;
019import org.openstreetmap.josm.data.StructUtils.StructEntry;
020import org.openstreetmap.josm.data.osm.PrimitiveId;
021import org.openstreetmap.josm.spi.preferences.Config;
022
023/**
024 * A menu displaying links to external history viewers for a changeset.
025 *
026 * @since 12871
027 */
028public class OpenChangesetPopupMenu extends JPopupMenu {
029
030    /**
031     * Constructs a new {@code OpenChangesetPopupMenu} for the given changeset id.
032     *
033     * @param changesetId the changeset id
034     * @param primitiveId the primitive id
035     * @since 14432
036     */
037    public OpenChangesetPopupMenu(final long changesetId, final PrimitiveId primitiveId) {
038        StructUtils.getListOfStructs(Config.getPref(), "history-dialog.tools", DEFAULT_ENTRIES, ChangesetViewerEntry.class)
039                .stream()
040                .map(entry -> entry.toAction(changesetId, primitiveId))
041                .filter(Objects::nonNull)
042                .forEach(this::add);
043    }
044
045    /**
046     * Displays the popup menu at the lower-left corner of {@code parent}.
047     *
048     * @param parent the parent component to use for positioning this menu
049     */
050    public void show(final JComponent parent) {
051        Container parentParent = parent.getParent();
052        if (parentParent.isShowing()) {
053            final Rectangle r = parent.getBounds();
054            show(parentParent, r.x, r.y + r.height);
055        }
056    }
057
058    private static final List<ChangesetViewerEntry> DEFAULT_ENTRIES = Arrays.asList(
059            new ChangesetViewerEntry(tr("View changeset in web browser"), Config.getUrls().getBaseBrowseUrl() + "/changeset/{0}"),
060            new ChangesetViewerEntry(tr("Open {0}", "achavi (Augmented OSM Change Viewer)"), "https://overpass-api.de/achavi/?changeset={0}"),
061            new ChangesetViewerEntry(tr("Open {0}", "OSMCha (OSM Changeset Analyzer)"), "https://osmcha.org/changesets/{0}"),
062            new ChangesetViewerEntry(tr("Open {0}", "OSM History Viewer (Mapki)"), "http://osm.mapki.com/history/{1}.php?id={2}"),
063            new ChangesetViewerEntry(tr("Open {0}", "OSM History Viewer (Pewu)"), "https://pewu.github.io/osm-history/#/{1}/{2}"),
064            new ChangesetViewerEntry(tr("Open {0}", "WhoDidIt (OSM Changeset Analyzer)"),
065                    "http://simon04.dev.openstreetmap.org/whodidit/index.html?changeset={0}&show=1")
066    );
067
068    /**
069     * Auxiliary class to save a link to a history viewer in the preferences.
070     */
071    public static class ChangesetViewerEntry {
072        /** Name to be displayed in popup menu */
073        @StructEntry
074        public String name;
075        /**
076         * Templated service url.
077         * <code>{0}</code> will be replaced by changeset id
078         * <code>{1}</code> will be replaced by object type (node, way, relation)
079         * <code>{2}</code> will be replaced by object id
080         */
081        @StructEntry
082        public String url;
083
084        /**
085         * Constructs a new {@code ChangesetViewerEntry}.
086         */
087        public ChangesetViewerEntry() {
088        }
089
090        ChangesetViewerEntry(String name, String url) {
091            this.name = Objects.requireNonNull(name);
092            this.url = Objects.requireNonNull(url);
093        }
094
095        Action toAction(final long changesetId, PrimitiveId primitiveId) {
096            if (primitiveId != null) {
097                return new OpenBrowserAction(name, MessageFormat.format(url,
098                        Long.toString(changesetId), primitiveId.getType().getAPIName(), Long.toString(primitiveId.getUniqueId())));
099            } else if (url.contains("{0}")) {
100                return new OpenBrowserAction(name, MessageFormat.format(url, Long.toString(changesetId)));
101            }
102            return null;
103        }
104    }
105
106}