/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE 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
*/
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}.
* This method enables specifying a position to convert
* within the range of >=0. If the value is -1, a position
* will be calculated automatically. If the value < -1,
* the {@code BadLocationException} will be thrown.
*
* @param pos the position to convert
* @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 the given position is not a valid
* position within the document
* @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 {
if (
pos < -1) {
throw new
BadLocationException("invalid position",
pos);
}
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
* @since 1.3
*/
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;
}