swingx-master/swingx-core/src/main/java/org/jdesktop/swingx/plaf/basic/BasicHyperlinkUI.java
/*
 * $Id$
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.plaf.basic;

import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.logging.Logger;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.ImageView;
import javax.swing.text.html.StyleSheet;

import org.jdesktop.swingx.JXHyperlink;
import org.jdesktop.swingx.SwingXUtilities;

/**
 * Basic implementation of the <code>JXHyperlink</code> UI. <br>
 * This is copied from org.jdesktop.jdnc.plaf.basic.BasicLinkButtonUI
 */
public class BasicHyperlinkUI extends BasicButtonUI {

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(BasicHyperlinkUI.class
            .getName());
    
    public static ComponentUI createUI(JComponent c) {
        return new BasicHyperlinkUI();
    }

    private static Rectangle viewRect = new Rectangle();

    private static Rectangle textRect = new Rectangle();

    private static Rectangle iconRect = new Rectangle();

//    private static MouseListener handCursorListener = new HandCursor();

    protected int dashedRectGapX;

    protected int dashedRectGapY;

    protected int dashedRectGapWidth;

    protected int dashedRectGapHeight;

    private Color focusColor;

    private View ulv;

    private PropertyChangeListener pcListener = new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // this method is called from the edt. only other place where ulv is used is in 
            // painting which also happens on edt so it should be safe even without synchronization
            // sole purpose of this call is to reinitialize view on every property change
            ulv = null;
        }};

    @Override
    protected void installDefaults(AbstractButton b) {
        super.installDefaults(b);

        JXHyperlink link = (JXHyperlink) b;
        
        LookAndFeel.installProperty(b, "opaque", false);
        
        if (SwingXUtilities.isUIInstallable(link.getUnclickedColor())) {
            link.setUnclickedColor(UIManager.getColor("Hyperlink.linkColor"));
        }
        
        if (SwingXUtilities.isUIInstallable(link.getClickedColor())) {
            link.setClickedColor(UIManager.getColor("Hyperlink.visitedColor"));
        }
        
        b.setBorderPainted(false);
        b.setRolloverEnabled(true);
        
        if (SwingXUtilities.isUIInstallable(b.getBorder())) {
            b.setBorder(new BorderUIResource(BorderFactory.createEmptyBorder(0, 1, 0, 0)));
        }

        dashedRectGapX = UIManager.getInt("ButtonUI.dashedRectGapX");
        dashedRectGapY = UIManager.getInt("ButtonUI.dashedRectGapY");
        dashedRectGapWidth = UIManager.getInt("ButtonUI.dashedRectGapWidth");
        dashedRectGapHeight = UIManager.getInt("ButtonUI.dashedRectGapHeight");
        focusColor = UIManager.getColor("ButtonUI.focus");

        b.setHorizontalAlignment(SwingConstants.LEADING);
    }

    @Override
    protected void installListeners(AbstractButton b) {
        super.installListeners(b);
//        b.addMouseListener(handCursorListener);
        b.addPropertyChangeListener(pcListener);
    }

    @Override
    protected void uninstallListeners(AbstractButton b) {
        super.uninstallListeners(b);
//        b.removeMouseListener(handCursorListener);
        b.removePropertyChangeListener(pcListener);
    }

    protected Color getFocusColor() {
        return focusColor;
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        AbstractButton b = (AbstractButton) c;
        ButtonModel model = b.getModel();

        FontMetrics fm = g.getFontMetrics();

        Insets i = c.getInsets();

        viewRect.x = i.left;
        viewRect.y = i.top;
        viewRect.width = b.getWidth() - (i.right + viewRect.x);
        viewRect.height = b.getHeight() - (i.bottom + viewRect.y);

        textRect.x = textRect.y = textRect.width = textRect.height = 0;
        iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;

        Font f = c.getFont();
        g.setFont(f);

        // layout the text and icon
        String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(), b
                .getIcon(), b.getVerticalAlignment(), b
                .getHorizontalAlignment(), b.getVerticalTextPosition(), b
                .getHorizontalTextPosition(), viewRect, iconRect, textRect, b
                .getText() == null ? 0 : b.getIconTextGap());

        clearTextShiftOffset();

        // perform UI specific press action, e.g. Windows L&F shifts text
        if (model.isArmed() && model.isPressed()) {
            paintButtonPressed(g, b);
        }

        // Paint the Icon
        if (b.getIcon() != null) {
            paintIcon(g, c, iconRect);
        }

