/*
 * @(#)ComponentView.java	1.54 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package javax.swing.text;

import java.awt.*;
import javax.swing.SwingUtilities;
import javax.swing.event.*;

/**
 * Component decorator that implements the view interface.  The
 * entire element is used to represent the component.  This acts
 * as a gateway from the display-only View implementations to
 * interactive lightweight components (ie it allows components
 * to be embedded into the View hierarchy).
 * <p>
 * The component is placed relative to the text baseline 
 * according to the value returned by 
 * <code>Component.getAlignmentY</code>.  For Swing components
 * this value can be conveniently set using the method
 * <code>JComponent.setAlignmentY</code>.  For example, setting
 * a value of <code>0.75</code> will cause 75 percent of the 
 * component to be above the baseline, and 25 percent of the
 * component to be below the baseline.
 * <p>
 * This class is implemented to do the extra work necessary to
 * work properly in the presence of multiple threads (i.e. from
 * asynchronous notification of model changes for example) by
 * ensuring that all component access is done on the event thread.
 * <p>
 * The component used is determined by the return value of the
 * createComponent method.  The default implementation of this
 * method is to return the component held as an attribute of
 * the element (by calling StyleConstants.getComponent).  A
 * limitation of this behavior is that the component cannot 
 * be used by more than one text component (i.e. with a shared
 * model).  Subclasses can remove this constraint by implementing
 * the createComponent to actually create a component based upon
 * some kind of specification contained in the attributes.  The
 * ObjectView class in the html package is an example of a
 * ComponentView implementation that supports multiple component
 * views of a shared model.
 *
 * @author Timothy Prinzing
 * @version 1.54 12/19/03
 */
public class ComponentView extends View  {

    /**
     * Creates a new ComponentView object.
     *
     * @param elem the element to decorate
     */
    public ComponentView(Element elem) {
	super(elem);
    }

    /**
     * Create the component that is associated with
     * this view.  This will be called when it has
     * been determined that a new component is needed.
     * This would result from a call to setParent or
     * as a result of being notified that attributes
     * have changed.
     */
    protected Component createComponent() {
	AttributeSet attr = getElement().getAttributes();
	Component comp = StyleConstants.getComponent(attr);
	return comp;
    }

    /**
     * Fetch the component associated with the view.
     */
    public final Component getComponent() {
	return createdC;
    }

    // --- View methods ---------------------------------------------

