- /*
- * @(#)CompositeView.java 1.67 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.util.Vector;
- import java.awt.*;
- import javax.swing.event.*;
- import javax.swing.SwingConstants;
-
- /**
- * <code>CompositeView</code> is an abstract <code>View</code>
- * implementation which manages one or more child views.
- * (Note that <code>CompositeView</code> is intended
- * for managing relatively small numbers of child views.)
- * <code>CompositeView</code> is intended to be used as
- * a starting point for <code>View</code> implementations,
- * such as <code>BoxView</code>, that will contain child
- * <code>View</code>s. Subclasses that wish to manage the
- * collection of child <code>View</code>s should use the
- * {@link #replace} method. As <code>View</code> invokes
- * <code>replace</code> during <code>DocumentListener</code>
- * notification, you normally won't need to directly
- * invoke <code>replace</code>.
- *
- * <p>While <code>CompositeView</code>
- * does not impose a layout policy on its child <code>View</code>s,
- * it does allow for inseting the child <code>View</code>s
- * it will contain. The insets can be set by either
- * {@link #setInsets} or {@link #setParagraphInsets}.
- *
- * <p>In addition to the abstract methods of
- * {@link javax.swing.text.View},
- * subclasses of <code>CompositeView</code> will need to
- * override:
- * <ul>
- * <li>{@link #isBefore} - Used to test if a given
- * <code>View</code> location is before the visual space
- * of the <code>CompositeView</code>.
- * <li>{@link #isAfter} - Used to test if a given
- * <code>View</code> location is after the visual space
- * of the <code>CompositeView</code>.
- * <li>{@link #getViewAtPoint} - Returns the view at
- * a given visual location.
- * <li>{@link #childAllocation} - Returns the bounds of
- * a particular child <code>View</code>.
- * <code>getChildAllocation</code> will invoke
- * <code>childAllocation</code> after offseting
- * the bounds by the <code>Inset</code>s of the
- * <code>CompositeView</code>.
- * </ul>
- *
- * @author Timothy Prinzing
- * @version 1.67 12/19/03
- */
- public abstract class CompositeView extends View {
-
- /**
- * Constructs a <code>CompositeView</code> for the given element.
- *
- * @param elem the element this view is responsible for
- */
- public CompositeView(Element elem) {
- super(elem);
- children = new View[1];
- nchildren = 0;
- childAlloc = new Rectangle();
- }
-
- /**
- * Loads all of the children to initialize the view.
- * This is called by the {@link #setParent}
- * method. Subclasses can reimplement this to initialize
- * their child views in a different manner. The default
- * implementation creates a child view for each
- * child element.
- *
- * @param f the view factory
- * @see #setParent
- */
- protected void loadChildren(ViewFactory f) {
- if (f == null) {
- // No factory. This most likely indicates the parent view
- // has changed out from under us, bail!
- return;
- }
- Element e = getElement();
- int n = e.getElementCount();
- if (n > 0) {
- View[] added = new View[n];
- for (int i = 0; i < n; i++) {
- added[i] = f.create(e.getElement(i));
- }
- replace(0, 0, added);
- }
- }
-
- // --- View methods ---------------------------------------------
-
- /**
- * Sets the parent of the view.
- * This is reimplemented to provide the superclass
- * behavior as well as calling the <code>loadChildren</code>
- * method if this view does not already have children.
- * The children should not be loaded in the
- * constructor because the act of setting the parent
- * may cause them to try to search up the hierarchy
- * (to get the hosting <code>Container</code> for example).
- * If this view has children (the view is being moved
- * from one place in the view hierarchy to another),
- * the <code>loadChildren</code> method will not be called.
- *
- * @param parent the parent of the view, <code>null</code> if none
- */
- public void setParent(View parent) {
- super.setParent(parent);
- if ((parent != null) && (nchildren == 0)) {
- ViewFactory f = getViewFactory();
- loadChildren(f);
- }
- }
-
- /**
- * Returns the number of child views of this view.
- *
- * @return the number of views >= 0
- * @see #getView
- */
- public int getViewCount() {
- return nchildren;
- }
-
- /**
- * Returns the n-th view in this container.
- *
- * @param n the number of the desired view, >= 0 && < getViewCount()
- * @return the view at index <code>n</code>
- */
- public View getView(int n) {
- return children[n];
- }
-
- /**
- * Replaces child views. If there are no views to remove
- * this acts as an insert. If there are no views to
- * add this acts as a remove. Views being removed will
- * have the parent set to <code>null</code>,
- * and the internal reference to them removed so that they
- * may be garbage collected.
- *
- * @param offset the starting index into the child views to insert
- * the new views; >= 0 and <= getViewCount
- * @param length the number of existing child views to remove;
- * this should be a value >= 0 and <= (getViewCount() - offset)
- * @param views 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 offset, int length, View[] views) {
- // make sure an array exists
- if (views == null) {
- views = ZERO;
- }
-
- // update parent reference on removed views
- for (int i = offset; i < offset + length; i++) {
- if (children[i].getParent() == this) {
- // in FlowView.java view might be referenced
- // from two super-views as a child. see logicalView
- children[i].setParent(null);
- }
- children[i] = null;
- }
-
- // update the array
- int delta = views.length - length;
- int src = offset + length;
- int nmove = nchildren - src;
- int dest = src + delta;
- if ((nchildren + delta) >= children.length) {
- // need to grow the array
- int newLength = Math.max(2*children.length, nchildren + delta);
- View[] newChildren = new View[newLength];
- System.arraycopy(children, 0, newChildren, 0, offset);
- System.arraycopy(views, 0, newChildren, offset, views.length);
- System.arraycopy(children, src, newChildren, dest, nmove);
- children = newChildren;
- } else {
- // patch the existing array
- System.arraycopy(children, src, children, dest, nmove);
- System.arraycopy(views, 0, children, offset, views.length);
- }
- nchildren = nchildren + delta;
-
- // update parent reference on added views
- for (int i = 0; i < views.length; i++) {
- views[i].setParent(this);
- }
- }
-
- /**
- * Fetches the allocation for the given child view to
- * render into. This enables finding out where various views
- * are located.
- *
- * @param index the index of the child, >= 0 && < getViewCount()
- * @param a the allocation to this view
- * @return the allocation to the child
- */
- public Shape getChildAllocation(int index, Shape a) {
- Rectangle alloc = getInsideAllocation(a);
- childAllocation(index, alloc);
- return alloc;
- }
-
- /**
- * 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 >= 0
- * @param a the allocated region to render into
- * @param b a bias value of either <code>Position.Bias.Forward</code>
- * or <code>Position.Bias.Backward</code>
- * @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 {
- boolean isBackward = (b == Position.Bias.Backward);
- int testPos = (isBackward) ? Math.max(0, pos - 1) : pos;
- if(isBackward && testPos < getStartOffset()) {
- return null;
- }
- int vIndex = getViewIndexAtPosition(testPos);
- if ((vIndex != -1) && (vIndex < getViewCount())) {
- View v = getView(vIndex);
- if(v != null && testPos >= v.getStartOffset() &&
- testPos < v.getEndOffset()) {
- Shape childShape = getChildAllocation(vIndex, a);
- if (childShape == null) {
- // We are likely invalid, fail.
- return null;
- }
- Shape retShape = v.modelToView(pos, childShape, b);
- if(retShape == null && v.getEndOffset() == pos) {
- if(++vIndex < getViewCount()) {
- v = getView(vIndex);
- retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
- }
- }
- return retShape;
- }
- }
- throw new BadLocationException("Position not represented by view",
- pos);
- }
-
- /**
- * 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; either
- * <code>Position.Bias.Forward</code> or
- * <code>Position.Bias.Backward</code>
- * @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
- */
- public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
- if (p0 == getStartOffset() && p1 == getEndOffset()) {
- return a;
- }
- Rectangle alloc = getInsideAllocation(a);
- Rectangle r0 = new Rectangle(alloc);
- View v0 = getViewAtPosition((b0 == Position.Bias.Backward) ?
- Math.max(0, p0 - 1) : p0, r0);
- Rectangle r1 = new Rectangle(alloc);
- View v1 = getViewAtPosition((b1 == Position.Bias.Backward) ?
- Math.max(0, p1 - 1) : p1, r1);
- if (v0 == v1) {
- if (v0 == null) {
- return a;
- }
- // Range contained in one view
- return v0.modelToView(p0, b0, p1, b1, r0);
- }
- // Straddles some views.
- int viewCount = getViewCount();
- int counter = 0;
- while (counter < viewCount) {
- View v;
- // Views may not be in same order as model.
- // v0 or v1 may be null if there is a gap in the range this
- // view contains.
- if ((v = getView(counter)) == v0 || v == v1) {
- View endView;
- Rectangle retRect;
- Rectangle tempRect = new Rectangle();
- if (v == v0) {
- retRect = v0.modelToView(p0, b0, v0.getEndOffset(),
- Position.Bias.Backward, r0).
- getBounds();
- endView = v1;
- }
- else {
- retRect = v1.modelToView(v1.getStartOffset(),
- Position.Bias.Forward,
- p1, b1, r1).getBounds();
- endView = v0;
- }
-
- // Views entirely covered by range.
- while (++counter < viewCount &&
- (v = getView(counter)) != endView) {
- tempRect.setBounds(alloc);
- childAllocation(counter, tempRect);
- retRect.add(tempRect);
- }
-
- // End view.
- if (endView != null) {
- Shape endShape;
- if (endView == v1) {
- endShape = v1.modelToView(v1.getStartOffset(),
- Position.Bias.Forward,
- p1, b1, r1);
- }
- else {
- endShape = v0.modelToView(p0, b0, v0.getEndOffset(),
- Position.Bias.Backward, r0);
- }
- if (endShape instanceof Rectangle) {
- retRect.add((Rectangle)endShape);
- }
- else {
- retRect.add(endShape.getBounds());
- }
- }
- return retRect;
- }
- counter++;
- }
- throw new BadLocationException("Position not represented by view", p0);
- }
-
- /**
- * 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
- * @param bias either <code>Position.Bias.Forward</code> or
- * <code>Position.Bias.Backward</code>
- * @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) {
- Rectangle alloc = getInsideAllocation(a);
- if (isBefore((int) x, (int) y, alloc)) {
- // point is before the range represented
- int retValue = -1;
-
- try {
- retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
- a, EAST, bias);
- } catch (BadLocationException ble) { }
- catch (IllegalArgumentException iae) { }
- if(retValue == -1) {
- retValue = getStartOffset();
- bias[0] = Position.Bias.Forward;
- }
- return retValue;
- } else if (isAfter((int) x, (int) y, alloc)) {
- // point is after the range represented.
- int retValue = -1;
- try {
- retValue = getNextVisualPositionFrom(-1, Position.Bias.Forward,
- a, WEST, bias);
- } catch (BadLocationException ble) { }
- catch (IllegalArgumentException iae) { }
-
- if(retValue == -1) {
- // NOTE: this could actually use end offset with backward.
- retValue = getEndOffset() - 1;
- bias[0] = Position.Bias.Forward;
- }
- return retValue;
- } else {
- // locate the child and pass along the request
- View v = getViewAtPoint((int) x, (int) y, alloc);
- if (v != null) {
- return v.viewToModel(x, y, alloc, bias);
- }
- }
- return -1;
- }
-
- /**
- * Provides a way to determine the next visually represented model
- * location that one might place a caret. Some views may not be visible,
- * they might not be in the same order found in the model, or they just
- * might not allow access to some of the locations in the model.
- * This is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
- * and {@link #getNextEastWestVisualPositionFrom}.
- *
- * @param pos the position to convert >= 0
- * @param b a bias value of either <code>Position.Bias.Forward</code>
- * or <code>Position.Bias.Backward</code>
- * @param a the allocated region to render into
- * @param direction the direction from the current position that can
- * be thought of as the arrow keys typically found on a keyboard;
- * this may be one of the following:
- * <ul>
- * <li><code>SwingConstants.WEST</code>
- * <li><code>SwingConstants.EAST</code>
- * <li><code>SwingConstants.NORTH</code>
- * <li><code>SwingConstants.SOUTH</code>
- * </ul>
- * @param biasRet an array containing the bias that was checked
- * @return the location within the model that best represents the next
- * location visual position
- * @exception BadLocationException
- * @exception IllegalArgumentException if <code>direction</code> is invalid
- */
- public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
- int direction, Position.Bias[] biasRet)
- throws BadLocationException {
- Rectangle alloc = getInsideAllocation(a);
-
- switch (direction) {
- case NORTH:
- return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
- biasRet);
- case SOUTH:
- return getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
- biasRet);
- case EAST:
- return getNextEastWestVisualPositionFrom(pos, b, a, direction,
- biasRet);
- case WEST:
- return getNextEastWestVisualPositionFrom(pos, b, a, direction,
- biasRet);
- default:
- throw new IllegalArgumentException("Bad direction: " + direction);
- }
- }
-
- /**
- * Returns the child view index representing the given
- * position in the model. This is implemented to call the
- * <code>getViewIndexByPosition</code>
- * method for backward compatibility.
- *
- * @param pos the position >= 0
- * @return index of the view representing the given position, or
- * -1 if no view represents that position
- */
- public int getViewIndex(int pos, Position.Bias b) {
- if(b == Position.Bias.Backward) {
- pos -= 1;
- }
- if ((pos >= getStartOffset()) && (pos < getEndOffset())) {
- return getViewIndexAtPosition(pos);
- }
- return -1;
- }
-
- // --- local methods ----------------------------------------------------
-
-
- /**
- * Tests whether a point lies before the rectangle range.
- *
- * @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 abstract boolean isBefore(int x, int y, Rectangle alloc);
-
- /**
- * Tests whether a point lies after the rectangle range.
- *
- * @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 abstract boolean isAfter(int x, int y, Rectangle alloc);
-
- /**
- * Fetches the child view at the given coordinates.
- *
- * @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 abstract View getViewAtPoint(int x, int y, Rectangle alloc);
-
- /**
- * Returns the allocation for a given child.
- *
- * @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 abstract void childAllocation(int index, Rectangle a);
-
- /**
- * Fetches the child view that represents the given position in
- * the model. This is implemented to fetch the view in the case
- * where there is a child view for each child element.
- *
- * @param pos the position >= 0
- * @param a the allocation to the interior of the box on entry,
- * and the allocation of the view containing the position on exit
- * @return the view representing the given position, or
- * <code>null</code> if there isn't one
- */
- protected View getViewAtPosition(int pos, Rectangle a) {
- int index = getViewIndexAtPosition(pos);
- if ((index >= 0) && (index < getViewCount())) {
- View v = getView(index);
- if (a != null) {
- childAllocation(index, a);
- }
- return v;
- }
- return null;
- }
-
- /**
- * Fetches the child view index representing the given position in
- * the model. This is implemented to fetch the view in the case
- * where there is a child view for each child element.
- *
- * @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) {
- Element elem = getElement();
- return elem.getElementIndex(pos);
- }
-
- /**
- * Translates the immutable allocation given to the view
- * to a mutable allocation that represents the interior
- * allocation (i.e. the bounds of the given allocation
- * with the top, left, bottom, and right insets removed.
- * It is expected that the returned value would be further
- * mutated to represent an allocation to a child view.
- * This is implemented to reuse an instance variable so
- * it avoids creating excessive Rectangles. Typically
- * the result of calling this method would be fed to
- * the <code>childAllocation</code> method.
- *
- * @param a the allocation given to the view
- * @return the allocation that represents the inside of the
- * view after the margins have all been removed; if the
- * given allocation was <code>null</code>,
- * the return value is <code>null</code>
- */
- protected Rectangle getInsideAllocation(Shape a) {
- if (a != null) {
- // get the bounds, hopefully without allocating
- // a new rectangle. The Shape argument should
- // not be modified... we copy it into the
- // child allocation.
- Rectangle alloc;
- if (a instanceof Rectangle) {
- alloc = (Rectangle) a;
- } else {
- alloc = a.getBounds();
- }
-
- childAlloc.setBounds(alloc);
- childAlloc.x += getLeftInset();
- childAlloc.y += getTopInset();
- childAlloc.width -= getLeftInset() + getRightInset();
- childAlloc.height -= getTopInset() + getBottomInset();
- return childAlloc;
- }
- return null;
- }
-
- /**
- * Sets the insets from the paragraph attributes specified in
- * the given attributes.
- *
- * @param attr the attributes
- */
- protected void setParagraphInsets(AttributeSet attr) {
- // Since version 1.1 doesn't have scaling and assumes
- // a pixel is equal to a point, we just cast the point
- // sizes to integers.
- top = (short) StyleConstants.getSpaceAbove(attr);
- left = (short) StyleConstants.getLeftIndent(attr);
- bottom = (short) StyleConstants.getSpaceBelow(attr);
- right = (short) StyleConstants.getRightIndent(attr);
- }
-
- /**
- * Sets the insets for the view.
- *
- * @param top the top inset >= 0
- * @param left the left inset >= 0
- * @param bottom the bottom inset >= 0
- * @param right the right inset >= 0
- */
- protected void setInsets(short top, short left, short bottom, short right) {
- this.top = top;
- this.left = left;
- this.right = right;
- this.bottom = bottom;
- }
-
- /**
- * Gets the left inset.
- *
- * @return the inset >= 0
- */
- protected short getLeftInset() {
- return left;
- }
-
- /**
- * Gets the right inset.
- *
- * @return the inset >= 0
- */
- protected short getRightInset() {
- return right;
- }
-
- /**
- * Gets the top inset.
- *
- * @return the inset >= 0
- */
- protected short getTopInset() {
- return top;
- }
-
- /**
- * Gets the bottom inset.
- *
- * @return the inset >= 0
- */
- protected short getBottomInset() {
- return bottom;
- }
-
- /**
- * Returns the next visual position for the cursor, in either the
- * north or south direction.
- *
- * @param pos the position to convert >= 0
- * @param b a bias value of either <code>Position.Bias.Forward</code>
- * or <code>Position.Bias.Backward</code>
- * @param a the allocated region to render into
- * @param direction the direction from the current position that can
- * be thought of as the arrow keys typically found on a keyboard;
- * this may be one of the following:
- * <ul>
- * <li><code>SwingConstants.NORTH</code>
- * <li><code>SwingConstants.SOUTH</code>
- * </ul>
- * @param biasRet an array containing the bias that was checked
- * @return the location within the model that best represents the next
- * north or south location
- * @exception BadLocationException
- * @exception IllegalArgumentException if <code>direction</code> is invalid
- * @see #getNextVisualPositionFrom
- *
- * @return the next position west of the passed in position
- */
- protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
- Shape a, int direction,
- Position.Bias[] biasRet)
- throws BadLocationException {
- return Utilities.getNextVisualPositionFrom(
- this, pos, b, a, direction, biasRet);
- }
-
- /**
- * Returns the next visual position for the cursor, in either the
- * east or west direction.
- *
- * @param pos the position to convert >= 0
- * @param b a bias value of either <code>Position.Bias.Forward</code>
- * or <code>Position.Bias.Backward</code>
- * @param a the allocated region to render into
- * @param direction the direction from the current position that can
- * be thought of as the arrow keys typically found on a keyboard;
- * this may be one of the following:
- * <ul>
- * <li><code>SwingConstants.WEST</code>
- * <li><code>SwingConstants.EAST</code>
- * </ul>
- * @param biasRet an array containing the bias that was checked
- * @return the location within the model that best represents the next
- * west or east location
- * @exception BadLocationException
- * @exception IllegalArgumentException if <code>direction</code> is invalid
- * @see #getNextVisualPositionFrom
- */
- protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
- Shape a,
- int direction,
- Position.Bias[] biasRet)
- throws BadLocationException {
- return Utilities.getNextVisualPositionFrom(
- this, pos, b, a, direction, biasRet);
- }
-
- /**
- * Determines in which direction the next view lays.
- * Consider the <code>View</code> 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.
- * <p>
- * This unconditionally returns false, subclasses should override this
- * method if there is the possibility for laying <code>View</code>s in
- * descending order.
- *
- * @param position position into the model
- * @param bias either <code>Position.Bias.Forward</code> or
- * <code>Position.Bias.Backward</code>
- * @return false
- */
- protected boolean flipEastAndWestAtEnds(int position,
- Position.Bias bias) {
- return false;
- }
-
-
- // ---- member variables ---------------------------------------------
-
-
- private static View[] ZERO = new View[0];
-
- private View[] children;
- private int nchildren;
- private short left;
- private short right;
- private short top;
- private short bottom;
- private Rectangle childAlloc;
- }