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