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}