001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.properties;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.awt.event.KeyEvent;
008import java.io.IOException;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.List;
012import java.util.Map;
013import java.util.Objects;
014import java.util.function.IntFunction;
015
016import javax.swing.AbstractAction;
017import javax.swing.JTable;
018import javax.swing.KeyStroke;
019import javax.xml.parsers.ParserConfigurationException;
020import javax.xml.xpath.XPathExpressionException;
021
022import org.openstreetmap.josm.data.osm.IRelation;
023import org.openstreetmap.josm.gui.MainApplication;
024import org.openstreetmap.josm.spi.preferences.Config;
025import org.openstreetmap.josm.tools.ImageProvider;
026import org.openstreetmap.josm.tools.LanguageInfo;
027import org.openstreetmap.josm.tools.Logging;
028import org.openstreetmap.josm.tools.Mediawiki;
029import org.openstreetmap.josm.tools.OpenBrowser;
030import org.openstreetmap.josm.tools.Utils;
031import org.xml.sax.SAXException;
032
033/**
034 * Launch browser with wiki help for selected object.
035 * @since 13521
036 */
037public class HelpAction extends AbstractAction {
038    private final JTable tagTable;
039    private final IntFunction<String> tagKeySupplier;
040    private final IntFunction<Map<String, Integer>> tagValuesSupplier;
041
042    private final JTable membershipTable;
043    private final IntFunction<IRelation<?>> memberValueSupplier;
044
045    /**
046     * Constructs a new {@code HelpAction}.
047     * @param tagTable The tag table. Cannot be null
048     * @param tagKeySupplier Finds the key from given row of tag table. Cannot be null
049     * @param tagValuesSupplier Finds the values from given row of tag table (map of values and number of occurrences). Cannot be null
050     * @param membershipTable The membership table. Can be null
051     * @param memberValueSupplier Finds the parent relation from given row of membership table. Can be null
052     * @since 13959 (signature)
053     */
054    public HelpAction(JTable tagTable, IntFunction<String> tagKeySupplier, IntFunction<Map<String, Integer>> tagValuesSupplier,
055            JTable membershipTable, IntFunction<IRelation<?>> memberValueSupplier) {
056        this.tagTable = Objects.requireNonNull(tagTable);
057        this.tagKeySupplier = Objects.requireNonNull(tagKeySupplier);
058        this.tagValuesSupplier = Objects.requireNonNull(tagValuesSupplier);
059        this.membershipTable = membershipTable;
060        this.memberValueSupplier = memberValueSupplier;
061        putValue(NAME, tr("Go to OSM wiki for tag help"));
062        putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help for selected object"));
063        new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
064        putValue(ACCELERATOR_KEY, getKeyStroke());
065    }
066
067    /**
068     * Returns the keystroke launching this action (F1).
069     * @return the keystroke launching this action
070     */
071    public KeyStroke getKeyStroke() {
072        return KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);
073    }
074
075    @Override
076    public void actionPerformed(ActionEvent e) {
077        if (tagTable.getSelectedRowCount() == 1) {
078            int row = tagTable.getSelectedRow();
079            String key = Utils.encodeUrl(tagKeySupplier.apply(row));
080            Map<String, Integer> m = tagValuesSupplier.apply(row);
081            if (!m.isEmpty()) {
082                String val = Utils.encodeUrl(m.entrySet().iterator().next().getKey());
083                MainApplication.worker.execute(() -> displayTagHelp(key, val));
084            }
085        } else if (membershipTable != null && membershipTable.getSelectedRowCount() == 1) {
086            int row = membershipTable.getSelectedRow();
087            final IRelation<?> relation = memberValueSupplier.apply(row);
088            MainApplication.worker.execute(() -> displayRelationHelp(relation));
089        } else {
090            // give the generic help page, if more than one element is selected
091            MainApplication.worker.execute(HelpAction::displayGenericHelp);
092        }
093    }
094
095    /**
096     * Displays the most specific wiki page for the given key/value.
097     * @param key Key
098     * @param val Value
099     * @since 14208
100     */
101    public static void displayTagHelp(String key, String val) {
102        final String lang = LanguageInfo.getWikiLanguagePrefix(LanguageInfo.LocaleType.OSM_WIKI);
103        final List<String> pages = Arrays.asList(
104                String.format("%sTag:%s=%s", lang, key, val),
105                String.format("Tag:%s=%s", key, val),
106                String.format("%sKey:%s", lang, key),
107                String.format("Key:%s", key),
108                String.format("%sMap_Features", lang),
109                "Map_Features"
110        );
111        displayHelp(pages);
112    }
113
114    /**
115     * Displays the most specific wiki page for the given relation.
116     * @param rel Relation
117     * @since 14208
118     */
119    public static void displayRelationHelp(IRelation<?> rel) {
120        final String lang = LanguageInfo.getWikiLanguagePrefix(LanguageInfo.LocaleType.OSM_WIKI);
121        final List<String> pages = new ArrayList<>();
122        String type = rel.get("type");
123        if (type != null) {
124            type = Utils.encodeUrl(type);
125        }
126
127        if (type != null && !type.isEmpty()) {
128            pages.add(String.format("%sRelation:%s", lang, type));
129            pages.add(String.format("Relation:%s", type));
130        }
131
132        pages.add(String.format("%sRelations", lang));
133        pages.add("Relations");
134        displayHelp(pages);
135    }
136
137    /**
138     * Displays the localized Map Features.
139     * @since 14208
140     */
141    public static void displayGenericHelp() {
142        final String lang = LanguageInfo.getWikiLanguagePrefix(LanguageInfo.LocaleType.OSM_WIKI);
143        final List<String> pages = Arrays.asList(
144                String.format("%sMap_Features", lang),
145                "Map_Features"
146        );
147        displayHelp(pages);
148    }
149
150    /**
151     * Display help by opening the first existing wiki page in the given list.
152     * @param pages list of wiki page names to test
153     * @since 14208
154     */
155    public static void displayHelp(final List<String> pages) {
156        try {
157            new Mediawiki(Config.getUrls().getOSMWiki())
158                    .findExistingPage(pages)
159                    .ifPresent(page -> OpenBrowser.displayUrl(Config.getUrls().getOSMWiki() + "/wiki/" + page));
160        } catch (IOException | ParserConfigurationException | XPathExpressionException | SAXException e1) {
161            Logging.error(e1);
162        }
163    }
164}