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