/*
 * @(#)FlowView.java	1.45 05/01/04
 *
 * Copyright 2005 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.event.*;
import javax.swing.SizeRequirements;

/**
 * A View that tries to flow it's children into some 
 * partially constrained space.  This can be used to
 * build things like paragraphs, pages, etc.  The 
 * flow is made up of the following pieces of functionality.
 * <ul>
 * <li>A logical set of child views, which as used as a
 * layout pool from which a physical view is formed.
 * <li>A strategy for translating the logical view to
 * a physical (flowed) view.
 * <li>Constraints for the strategy to work against.
 * <li>A physical structure, that represents the flow.
 * The children of this view are where the pieces of 
 * of the logical views are placed to create the flow.
 * </ul>
 *
 * @author  Timothy Prinzing
 * @version 1.45 01/04/05
 * @see     View
 */
public abstract class FlowView extends BoxView {

    private final static FlowStrategy STRATEGY = new FlowStrategy();
    
    /**
     * Constructs a FlowView for the given element.
     *
     * @param elem the element that this view is responsible for
     * @param axis may be either View.X_AXIS or View.Y_AXIS
     */
    public FlowView(Element elem, int axis) {
	super(elem, axis);
	layoutSpan = Short.MAX_VALUE;
	strategy = STRATEGY;
    }

    /**
     * Fetches the axis along which views should be
     * flowed.  By default, this will be the axis 
     * orthogonal to the axis along which the flow
     * rows are tiled (the axis of the default flow
     * rows themselves).  This is typically used
     * by the <code>FlowStrategy</code>.
     */
    public int getFlowAxis() {
	if (getAxis() == Y_AXIS) {
	    return X_AXIS;
	}
	return Y_AXIS;
    }

    /**
     * Fetch the constraining span to flow against for
     * the given child index.  This is called by the
     * FlowStrategy while it is updating the flow.
     * A flow can be shaped by providing different values
     * for the row constraints.  By default, the entire
     * span inside of the insets along the flow axis
     * is returned.
     *
     * @param index the index of the row being updated.
     *   This should be a value >= 0 and < getViewCount().
     * @see #getFlowStart
     */
    public int getFlowSpan(int index) {
	return layoutSpan;
    }

    /**
     * Fetch the location along the flow axis that the
     * flow span will start at.  This is called by the
     * FlowStrategy while it is updating the flow.
     * A flow can be shaped by providing different values
     * for the row constraints.

     * @param index the index of the row being updated.
     *   This should be a value >= 0 and < getViewCount().
     * @see #getFlowSpan
     */
    public int getFlowStart(int index) {
	return 0;
    }

    /**
     * Create a View that should be used to hold a 
     * a rows worth of children in a flow.  This is
     * called by the FlowStrategy when new children
     * are added or removed (i.e. rows are added or
     * removed) in the process of updating the flow.
     */
    protected abstract View createRow();
	
    // ---- BoxView methods -------------------------------------

    /**
     * Loads all of the children to initialize the view.
     * This is called by the <code>setParent</code> method.
     * This is reimplemented to not load any children directly
     * (as they are created in the process of formatting).
     * If the layoutPool variable is null, an instance of
     * LogicalView is created to represent the logical view
     * that is used in the process of formatting.
     *
     * @param f the view factory
     */
    protected void loadChildren(ViewFactory f) {
	if (layoutPool == null) {
	    layoutPool = new LogicalView(getElement());
	}
        layoutPool.setParent(this);

        // This synthetic insertUpdate call gives the strategy a chance
        // to initialize.
        strategy.insertUpdate( this, null, null );
    }

    /**
     * Fetches the child view index representing the given position in
     * the model.  
     *
     * @param pos the position >= 0
     * @return  index of the view representing the given position, or 
     *   -1 if no view represents that position
     */
    protected int getViewIndexAtPosition(int pos) {
	if (pos >= getStartOffset() && (pos < getEndOffset())) {
	    for(int counter = getViewCount() - 1; counter >= 0; counter--) {
		View v = getView(counter);
		if(pos >= v.getStartOffset() &&
		   pos < v.getEndOffset()) {
		    return counter;
		}
	    }
	}
	return -1;
    }

