001 /*
002 * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.plaf.basic;
026
027 import java.io.*;
028 import java.awt.*;
029 import java.net.URL;
030
031 import javax.swing.*;
032 import javax.swing.text.*;
033 import javax.swing.text.html.*;
034
035 import sun.swing.SwingUtilities2;
036
037 /**
038 * Support for providing html views for the swing components.
039 * This translates a simple html string to a javax.swing.text.View
040 * implementation that can render the html and provide the necessary
041 * layout semantics.
042 *
043 * @author Timothy Prinzing
044 * @version 1.33 05/05/07
045 * @since 1.3
046 */
047 public class BasicHTML {
048
049 /**
050 * Create an html renderer for the given component and
051 * string of html.
052 */
053 public static View createHTMLView(JComponent c, String html) {
054 BasicEditorKit kit = getFactory();
055 Document doc = kit.createDefaultDocument(c.getFont(), c
056 .getForeground());
057 Object base = c.getClientProperty(documentBaseKey);
058 if (base instanceof URL) {
059 ((HTMLDocument) doc).setBase((URL) base);
060 }
061 Reader r = new StringReader(html);
062 try {
063 kit.read(r, doc, 0);
064 } catch (Throwable e) {
065 }
066 ViewFactory f = kit.getViewFactory();
067 View hview = f.create(doc.getDefaultRootElement());
068 View v = new Renderer(c, f, hview);
069 return v;
070 }
071
072 /**
073 * Returns the baseline for the html renderer.
074 *
075 * @param view the View to get the baseline for
076 * @param w the width to get the baseline for
077 * @param h the height to get the baseline for
078 * @throws IllegalArgumentException if width or height is < 0
079 * @return baseline or a value < 0 indicating there is no reasonable
080 * baseline
081 * @see java.awt.FontMetrics
082 * @see javax.swing.JComponent#getBaseline(int,int)
083 * @since 1.6
084 */
085 public static int getHTMLBaseline(View view, int w, int h) {
086 if (w < 0 || h < 0) {
087 throw new IllegalArgumentException(
088 "Width and height must be >= 0");
089 }
090 if (view instanceof Renderer) {
091 return getBaseline(view.getView(0), w, h);
092 }
093 return -1;
094 }
095
096 /**
097 * Gets the baseline for the specified component. This digs out
098 * the View client property, and if non-null the baseline is calculated
099 * from it. Otherwise the baseline is the value <code>y + ascent</code>.
100 */
101 static int getBaseline(JComponent c, int y, int ascent, int w, int h) {
102 View view = (View) c.getClientProperty(BasicHTML.propertyKey);
103 if (view != null) {
104 int baseline = getHTMLBaseline(view, w, h);
105 if (baseline < 0) {
106 return baseline;
107 }
108 return y + baseline;
109 }
110 return y + ascent;
111 }
112
113 /**
114 * Gets the baseline for the specified View.
115 */
116 static int getBaseline(View view, int w, int h) {
117 if (hasParagraph(view)) {
118 view.setSize(w, h);
119 return getBaseline(view, new Rectangle(0, 0, w, h));
120 }
121 return -1;
122 }
123
124 private static int getBaseline(View view, Shape bounds) {
125 if (view.getViewCount() == 0) {
126 return -1;
127 }
128 AttributeSet attributes = view.getElement().getAttributes();
129 Object name = null;
130 if (attributes != null) {
131 name = attributes
132 .getAttribute(StyleConstants.NameAttribute);
133 }
134 int index = 0;
135 if (name == HTML.Tag.HTML && view.getViewCount() > 1) {
136 // For html on widgets the header is not visible, skip it.
137 index++;
138 }
139 bounds = view.getChildAllocation(index, bounds);
140 if (bounds == null) {
141 return -1;
142 }
143 View child = view.getView(index);
144 if (view instanceof javax.swing.text.ParagraphView) {
145 Rectangle rect;
146 if (bounds instanceof Rectangle) {
147 rect = (Rectangle) bounds;
148 } else {
149 rect = bounds.getBounds();
150 }
151 return rect.y
152 + (int) (rect.height * child
153 .getAlignment(View.Y_AXIS));
154 }
155 return getBaseline(child, bounds);
156 }
157
158 private static boolean hasParagraph(View view) {
159 if (view instanceof javax.swing.text.ParagraphView) {
160 return true;
161 }
162 if (view.getViewCount() == 0) {
163 return false;
164 }
165 AttributeSet attributes = view.getElement().getAttributes();
166 Object name = null;
167 if (attributes != null) {
168 name = attributes
169 .getAttribute(StyleConstants.NameAttribute);
170 }
171 int index = 0;
172 if (name == HTML.Tag.HTML && view.getViewCount() > 1) {
173 // For html on widgets the header is not visible, skip it.
174 index = 1;
175 }
176 return hasParagraph(view.getView(index));
177 }
178
179 /**
180 * Check the given string to see if it should trigger the
181 * html rendering logic in a non-text component that supports
182 * html rendering.
183 */
184 public static boolean isHTMLString(String s) {
185 if (s != null) {
186 if ((s.length() >= 6) && (s.charAt(0) == '<')
187 && (s.charAt(5) == '>')) {
188 String tag = s.substring(1, 5);
189 return tag.equalsIgnoreCase(propertyKey);
190 }
191 }
192 return false;
193 }
194
195 /**
196 * Stash the HTML render for the given text into the client
197 * properties of the given JComponent. If the given text is
198 * <em>NOT HTML</em> the property will be cleared of any
199 * renderer.
200 * <p>
201 * This method is useful for ComponentUI implementations
202 * that are static (i.e. shared) and get their state
203 * entirely from the JComponent.
204 */
205 public static void updateRenderer(JComponent c, String text) {
206 View value = null;
207 View oldValue = (View) c
208 .getClientProperty(BasicHTML.propertyKey);
209 Boolean htmlDisabled = (Boolean) c
210 .getClientProperty(htmlDisable);
211 if (htmlDisabled != Boolean.TRUE
212 && BasicHTML.isHTMLString(text)) {
213 value = BasicHTML.createHTMLView(c, text);
214 }
215 if (value != oldValue && oldValue != null) {
216 for (int i = 0; i < oldValue.getViewCount(); i++) {
217 oldValue.getView(i).setParent(null);
218 }
219 }
220 c.putClientProperty(BasicHTML.propertyKey, value);
221 }
222
223 /**
224 * If this client property of a JComponent is set to Boolean.TRUE
225 * the component's 'text' property is never treated as HTML.
226 */
227 private static final String htmlDisable = "html.disable";
228
229 /**
230 * Key to use for the html renderer when stored as a
231 * client property of a JComponent.
232 */
233 public static final String propertyKey = "html";
234
235 /**
236 * Key stored as a client property to indicate the base that relative
237 * references are resolved against. For example, lets say you keep
238 * your images in the directory resources relative to the code path,
239 * you would use the following the set the base:
240 * <pre>
241 * jComponent.putClientProperty(documentBaseKey,
242 * xxx.class.getResource("resources/"));
243 * </pre>
244 */
245 public static final String documentBaseKey = "html.base";
246
247 static BasicEditorKit getFactory() {
248 if (basicHTMLFactory == null) {
249 basicHTMLViewFactory = new BasicHTMLViewFactory();
250 basicHTMLFactory = new BasicEditorKit();
251 }
252 return basicHTMLFactory;
253 }
254
255 /**
256 * The source of the html renderers
257 */
258 private static BasicEditorKit basicHTMLFactory;
259
260 /**
261 * Creates the Views that visually represent the model.
262 */
263 private static ViewFactory basicHTMLViewFactory;
264
265 /**
266 * Overrides to the default stylesheet. Should consider
267 * just creating a completely fresh stylesheet.
268 */
269 private static final String styleChanges = "p { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0 }"
270 + "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0 }";
271
272 /**
273 * The views produced for the ComponentUI implementations aren't
274 * going to be edited and don't need full html support. This kit
275 * alters the HTMLEditorKit to try and trim things down a bit.
276 * It does the following:
277 * <ul>
278 * <li>It doesn't produce Views for things like comments,
279 * head, title, unknown tags, etc.
280 * <li>It installs a different set of css settings from the default
281 * provided by HTMLEditorKit.
282 * </ul>
283 */
284 static class BasicEditorKit extends HTMLEditorKit {
285 /** Shared base style for all documents created by us use. */
286 private static StyleSheet defaultStyles;
287
288 /**
289 * Overriden to return our own slimmed down style sheet.
290 */
291 public StyleSheet getStyleSheet() {
292 if (defaultStyles == null) {
293 defaultStyles = new StyleSheet();
294 StringReader r = new StringReader(styleChanges);
295 try {
296 defaultStyles.loadRules(r, null);
297 } catch (Throwable e) {
298 // don't want to die in static initialization...
299 // just display things wrong.
300 }
301 r.close();
302 defaultStyles.addStyleSheet(super .getStyleSheet());
303 }
304 return defaultStyles;
305 }
306
307 /**
308 * Sets the async policy to flush everything in one chunk, and
309 * to not display unknown tags.
310 */
311 public Document createDefaultDocument(Font defaultFont,
312 Color foreground) {
313 StyleSheet styles = getStyleSheet();
314 StyleSheet ss = new StyleSheet();
315 ss.addStyleSheet(styles);
316 BasicDocument doc = new BasicDocument(ss, defaultFont,
317 foreground);
318 doc.setAsynchronousLoadPriority(Integer.MAX_VALUE);
319 doc.setPreservesUnknownTags(false);
320 return doc;
321 }
322
323 /**
324 * Returns the ViewFactory that is used to make sure the Views don't
325 * load in the background.
326 */
327 public ViewFactory getViewFactory() {
328 return basicHTMLViewFactory;
329 }
330 }
331
332 /**
333 * BasicHTMLViewFactory extends HTMLFactory to force images to be loaded
334 * synchronously.
335 */
336 static class BasicHTMLViewFactory extends HTMLEditorKit.HTMLFactory {
337 public View create(Element elem) {
338 View view = super .create(elem);
339
340 if (view instanceof ImageView) {
341 ((ImageView) view).setLoadsSynchronously(true);
342 }
343 return view;
344 }
345 }
346
347 /**
348 * The subclass of HTMLDocument that is used as the model. getForeground
349 * is overridden to return the foreground property from the Component this
350 * was created for.
351 */
352 static class BasicDocument extends HTMLDocument {
353 /** The host, that is where we are rendering. */
354 // private JComponent host;
355 BasicDocument(StyleSheet s, Font defaultFont, Color foreground) {
356 super (s);
357 setPreservesUnknownTags(false);
358 setFontAndColor(defaultFont, foreground);
359 }
360
361 /**
362 * Sets the default font and default color. These are set by
363 * adding a rule for the body that specifies the font and color.
364 * This allows the html to override these should it wish to have
365 * a custom font or color.
366 */
367 private void setFontAndColor(Font font, Color fg) {
368 getStyleSheet().addRule(
369 sun.swing.SwingUtilities2.displayPropertiesToCSS(
370 font, fg));
371 }
372 }
373
374 /**
375 * Root text view that acts as an HTML renderer.
376 */
377 static class Renderer extends View {
378
379 Renderer(JComponent c, ViewFactory f, View v) {
380 super (null);
381 host = c;
382 factory = f;
383 view = v;
384 view.setParent(this );
385 // initially layout to the preferred size
386 setSize(view.getPreferredSpan(X_AXIS), view
387 .getPreferredSpan(Y_AXIS));
388 }
389
390 /**
391 * Fetches the attributes to use when rendering. At the root
392 * level there are no attributes. If an attribute is resolved
393 * up the view hierarchy this is the end of the line.
394 */
395 public AttributeSet getAttributes() {
396 return null;
397 }
398
399 /**
400 * Determines the preferred span for this view along an axis.
401 *
402 * @param axis may be either X_AXIS or Y_AXIS
403 * @return the span the view would like to be rendered into.
404 * Typically the view is told to render into the span
405 * that is returned, although there is no guarantee.
406 * The parent may choose to resize or break the view.
407 */
408 public float getPreferredSpan(int axis) {
409 if (axis == X_AXIS) {
410 // width currently laid out to
411 return width;
412 }
413 return view.getPreferredSpan(axis);
414 }
415
416 /**
417 * Determines the minimum span for this view along an axis.
418 *
419 * @param axis may be either X_AXIS or Y_AXIS
420 * @return the span the view would like to be rendered into.
421 * Typically the view is told to render into the span
422 * that is returned, although there is no guarantee.
423 * The parent may choose to resize or break the view.
424 */
425 public float getMinimumSpan(int axis) {
426 return view.getMinimumSpan(axis);
427 }
428
429 /**
430 * Determines the maximum span for this view along an axis.
431 *
432 * @param axis may be either X_AXIS or Y_AXIS
433 * @return the span the view would like to be rendered into.
434 * Typically the view is told to render into the span
435 * that is returned, although there is no guarantee.
436 * The parent may choose to resize or break the view.
437 */
438 public float getMaximumSpan(int axis) {
439 return Integer.MAX_VALUE;
440 }
441
442 /**
443 * Specifies that a preference has changed.
444 * Child views can call this on the parent to indicate that
445 * the preference has changed. The root view routes this to
446 * invalidate on the hosting component.
447 * <p>
448 * This can be called on a different thread from the
449 * event dispatching thread and is basically unsafe to
450 * propagate into the component. To make this safe,
451 * the operation is transferred over to the event dispatching
452 * thread for completion. It is a design goal that all view
453 * methods be safe to call without concern for concurrency,
454 * and this behavior helps make that true.
455 *
456 * @param child the child view
457 * @param width true if the width preference has changed
458 * @param height true if the height preference has changed
459 */
460 public void preferenceChanged(View child, boolean width,
461 boolean height) {
462 host.revalidate();
463 host.repaint();
464 }
465
466 /**
467 * Determines the desired alignment for this view along an axis.
468 *
469 * @param axis may be either X_AXIS or Y_AXIS
470 * @return the desired alignment, where 0.0 indicates the origin
471 * and 1.0 the full span away from the origin
472 */
473 public float getAlignment(int axis) {
474 return view.getAlignment(axis);
475 }
476
477 /**
478 * Renders the view.
479 *
480 * @param g the graphics context
481 * @param allocation the region to render into
482 */
483 public void paint(Graphics g, Shape allocation) {
484 Rectangle alloc = allocation.getBounds();
485 view.setSize(alloc.width, alloc.height);
486 view.paint(g, allocation);
487 }
488
489 /**
490 * Sets the view parent.
491 *
492 * @param parent the parent view
493 */
494 public void setParent(View parent) {
495 throw new Error("Can't set parent on root view");
496 }
497
498 /**
499 * Returns the number of views in this view. Since
500 * this view simply wraps the root of the view hierarchy
501 * it has exactly one child.
502 *
503 * @return the number of views
504 * @see #getView
505 */
506 public int getViewCount() {
507 return 1;
508 }
509
510 /**
511 * Gets the n-th view in this container.
512 *
513 * @param n the number of the view to get
514 * @return the view
515 */
516 public View getView(int n) {
517 return view;
518 }
519
520 /**
521 * Provides a mapping from the document model coordinate space
522 * to the coordinate space of the view mapped to it.
523 *
524 * @param pos the position to convert
525 * @param a the allocated region to render into
526 * @return the bounding box of the given position
527 */
528 public Shape modelToView(int pos, Shape a, Position.Bias b)
529 throws BadLocationException {
530 return view.modelToView(pos, a, b);
531 }
532
533 /**
534 * Provides a mapping from the document model coordinate space
535 * to the coordinate space of the view mapped to it.
536 *
537 * @param p0 the position to convert >= 0
538 * @param b0 the bias toward the previous character or the
539 * next character represented by p0, in case the
540 * position is a boundary of two views.
541 * @param p1 the position to convert >= 0
542 * @param b1 the bias toward the previous character or the
543 * next character represented by p1, in case the
544 * position is a boundary of two views.
545 * @param a the allocated region to render into
546 * @return the bounding box of the given position is returned
547 * @exception BadLocationException if the given position does
548 * not represent a valid location in the associated document
549 * @exception IllegalArgumentException for an invalid bias argument
550 * @see View#viewToModel
551 */
552 public Shape modelToView(int p0, Position.Bias b0, int p1,
553 Position.Bias b1, Shape a) throws BadLocationException {
554 return view.modelToView(p0, b0, p1, b1, a);
555 }
556
557 /**
558 * Provides a mapping from the view coordinate space to the logical
559 * coordinate space of the model.
560 *
561 * @param x x coordinate of the view location to convert
562 * @param y y coordinate of the view location to convert
563 * @param a the allocated region to render into
564 * @return the location within the model that best represents the
565 * given point in the view
566 */
567 public int viewToModel(float x, float y, Shape a,
568 Position.Bias[] bias) {
569 return view.viewToModel(x, y, a, bias);
570 }
571
572 /**
573 * Returns the document model underlying the view.
574 *
575 * @return the model
576 */
577 public Document getDocument() {
578 return view.getDocument();
579 }
580
581 /**
582 * Returns the starting offset into the model for this view.
583 *
584 * @return the starting offset
585 */
586 public int getStartOffset() {
587 return view.getStartOffset();
588 }
589
590 /**
591 * Returns the ending offset into the model for this view.
592 *
593 * @return the ending offset
594 */
595 public int getEndOffset() {
596 return view.getEndOffset();
597 }
598
599 /**
600 * Gets the element that this view is mapped to.
601 *
602 * @return the view
603 */
604 public Element getElement() {
605 return view.getElement();
606 }
607
608 /**
609 * Sets the view size.
610 *
611 * @param width the width
612 * @param height the height
613 */
614 public void setSize(float width, float height) {
615 this .width = (int) width;
616 view.setSize(width, height);
617 }
618
619 /**
620 * Fetches the container hosting the view. This is useful for
621 * things like scheduling a repaint, finding out the host
622 * components font, etc. The default implementation
623 * of this is to forward the query to the parent view.
624 *
625 * @return the container
626 */
627 public Container getContainer() {
628 return host;
629 }
630
631 /**
632 * Fetches the factory to be used for building the
633 * various view fragments that make up the view that
634 * represents the model. This is what determines
635 * how the model will be represented. This is implemented
636 * to fetch the factory provided by the associated
637 * EditorKit.
638 *
639 * @return the factory
640 */
641 public ViewFactory getViewFactory() {
642 return factory;
643 }
644
645 private int width;
646 private View view;
647 private ViewFactory factory;
648 private JComponent host;
649
650 }
651 }
|