Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]

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

Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]