    /**
     * Lays out the children.  If the span along the flow
     * axis has changed, layout is marked as invalid which
     * which will cause the superclass behavior to recalculate
     * the layout along the box axis.  The FlowStrategy.layout
     * method will be called to rebuild the flow rows as 
     * appropriate.  If the height of this view changes 
     * (determined by the perferred size along the box axis),
     * a preferenceChanged is called.  Following all of that,
     * the normal box layout of the superclass is performed.
     *
     * @param width  the width to lay out against >= 0.  This is
     *   the width inside of the inset area.
     * @param height the height to lay out against >= 0 This
     *   is the height inside of the inset area.
     */
    protected void layout(int width, int height) {
	final int faxis = getFlowAxis();
	int newSpan;
	if (faxis == X_AXIS) {
	    newSpan = (int)width;
	} else {
	    newSpan = (int)height;
	}
	if (layoutSpan != newSpan) {
	    layoutChanged(faxis);
	    layoutChanged(getAxis());
	    layoutSpan = newSpan;
	}

	// repair the flow if necessary
	if (! isLayoutValid(faxis)) {
	    final int heightAxis = getAxis();
	    int oldFlowHeight = (int)((heightAxis == X_AXIS)? getWidth() : getHeight());
	    strategy.layout(this);
	    int newFlowHeight = (int) getPreferredSpan(heightAxis);
	    if (oldFlowHeight != newFlowHeight) {
		View p = getParent();
		if (p != null) {
		    p.preferenceChanged(this, (heightAxis == X_AXIS), (heightAxis == Y_AXIS));
		}

                // PENDING(shannonh)
                // Temporary fix for 4250847
                // Can be removed when TraversalContext is added
		Component host = getContainer();
		if (host != null) {
		    //nb idk 12/12/2001 host should not be equal to null. We need to add assertion here
		    host.repaint();
		}
	    }
	}

	super.layout(width, height);
    }

