001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Color;
005import java.awt.Font;
006import java.io.IOException;
007import java.io.InputStream;
008import java.net.URL;
009import java.text.MessageFormat;
010
011import javax.swing.JEditorPane;
012import javax.swing.LookAndFeel;
013import javax.swing.UIDefaults;
014import javax.swing.UIManager;
015import javax.swing.text.html.StyleSheet;
016
017import org.openstreetmap.josm.gui.util.GuiHelper;
018import org.openstreetmap.josm.tools.Destroyable;
019import org.openstreetmap.josm.tools.HttpClient;
020import org.openstreetmap.josm.tools.LanguageInfo;
021
022/**
023 * Subclass of {@link JEditorPane} that adds a "native" context menu (cut/copy/paste/select all)
024 * and effectively uses JOSM user agent when performing HTTP request in {@link #setPage(URL)} method.
025 * @since 5886
026 */
027public class JosmEditorPane extends JEditorPane implements Destroyable {
028
029    private final PopupMenuLauncher launcher;
030
031    /**
032     * Creates a new <code>JosmEditorPane</code>.
033     * The document model is set to <code>null</code>.
034     */
035    public JosmEditorPane() {
036        launcher = TextContextualPopupMenu.enableMenuFor(this, true);
037    }
038
039    /**
040     * Creates a <code>JosmEditorPane</code> based on a specified URL for input.
041     *
042     * @param initialPage the URL
043     * @throws IOException if the URL is <code>null</code> or cannot be accessed
044     */
045    public JosmEditorPane(URL initialPage) throws IOException {
046        this();
047        setPage(initialPage);
048    }
049
050    /**
051     * Creates a <code>JosmEditorPane</code> based on a string containing
052     * a URL specification.
053     *
054     * @param url the URL
055     * @throws IOException if the URL is <code>null</code> or cannot be accessed
056     */
057    public JosmEditorPane(String url) throws IOException {
058        this();
059        setPage(url);
060    }
061
062    /**
063     * Creates a <code>JosmEditorPane</code> that has been initialized
064     * to the given text.  This is a convenience constructor that calls the
065     * <code>setContentType</code> and <code>setText</code> methods.
066     *
067     * @param type mime type of the given text
068     * @param text the text to initialize with; may be <code>null</code>
069     * @throws NullPointerException if the <code>type</code> parameter
070     *      is <code>null</code>
071     */
072    public JosmEditorPane(String type, String text) {
073        this();
074        setContentType(type);
075        setText(text);
076    }
077
078    @Override
079    protected InputStream getStream(URL page) throws IOException {
080        final HttpClient.Response conn = HttpClient.create(page).connect();
081        String type = conn.getContentType();
082        if (type != null) {
083            setContentType(type);
084        }
085        return conn.getContent();
086    }
087
088    /**
089     * Adapts a {@link JEditorPane} to be used as a powerful replacement of {@link javax.swing.JLabel}.
090     * @param pane The editor pane to adapt
091     * @param allBold If {@code true}, makes all text to be displayed in bold
092     */
093    public static void makeJLabelLike(JEditorPane pane, boolean allBold) {
094        pane.setContentType("text/html");
095        pane.setOpaque(false);
096        pane.setEditable(false);
097        adaptForNimbus(pane);
098
099        JosmHTMLEditorKit kit = new JosmHTMLEditorKit();
100        final Font f = UIManager.getFont("Label.font");
101        final StyleSheet ss = new StyleSheet();
102        ss.addRule((allBold ? "html" : "strong, b") + " {" + getFontRule(f) + '}');
103        ss.addRule("a {text-decoration: underline; color: blue}");
104        ss.addRule("h1 {" + getFontRule(GuiHelper.getTitleFont()) + '}');
105        ss.addRule("ol {margin-left: 1cm; margin-top: 0.1cm; margin-bottom: 0.2cm; list-style-type: decimal}");
106        ss.addRule("ul {margin-left: 1cm; margin-top: 0.1cm; margin-bottom: 0.2cm; list-style-type: disc}");
107        if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
108            // Fix rendering problem for Khmer script
109            ss.addRule("p {" + getFontRule(UIManager.getFont("Label.font")) + '}');
110        }
111        kit.setStyleSheet(ss);
112        pane.setEditorKit(kit);
113    }
114
115    /**
116     * Adapts a {@link JEditorPane} for Nimbus look and feel.
117     * See <a href="https://stackoverflow.com/q/15228336/2257172">this StackOverflow question</a>.
118     * @param pane The editor pane to adapt
119     * @since 6935
120     */
121    public static void adaptForNimbus(JEditorPane pane) {
122        LookAndFeel currentLAF = UIManager.getLookAndFeel();
123        if (currentLAF != null && "Nimbus".equals(currentLAF.getName())) {
124            Color bgColor = UIManager.getColor("Label.background");
125            UIDefaults defaults = new UIDefaults();
126            defaults.put("EditorPane[Enabled].backgroundPainter", bgColor);
127            pane.putClientProperty("Nimbus.Overrides", defaults);
128            pane.putClientProperty("Nimbus.Overrides.InheritDefaults", Boolean.TRUE);
129            pane.setBackground(bgColor);
130        }
131    }
132
133    private static String getFontRule(Font f) {
134        return MessageFormat.format(
135                "font-family: ''{0}'';font-size: {1,number}pt; font-weight: {2}; font-style: {3}",
136                f.getName(),
137                f.getSize(),
138                "bold",
139                f.isItalic() ? "italic" : "normal"
140        );
141    }
142
143    @Override
144    public void destroy() {
145        TextContextualPopupMenu.disableMenuFor(this, launcher);
146    }
147}