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