- /*
- * @(#)BoxView.java 1.61 04/03/05
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text;
-
- import java.io.PrintStream;
- import java.util.Vector;
- import java.awt.*;
- import javax.swing.event.DocumentEvent;
- import javax.swing.SizeRequirements;
-
- /**
- * A view that arranges its children into a box shape by tiling
- * its children along an axis. The box is somewhat like that
- * found in TeX where there is alignment of the
- * children, flexibility of the children is considered, etc.
- * This is a building block that might be useful to represent
- * things like a collection of lines, paragraphs,
- * lists, columns, pages, etc. The axis along which the children are tiled is
- * considered the major axis. The orthoginal axis is the minor axis.
- * <p>
- * Layout for each axis is handled separately by the methods
- * <code>layoutMajorAxis</code> and <code>layoutMinorAxis</code>.
- * Subclasses can change the layout algorithm by
- * reimplementing these methods. These methods will be called
- * as necessary depending upon whether or not there is cached
- * layout information and the cache is considered
- * valid. These methods are typically called if the given size
- * along the axis changes, or if <code>layoutChanged</code> is
- * called to force an updated layout. The <code>layoutChanged</code>
- * method invalidates cached layout information, if there is any.
- * The requirements published to the parent view are calculated by
- * the methods <code>calculateMajorAxisRequirements</code>
- * and <code>calculateMinorAxisRequirements</code>.
- * If the layout algorithm is changed, these methods will
- * likely need to be reimplemented.
- *
- * @author Timothy Prinzing
- * @version 1.61 03/05/04
- */
- public class BoxView extends CompositeView {
-
- /**
- * Constructs a <code>BoxView</code>.
- *
- * @param elem the element this view is responsible for
- * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- */
- public BoxView(Element elem, int axis) {
- super(elem);
- tempRect = new Rectangle();
- this.majorAxis = axis;
-
- majorOffsets = new int[0];
- majorSpans = new int[0];
- majorReqValid = false;
- majorAllocValid = false;
- minorOffsets = new int[0];
- minorSpans = new int[0];
- minorReqValid = false;
- minorAllocValid = false;
- }
-
- /**
- * Fetches the tile axis property. This is the axis along which
- * the child views are tiled.
- *
- * @return the major axis of the box, either
- * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- *
- * @since 1.3
- */
- public int getAxis() {
- return majorAxis;
- }
-
- /**
- * Sets the tile axis property. This is the axis along which
- * the child views are tiled.
- *
- * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- *
- * @since 1.3
- */
- public void setAxis(int axis) {
- boolean axisChanged = (axis != majorAxis);
- majorAxis = axis;
- if (axisChanged) {
- preferenceChanged(null, true, true);
- }
- }
-
- /**
- * Invalidates the layout along an axis. This happens
- * automatically if the preferences have changed for
- * any of the child views. In some cases the layout
- * may need to be recalculated when the preferences
- * have not changed. The layout can be marked as
- * invalid by calling this method. The layout will
- * be updated the next time the <code>setSize</code> method
- * is called on this view (typically in paint).
- *
- * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- *
- * @since 1.3
- */
- public void layoutChanged(int axis) {
- if (axis == majorAxis) {
- majorAllocValid = false;
- } else {
- minorAllocValid = false;
- }
- }
-
- /**
- * Determines if the layout is valid along the given axis.
- *
- * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- *
- * @since 1.4
- */
- protected boolean isLayoutValid(int axis) {
- if (axis == majorAxis) {
- return majorAllocValid;
- } else {
- return minorAllocValid;
- }
- }
-
- /**
- * Paints a child. By default
- * that is all it does, but a subclass can use this to paint
- * things relative to the child.
- *
- * @param g the graphics context
- * @param alloc the allocated region to paint into
- * @param index the child index, >= 0 && < getViewCount()
- */
- protected void paintChild(Graphics g, Rectangle alloc, int index) {
- View child = getView(index);
- child.paint(g, alloc);
- }
-
- // --- View methods ---------------------------------------------
-
- /**
- * Invalidates the layout and resizes the cache of
- * requests/allocations. The child allocations can still
- * be accessed for the old layout, but the new children
- * will have an offset and span of 0.
- *
- * @param index the starting index into the child views to insert
- * the new views; this should be a value >= 0 and <= getViewCount
- * @param length the number of existing child views to remove;
- * This should be a value >= 0 and <= (getViewCount() - offset)
- * @param elems the child views to add; this value can be
- * <code>null</code>to indicate no children are being added
- * (useful to remove)
- */
- public void replace(int index, int length, View[] elems) {
- super.replace(index, length, elems);
-
- // invalidate cache
- int nInserted = (elems != null) ? elems.length : 0;
- majorOffsets = updateLayoutArray(majorOffsets, index, nInserted);
- majorSpans = updateLayoutArray(majorSpans, index, nInserted);
- majorReqValid = false;
- majorAllocValid = false;
- minorOffsets = updateLayoutArray(minorOffsets, index, nInserted);
- minorSpans = updateLayoutArray(minorSpans, index, nInserted);
- minorReqValid = false;
- minorAllocValid = false;
- }
-
- /**
- * Resizes the given layout array to match the new number of
- * child views. The current number of child views are used to
- * produce the new array. The contents of the old array are
- * inserted into the new array at the appropriate places so that
- * the old layout information is transferred to the new array.
- *
- * @param oldArray the original layout array
- * @param offset location where new views will be inserted
- * @param nInserted the number of child views being inserted;
- * therefore the number of blank spaces to leave in the
- * new array at location <code>offset</code>
- * @return the new layout array
- */
- int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) {
- int n = getViewCount();
- int[] newArray = new int[n];
-
- System.arraycopy(oldArray, 0, newArray, 0, offset);
- System.arraycopy(oldArray, offset,
- newArray, offset + nInserted, n - nInserted - offset);
- return newArray;
- }
-
- /**
- * Forwards the given <code>DocumentEvent</code> to the child views
- * that need to be notified of the change to the model.
- * If a child changed its requirements and the allocation
- * was valid prior to forwarding the portion of the box
- * from the starting child to the end of the box will
- * be repainted.
- *
- * @param ec changes to the element this view is responsible
- * for (may be <code>null</code> if there were no changes)
- * @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 #insertUpdate
- * @see #removeUpdate
- * @see #changedUpdate
- */
- protected void forwardUpdate(DocumentEvent.ElementChange ec,
- DocumentEvent e, Shape a, ViewFactory f) {
- boolean wasValid = isLayoutValid(majorAxis);
- super.forwardUpdate(ec, e, a, f);
-
- // determine if a repaint is needed
- if (wasValid && (! isLayoutValid(majorAxis))) {
- // Repaint is needed because one of the tiled children
- // have changed their span along the major axis. If there
- // is a hosting component and an allocated shape we repaint.
- Component c = getContainer();
- if ((a != null) && (c != null)) {
- int pos = e.getOffset();
- int index = getViewIndexAtPosition(pos);
- Rectangle alloc = getInsideAllocation(a);
- if (majorAxis == X_AXIS) {
- alloc.x += majorOffsets[index];
- alloc.width -= majorOffsets[index];
- } else {
- alloc.y += minorOffsets[index];
- alloc.height -= minorOffsets[index];
- }
- c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
- }
- }
- }
-
- /**
- * This is called by a child to indicate its
- * preferred span has changed. This is implemented to
- * throw away cached layout information so that new
- * calculations will be done the next time the children
- * need an allocation.
- *
- * @param child the child view
- * @param width true if the width preference should change
- * @param height true if the height preference should change
- */
- public void preferenceChanged(View child, boolean width, boolean height) {
- boolean majorChanged = (majorAxis == X_AXIS) ? width : height;
- boolean minorChanged = (majorAxis == X_AXIS) ? height : width;
- if (majorChanged) {
- majorReqValid = false;
- majorAllocValid = false;
- }
- if (minorChanged) {
- minorReqValid = false;
- minorAllocValid = false;
- }
- super.preferenceChanged(child, width, height);
- }
-
- /**
- * Gets the resize weight. A value of 0 or less is not resizable.
- *
- * @param axis may be either <code>View.X_AXIS</code> or
- * <code>View.Y_AXIS</code>
- * @return the weight
- * @exception IllegalArgumentException for an invalid axis
- */
- public int getResizeWeight(int axis) {
- checkRequests(axis);
- if (axis == majorAxis) {
- if ((majorRequest.preferred != majorRequest.minimum) ||
- (majorRequest.preferred != majorRequest.maximum)) {
- return 1;
- }
- } else {
- if ((minorRequest.preferred != minorRequest.minimum) ||
- (minorRequest.preferred != minorRequest.maximum)) {
- return 1;
- }
- }
- return 0;
- }
-
- /**
- * Sets the size of the view along an axis. This should cause
- * layout of the view along the given axis.
- *
- * @param axis may be either <code>View.X_AXIS</code> or
- * <code>View.Y_AXIS</code>
- * @param span the span to layout to >= 0
- */
- void setSpanOnAxis(int axis, float span) {
- if (axis == majorAxis) {
- if (majorSpan != (int) span) {
- majorAllocValid = false;
- }
- if (! majorAllocValid) {
- // layout the major axis
- majorSpan = (int) span;
- checkRequests(majorAxis);
- layoutMajorAxis(majorSpan, axis, majorOffsets, majorSpans);
- majorAllocValid = true;
-
- // flush changes to the children
- updateChildSizes();
- }
- } else {
- if (((int) span) != minorSpan) {
- minorAllocValid = false;
- }
- if (! minorAllocValid) {
- // layout the minor axis
- minorSpan = (int) span;
- checkRequests(axis);
- layoutMinorAxis(minorSpan, axis, minorOffsets, minorSpans);
- minorAllocValid = true;
-
- // flush changes to the children
- updateChildSizes();
- }
- }
- }
-
- /**
- * Propagates the current allocations to the child views.
- */
- void updateChildSizes() {
- int n = getViewCount();
- if (majorAxis == X_AXIS) {
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- v.setSize((float) majorSpans[i], (float) minorSpans[i]);
- }
- } else {
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- v.setSize((float) minorSpans[i], (float) majorSpans[i]);
- }
- }
- }
-
- /**
- * Returns the size of the view along an axis. This is implemented
- * to return zero.
- *
- * @param axis may be either <code>View.X_AXIS</code> or
- * <code>View.Y_AXIS</code>
- * @return the current span of the view along the given axis, >= 0
- */
- float getSpanOnAxis(int axis) {
- if (axis == majorAxis) {
- return majorSpan;
- } else {
- return minorSpan;
- }
- }
-
- /**
- * Sets the size of the view. This should cause
- * layout of the view if the view caches any layout
- * information. This is implemented to call the
- * layout method with the sizes inside of the insets.
- *
- * @param width the width >= 0
- * @param height the height >= 0
- */
- public void setSize(float width, float height) {
- layout((int)(width - getLeftInset() - getRightInset()),
- (int)(height - getTopInset() - getBottomInset()));
- }
-
- /**
- * Renders the <code>BoxView</code> using the given
- * rendering surface and area
- * on that surface. Only the children that intersect
- * the clip bounds of the given <code>Graphics</code>
- * will be rendered.
- *
- * @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) {
- Rectangle alloc = (allocation instanceof Rectangle) ?
- (Rectangle)allocation : allocation.getBounds();
- int n = getViewCount();
- int x = alloc.x + getLeftInset();
- int y = alloc.y + getTopInset();
- Rectangle clip = g.getClipBounds();
- for (int i = 0; i < n; i++) {
- tempRect.x = x + getOffset(X_AXIS, i);
- tempRect.y = y + getOffset(Y_AXIS, i);
- tempRect.width = getSpan(X_AXIS, i);
- tempRect.height = getSpan(Y_AXIS, i);
- if (tempRect.intersects(clip)) {
- paintChild(g, tempRect, i);
- }
- }
- }
-
- /**
- * Fetches the allocation for the given child view.
- * This enables finding out where various views
- * are located. This is implemented to return
- * <code>null</code> if the layout is invalid,
- * otherwise the superclass behavior is executed.
- *
- * @param index the index of the child, >= 0 && < getViewCount()
- * @param a the allocation to this view
- * @return the allocation to the child; or <code>null</code>
- * if <code>a</code> is <code>null</code>
- * or <code>null</code> if the layout is invalid
- */
- public Shape getChildAllocation(int index, Shape a) {
- if (a != null) {
- Shape ca = super.getChildAllocation(index, a);
- if ((ca != null) && (! isAllocationValid())) {
- // The child allocation may not have been set yet.
- Rectangle r = (ca instanceof Rectangle) ?
- (Rectangle) ca : ca.getBounds();
- if ((r.width == 0) && (r.height == 0)) {
- return null;
- }
- }
- return ca;
- }
- return null;
- }
-
- /**
- * Provides a mapping from the document model coordinate space
- * to the coordinate space of the view mapped to it. This makes
- * sure the allocation is valid before calling the superclass.
- *
- * @param pos the position to convert >= 0
- * @param a the allocated region to render into
- * @return the bounding box of the given position
- * @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 {
- if (! isAllocationValid()) {
- Rectangle alloc = a.getBounds();
- setSize(alloc.width, alloc.height);
- }
- return super.modelToView(pos, a, b);
- }
-
- /**
- * 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 >= 0
- * @param y y coordinate of the view location to convert >= 0
- * @param a the allocated region to render into
- * @return the location within the model that best represents the
- * given point in the view >= 0
- * @see View#viewToModel
- */
- public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
- if (! isAllocationValid()) {
- Rectangle alloc = a.getBounds();
- setSize(alloc.width, alloc.height);
- }
- return super.viewToModel(x, y, a, bias);
- }
-
- /**
- * Determines the desired alignment for this view along an
- * axis. This is implemented to give the total alignment
- * needed to position the children with the alignment points
- * lined up along the axis orthoginal to the axis that is
- * being tiled. The axis being tiled will request to be
- * centered (i.e. 0.5f).
- *
- * @param axis may be either <code>View.X_AXIS</code>
- * or <code>View.Y_AXIS</code>
- * @return the desired alignment >= 0.0f && <= 1.0f; 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
- * @exception IllegalArgumentException for an invalid axis
- */
- public float getAlignment(int axis) {
- checkRequests(axis);
- if (axis == majorAxis) {
- return majorRequest.alignment;
- } else {
- return minorRequest.alignment;
- }
- }
-
- /**
- * Determines the preferred span for this view along an
- * axis.
- *
- * @param axis may be either <code>View.X_AXIS</code>
- * or <code>View.Y_AXIS</code>
- * @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 type
- */
- public float getPreferredSpan(int axis) {
- checkRequests(axis);
- float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
- getTopInset() + getBottomInset();
- if (axis == majorAxis) {
- return ((float)majorRequest.preferred) + marginSpan;
- } else {
- return ((float)minorRequest.preferred) + marginSpan;
- }
- }
-
- /**
- * Determines the minimum span for this view along an
- * axis.
- *
- * @param axis may be either <code>View.X_AXIS</code>
- * or <code>View.Y_AXIS</code>
- * @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 type
- */
- public float getMinimumSpan(int axis) {
- checkRequests(axis);
- float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
- getTopInset() + getBottomInset();
- if (axis == majorAxis) {
- return ((float)majorRequest.minimum) + marginSpan;
- } else {
- return ((float)minorRequest.minimum) + marginSpan;
- }
- }
-
- /**
- * Determines the maximum span for this view along an
- * axis.
- *
- * @param axis may be either <code>View.X_AXIS</code>
- * or <code>View.Y_AXIS</code>
- * @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 type
- */
- public float getMaximumSpan(int axis) {
- checkRequests(axis);
- float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
- getTopInset() + getBottomInset();
- if (axis == majorAxis) {
- return ((float)majorRequest.maximum) + marginSpan;
- } else {
- return ((float)minorRequest.maximum) + marginSpan;
- }
- }
-
- // --- local methods ----------------------------------------------------
-
- /**
- * Are the allocations for the children still
- * valid?
- *
- * @return true if allocations still valid
- */
- protected boolean isAllocationValid() {
- return (majorAllocValid && minorAllocValid);
- }
-
- /**
- * Determines if a point falls before an allocated region.
- *
- * @param x the X coordinate >= 0
- * @param y the Y coordinate >= 0
- * @param innerAlloc the allocated region; this is the area
- * inside of the insets
- * @return true if the point lies before the region else false
- */
- protected boolean isBefore(int x, int y, Rectangle innerAlloc) {
- if (majorAxis == View.X_AXIS) {
- return (x < innerAlloc.x);
- } else {
- return (y < innerAlloc.y);
- }
- }
-
- /**
- * Determines if a point falls after an allocated region.
- *
- * @param x the X coordinate >= 0
- * @param y the Y coordinate >= 0
- * @param innerAlloc the allocated region; this is the area
- * inside of the insets
- * @return true if the point lies after the region else false
- */
- protected boolean isAfter(int x, int y, Rectangle innerAlloc) {
- if (majorAxis == View.X_AXIS) {
- return (x > (innerAlloc.width + innerAlloc.x));
- } else {
- return (y > (innerAlloc.height + innerAlloc.y));
- }
- }
-
- /**
- * Fetches the child view at the given coordinates.
- *
- * @param x the X coordinate >= 0
- * @param y the Y coordinate >= 0
- * @param alloc the parents inner allocation on entry, which should
- * be changed to the childs allocation on exit
- * @return the view
- */
- protected View getViewAtPoint(int x, int y, Rectangle alloc) {
- int n = getViewCount();
- if (majorAxis == View.X_AXIS) {
- if (x < (alloc.x + majorOffsets[0])) {
- childAllocation(0, alloc);
- return getView(0);
- }
- for (int i = 0; i < n; i++) {
- if (x < (alloc.x + majorOffsets[i])) {
- childAllocation(i - 1, alloc);
- return getView(i - 1);
- }
- }
- childAllocation(n - 1, alloc);
- return getView(n - 1);
- } else {
- if (y < (alloc.y + majorOffsets[0])) {
- childAllocation(0, alloc);
- return getView(0);
- }
- for (int i = 0; i < n; i++) {
- if (y < (alloc.y + majorOffsets[i])) {
- childAllocation(i - 1, alloc);
- return getView(i - 1);
- }
- }
- childAllocation(n - 1, alloc);
- return getView(n - 1);
- }
- }
-
- /**
- * Allocates a region for a child view.
- *
- * @param index the index of the child view to
- * allocate, >= 0 && < getViewCount()
- * @param alloc the allocated region
- */
- protected void childAllocation(int index, Rectangle alloc) {
- alloc.x += getOffset(X_AXIS, index);
- alloc.y += getOffset(Y_AXIS, index);
- alloc.width = getSpan(X_AXIS, index);
- alloc.height = getSpan(Y_AXIS, index);
- }
-
- /**
- * Perform layout on the box
- *
- * @param width the width (inside of the insets) >= 0
- * @param height the height (inside of the insets) >= 0
- */
- protected void layout(int width, int height) {
- setSpanOnAxis(X_AXIS, width);
- setSpanOnAxis(Y_AXIS, height);
- }
-
- /**
- * Returns the current width of the box. This is the width that
- * it was last allocated.
- * @return the current width of the box
- */
- public int getWidth() {
- int span;
- if (majorAxis == X_AXIS) {
- span = majorSpan;
- } else {
- span = minorSpan;
- }
- span += getLeftInset() - getRightInset();
- return span;
- }
-
- /**
- * Returns the current height of the box. This is the height that
- * it was last allocated.
- * @return the current height of the box
- */
- public int getHeight() {
- int span;
- if (majorAxis == Y_AXIS) {
- span = majorSpan;
- } else {
- span = minorSpan;
- }
- span += getTopInset() - getBottomInset();
- return span;
- }
-
- /**
- * Performs layout for the major axis of the box (i.e. the
- * axis that it represents). The results of the layout should
- * be placed in the given arrays which represent the allocations
- * to the children along the major axis.
- *
- * @param targetSpan the total span given to the view, which
- * would be used to layout the children
- * @param axis the axis being layed out
- * @param offsets the offsets from the origin of the view for
- * each of the child views; this is a return value and is
- * filled in by the implementation of this method
- * @param spans the span of each child view; this is a return
- * value and is filled in by the implementation of this method
- * @return the offset and span for each child view in the
- * offsets and spans parameters
- */
- protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
- /*
- * first pass, calculate the preferred sizes
- * and the flexibility to adjust the sizes.
- */
- long preferred = 0;
- int n = getViewCount();
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- spans[i] = (int) v.getPreferredSpan(axis);
- preferred += spans[i];
- }
-
- /*
- * Second pass, expand or contract by as much as possible to reach
- * the target span.
- */
-
- // determine the adjustment to be made
- long desiredAdjustment = targetSpan - preferred;
- float adjustmentFactor = 0.0f;
- int[] diffs = null;
-
- if (desiredAdjustment != 0) {
- long totalSpan = 0;
- diffs = new int[n];
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- int tmp;
- if (desiredAdjustment < 0) {
- tmp = (int)v.getMinimumSpan(axis);
- diffs[i] = spans[i] - tmp;
- } else {
- tmp = (int)v.getMaximumSpan(axis);
- diffs[i] = tmp - spans[i];
- }
- totalSpan += tmp;
- }
-
- float maximumAdjustment = Math.abs(totalSpan - preferred);
- adjustmentFactor = desiredAdjustment / maximumAdjustment;
- adjustmentFactor = Math.min(adjustmentFactor, 1.0f);
- adjustmentFactor = Math.max(adjustmentFactor, -1.0f);
- }
-
- // make the adjustments
- int totalOffset = 0;
- for (int i = 0; i < n; i++) {
- offsets[i] = totalOffset;
- if (desiredAdjustment != 0) {
- float adjF = adjustmentFactor * diffs[i];
- spans[i] += Math.round(adjF);
- }
- totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
- }
- }
-
- /**
- * Performs layout for the minor axis of the box (i.e. the
- * axis orthoginal to the axis that it represents). The results
- * of the layout should be placed in the given arrays which represent
- * the allocations to the children along the minor axis.
- *
- * @param targetSpan the total span given to the view, which
- * would be used to layout the children
- * @param axis the axis being layed out
- * @param offsets the offsets from the origin of the view for
- * each of the child views; this is a return value and is
- * filled in by the implementation of this method
- * @param spans the span of each child view; this is a return
- * value and is filled in by the implementation of this method
- * @return the offset and span for each child view in the
- * offsets and spans parameters
- */
- protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
- int n = getViewCount();
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- int max = (int) v.getMaximumSpan(axis);
- if (max < targetSpan) {
- // can't make the child this wide, align it
- float align = v.getAlignment(axis);
- offsets[i] = (int) ((targetSpan - max) * align);
- spans[i] = max;
- } else {
- // make it the target width, or as small as it can get.
- int min = (int)v.getMinimumSpan(axis);
- offsets[i] = 0;
- spans[i] = Math.max(min, targetSpan);
- }
- }
- }
-
- /**
- * Calculates the size requirements for the major axis
- * </code>axis</code>.
- *
- * @param axis the axis being studied
- * @param r the <code>SizeRequirements</code> object;
- * if <code>null</code> one will be created
- * @return the newly initialized <code>SizeRequirements</code> object
- * @see javax.swing.SizeRequirements
- */
- protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
- // calculate tiled request
- float min = 0;
- float pref = 0;
- float max = 0;
-
- int n = getViewCount();
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- min += v.getMinimumSpan(axis);
- pref += v.getPreferredSpan(axis);
- max += v.getMaximumSpan(axis);
- }
-
- if (r == null) {
- r = new SizeRequirements();
- }
- r.alignment = 0.5f;
- r.minimum = (int) min;
- r.preferred = (int) pref;
- r.maximum = (int) max;
- return r;
- }
-
- /**
- * Calculates the size requirements for the minor axis
- * <code>axis</code>.
- *
- * @param axis the axis being studied
- * @param r the <code>SizeRequirements</code> object;
- * if <code>null</code> one will be created
- * @return the newly initialized <code>SizeRequirements</code> object
- * @see javax.swing.SizeRequirements
- */
- protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
- int min = 0;
- long pref = 0;
- int max = Integer.MAX_VALUE;
- int n = getViewCount();
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- min = Math.max((int) v.getMinimumSpan(axis), min);
- pref = Math.max((int) v.getPreferredSpan(axis), pref);
- max = Math.max((int) v.getMaximumSpan(axis), max);
- }
-
- if (r == null) {
- r = new SizeRequirements();
- r.alignment = 0.5f;
- }
- r.preferred = (int) pref;
- r.minimum = min;
- r.maximum = max;
- return r;
- }
-
- /**
- * Checks the request cache and update if needed.
- * @param axis the axis being studied
- * @exception IllegalArgumentException if <code>axis</code> is
- * neither <code>View.X_AXIS</code> nor </code>View.Y_AXIS</code>
- */
- void checkRequests(int axis) {
- if ((axis != X_AXIS) && (axis != Y_AXIS)) {
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- if (axis == majorAxis) {
- if (!majorReqValid) {
- majorRequest = calculateMajorAxisRequirements(axis,
- majorRequest);
- majorReqValid = true;
- }
- } else if (! minorReqValid) {
- minorRequest = calculateMinorAxisRequirements(axis, minorRequest);
- minorReqValid = true;
- }
- }
-
- /**
- * Computes the location and extent of each child view
- * in this <code>BoxView</code> given the <code>targetSpan</code>,
- * which is the width (or height) of the region we have to
- * work with.
- *
- * @param targetSpan the total span given to the view, which
- * would be used to layout the children
- * @param axis the axis being studied, either
- * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
- * @param offsets an empty array filled by this method with
- * values specifying the location of each child view
- * @param spans an empty array filled by this method with
- * values specifying the extent of each child view
- */
- protected void baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans) {
- int totalAscent = (int)(targetSpan * getAlignment(axis));
- int totalDescent = targetSpan - totalAscent;
-
- int n = getViewCount();
-
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- float align = v.getAlignment(axis);
- int viewSpan;
-
- if (v.getResizeWeight(axis) > 0) {
- // if resizable then resize to the best fit
-
- // the smallest span possible
- int minSpan = (int)v.getMinimumSpan(axis);
- // the largest span possible
- int maxSpan = (int)v.getMaximumSpan(axis);
-
- if (align == 0.0f) {
- // if the alignment is 0 then we need to fit into the descent
- viewSpan = Math.max(Math.min(maxSpan, totalDescent), minSpan);
- } else if (align == 1.0f) {
- // if the alignment is 1 then we need to fit into the ascent
- viewSpan = Math.max(Math.min(maxSpan, totalAscent), minSpan);
- } else {
- // figure out the span that we must fit into
- int fitSpan = (int)Math.min(totalAscent / align,
- totalDescent / (1.0f - align));
- // fit into the calculated span
- viewSpan = Math.max(Math.min(maxSpan, fitSpan), minSpan);
- }
- } else {
- // otherwise use the preferred spans
- viewSpan = (int)v.getPreferredSpan(axis);
- }
-
- offsets[i] = totalAscent - (int)(viewSpan * align);
- spans[i] = viewSpan;
- }
- }
-
- /**
- * Calculates the size requirements for this <code>BoxView</code>
- * by examining the size of each child view.
- *
- * @param axis the axis being studied
- * @param r the <code>SizeRequirements</code> object;
- * if <code>null</code> one will be created
- * @return the newly initialized <code>SizeRequirements</code> object
- */
- protected SizeRequirements baselineRequirements(int axis, SizeRequirements r) {
- SizeRequirements totalAscent = new SizeRequirements();
- SizeRequirements totalDescent = new SizeRequirements();
-
- if (r == null) {
- r = new SizeRequirements();
- }
-
- r.alignment = 0.5f;
-
- int n = getViewCount();
-
- // loop through all children calculating the max of all their ascents and
- // descents at minimum, preferred, and maximum sizes
- for (int i = 0; i < n; i++) {
- View v = getView(i);
- float align = v.getAlignment(axis);
- int span;
- int ascent;
- int descent;
-
- // find the maximum of the preferred ascents and descents
- span = (int)v.getPreferredSpan(axis);
- ascent = (int)(align * span);
- descent = span - ascent;
- totalAscent.preferred = Math.max(ascent, totalAscent.preferred);
- totalDescent.preferred = Math.max(descent, totalDescent.preferred);
-
- if (v.getResizeWeight(axis) > 0) {
- // if the view is resizable then do the same for the minimum and
- // maximum ascents and descents
- span = (int)v.getMinimumSpan(axis);
- ascent = (int)(align * span);
- descent = span - ascent;
- totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
- totalDescent.minimum = Math.max(descent, totalDescent.minimum);
-
- span = (int)v.getMaximumSpan(axis);
- ascent = (int)(align * span);
- descent = span - ascent;
- totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
- totalDescent.maximum = Math.max(descent, totalDescent.maximum);
- } else {
- // otherwise use the preferred
- totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
- totalDescent.minimum = Math.max(descent, totalDescent.minimum);
- totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
- totalDescent.maximum = Math.max(descent, totalDescent.maximum);
- }
- }
-
- // we now have an overall preferred, minimum, and maximum ascent and descent
-
- // calculate the preferred span as the sum of the preferred ascent and preferred descent
- r.preferred = (int)Math.min((long)totalAscent.preferred + (long)totalDescent.preferred,
- Integer.MAX_VALUE);
-
- // calculate the preferred alignment as the preferred ascent divided by the preferred span
- if (r.preferred > 0) {
- r.alignment = (float)totalAscent.preferred / r.preferred;
- }
-
-
- if (r.alignment == 0.0f) {
- // if the preferred alignment is 0 then the minimum and maximum spans are simply
- // the minimum and maximum descents since there's nothing above the baseline
- r.minimum = totalDescent.minimum;
- r.maximum = totalDescent.maximum;
- } else if (r.alignment == 1.0f) {
- // if the preferred alignment is 1 then the minimum and maximum spans are simply
- // the minimum and maximum ascents since there's nothing below the baseline
- r.minimum = totalAscent.minimum;
- r.maximum = totalAscent.maximum;
- } else {
- // we want to honor the preferred alignment so we calculate two possible minimum
- // span values using 1) the minimum ascent and the alignment, and 2) the minimum
- // descent and the alignment. We'll choose the larger of these two numbers.
- r.minimum = Math.max((int)(totalAscent.minimum / r.alignment),
- (int)(totalDescent.minimum / (1.0f - r.alignment)));
- // a similar calculation is made for the maximum but we choose the smaller number.
- r.maximum = Math.min((int)(totalAscent.maximum / r.alignment),
- (int)(totalDescent.maximum / (1.0f - r.alignment)));
- }
-
- return r;
- }
-
- /**
- * Fetches the offset of a particular child's current layout.
- * @param axis the axis being studied
- * @param childIndex the index of the requested child
- * @return the offset (location) for the specified child
- */
- protected int getOffset(int axis, int childIndex) {
- int[] offsets = (axis == majorAxis) ? majorOffsets : minorOffsets;
- return offsets[childIndex];
- }
-
- /**
- * Fetches the span of a particular childs current layout.
- * @param axis the axis being studied
- * @param childIndex the index of the requested child
- * @return the span (width or height) of the specified child
- */
- protected int getSpan(int axis, int childIndex) {
- int[] spans = (axis == majorAxis) ? majorSpans : minorSpans;
- return spans[childIndex];
- }
-
- /**
- * Determines in which direction the next view lays.
- * Consider the View at index n. Typically the <code>View</code>s
- * are layed out from left to right, so that the <code>View</code>
- * to the EAST will be at index n + 1, and the <code>View</code>
- * to the WEST will be at index n - 1. In certain situations,
- * such as with bidirectional text, it is possible
- * that the <code>View</code> to EAST is not at index n + 1,
- * but rather at index n - 1, or that the <code>View</code>
- * to the WEST is not at index n - 1, but index n + 1.
- * In this case this method would return true,
- * indicating the <code>View</code>s are layed out in
- * descending order. Otherwise the method would return false
- * indicating the <code>View</code>s are layed out in ascending order.
- * <p>
- * If the receiver is laying its <code>View</code>s along the
- * <code>Y_AXIS</code>, this will will return the value from
- * invoking the same method on the <code>View</code>
- * responsible for rendering <code>position</code> and
- * <code>bias</code>. Otherwise this will return false.
- *
- * @param position position into the model
- * @param bias either <code>Position.Bias.Forward</code> or
- * <code>Position.Bias.Backward</code>
- * @return true if the <code>View</code>s surrounding the
- * <code>View</code> responding for rendering
- * <code>position</code> and <code>bias</code>
- * are layed out in descending order; otherwise false
- */
- protected boolean flipEastAndWestAtEnds(int position,
- Position.Bias bias) {
- if(majorAxis == Y_AXIS) {
- int testPos = (bias == Position.Bias.Backward) ?
- Math.max(0, position - 1) : position;
- int index = getViewIndexAtPosition(testPos);
- if(index != -1) {
- View v = getView(index);
- if(v != null && v instanceof CompositeView) {
- return ((CompositeView)v).flipEastAndWestAtEnds(position,
- bias);
- }
- }
- }
- return false;
- }
-
- // --- variables ------------------------------------------------
-
- int majorAxis;
-
- int majorSpan;
- int minorSpan;
-
- /*
- * Request cache
- */
- boolean majorReqValid;
- boolean minorReqValid;
- SizeRequirements majorRequest;
- SizeRequirements minorRequest;
-
- /*
- * Allocation cache
- */
- boolean majorAllocValid;
- int[] majorOffsets;
- int[] majorSpans;
- boolean minorAllocValid;
- int[] minorOffsets;
- int[] minorSpans;
-
- /** used in paint. */
- Rectangle tempRect;
- }