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