    /**
     * The real paint behavior occurs naturally from the association
     * that the component has with its parent container (the same
     * container hosting this view).  This is implemented to do nothing.
     *
     * @param g the graphics context
     * @param a the shape
     * @see View#paint
     */
    public void paint(Graphics g, Shape a) {
	if (c != null) {
	    Rectangle alloc = (a instanceof Rectangle) ?
		(Rectangle) a : a.getBounds();
	    c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height);
	}
    }

    /**
     * Determines the preferred span for this view along an
     * axis.  This is implemented to return the value
     * returned by Component.getPreferredSize along the
     * axis of interest.
     *
     * @param axis may be either View.X_AXIS or View.Y_AXIS
     * @return   the span the view would like to be rendered into >= 0.
     *           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.
     * @exception IllegalArgumentException for an invalid axis
     */
    public float getPreferredSpan(int axis) {
	if ((axis != X_AXIS) && (axis != Y_AXIS)) {
	    throw new IllegalArgumentException("Invalid axis: " + axis);
	}
	if (c != null) {
	    Dimension size = c.getPreferredSize();
	    if (axis == View.X_AXIS) {
		return size.width;
	    } else {
		return size.height;
	    }
	}
	return 0;
    }

    /**
     * Determines the minimum span for this view along an
     * axis.  This is implemented to return the value
     * returned by Component.getMinimumSize along the
     * axis of interest.
     *
     * @param axis may be either View.X_AXIS or View.Y_AXIS
     * @return   the span the view would like to be rendered into >= 0.
     *           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.
     * @exception IllegalArgumentException for an invalid axis
     */
    public float getMinimumSpan(int axis) {
	if ((axis != X_AXIS) && (axis != Y_AXIS)) {
	    throw new IllegalArgumentException("Invalid axis: " + axis);
	}
	if (c != null) {
	    Dimension size = c.getMinimumSize();
	    if (axis == View.X_AXIS) {
		return size.width;
	    } else {
		return size.height;
	    }
	}
	return 0;
    }

    /**
     * Determines the maximum span for this view along an
     * axis.  This is implemented to return the value
     * returned by Component.getMaximumSize along the
     * axis of interest.
     *
     * @param axis may be either View.X_AXIS or View.Y_AXIS
     * @return   the span the view would like to be rendered into >= 0.
     *           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.
     * @exception IllegalArgumentException for an invalid axis
     */
    public float getMaximumSpan(int axis) {
	if ((axis != X_AXIS) && (axis != Y_AXIS)) {
	    throw new IllegalArgumentException("Invalid axis: " + axis);
	}
	if (c != null) {
	    Dimension size = c.getMaximumSize();
	    if (axis == View.X_AXIS) {
		return size.width;
	    } else {
		return size.height;
	    }
	}
	return 0;
    }

    /**
     * Determines the desired alignment for this view along an
     * axis.  This is implemented to give the alignment of the
     * embedded component.
     *
     * @param axis may be either View.X_AXIS or View.Y_AXIS
     * @return the desired alignment.  This should be a value
     *   between 0.0 and 1.0 where 0 indicates alignment at the
     *   origin and 1.0 indicates alignment to the full span
     *   away from the origin.  An alignment of 0.5 would be the
     *   center of the view.
     */
    public float getAlignment(int axis) {
	if (c != null) {
	    switch (axis) {
	    case View.X_AXIS:
		return c.getAlignmentX();
	    case View.Y_AXIS:
		return c.getAlignmentY();
	    }
	}
	return super.getAlignment(axis);
    }

    /**
     * Sets the parent for a child view.
     * The parent calls this on the child to tell it who its
     * parent is, giving the view access to things like
     * the hosting Container.  The superclass behavior is
     * executed, followed by a call to createComponent if
     * the parent view parameter is non-null and a component
     * has not yet been created. The embedded components parent
     * is then set to the value returned by <code>getContainer</code>.
     * If the parent view parameter is null, this view is being
     * cleaned up, thus the component is removed from its parent.
     * <p>
     * The changing of the component hierarchy will
     * touch the component lock, which is the one thing 
     * that is not safe from the View hierarchy.  Therefore,
     * this functionality is executed immediately if on the
     * event thread, or is queued on the event queue if
     * called from another thread (notification of change
     * from an asynchronous update).
     *
     * @param p the parent
     */
    public void setParent(View p) {
	super.setParent(p);
        if (SwingUtilities.isEventDispatchThread()) {
	    setComponentParent();
        } else {
            Runnable callSetComponentParent = new Runnable() {
                public void run() {
		    Document doc = getDocument();
		    try {
			if (doc instanceof AbstractDocument) {
			    ((AbstractDocument)doc).readLock();
			}
			setComponentParent();
			Container host = getContainer();
			if (host != null) {
			    preferenceChanged(null, true, true);
			    host.repaint();
			}
		    } finally {
			if (doc instanceof AbstractDocument) {
			    ((AbstractDocument)doc).readUnlock();
			}
		    }			
                }
            };
            SwingUtilities.invokeLater(callSetComponentParent);
        }
    }

    /**
     * Set the parent of the embedded component 
     * with assurance that it is thread-safe.
     */
    void setComponentParent() {
	View p = getParent();
	if (p != null) {
	    Container parent = getContainer();
	    if (parent != null) {
		if (c == null) {
		    // try to build a component
		    Component comp = createComponent();
		    if (comp != null) {
			createdC = comp;
			c = new Invalidator(comp);
		    }
		}
		if (c != null) {
		    if (c.getParent() == null) {
			// components associated with the View tree are added
			// to the hosting container with the View as a constraint.
			parent.add(c, this);
		    }
		}
	    }
	} else {
            if (c != null) {
                Container parent = c.getParent();
                if (parent != null) {
                    // remove the component from its hosting container
                    parent.remove(c);
                }
            }
        }
    }

    /**
     * Provides a mapping from the coordinate space of the model to
     * that of the view.
     *
     * @param pos the position to convert >= 0
     * @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
     * @see View#modelToView
     */
    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
	int p0 = getStartOffset();
	int p1 = getEndOffset();
	if ((pos >= p0) && (pos <= p1)) {
	    Rectangle r = a.getBounds();
	    if (pos == p1) {
		r.x += r.width;
	    }
	    r.width = 0;
	    return r;
	}
	throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
    }

    /**
     * Provides a mapping from the view coordinate space to the logical
     * coordinate space of the model.
     *
     * @param x the X coordinate >= 0
     * @param y the Y coordinate >= 0
     * @param a the allocated region to render into
     * @return the location within the model that best represents
     *    the given point in the view
     * @see View#viewToModel
     */
    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
	Rectangle alloc = (Rectangle) a;
	if (x < alloc.x + (alloc.width / 2)) {
	    bias[0] = Position.Bias.Forward;
	    return getStartOffset();
	}
	bias[0] = Position.Bias.Backward;
	return getEndOffset();
    }

    // --- member variables ------------------------------------------------

    private Component createdC;
    private Component c;

    /**
     * This class feeds the invalidate back to the
     * hosting View.  This is needed to get the View
     * hierarchy to consider giving the component
     * a different size (i.e. layout may have been
     * cached between the associated view and the
     * container hosting this component).
     */
    class Invalidator extends Container {

        // NOTE: When we remove this class we are going to have to some
        // how enforce setting of the focus traversal keys on the children
        // so that they don't inherit them from the JEditorPane. We need
        // to do this as JEditorPane has abnormal bindings (it is a focus cycle
        // root) and the children typically don't want these bindings as well.

	Invalidator(Component child) {
	    setLayout(null);
	    add(child);
	    cacheChildSizes(); 
	}
        
	/**
	 * The components invalid layout needs 
	 * to be propagated through the view hierarchy
	 * so the views (which position the component)
	 * can have their layout recomputed.
	 */
	public void invalidate() {
	    super.invalidate();
	    if (getParent() != null) {
		preferenceChanged(null, true, true);
	    }
        }
	
	public void doLayout() {
	    cacheChildSizes();
	}
	
	public void setBounds(int x, int y, int w, int h) { 
            super.setBounds(x, y, w, h);
            if (getComponentCount() > 0) {
                getComponent(0).setSize(w, h);
            }
            cacheChildSizes();
        }

        public void validateIfNecessary() {
	    if (!isValid()) {
		validate(); 
	     }
	}

	private void cacheChildSizes() {
            if (getComponentCount() > 0) {
                Component child = getComponent(0);
                min = child.getMinimumSize();
                pref = child.getPreferredSize();
                max = child.getMaximumSize();
                yalign = child.getAlignmentY();
                xalign = child.getAlignmentX();
            } else {
                min = pref = max = new Dimension(0, 0);
            }
        }
        
	/**
	 * Shows or hides this component depending on the value of parameter 
	 * <code>b</code>.
	 * @param <code>b</code>  If <code>true</code>, shows this component; 
	 * otherwise, hides this component.
	 * @see #isVisible
	 * @since JDK1.1
	 */
        public void setVisible(boolean b) {
	    super.setVisible(b);
            if (getComponentCount() > 0) {
                getComponent(0).setVisible(b);
            }
        }
        
        /**
         * Overridden to fix 4759054. Must return true so that content
         * is painted when inside a CellRendererPane which is normally
         * invisible.
         */
        public boolean isShowing() {
            return true;
        }

        public Dimension getMinimumSize() {
	    validateIfNecessary();
	    return min;
	}

        public Dimension getPreferredSize() {
	    validateIfNecessary();
	    return pref;
	}

        public Dimension getMaximumSize() {
	    validateIfNecessary();
	    return max;
	}

	public float getAlignmentX() {
	    validateIfNecessary();
	    return xalign;
	}

	public float getAlignmentY() {
	    validateIfNecessary();
	    return yalign;
	}

        public java.util.Set getFocusTraversalKeys(int id) {
            return KeyboardFocusManager.getCurrentKeyboardFocusManager().
		    getDefaultFocusTraversalKeys(id);
        }

	Dimension min;
	Dimension pref;
	Dimension max;
	float yalign;
	float xalign;

    }

}

