home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programming Languages Suite
/
ProgLangD.iso
/
VCAFE.3.0A
/
JFC.bin
/
BoxView.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
21KB
|
717 lines
/*
* @(#)BoxView.java 1.16 98/04/09
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.text;
import java.io.PrintStream;
import java.util.Vector;
import java.awt.*;
import com.sun.java.swing.event.DocumentEvent;
/**
* A view of a text model that arranges its children into a
* box. It might be useful to represent something like a
* collection of lines, paragraphs, list items, chunks of text,
* etc. The box is somewhat like that found in TeX where
* there is alignment of the children, flexibility of the
* children is considered, etc.
*
* @author Timothy Prinzing
* @version 1.16 04/09/98
*/
public class BoxView extends CompositeView {
/**
* Constructs a BoxView.
*
* @param elem the element this view is responsible for
* @param axis either View.X_AXIS or View.Y_AXIS
*/
public BoxView(Element elem, int axis) {
super(elem);
this.axis = axis;
}
/**
* 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);
}
/**
* Invalidates the layout and resizes the cache of requests/allocations.
*
* @param offset the starting offset into the child views >= 0
* @param length the number of existing views to replace >= 0
* @param elems the child views to insert
*/
public void replace(int offset, int length, View[] elems) {
super.replace(offset, length, elems);
// invalidate cache
xOffsets = null;
xSpans = null;
xValid = false;
xAllocValid = false;
yOffsets = null;
ySpans = null;
yValid = false;
yAllocValid = false;
}
// --- View methods ---------------------------------------------
/**
* This is called by a child to indicated 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) {
if (width) {
xValid = false;
xAllocValid = false;
}
if (height) {
yValid = false;
yAllocValid = false;
}
super.preferenceChanged(child, width, height);
}
/**
* Sets the size of the view. If the size has changed, layout
* is redone. The size is the full size of the view including
* the inset areas.
*
* @param width the width >= 0
* @param height the height >= 0
*/
public void setSize(float width, float height) {
if (((int) width) != this.width) {
xAllocValid = false;
}
if (((int) height) != this.height) {
yAllocValid = false;
}
if ((! xAllocValid) || (! yAllocValid)) {
this.width = (int) width;
this.height = (int) height;
layout((int) (this.width - getLeftInset() - getRightInset()),
(int) (this.height - getTopInset() - getBottomInset()));
}
}
/**
* Renders using the given rendering surface and area on that
* surface.
*
* @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.getBounds();
setSize(alloc.width, alloc.height);
int n = getViewCount();
int x = alloc.x + getLeftInset();
int y = alloc.y + getTopInset();
Rectangle clip = g.getClipBounds();
for (int i = 0; i < n; i++) {
alloc.x = x + xOffsets[i];
alloc.y = y + yOffsets[i];
alloc.width = xSpans[i];
alloc.height = ySpans[i];
if (alloc.intersects(clip)) {
paintChild(g, alloc, i);
}
}
}
/**
* 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 letting the superclass
* do its thing.
*
* @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) throws BadLocationException {
if (! isAllocationValid()) {
Rectangle alloc = a.getBounds();
setSize(alloc.width, alloc.height);
}
return super.modelToView(pos, a);
}
/**
* 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) {
if (! isAllocationValid()) {
Rectangle alloc = a.getBounds();
setSize(alloc.width, alloc.height);
}
return super.viewToModel(x, y, a);
}
/**
* 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 View.X_AXIS or View.Y_AXIS
* @returns 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();
switch (axis) {
case View.X_AXIS:
case View.Y_AXIS:
return alignment[axis];
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
/**
* Determines the resizability of the view along the
* given axis. A value of 0 or less is not resizable.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @return the resize weight
* @exception IllegalArgumentException for an invalid axis
*/
public int getResizeWeight(int axis) {
checkRequests();
switch (axis) {
case View.X_AXIS:
case View.Y_AXIS:
return resizeWeight[axis];
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
/**
* Determines the preferred span for this view along an
* axis.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @returns 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();
switch (axis) {
case View.X_AXIS:
return preferredSpan[axis] + getLeftInset() + getRightInset();
case View.Y_AXIS:
return preferredSpan[axis] + getTopInset() + getBottomInset();
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
/**
* Gives notification that something was inserted into the document
* in a location that this view is responsible for.
*
* @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 View#insertUpdate
*/
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
Element elem = getElement();
DocumentEvent.ElementChange ec = e.getChange(elem);
if (ec != null) {
// the structure of this element changed.
Element[] removedElems = ec.getChildrenRemoved();
Element[] addedElems = ec.getChildrenAdded();
View[] added = new View[addedElems.length];
for (int i = 0; i < addedElems.length; i++) {
added[i] = f.create(addedElems[i]);
}
replace(ec.getIndex(), removedElems.length, added);
// should damge a little more intelligently.
if (a != null) {
preferenceChanged(null, true, true);
getContainer().repaint();
}
}
// find and forward if there is anything there to
// forward to. If children were removed then there was
// a replacement of the removal range and there is no
// need to forward.
// PENDING(prinz) fixup DocumentEvent to provide more
// info so forwarding can be properly done.
Rectangle alloc = ((a != null) && isAllocationValid()) ?
getInsideAllocation(a) : null;
int pos = e.getOffset();
View v = getViewAtPosition(pos, alloc);
if (v != null) {
v.insertUpdate(e, alloc, f);
if ((v.getStartOffset() == pos) && (pos > 0)) {
v = getViewAtPosition(pos-1, alloc);
v.insertUpdate(e, alloc, f);
}
}
}
/**
* Gives notification that something was removed from the document
* in a location that this view is responsible for.
*
* @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 View#removeUpdate
*/
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
Element elem = getElement();
DocumentEvent.ElementChange ec = e.getChange(elem);
boolean shouldForward = true;
if (ec != null) {
Element[] removedElems = ec.getChildrenRemoved();
Element[] addedElems = ec.getChildrenAdded();
View[] added = new View[addedElems.length];
for (int i = 0; i < addedElems.length; i++) {
added[i] = f.create(addedElems[i]);
}
replace(ec.getIndex(), removedElems.length, added);
if (added.length != 0) {
shouldForward = false;
}
// should damge a little more intelligently.
if (a != null) {
preferenceChanged(null, true, true);
getContainer().repaint();
}
}
// find and forward if there is anything there to
// forward to. If children were added then there was
// a replacement of the removal range and there is no
// need to forward.
if (shouldForward) {
Rectangle alloc = ((a != null) && isAllocationValid()) ?
getInsideAllocation(a) : null;
int pos = e.getOffset();
View v = getViewAtPosition(pos, alloc);
if (v != null) {
v.removeUpdate(e, alloc, f);
}
}
}
/**
* Gives notification from the document that attributes were changed
* in a location that this view is responsible for.
*
* @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 View#changedUpdate
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
Element elem = getElement();
// forward
Rectangle alloc = ((a != null) && isAllocationValid()) ?
getInsideAllocation(a) : null;
int x = 0;
int y = 0;
int width = 0;
int height = 0;
if (alloc != null) {
x = alloc.x;
y = alloc.y;
width = alloc.width;
height = alloc.height;
}
int index0 = elem.getElementIndex(e.getOffset());
int index1 = elem.getElementIndex(e.getOffset() + Math.max(e.getLength() - 1, 0));
for (int i = index0; i <= index1; i++) {
View v = getView(i);
if (alloc != null) {
alloc.x = x + xOffsets[i];
alloc.y = y + yOffsets[i];
alloc.width = xSpans[i];
alloc.height = ySpans[i];
}
v.changedUpdate(e, alloc, f);
}
// replace children if necessary.
DocumentEvent.ElementChange ec = e.getChange(elem);
if (ec != null) {
Element[] removedElems = ec.getChildrenRemoved();
Element[] addedElems = ec.getChildrenAdded();
View[] added = new View[addedElems.length];
for (int i = 0; i < addedElems.length; i++) {
added[i] = f.create(addedElems[i]);
}
replace(ec.getIndex(), removedElems.length, added);
}
if ((a != null) && ! isAllocationValid()) {
// size changed
Component c = getContainer();
c.repaint(x, y, width, height);
}
}
// --- local methods ----------------------------------------------------
/**
* Are the allocations for the children still
* valid?
*
* @return true if allocations still valid
*/
protected boolean isAllocationValid() {
return (xAllocValid && yAllocValid);
}
/**
* 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 (axis == 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 (axis == View.X_AXIS) {
return (x > (innerAlloc.width + innerAlloc.x));
} else {
return (y > (innerAlloc.height + innerAlloc.y));
}
}
/**
* Fetches the child view at the given point.
*
* @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 (axis == View.X_AXIS) {
if (x < (alloc.x + xOffsets[0])) {
childAllocation(0, alloc);
return getView(0);
}
for (int i = 0; i < n; i++) {
if (x < (alloc.x + xOffsets[i])) {
childAllocation(i - 1, alloc);
return getView(i - 1);
}
}
childAllocation(n - 1, alloc);
return getView(n - 1);
} else {
if (y < (alloc.y + yOffsets[0])) {
childAllocation(0, alloc);
return getView(0);
}
for (int i = 0; i < n; i++) {
if (y < (alloc.y + yOffsets[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 += xOffsets[index];
alloc.y += yOffsets[index];
alloc.width = xSpans[index];
alloc.height = ySpans[index];
}
/**
* Performs layout of the children. The size is the
* area inside of the insets.
*
* @param width the width >= 0
* @param height the height >= 0
*/
protected void layout(int width, int height) {
checkRequests();
// rebuild the allocation arrays if they've been removed
// due to a change in child count.
if (xSpans == null) {
int n = getViewCount();
xSpans = new int[n];
ySpans = new int[n];
xOffsets = new int[n];
yOffsets = new int[n];
}
if (axis == X_AXIS) {
if (! xAllocValid) {
calculateTiledPositions(width, View.X_AXIS);
}
if (! yAllocValid) {
calculateAlignedPositions(height, View.Y_AXIS);
}
} else {
if (! xAllocValid) {
calculateAlignedPositions(width, View.X_AXIS);
}
if (! yAllocValid) {
calculateTiledPositions(height, View.Y_AXIS);
}
}
xAllocValid = true;
yAllocValid = true;
// flush changes to the children
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
v.setSize((float) xSpans[i], (float) ySpans[i]);
}
}
/**
* The current width of the box. This is the width that
* it was last allocated.
*/
public final int getWidth() {
return width;
}
/**
* The current height of the box. This is the height that
* it was last allocated.
*/
public final int getHeight() {
return height;
}
/**
* Checks the request cache and update if needed.
*/
void checkRequests() {
if (axis == X_AXIS) {
if (! xValid) {
calculateTiledSizeRequirements(View.X_AXIS);
}
if (! yValid) {
calculateAlignedSizeRequirements(View.Y_AXIS);
}
} else {
if (! xValid) {
calculateAlignedSizeRequirements(View.X_AXIS);
}
if (! yValid) {
calculateTiledSizeRequirements(View.Y_AXIS);
}
}
yValid = true;
xValid = true;
}
/**
* Determines the total space necessary to
* place a set of components end-to-end.
*/
void calculateTiledSizeRequirements(int axis) {
alignment[axis] = 0.5f;
preferredSpan[axis] = 0;
resizeWeight[axis] = 0;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
preferredSpan[axis] += v.getPreferredSpan(axis);
resizeWeight[axis] += v.getResizeWeight(axis);
}
}
/**
* Determines the total space necessary to
* align a set of views along the given axis.
*/
void calculateAlignedSizeRequirements(int axis) {
int totalAbove = 0;
int totalBelow = 0;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
int span = (int) v.getPreferredSpan(axis);
int below = (int) (v.getAlignment(axis) * span);
int above = span - below;
totalAbove = Math.max(above, totalAbove);
totalBelow = Math.max(below, totalBelow);
resizeWeight[axis] += v.getResizeWeight(axis);
}
preferredSpan[axis] = (int) (totalAbove + totalBelow);
alignment[axis] = 0.5f;
if (preferredSpan[axis] > 0) {
alignment[axis] = (float) totalBelow / preferredSpan[axis];
}
}
void calculateAlignedPositions(int allocated, int axis) {
int[] offsets = (axis == View.X_AXIS) ? xOffsets : yOffsets;
int[] spans = (axis == View.X_AXIS) ? xSpans : ySpans;
int totalBelow = (int) (allocated * alignment[axis]);
int totalAbove = allocated - totalBelow;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
float align = v.getAlignment(axis);
int span = (int) v.getPreferredSpan(axis);
int below = (int) (span * align);
int above = span - below;
if (v.getResizeWeight(axis) > 0) {
below = totalBelow;
above = totalAbove;
}
offsets[i] = totalBelow - below;
spans[i] = (int) (below + above);
}
}
void calculateTiledPositions(int allocated, int axis) {
int[] offsets = (axis == View.X_AXIS) ? xOffsets : yOffsets;
int[] spans = (axis == View.X_AXIS) ? xSpans : ySpans;
int totalPlay = allocated - preferredSpan[axis];
int totalWeight = resizeWeight[axis];
int totalOffset = 0;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
offsets[i] = totalOffset;
int span = (int) v.getPreferredSpan(axis);
int weight = v.getResizeWeight(axis);
if ((weight != 0) && (totalWeight != 0)) {
// adjust the span
float factor = weight / totalWeight;
span += totalPlay * factor;
}
spans[i] = span;
totalOffset += span;
}
}
// --- variables ------------------------------------------------
int axis;
int width;
int height;
/**
* Request cache
*/
boolean xValid;
boolean yValid;
int[] preferredSpan = new int[2];
int[] resizeWeight = new int[2];
float[] alignment = new float[2];
/**
* Allocation cache
*/
boolean xAllocValid;
int[] xOffsets;
int[] xSpans;
boolean yAllocValid;
int[] yOffsets;
int[] ySpans;
}