001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.help;
003
004import java.awt.Component;
005import java.util.Locale;
006
007import javax.swing.AbstractButton;
008import javax.swing.Action;
009import javax.swing.JComponent;
010import javax.swing.JMenu;
011import javax.swing.KeyStroke;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.actions.HelpAction;
015import org.openstreetmap.josm.tools.LanguageInfo;
016import org.openstreetmap.josm.tools.LanguageInfo.LocaleType;
017
018/**
019 * Provides utility methods for help system.
020 * @since 2252
021 */
022public final class HelpUtil {
023
024    private HelpUtil() {
025        // Hide default constructor for utils classes
026    }
027
028    /**
029     * Replies the base wiki URL.
030     *
031     * @return the base wiki URL
032     */
033    public static String getWikiBaseUrl() {
034        return Main.pref.get("help.baseurl", Main.getJOSMWebsite());
035    }
036
037    /**
038     * Replies the base wiki URL for help pages
039     *
040     * @return the base wiki URL for help pages
041     */
042    public static String getWikiBaseHelpUrl() {
043        return getWikiBaseUrl() + "/wiki";
044    }
045
046    /**
047     * Replies the URL on the wiki for an absolute help topic. The URL is encoded in UTF-8.
048     *
049     * @param absoluteHelpTopic the absolute help topic
050     * @return the url
051     * @see #buildAbsoluteHelpTopic
052     */
053    public static String getHelpTopicUrl(String absoluteHelpTopic) {
054        if(absoluteHelpTopic == null)
055            return null;
056        String ret = getWikiBaseHelpUrl();
057        ret = ret.replaceAll("\\/+$", "");
058        absoluteHelpTopic = absoluteHelpTopic.replace(" ", "%20");
059        absoluteHelpTopic = absoluteHelpTopic.replaceAll("^\\/+", "/");
060        return ret + absoluteHelpTopic;
061    }
062
063    /**
064     * Replies the URL to the edit page for the absolute help topic.
065     *
066     * @param absoluteHelpTopic the absolute help topic
067     * @return the URL to the edit page
068     */
069    public static String getHelpTopicEditUrl(String absoluteHelpTopic) {
070        String topicUrl = getHelpTopicUrl(absoluteHelpTopic);
071        topicUrl = topicUrl.replaceAll("#[^#]*$", ""); // remove optional fragment
072        return topicUrl + "?action=edit";
073    }
074
075    /**
076     * Extracts the relative help topic from an URL. Replies null, if
077     * no relative help topic is found.
078     *
079     * @param url the url
080     * @return the relative help topic in the URL, i.e. "/Action/New"
081     */
082    public static String extractRelativeHelpTopic(String url) {
083        String topic = extractAbsoluteHelpTopic(url);
084        if (topic == null)
085            return null;
086        String pattern = "/[A-Z][a-z]{1,2}(_[A-Z]{2})?:" + getHelpTopicPrefix(LocaleType.ENGLISH).replaceAll("^\\/+", "");
087        if (url.matches(pattern)) {
088            return topic.substring(pattern.length());
089        }
090        return null;
091    }
092
093    /**
094     * Extracts the absolute help topic from an URL. Replies null, if
095     * no absolute help topic is found.
096     *
097     * @param url the url
098     * @return the absolute help topic in the URL, i.e. "/De:Help/Action/New"
099     */
100    public static String extractAbsoluteHelpTopic(String url) {
101        if (!url.startsWith(getWikiBaseHelpUrl())) return null;
102        url = url.substring(getWikiBaseHelpUrl().length());
103        String prefix = getHelpTopicPrefix(LocaleType.ENGLISH);
104        if (url.startsWith(prefix))
105            return url;
106
107        String pattern = "/[A-Z][a-z]{1,2}(_[A-Z]{2})?:" + prefix.replaceAll("^\\/+", "");
108        if (url.matches(pattern))
109            return url;
110
111        return null;
112    }
113
114    /**
115     * Replies the help topic prefix for the given locale. Examples:
116     * <ul>
117     *   <li>/Help if the  locale is a locale with language "en"</li>
118     *   <li>/De:Help if the  locale is a locale with language "de"</li>
119     * </ul>
120     *
121     * @param type the type of the locale to use
122     * @return the help topic prefix
123     * @since 5915
124     */
125    private static String getHelpTopicPrefix(LocaleType type) {
126        String ret = LanguageInfo.getWikiLanguagePrefix(type);
127        if(ret == null)
128            return ret;
129        ret = "/" + ret + Main.pref.get("help.pathhelp", "/Help").replaceAll("^\\/+", ""); // remove leading /
130        return ret.replaceAll("\\/+", "\\/"); // collapse sequences of //
131    }
132
133    /**
134     * Replies the absolute, localized help topic for the given topic.
135     *
136     * Example: for a topic "/Dialog/RelationEditor" and the locale "de", this method
137     * replies "/De:Help/Dialog/RelationEditor"
138     *
139     * @param topic the relative help topic. Home help topic assumed, if null.
140     * @param type the locale. {@link Locale#ENGLISH} assumed, if null.
141     * @return the absolute, localized help topic
142     * @since 5915
143     */
144    public static String buildAbsoluteHelpTopic(String topic, LocaleType type) {
145        String prefix = getHelpTopicPrefix(type);
146        if (prefix == null || topic == null || topic.trim().length() == 0 || "/".equals(topic.trim()))
147            return prefix;
148        prefix += "/" + topic;
149        return prefix.replaceAll("\\/+", "\\/"); // collapse sequences of //
150    }
151
152    /**
153     * Replies the context specific help topic configured for <code>context</code>.
154     * @param context The UI object used as context
155     *
156     * @return the help topic. null, if no context specific help topic is found
157     */
158    public static String getContextSpecificHelpTopic(Object context) {
159        if (context == null)
160            return null;
161        if (context instanceof Helpful)
162            return ((Helpful)context).helpTopic();
163        if (context instanceof JMenu) {
164            JMenu b = (JMenu)context;
165            if (b.getClientProperty("help") != null)
166                return (String)b.getClientProperty("help");
167            return null;
168        }
169        if (context instanceof AbstractButton) {
170            AbstractButton b = (AbstractButton)context;
171            if (b.getClientProperty("help") != null)
172                return (String)b.getClientProperty("help");
173            return getContextSpecificHelpTopic(b.getAction());
174        }
175        if (context instanceof Action)
176            return (String)((Action)context).getValue("help");
177        if (context instanceof JComponent && ((JComponent)context).getClientProperty("help") != null)
178            return (String)((JComponent)context).getClientProperty("help");
179        if (context instanceof Component)
180            return getContextSpecificHelpTopic(((Component)context).getParent());
181        return null;
182    }
183
184    /**
185     * Replies the global help action, if available. Otherwise, creates an instance
186     * of {@link HelpAction}.
187     *
188     * @return instance of help action
189     */
190    private static Action getHelpAction() {
191        if (Main.main != null && Main.main.menu != null) {
192            return Main.main.menu.help;
193        }
194        return new HelpAction();
195    }
196
197    /**
198     * Makes a component aware of context sensitive help.
199     *
200     * A relative help topic doesn't start with /Help and doesn't include a locale
201     * code. Example: /Dialog/RelationEditor is a relative help topic, /De:Help/Dialog/RelationEditor
202     * is not.
203     *
204     * @param component the component
205     * @param relativeHelpTopic the help topic. Set to the default help topic if null.
206     */
207    public static void setHelpContext(JComponent component, String relativeHelpTopic) {
208        component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F1"), "help");
209        component.getActionMap().put("help", getHelpAction());
210        component.putClientProperty("help", relativeHelpTopic == null ? "/" : relativeHelpTopic);
211    }
212
213    /**
214     * This is a simple marker method for help topic literals. If you declare a help
215     * topic literal in the source you should enclose it in ht(...).
216     *
217     *  <strong>Example</strong>
218     *  <pre>
219     *     String helpTopic = ht("/Dialog/RelationEditor");
220     *  or
221     *     putValue("help", ht("/Dialog/RelationEditor"));
222     *  </pre>
223     *
224     * @param helpTopic Help topic to mark
225     * @return {@code helpTopic}
226     */
227    public static String ht(String helpTopic) {
228        // this is just a marker method
229        return helpTopic;
230    }
231}