1. /*
  2. * @(#)BasicHTML.java 1.11 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import java.io.*;
  12. import java.awt.*;
  13. import java.net.URL;
  14. import javax.swing.*;
  15. import javax.swing.text.*;
  16. import javax.swing.text.html.*;
  17. /**
  18. * Support for providing html views for the swing components.
  19. * This translates a simple html string to a javax.swing.text.View
  20. * implementation that can render the html and provide the necessary
  21. * layout semantics.
  22. *
  23. * @author Timothy Prinzing
  24. * @version 1.11 02/02/00
  25. */
  26. public class BasicHTML {
  27. /**
  28. * Create an html renderer for the given component and
  29. * string of html.
  30. */
  31. public static View createHTMLView(JComponent c, String html) {
  32. HTMLEditorKit kit = getFactory();
  33. Document doc = kit.createDefaultDocument();
  34. Object base = c.getClientProperty(documentBaseKey);
  35. ((BasicDocument)doc).setHost(c);
  36. if (base instanceof URL) {
  37. ((HTMLDocument)doc).setBase((URL)base);
  38. }
  39. Reader r = new StringReader(html);
  40. try {
  41. kit.read(r, doc, 0);
  42. } catch (Throwable e) {
  43. }
  44. ViewFactory f = kit.getViewFactory();
  45. View hview = f.create(doc.getDefaultRootElement());
  46. View v = new Renderer(c, f, hview);
  47. return v;
  48. }
  49. /**
  50. * Check the given string to see if it should trigger the
  51. * html rendering logic in a non-text component that supports
  52. * html rendering.
  53. */
  54. public static boolean isHTMLString(String s) {
  55. if (s != null) {
  56. if ((s.length() >= 6) && (s.charAt(0) == '<') && (s.charAt(5) == '>')) {
  57. String tag = s.substring(1,5);
  58. return tag.equalsIgnoreCase(propertyKey);
  59. }
  60. }
  61. return false;
  62. }
  63. /**
  64. * Stash the HTML render for the given text into the client
  65. * properties of the given JComponent. If the given text is
  66. * <em>NOT HTML</em> the property will be cleared of any
  67. * renderer.
  68. * <p>
  69. * This method is useful for ComponentUI implementations
  70. * that are static (i.e. shared) and get their state
  71. * entirely from the JComponent.
  72. */
  73. public static void updateRenderer(JComponent c, String text) {
  74. View value = null;
  75. String key = null;
  76. if (BasicHTML.isHTMLString(text)) {
  77. value = BasicHTML.createHTMLView(c, text);
  78. }
  79. c.putClientProperty(BasicHTML.propertyKey, value);
  80. }
  81. /**
  82. * Key to use for the html renderer when stored as a
  83. * client property of a JComponent.
  84. */
  85. public static final String propertyKey = "html";
  86. /**
  87. * Key stored as a client property to indicate the base that relative
  88. * references are resolved against. For example, lets say you keep
  89. * your images in the directory resources relative to the code path,
  90. * you would use the following the set the base:
  91. * <pre>
  92. * jComponent.putClientProperty(documentBaseKey,
  93. * xxx.class.getResource("resources/"));
  94. * </pre>
  95. */
  96. public static final String documentBaseKey = "html.base";
  97. static HTMLEditorKit getFactory() {
  98. if (basicHTMLFactory == null) {
  99. basicHTMLFactory = new BasicEditorKit();
  100. }
  101. return basicHTMLFactory;
  102. }
  103. /**
  104. * The source of the html renderers
  105. */
  106. private static HTMLEditorKit basicHTMLFactory;
  107. /**
  108. * Overrides to the default stylesheet. Should consider
  109. * just creating a completely fresh stylesheet.
  110. */
  111. private static final String styleChanges =
  112. "p { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0 }" +
  113. "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0 }";
  114. /**
  115. * The views produced for the ComponentUI implementations aren't
  116. * going to be edited and don't need full html support. This kit
  117. * alters the HTMLEditorKit to try and trim things down a bit.
  118. * It does the following:
  119. * <ul>
  120. * <li>It doesn't produce Views for things like comments,
  121. * head, title, unknown tags, etc.
  122. * <li>It installs a different set of css settings from the default
  123. * provided by HTMLEditorKit.
  124. * </ul>
  125. */
  126. static class BasicEditorKit extends HTMLEditorKit {
  127. /** Shared base style for all documents created by us use. */
  128. private static StyleSheet defaultStyles;
  129. /**
  130. * Overriden to return our own slimmed down style sheet.
  131. */
  132. public StyleSheet getStyleSheet() {
  133. if (defaultStyles == null) {
  134. defaultStyles = new StyleSheet();
  135. StringReader r = new StringReader(styleChanges);
  136. try {
  137. defaultStyles.loadRules(r, null);
  138. } catch (Throwable e) {
  139. // don't want to die in static initialization...
  140. // just display things wrong.
  141. }
  142. Style bodyStyle = defaultStyles.getStyle("body");
  143. if (bodyStyle != null) {
  144. StyleConstants.setForeground(bodyStyle,
  145. new TaggedColor(0, 0, 0));
  146. }
  147. r.close();
  148. defaultStyles.addStyleSheet(super.getStyleSheet());
  149. }
  150. return defaultStyles;
  151. }
  152. /**
  153. * Sets the async policy to flush everything in one chunk, and
  154. * to not display unknown tags.
  155. */
  156. public Document createDefaultDocument() {
  157. StyleSheet styles = getStyleSheet();
  158. StyleSheet ss = new StyleSheet();
  159. ss.addStyleSheet(styles);
  160. BasicDocument doc = new BasicDocument(ss);
  161. doc.setAsynchronousLoadPriority(Integer.MAX_VALUE);
  162. doc.setPreservesUnknownTags(false);
  163. return doc;
  164. }
  165. }
  166. /**
  167. * A tagged color is used by BasicDocument to indicate the color should
  168. * come from the foreground of the Component.
  169. */
  170. static class TaggedColor extends Color {
  171. TaggedColor(int r, int g, int b) {
  172. super(r, g, b);
  173. }
  174. }
  175. /**
  176. * The subclass of HTMLDocument that is used as the model. getForeground
  177. * is overriden to return the foreground property from the Component this
  178. * was created for.
  179. */
  180. static class BasicDocument extends HTMLDocument {
  181. /** The host, that is where we are rendering. */
  182. private JComponent host;
  183. BasicDocument(StyleSheet s) {
  184. super(s);
  185. setPreservesUnknownTags(false);
  186. }
  187. public Color getForeground(AttributeSet attr) {
  188. Color color = super.getForeground(attr);
  189. if (color instanceof TaggedColor && host != null) {
  190. return host.getForeground();
  191. }
  192. return color;
  193. }
  194. void setHost(JComponent host) {
  195. this.host = host;
  196. }
  197. }
  198. /**
  199. * Root text view that acts as an HTML renderer.
  200. */
  201. static class Renderer extends View {
  202. Renderer(JComponent c, ViewFactory f, View v) {
  203. super(null);
  204. host = c;
  205. factory = f;
  206. view = v;
  207. view.setParent(this);
  208. // initially layout to the preferred size
  209. setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS));
  210. }
  211. /**
  212. * Fetches the attributes to use when rendering. At the root
  213. * level there are no attributes. If an attribute is resolved
  214. * up the view hierarchy this is the end of the line.
  215. */
  216. public AttributeSet getAttributes() {
  217. return null;
  218. }
  219. /**
  220. * Determines the preferred span for this view along an axis.
  221. *
  222. * @param axis may be either X_AXIS or Y_AXIS
  223. * @return the span the view would like to be rendered into.
  224. * Typically the view is told to render into the span
  225. * that is returned, although there is no guarantee.
  226. * The parent may choose to resize or break the view.
  227. */
  228. public float getPreferredSpan(int axis) {
  229. if (axis == X_AXIS) {
  230. // width currently laid out to
  231. return width;
  232. }
  233. return view.getPreferredSpan(axis);
  234. }
  235. /**
  236. * Determines the minimum span for this view along an axis.
  237. *
  238. * @param axis may be either X_AXIS or Y_AXIS
  239. * @return the span the view would like to be rendered into.
  240. * Typically the view is told to render into the span
  241. * that is returned, although there is no guarantee.
  242. * The parent may choose to resize or break the view.
  243. */
  244. public float getMinimumSpan(int axis) {
  245. return view.getMinimumSpan(axis);
  246. }
  247. /**
  248. * Determines the maximum span for this view along an axis.
  249. *
  250. * @param axis may be either X_AXIS or Y_AXIS
  251. * @return the span the view would like to be rendered into.
  252. * Typically the view is told to render into the span
  253. * that is returned, although there is no guarantee.
  254. * The parent may choose to resize or break the view.
  255. */
  256. public float getMaximumSpan(int axis) {
  257. return Integer.MAX_VALUE;
  258. }
  259. /**
  260. * Specifies that a preference has changed.
  261. * Child views can call this on the parent to indicate that
  262. * the preference has changed. The root view routes this to
  263. * invalidate on the hosting component.
  264. * <p>
  265. * This can be called on a different thread from the
  266. * event dispatching thread and is basically unsafe to
  267. * propagate into the component. To make this safe,
  268. * the operation is transferred over to the event dispatching
  269. * thread for completion. It is a design goal that all view
  270. * methods be safe to call without concern for concurrency,
  271. * and this behavior helps make that true.
  272. *
  273. * @param child the child view
  274. * @param width true if the width preference has changed
  275. * @param height true if the height preference has changed
  276. */
  277. public void preferenceChanged(View child, boolean width, boolean height) {
  278. host.revalidate();
  279. host.repaint();
  280. }
  281. /**
  282. * Determines the desired alignment for this view along an axis.
  283. *
  284. * @param axis may be either X_AXIS or Y_AXIS
  285. * @return the desired alignment, where 0.0 indicates the origin
  286. * and 1.0 the full span away from the origin
  287. */
  288. public float getAlignment(int axis) {
  289. return view.getAlignment(axis);
  290. }
  291. /**
  292. * Renders the view.
  293. *
  294. * @param g the graphics context
  295. * @param allocation the region to render into
  296. */
  297. public void paint(Graphics g, Shape allocation) {
  298. Rectangle alloc = allocation.getBounds();
  299. view.setSize(alloc.width, alloc.height);
  300. view.paint(g, allocation);
  301. }
  302. /**
  303. * Sets the view parent.
  304. *
  305. * @param parent the parent view
  306. */
  307. public void setParent(View parent) {
  308. throw new Error("Can't set parent on root view");
  309. }
  310. /**
  311. * Returns the number of views in this view. Since
  312. * this view simply wraps the root of the view hierarchy
  313. * it has exactly one child.
  314. *
  315. * @return the number of views
  316. * @see #getView
  317. */
  318. public int getViewCount() {
  319. return 1;
  320. }
  321. /**
  322. * Gets the n-th view in this container.
  323. *
  324. * @param n the number of the view to get
  325. * @return the view
  326. */
  327. public View getView(int n) {
  328. return view;
  329. }
  330. /**
  331. * Provides a mapping from the document model coordinate space
  332. * to the coordinate space of the view mapped to it.
  333. *
  334. * @param pos the position to convert
  335. * @param a the allocated region to render into
  336. * @return the bounding box of the given position
  337. */
  338. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  339. return view.modelToView(pos, a, b);
  340. }
  341. /**
  342. * Provides a mapping from the document model coordinate space
  343. * to the coordinate space of the view mapped to it.
  344. *
  345. * @param p0 the position to convert >= 0
  346. * @param b0 the bias toward the previous character or the
  347. * next character represented by p0, in case the
  348. * position is a boundary of two views.
  349. * @param p1 the position to convert >= 0
  350. * @param b1 the bias toward the previous character or the
  351. * next character represented by p1, in case the
  352. * position is a boundary of two views.
  353. * @param a the allocated region to render into
  354. * @return the bounding box of the given position is returned
  355. * @exception BadLocationException if the given position does
  356. * not represent a valid location in the associated document
  357. * @exception IllegalArgumentException for an invalid bias argument
  358. * @see View#viewToModel
  359. */
  360. public Shape modelToView(int p0, Position.Bias b0, int p1,
  361. Position.Bias b1, Shape a) throws BadLocationException {
  362. return view.modelToView(p0, b0, p1, b1, a);
  363. }
  364. /**
  365. * Provides a mapping from the view coordinate space to the logical
  366. * coordinate space of the model.
  367. *
  368. * @param x x coordinate of the view location to convert
  369. * @param y y coordinate of the view location to convert
  370. * @param a the allocated region to render into
  371. * @return the location within the model that best represents the
  372. * given point in the view
  373. */
  374. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  375. return view.viewToModel(x, y, a, bias);
  376. }
  377. /**
  378. * Returns the document model underlying the view.
  379. *
  380. * @return the model
  381. */
  382. public Document getDocument() {
  383. return view.getDocument();
  384. }
  385. /**
  386. * Returns the starting offset into the model for this view.
  387. *
  388. * @return the starting offset
  389. */
  390. public int getStartOffset() {
  391. return view.getStartOffset();
  392. }
  393. /**
  394. * Returns the ending offset into the model for this view.
  395. *
  396. * @return the ending offset
  397. */
  398. public int getEndOffset() {
  399. return view.getEndOffset();
  400. }
  401. /**
  402. * Gets the element that this view is mapped to.
  403. *
  404. * @return the view
  405. */
  406. public Element getElement() {
  407. return view.getElement();
  408. }
  409. /**
  410. * Sets the view size.
  411. *
  412. * @param width the width
  413. * @param height the height
  414. */
  415. public void setSize(float width, float height) {
  416. this.width = (int) width;
  417. view.setSize(width, height);
  418. }
  419. /**
  420. * Fetches the container hosting the view. This is useful for
  421. * things like scheduling a repaint, finding out the host
  422. * components font, etc. The default implementation
  423. * of this is to forward the query to the parent view.
  424. *
  425. * @return the container
  426. */
  427. public Container getContainer() {
  428. return host;
  429. }
  430. /**
  431. * Fetches the factory to be used for building the
  432. * various view fragments that make up the view that
  433. * represents the model. This is what determines
  434. * how the model will be represented. This is implemented
  435. * to fetch the factory provided by the associated
  436. * EditorKit.
  437. *
  438. * @return the factory
  439. */
  440. public ViewFactory getViewFactory() {
  441. return factory;
  442. }
  443. private int width;
  444. private View view;
  445. private ViewFactory factory;
  446. private JComponent host;
  447. }
  448. }