    /**
     * Calculate equirements along the minor axis.  This
     * is implemented to forward the request to the logical 
     * view by calling getMinimumSpan, getPreferredSpan, and
     * getMaximumSpan on it.  
     */
    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
	if (r == null) {
	    r = new SizeRequirements();
	}
	float pref = layoutPool.getPreferredSpan(axis);
	float min = layoutPool.getMinimumSpan(axis);
        // Don't include insets, Box.getXXXSpan will include them.
	r.minimum = (int)min;
	r.preferred = Math.max(r.minimum, (int) pref);
	r.maximum = Integer.MAX_VALUE;
	r.alignment = 0.5f;
	return r;
    }

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

    /**
     * Gives notification that something was inserted into the document
     * in a location that this view is responsible for.
     *
     * @param changes the change information from the associated document
     * @param a the current allocation of the view
     * @param f the factory to use to rebuild if the view has children
     * @see View#insertUpdate
     */
    public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
	layoutPool.insertUpdate(changes, a, f);
	strategy.insertUpdate(this, changes, getInsideAllocation(a));
    }

    /**
     * Gives notification that something was removed from the document
     * in a location that this view is responsible for.
     *
     * @param changes the change information from the associated document
     * @param a the current allocation of the view
     * @param f the factory to use to rebuild if the view has children
     * @see View#removeUpdate
     */
    public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
	layoutPool.removeUpdate(changes, a, f);
	strategy.removeUpdate(this, changes, getInsideAllocation(a));
    }

    /**
     * Gives notification from the document that attributes were changed
     * in a location that this view is responsible for.
     *
     * @param changes the change information from the associated document
     * @param a the current allocation of the view
     * @param f the factory to use to rebuild if the view has children
     * @see View#changedUpdate
     */
    public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
	layoutPool.changedUpdate(changes, a, f);
	strategy.changedUpdate(this, changes, getInsideAllocation(a));
    }

    /** {@inheritDoc} */
    public void setParent(View parent) {
	super.setParent(parent);
        if (parent == null 
                && layoutPool != null ) {
            layoutPool.setParent(null);
        }
    }

    // --- variables -----------------------------------------------

    /**
     * Default constraint against which the flow is 
     * created against.  
     */
    protected int layoutSpan;

    /**
     * These are the views that represent the child elements
     * of the element this view represents (The logical view
     * to translate to a physical view).  These are not
     * directly children of this view.  These are either 
     * placed into the rows directly or used for the purpose
     * of breaking into smaller chunks, to form the physical
     * view.
     */
    protected View layoutPool;

    /**
     * The behavior for keeping the flow updated.  By
     * default this is a singleton shared by all instances
     * of FlowView (FlowStrategy is stateless).  Subclasses
     * can create an alternative strategy, which might keep
     * state.
     */
    protected FlowStrategy strategy;

    /**
     * Strategy for maintaining the physical form
     * of the flow.  The default implementation is
     * completely stateless, and recalculates the
     * entire flow if the layout is invalid on the
     * given FlowView.  Alternative strategies can
     * be implemented by subclassing, and might 
     * perform incrementatal repair to the layout
     * or alternative breaking behavior.
     */
    public static class FlowStrategy {

	/**
	 * Gives notification that something was inserted into the document
	 * in a location that the given flow view is responsible for.  The
	 * strategy should update the appropriate changed region (which
	 * depends upon the strategy used for repair).
	 *
	 * @param e the change information from the associated document
	 * @param alloc the current allocation of the view inside of the insets.
	 *   This value will be null if the view has not yet been displayed.
	 * @see View#insertUpdate
	 */
        public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
	    if (alloc != null) {
		Component host = fv.getContainer();
		if (host != null) {
		    host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
		}
	    } else {
		fv.preferenceChanged(null, true, true);
	    }
	}

	/**
	 * Gives notification that something was removed from the document
	 * in a location that the given flow view is responsible for.
	 *
	 * @param e the change information from the associated document
	 * @param alloc the current allocation of the view inside of the insets.
	 * @see View#removeUpdate
	 */
        public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
	    if (alloc != null) {
		Component host = fv.getContainer();
		if (host != null) {
		    host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
		}
	    } else {
		fv.preferenceChanged(null, true, true);
	    }
	}

	/**
	 * Gives notification from the document that attributes were changed
	 * in a location that this view is responsible for.
	 *
         * @param fv     the <code>FlowView</code> containing the changes
         * @param e      the <code>DocumentEvent</code> describing the changes
         *               done to the Document
         * @param alloc  Bounds of the View
	 * @see View#changedUpdate
	 */
        public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
	    if (alloc != null) {
		Component host = fv.getContainer();
		if (host != null) {
		    host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
		}
	    } else {
		fv.preferenceChanged(null, true, true);
	    }
	}

	/**
	 * This method gives flow strategies access to the logical
	 * view of the FlowView.
	 */
	protected View getLogicalView(FlowView fv) {
	    return fv.layoutPool;
	}

	/** 
	 * Update the flow on the given FlowView.  By default, this causes 
	 * all of the rows (child views) to be rebuilt to match the given 
	 * constraints for each row.  This is called by a FlowView.layout 
	 * to update the child views in the flow.
	 *
	 * @param fv the view to reflow
	 */
	public void layout(FlowView fv) {
	    int p0 = fv.getStartOffset(); 
	    int p1 = fv.getEndOffset();

	    // we want to preserve all views from the logicalView from being 
	    // removed
	    View lv = getLogicalView(fv);
	    int n = lv.getViewCount();
	    for( int i = 0; i < n; i++ ) {
		View v = lv.getView(i);
		v.setParent(lv);
	    }
	    fv.removeAll();
	    for (int rowIndex = 0; p0 < p1; rowIndex++) {
		View row = fv.createRow();
		fv.append(row);
		
		// layout the row to the current span.  If nothing fits,
		// force something.
		int next = layoutRow(fv, rowIndex, p0);
		if (row.getViewCount() == 0) {
		    row.append(createView(fv, p0, Integer.MAX_VALUE, rowIndex));
		    next = row.getEndOffset();
		}
		if (next <= p0) {
		    throw new StateInvariantError("infinite loop in formatting");
		} else {
		    p0 = next;
		}
	    }
	}

	/**
	 * Creates a row of views that will fit within the 
	 * layout span of the row.  This is called by the layout method.
	 * This is implemented to fill the row by repeatedly calling
	 * the createView method until the available span has been
	 * exhausted, a forced break was encountered, or the createView
	 * method returned null.  If the remaining span was exhaused, 
	 * the adjustRow method will be called to perform adjustments
	 * to the row to try and make it fit into the given span.
	 *
	 * @param rowIndex the index of the row to fill in with views.  The
	 *   row is assumed to be empty on entry.
	 * @param pos  The current position in the children of
	 *   this views element from which to start.  
	 * @return the position to start the next row
	 */
	protected int layoutRow(FlowView fv, int rowIndex, int pos) {
	    View row = fv.getView(rowIndex);
	    int x = fv.getFlowStart(rowIndex);
	    int spanLeft = fv.getFlowSpan(rowIndex);
	    int end = fv.getEndOffset();
	    TabExpander te = (fv instanceof TabExpander) ? (TabExpander)fv : null;

	    // Indentation.
	    int preX = x;
	    int availableSpan = spanLeft;
	    preX = x;
	    
	    final int flowAxis = fv.getFlowAxis();
	    boolean forcedBreak = false;
	    while (pos < end  && spanLeft >= 0) {
		View v = createView(fv, pos, spanLeft, rowIndex);
		if ((v == null) 
                    || (spanLeft == 0 
                        &&  v.getPreferredSpan(flowAxis) > 0)) {
		    break;
		}
		
		int chunkSpan;
		if ((flowAxis == X_AXIS) && (v instanceof TabableView)) {
		    chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, te);
		} else {
		    chunkSpan = (int) v.getPreferredSpan(flowAxis);
		}

		// If a forced break is necessary, break
		if (v.getBreakWeight(flowAxis, pos, spanLeft) >= ForcedBreakWeight) {
		    int n = row.getViewCount();
		    if (n > 0) {
			/* If this is a forced break and it's not the only view
			 * the view should be replaced with a call to breakView.
			 * If it's it only view, it should be used directly.  In
			 * either case no more children should be added beyond this
			 * view.
			 */
			v = v.breakView(flowAxis, pos, x, spanLeft);
			if (v != null) {
			    if ((flowAxis == X_AXIS) && (v instanceof TabableView)) {
				chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, te);
			    } else {
				chunkSpan = (int) v.getPreferredSpan(flowAxis);
			    }
			} else {
			    chunkSpan = 0;
			}
		    }
		    forcedBreak = true;
		}
		
		spanLeft -= chunkSpan;
		x += chunkSpan;
		if (v != null) {
		    row.append(v);
		    pos = v.getEndOffset();
		}
		if (forcedBreak) {
		    break;
		}
		
	    }
	    if (spanLeft < 0) {
		// This row is too long and needs to be adjusted.
		adjustRow(fv, rowIndex, availableSpan, preX);
	    } else if (row.getViewCount() == 0) {
		// Impossible spec... put in whatever is left.
		View v = createView(fv, pos, Integer.MAX_VALUE, rowIndex);
		row.append(v);
	    }
	    return row.getEndOffset();
	}

	/**
	 * Adjusts the given row if possible to fit within the
	 * layout span.  By default this will try to find the 
	 * highest break weight possible nearest the end of
	 * the row.  If a forced break is encountered, the
	 * break will be positioned there.
	 * 
	 * @param rowIndex the row to adjust to the current layout
	 *  span.
	 * @param desiredSpan the current layout span >= 0
	 * @param x the location r starts at.
	 */
        protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
	    final int flowAxis = fv.getFlowAxis();
	    View r = fv.getView(rowIndex);
	    int n = r.getViewCount();
	    int span = 0;
	    int bestWeight = BadBreakWeight;
	    int bestSpan = 0;
	    int bestIndex = -1;
	    int bestOffset = 0;
	    View v;
	    for (int i = 0; i < n; i++) {
		v = r.getView(i);
		int spanLeft = desiredSpan - span;
		
		int w = v.getBreakWeight(flowAxis, x + span, spanLeft);
		if ((w >= bestWeight) && (w > BadBreakWeight)) {
		    bestWeight = w;
		    bestIndex = i;
		    bestSpan = span;
		    if (w >= ForcedBreakWeight) {
			// it's a forced break, so there is
			// no point in searching further.
			break;
		    }
		}
		span += v.getPreferredSpan(flowAxis);
	    }
	    if (bestIndex < 0) {
		// there is nothing that can be broken, leave
		// it in it's current state.
		return;
	    }

	    // Break the best candidate view, and patch up the row.
	    int spanLeft = desiredSpan - bestSpan;
	    v = r.getView(bestIndex);
	    v = v.breakView(flowAxis, v.getStartOffset(), x + bestSpan, spanLeft);
	    View[] va = new View[1];
	    va[0] = v;
	    View lv = getLogicalView(fv);
	    for (int i = bestIndex; i < n; i++) {
		View tmpView = r.getView(i);
		if (contains(lv,tmpView)) {
		    tmpView.setParent(lv);
		} else if (tmpView.getViewCount() > 0) {
		    recursiveReparent(tmpView, lv);
		}
	    }
	    r.replace(bestIndex, n - bestIndex, va);
	}

	private void recursiveReparent(View v, View logicalView) {
	    int n = v.getViewCount();
	    for (int i = 0; i < n; i++) {
		View tmpView = v.getView(i);
		if (contains(logicalView,tmpView)) {
		    tmpView.setParent(logicalView);
		} else {
		    recursiveReparent(tmpView, logicalView);
		}
	    }
	}

	private boolean contains(View logicalView, View v) {
	    int n = logicalView.getViewCount();
	    for (int i = 0; i < n; i++) {
		if (logicalView.getView(i) == v) {
		    return true;
		}
	    }
	    return false;
	}

	/**
	 * Creates a view that can be used to represent the current piece
	 * of the flow.  This can be either an entire view from the
	 * logical view, or a fragment of the logical view.
	 *
	 * @param fv the view holding the flow
	 * @param startOffset the start location for the view being created
	 * @param spanLeft the about of span left to fill in the row
	 * @param rowIndex the row the view will be placed into
	 */
	protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
	    // Get the child view that contains the given starting position
	    View lv = getLogicalView(fv);
	    int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
	    View v = lv.getView(childIndex);
	    if (startOffset==v.getStartOffset()) {
		// return the entire view
		return v;
	    }
	    
	    // return a fragment.
	    v = v.createFragment(startOffset, v.getEndOffset());
	    return v;
	}
    }

    /**
     * This class can be used to represent a logical view for 
     * a flow.  It keeps the children updated to reflect the state
     * of the model, gives the logical child views access to the
     * view hierarchy, and calculates a preferred span.  It doesn't
     * do any rendering, layout, or model/view translation.
     */
    static class LogicalView extends CompositeView {

	LogicalView(Element elem) {
	    super(elem);
	}

        protected int getViewIndexAtPosition(int pos) {
	    Element elem = getElement();
	    if (elem.isLeaf()) {
		return 0;
	    }
	    return super.getViewIndexAtPosition(pos);
	}
	
        protected void loadChildren(ViewFactory f) {
	    Element elem = getElement();
	    if (elem.isLeaf()) {
		View v = new LabelView(elem);
		append(v);
	    } else {
		super.loadChildren(f);
	    }
	}

	/**
	 * Fetches the attributes to use when rendering.  This view
	 * isn't directly responsible for an element so it returns
	 * the outer classes attributes.
	 */
        public AttributeSet getAttributes() {
	    View p = getParent();
	    return (p != null) ? p.getAttributes() : null;
	}

	/**
	 * Determines the preferred span for this view along an
	 * axis.
	 *
	 * @param axis may be either View.X_AXIS or View.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.
	 * @see View#getPreferredSpan
	 */
        public float getPreferredSpan(int axis) {
	    float maxpref = 0;
	    float pref = 0;
	    int n = getViewCount();
	    for (int i = 0; i < n; i++) {
		View v = getView(i);
		pref += v.getPreferredSpan(axis);
		if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) {
		    maxpref = Math.max(maxpref, pref);
		    pref = 0;
		}
	    }
	    maxpref = Math.max(maxpref, pref);
	    return maxpref;
	}

	/**
	 * Determines the minimum span for this view along an
	 * axis.  The is implemented to find the minimum unbreakable
	 * span.
	 *
	 * @param axis may be either View.X_AXIS or View.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.
	 * @see View#getPreferredSpan
	 */
        public float getMinimumSpan(int axis) {
	    float maxmin = 0;
	    float min = 0;
	    boolean nowrap = false;
	    int n = getViewCount();
	    for (int i = 0; i < n; i++) {
		View v = getView(i);
		if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) == BadBreakWeight) {
		    min += v.getPreferredSpan(axis);
		    nowrap = true;
		} else if (nowrap) {
		    maxmin = Math.max(min, maxmin);
		    nowrap = false;
		    min = 0;
		}
	    }
	    maxmin = Math.max(maxmin, min);
	    return maxmin;
	}

	/**
	 * Forward the DocumentEvent to the given child view.  This
	 * is implemented to reparent the child to the logical view
	 * (the children may have been parented by a row in the flow
	 * if they fit without breaking) and then execute the superclass 
	 * behavior.
	 *
	 * @param v the child view to forward the event to.
	 * @param e the change information from the associated document
	 * @param a the current allocation of the view
	 * @param f the factory to use to rebuild if the view has children
	 * @see #forwardUpdate
	 * @since 1.3
	 */
        protected void forwardUpdateToView(View v, DocumentEvent e, 
					   Shape a, ViewFactory f) {
	    v.setParent(this);
	    super.forwardUpdateToView(v, e, a, f);
	}

	// The following methods don't do anything useful, they
	// simply keep the class from being abstract.

	/**
	 * Renders using the given rendering surface and area on that
	 * surface.  This is implemented to do nothing, the logical
	 * view is never visible.
	 *
	 * @param g the rendering surface to use
	 * @param allocation the allocated region to render into
	 * @see View#paint
	 */
        public void paint(Graphics g, Shape allocation) {
	}

	/**
	 * Tests whether a point lies before the rectangle range.
	 * Implemented to return false, as hit detection is not
	 * performed on the logical view.
	 *
	 * @param x the X coordinate >= 0
	 * @param y the Y coordinate >= 0
	 * @param alloc the rectangle
	 * @return true if the point is before the specified range
	 */
        protected boolean isBefore(int x, int y, Rectangle alloc) {
	    return false;
	}

	/**
	 * Tests whether a point lies after the rectangle range.
	 * Implemented to return false, as hit detection is not
	 * performed on the logical view.
	 *
	 * @param x the X coordinate >= 0
	 * @param y the Y coordinate >= 0
	 * @param alloc the rectangle
	 * @return true if the point is after the specified range
	 */
        protected boolean isAfter(int x, int y, Rectangle alloc) {
	    return false;
	}

	/**
	 * Fetches the child view at the given point.
	 * Implemented to return null, as hit detection is not
	 * performed on the logical view.
	 *
	 * @param x the X coordinate >= 0
	 * @param y the Y coordinate >= 0
	 * @param alloc the parent's allocation on entry, which should
	 *   be changed to the child's allocation on exit
	 * @return the child view
	 */
        protected View getViewAtPoint(int x, int y, Rectangle alloc) {
	    return null;
	}

	/**
	 * Returns the allocation for a given child.
	 * Implemented to do nothing, as the logical view doesn't
	 * perform layout on the children.
	 *
	 * @param index the index of the child, >= 0 && < getViewCount()
	 * @param a  the allocation to the interior of the box on entry, 
	 *   and the allocation of the child view at the index on exit.
	 */
        protected void childAllocation(int index, Rectangle a) {
	}
    }


}