//        Composite oldComposite = ((Graphics2D) g).getComposite();
//
//        if (model.isRollover()) {
//            ((Graphics2D) g).setComposite(AlphaComposite.getInstance(
//                    AlphaComposite.SRC_OVER, 0.5f));
//        }

        if (text != null && !text.equals("")) {
            View v = (View) c.getClientProperty(BasicHTML.propertyKey);
            if (v != null) {
                paintHTMLText(g, b, textRect, text, v);
            } else {
                paintText(g, b, textRect, text);
            }
        }

        if (b.isFocusPainted() && b.hasFocus()) {
            // paint UI specific focus
            paintFocus(g, b, viewRect, textRect, iconRect);
        }

//        ((Graphics2D) g).setComposite(oldComposite);
    }

    /**
     * Method which renders the text of the current button if html.
     * <p>
     * @param g Graphics context
     * @param b Current button to render
     * @param textRect Bounding rectangle to render the text.
     * @param text String to render
     * @param v the View to use.
     */
    protected void paintHTMLText(Graphics g, AbstractButton b, 
            Rectangle textRect, String text, View v) {
        textRect.x += getTextShiftOffset();
        textRect.y += getTextShiftOffset();
        // fix #441-swingx - underline not painted for html
        if (b.getModel().isRollover()) {
            //paintUnderline(g, b, textRect, text);
            if (ulv == null) {
                ulv = ULHtml.createHTMLView(b, text);
            }
            ulv.paint(g, textRect);
        } else {
            v.paint(g, textRect);
        }
        textRect.x -= getTextShiftOffset();
        textRect.y -= getTextShiftOffset();
    }

    /**
     * {@inheritDoc} <p>
     * Overridden to paint the underline on rollover.
     */
    @Override
    protected void paintText(Graphics g, AbstractButton b, Rectangle textRect,
            String text) {
        //kgs -- SwingX #415: pixel-shift when disabled
        //BasicButtonUI shifts disabled text to the left by 1 pixel
        //we compensate for that here, so that all Hyperlinks paint
        //at the same location regardless of state
        if (!b.getModel().isEnabled()) {
            textRect.x += 1;
        }
            
        super.paintText(g, b, textRect, text);
        if (b.getModel().isRollover()) {
            paintUnderline(g, b, textRect, text);
        }
    }

    private void paintUnderline(Graphics g, AbstractButton b, Rectangle rect,
            String text) {
        // JW: copied from JXTable.LinkRenderer
        FontMetrics fm = g.getFontMetrics();
        int descent = fm.getDescent();

        // REMIND(aim): should we be basing the underline on
        // the font's baseline instead of the text bounds?
        g.drawLine(rect.x + getTextShiftOffset(),
          (rect.y + rect.height) - descent + 1 + getTextShiftOffset(),
          rect.x + rect.width + getTextShiftOffset(),
          (rect.y + rect.height) - descent + 1 + getTextShiftOffset());
    }

    @Override
    protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect,
            Rectangle textRect, Rectangle iconRect) {
        if (b.getParent() instanceof JToolBar) {
            // Windows doesn't draw the focus rect for buttons in a toolbar.
            return;
        }

        // focus painted same color as text
        g.setColor(getFocusColor());
        // paint the focus rect around the union of text rect and icon rect
        // PENDING JW: duplicated to handle insets
        Rectangle iconTextRect = getIconTextRect(b); 
        // PENDING JW: better factor handling of insets - the bare union doesn't respect insets
//        Rectangle iconTextRect = textRect.union(iconRect);
        BasicGraphicsUtils.drawDashedRect(g, iconTextRect.x, iconTextRect.y, 
                iconTextRect.width, iconTextRect.height);
        // pre-#167-swingx: active area too large
//        int width = b.getWidth();
//        int height = b.getHeight();
//        BasicGraphicsUtils.drawDashedRect(g, dashedRectGapX, dashedRectGapY,
//                width - dashedRectGapWidth, height - dashedRectGapHeight);
    }

    @Override
    protected void paintButtonPressed(Graphics g, AbstractButton b) {
         setTextShiftOffset();
    }

    
    @Override
    protected BasicButtonListener createButtonListener(AbstractButton b) {
        return new BasicHyperlinkListener(b);
    }

    /**
     * {@inheritDoc} <p>
     * 
     * Overridden to return true if the position is inside the union of the
     * text and icon rectangle, false otherwise.
     */
    @Override
    public boolean contains(JComponent c, int x, int y) {
        AbstractButton button = (AbstractButton) c;
        return isInside(getIconTextRect(button), x, y);
    }

    /**
     * @param iconTextRect
     * @param point
     * @return
     */
    private boolean isInside(Rectangle iconTextRect, int x, int y) {
        if (iconTextRect == null) return false;
        return iconTextRect.contains(x, y);
    }

    /**
     * C&p'ed from BasicGraphicsUtils (getPreferredButtonSize).
     * 
     * @param b the button to analyse.
     * @return the union of the text and icon rectangle of the AbstractButton
     *   or null if the button has children (??) 
     */
    protected Rectangle getIconTextRect(AbstractButton b) {
        if (b.getComponentCount() > 0) {
            return null;
        }

        Icon icon = b.getIcon();
        String text = b.getText();

        Font font = b.getFont();
        FontMetrics fm = b.getFontMetrics(font);

        Rectangle iconR = new Rectangle();
        Rectangle textR = new Rectangle();
        Rectangle viewR = new Rectangle(b.getSize());

        SwingUtilities.layoutCompoundLabel(b, fm, text, icon,
                b.getVerticalAlignment(), b.getHorizontalAlignment(), b
                        .getVerticalTextPosition(), b
                        .getHorizontalTextPosition(), viewR, iconR, textR,
                (text == null ? 0 : b.getIconTextGap()));

        /*
         * The preferred size of the button is the size of the text and icon
         * rectangles plus the buttons insets.
         */

        Rectangle r = iconR.union(textR);

        Insets insets = b.getInsets();
        r.width += insets.left + insets.right;
        r.height += insets.top + insets.bottom;
        // PENDING JW: why not?
//        r.x -= insets.left;
        r.y -= insets.top;
        return r;
    }

    /**
     * A BasicButtonListener specialized to the needs of a Hyperlink. 
     * 
     * @author Jeanette Winzenburg
     */
    public static class BasicHyperlinkListener extends BasicButtonListener {

        /**
         * @param b
         */
        public BasicHyperlinkListener(AbstractButton b) {
            super(b);
        }

        
        @Override
        public void stateChanged(ChangeEvent e) {
            AbstractButton button = (AbstractButton) e.getSource();
            if (button.isRolloverEnabled()) {
                button.setCursor(button.getModel().isRollover() ? 
                        // PENDING JW: support customizable cursor
                        Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : null);
            }
            super.stateChanged(e);
        }
    }
    
    static class ULHtml extends BasicHTML {
        /**
         * Create an html renderer for the given component and
         * string of html.
         */
        public static View createHTMLView(JComponent c, String html) {
            BasicEditorKit kit = getFactory();
            Document doc = kit.createDefaultDocument(c.getFont(),
                                                     c.getForeground());
            Object base = c.getClientProperty(documentBaseKey);
            if (base instanceof URL) {
                ((HTMLDocument)doc).setBase((URL)base);
            }
            Reader r = new StringReader(html);
            try {
                kit.read(r, doc, 0);
            } catch (Throwable e) {
            }
            ViewFactory f = kit.getViewFactory();
            View hview = f.create(doc.getDefaultRootElement());
            View v = new Renderer(c, f, hview);
            return v;
        }

        static BasicEditorKit getFactory() {
            if (basicHTMLFactory == null) {
                    basicHTMLViewFactory = new BasicHTMLViewFactory();
                basicHTMLFactory = new BasicEditorKit();
            }
            return basicHTMLFactory;
        }

        /**
         * The source of the html renderers
         */
        private static BasicEditorKit basicHTMLFactory;
    
        /**
         * Creates the Views that visually represent the model.
         */
        private static ViewFactory basicHTMLViewFactory;
    
        /**
         * Overrides to the default stylesheet.  Should consider
         * just creating a completely fresh stylesheet.
         */
        private static final String styleChanges = 
            "p { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0; text-decoration: underline }" +
            "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0; text-decoration: underline }"+
            "font {text-decoration: underline}";

        static class BasicEditorKit extends HTMLEditorKit {
            /** Shared base style for all documents created by us use. */
            private static StyleSheet defaultStyles;
    
            /**
             * Overriden to return our own slimmed down style sheet.
             */
            @Override
            public StyleSheet getStyleSheet() {
                if (defaultStyles == null) {
                defaultStyles = new StyleSheet();
                StringReader r = new StringReader(styleChanges);
                try {
                    defaultStyles.loadRules(r, null);
                } catch (Throwable e) {
                    // don't want to die in static initialization... 
                    // just display things wrong.
                }
                r.close();
                defaultStyles.addStyleSheet(super.getStyleSheet());
            }
            return defaultStyles;
        }

        /**
         * Sets the async policy to flush everything in one chunk, and
         * to not display unknown tags.
         */
        public Document createDefaultDocument(Font defaultFont,
                                                  Color foreground) {
            StyleSheet styles = getStyleSheet();
            StyleSheet ss = new StyleSheet();
            ss.addStyleSheet(styles);
            BasicDocument doc = new BasicDocument(ss, defaultFont, foreground);
            doc.setAsynchronousLoadPriority(Integer.MAX_VALUE);
            doc.setPreservesUnknownTags(false);
            return doc;
        }

            /**
             * Returns the ViewFactory that is used to make sure the Views don't
             * load in the background.
             */
            @Override
            public ViewFactory getViewFactory() {
                return basicHTMLViewFactory;
            }
        }


        /**
         * BasicHTMLViewFactory extends HTMLFactory to force images to be loaded
         * synchronously.
         */
        static class BasicHTMLViewFactory extends HTMLEditorKit.HTMLFactory {
            @Override
            public View create(Element elem) {
                View view = super.create(elem);

                if (view instanceof ImageView) {
                    ((ImageView)view).setLoadsSynchronously(true);
                }
                return view;
            }
        }


        /**
         * The subclass of HTMLDocument that is used as the model. getForeground
         * is overridden to return the foreground property from the Component this
         * was created for.
         */
        static class BasicDocument extends HTMLDocument {
        private static Class<?> clz;
        private static Method displayPropertiesToCSS;

        /** The host, that is where we are rendering. */
        // private JComponent host;
        // --------- 1.5 x 1.6 incompatibility handling ....
        static {
            String j5 = "com.sun.java.swing.SwingUtilities2";
            String j6 = "sun.swing.SwingUtilities2";
            try {
                // assume 1.6
                clz = Class.forName(j6);
            } catch (ClassNotFoundException e) {
                // or maybe not ..
                try {
                    clz = Class.forName(j5);
                } catch (ClassNotFoundException e1) {
                    throw new RuntimeException("Failed to find SwingUtilities2. Check the classpath.");
                }
            }
            try {
                displayPropertiesToCSS = clz.getMethod("displayPropertiesToCSS", new Class[] { Font.class, Color.class});
            } catch (Exception e) {
                throw new RuntimeException("Failed to use SwingUtilities2. Check the permissions and class version.");
            }
        }

        private static String displayPropertiesToCSS(Font f, Color c) {
            try {
                return (String) displayPropertiesToCSS.invoke(null, new Object[] { f, c });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // --------- EO 1.5 x 1.6 incompatibility handling ....

        BasicDocument(StyleSheet s, Font defaultFont, Color foreground) {
            super(s);
            setPreservesUnknownTags(false);
                setFontAndColor(defaultFont, foreground);
        }

        /**
         * Sets the default font and default color. These are set by
         * adding a rule for the body that specifies the font and color.
         * This allows the html to override these should it wish to have
         * a custom font or color.
         */
        private void setFontAndColor(Font font, Color fg) {
                getStyleSheet().addRule(displayPropertiesToCSS(font,fg));
        }
    }


    /**
     * Root text view that acts as an HTML renderer.
     */
    static class Renderer extends View {

        Renderer(JComponent c, ViewFactory f, View v) {
                super(null);
            host = c;
            factory = f;
            view = v;
            view.setParent(this);
            // initially layout to the preferred size
            setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS));
        }

        /**
         * Fetches the attributes to use when rendering.  At the root
         * level there are no attributes.  If an attribute is resolved
         * up the view hierarchy this is the end of the line.
         */
            @Override
            public AttributeSet getAttributes() {
            return null;
        }

        /**
         * Determines the preferred span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        @Override
        public float getPreferredSpan(int axis) {
            if (axis == X_AXIS) {
                // width currently laid out to
                return width;
            }
            return view.getPreferredSpan(axis);
        }

        /**
         * Determines the minimum span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        @Override
        public float getMinimumSpan(int axis) {
            return view.getMinimumSpan(axis);
        }

        /**
         * Determines the maximum span for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the span the view would like to be rendered into.
         *         Typically the view is told to render into the span
         *         that is returned, although there is no guarantee.
         *         The parent may choose to resize or break the view.
         */
        @Override
        public float getMaximumSpan(int axis) {
            return Integer.MAX_VALUE;
        }

        /**
         * Specifies that a preference has changed.
         * Child views can call this on the parent to indicate that
         * the preference has changed.  The root view routes this to
         * invalidate on the hosting component.
         * <p>
         * This can be called on a different thread from the
         * event dispatching thread and is basically unsafe to
         * propagate into the component.  To make this safe,
         * the operation is transferred over to the event dispatching 
         * thread for completion.  It is a design goal that all view
         * methods be safe to call without concern for concurrency,
         * and this behavior helps make that true.
         *
         * @param child the child view
         * @param width true if the width preference has changed
         * @param height true if the height preference has changed
         */ 
        @Override
        public void preferenceChanged(View child, boolean width, boolean height) {
            host.revalidate();
            host.repaint();
        }

        /**
         * Determines the desired alignment for this view along an axis.
         *
         * @param axis may be either X_AXIS or Y_AXIS
         * @return the desired alignment, where 0.0 indicates the origin
         *     and 1.0 the full span away from the origin
         */
        @Override
        public float getAlignment(int axis) {
            return view.getAlignment(axis);
        }

        /**
         * Renders the view.
         *
         * @param g the graphics context
         * @param allocation the region to render into
         */
        @Override
        public void paint(Graphics g, Shape allocation) {
            Rectangle alloc = allocation.getBounds();
            view.setSize(alloc.width, alloc.height);
            view.paint(g, allocation);
        }
        
        /**
         * Sets the view parent.
         *
         * @param parent the parent view
         */
        @Override
        public void setParent(View parent) {
            throw new Error("Can't set parent on root view");
        }

        /** 
         * Returns the number of views in this view.  Since
         * this view simply wraps the root of the view hierarchy
         * it has exactly one child.
         *
         * @return the number of views
         * @see #getView
         */
        @Override
        public int getViewCount() {
            return 1;
        }

        /** 
         * Gets the n-th view in this container.
         *
         * @param n the number of the view to get
         * @return the view
         */
        @Override
        public View getView(int n) {
            return view;
        }

        /**
         * Provides a mapping from the document model coordinate space
         * to the coordinate space of the view mapped to it.
         *
         * @param pos the position to convert
         * @param a the allocated region to render into
         * @return the bounding box of the given position
         */
        @Override
        public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
            return view.modelToView(pos, a, b);
        }

        /**
         * Provides a mapping from the document model coordinate space
         * to the coordinate space of the view mapped to it.
         *
         * @param p0 the position to convert >= 0
         * @param b0 the bias toward the previous character or the
         *  next character represented by p0, in case the 
         *  position is a boundary of two views. 
         * @param p1 the position to convert >= 0
         * @param b1 the bias toward the previous character or the
         *  next character represented by p1, in case the 
         *  position is a boundary of two views. 
         * @param a the allocated region to render into
         * @return the bounding box of the given position is returned
         * @exception BadLocationException  if the given position does
         *   not represent a valid location in the associated document
         * @exception IllegalArgumentException for an invalid bias argument
         * @see View#viewToModel
         */
        @Override
        public Shape modelToView(int p0, Position.Bias b0, int p1, 
                     Position.Bias b1, Shape a) throws BadLocationException {
            return view.modelToView(p0, b0, p1, b1, a);
        }

        /**
         * Provides a mapping from the view coordinate space to the logical
         * coordinate space of the model.
         *
         * @param x x coordinate of the view location to convert
         * @param y y coordinate of the view location to convert
         * @param a the allocated region to render into
         * @return the location within the model that best represents the
         *    given point in the view
         */
        @Override
        public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
            return view.viewToModel(x, y, a, bias);
        }

        /**
         * Returns the document model underlying the view.
         *
         * @return the model
         */
        @Override
        public Document getDocument() {
            return view.getDocument();
        }
        
        /**
         * Returns the starting offset into the model for this view.
         *
         * @return the starting offset
         */
        @Override
        public int getStartOffset() {
            return view.getStartOffset();
        }

        /**
         * Returns the ending offset into the model for this view.
         *
         * @return the ending offset
         */
        @Override
        public int getEndOffset() {
            return view.getEndOffset();
        }

        /**
         * Gets the element that this view is mapped to.
         *
         * @return the view
         */
        @Override
        public Element getElement() {
            return view.getElement();
        }

        /**
         * Sets the view size.
         *
         * @param width the width
         * @param height the height
         */
        @Override
        public void setSize(float width, float height) {
            this.width = (int) width;
            view.setSize(width, height);
        }

        /**
         * Fetches the container hosting the view.  This is useful for
         * things like scheduling a repaint, finding out the host 
         * components font, etc.  The default implementation
         * of this is to forward the query to the parent view.
         *
         * @return the container
         */
        @Override
        public Container getContainer() {
            return host;
        }
            
        /**
         * Fetches the factory to be used for building the
         * various view fragments that make up the view that
         * represents the model.  This is what determines
         * how the model will be represented.  This is implemented
         * to fetch the factory provided by the associated
         * EditorKit.
         *
         * @return the factory
         */
        @Override
        public ViewFactory getViewFactory() {
            return factory;
        }

        private int width;
        private View view;
        private ViewFactory factory;
        private JComponent host;

        }
    }
}
