diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/javax/swing/text/AsyncBoxView.java | |
download | cbb-gcc-4.6.4-15d2061ac0796199866debe9ac87130894b0cdd3.tar.bz2 cbb-gcc-4.6.4-15d2061ac0796199866debe9ac87130894b0cdd3.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/javax/swing/text/AsyncBoxView.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/AsyncBoxView.java | 1480 |
1 files changed, 1480 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/AsyncBoxView.java b/libjava/classpath/javax/swing/text/AsyncBoxView.java new file mode 100644 index 000000000..aca77aa3b --- /dev/null +++ b/libjava/classpath/javax/swing/text/AsyncBoxView.java @@ -0,0 +1,1480 @@ +/* AsyncBoxView.java -- A box view that performs layout asynchronously + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; +import java.util.ArrayList; + +import javax.swing.event.DocumentEvent; +import javax.swing.text.Position.Bias; + +/** + * A {@link View} implementation that lays out its child views in a box, either + * vertically or horizontally. The difference to {@link BoxView} is that the + * layout is performed in an asynchronous manner. This helps to keep the + * eventqueue free from non-GUI related tasks. + * + * This view is currently not used in standard text components. In order to + * use it you would have to implement a special {@link EditorKit} with a + * {@link ViewFactory} that returns this view. For example: + * + * <pre> + * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory + * { + * public View create(Element el) + * { + * if (el.getName().equals(AbstractDocument.SectionElementName)) + * return new AsyncBoxView(el, View.Y_AXIS); + * return super.getViewFactory().create(el); + * } + * public ViewFactory getViewFactory() { + * return this; + * } + * } + * </pre> + * + * @author Roman Kennke (kennke@aicas.com) + * + * @since 1.3 + */ +public class AsyncBoxView + extends View +{ + + /** + * Manages the effective position of child views. That keeps the visible + * layout stable while the AsyncBoxView might be changing until the layout + * thread decides to publish the new layout. + */ + public class ChildLocator + { + + /** + * The last valid location. + */ + protected ChildState lastValidOffset; + + /** + * The last allocation. + */ + protected Rectangle lastAlloc; + + /** + * A Rectangle used for child allocation calculation to avoid creation + * of lots of garbage Rectangle objects. + */ + protected Rectangle childAlloc; + + /** + * Creates a new ChildLocator. + */ + public ChildLocator() + { + lastAlloc = new Rectangle(); + childAlloc = new Rectangle(); + } + + /** + * Receives notification that a child has changed. This is called by + * child state objects that have changed it's major span. + * + * This sets the {@link #lastValidOffset} field to <code>cs</code> if + * the new child state's view start offset is smaller than the start offset + * of the current child state's view or when <code>lastValidOffset</code> + * is <code>null</code>. + * + * @param cs the child state object that has changed + */ + public synchronized void childChanged(ChildState cs) + { + if (lastValidOffset == null + || cs.getChildView().getStartOffset() + < lastValidOffset.getChildView().getStartOffset()) + { + lastValidOffset = cs; + } + } + + /** + * Returns the view index of the view that occupies the specified area, or + * <code>-1</code> if there is no such child view. + * + * @param x the x coordinate (relative to <code>a</code>) + * @param y the y coordinate (relative to <code>a</code>) + * @param a the current allocation of this view + * + * @return the view index of the view that occupies the specified area, or + * <code>-1</code> if there is no such child view + */ + public int getViewIndexAtPoint(float x, float y, Shape a) + { + setAllocation(a); + float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x + : y - lastAlloc.y; + int index = getViewIndexAtVisualOffset(targetOffset); + return index; + } + + /** + * Returns the current allocation for a child view. This updates the + * offsets for all children <em>before</em> the requested child view. + * + * @param index the index of the child view + * @param a the current allocation of this view + * + * @return the current allocation for a child view + */ + public synchronized Shape getChildAllocation(int index, Shape a) + { + if (a == null) + return null; + setAllocation(a); + ChildState cs = getChildState(index); + if (cs.getChildView().getStartOffset() + > lastValidOffset.getChildView().getStartOffset()) + { + updateChildOffsetsToIndex(index); + } + Shape ca = getChildAllocation(index); + return ca; + } + + /** + * Paints all child views. + * + * @param g the graphics context to use + */ + public synchronized void paintChildren(Graphics g) + { + Rectangle clip = g.getClipBounds(); + float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x + : clip.y - lastAlloc.y; + int index = getViewIndexAtVisualOffset(targetOffset); + int n = getViewCount(); + float offs = getChildState(index).getMajorOffset(); + for (int i = index; i < n; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(offs); + Shape ca = getChildAllocation(i); + if (ca.intersects(clip)) + { + synchronized (cs) + { + View v = cs.getChildView(); + v.paint(g, ca); + } + } + else + { + // done painting intersection + break; + } + offs += cs.getMajorSpan(); + } + } + + /** + * Returns the current allocation of the child view with the specified + * index. Note that this will <em>not</em> update any location information. + * + * @param index the index of the requested child view + * + * @return the current allocation of the child view with the specified + * index + */ + protected Shape getChildAllocation(int index) + { + ChildState cs = getChildState(index); + if (! cs.isLayoutValid()) + cs.run(); + + if (getMajorAxis() == X_AXIS) + { + childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset(); + childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset(); + childAlloc.width = (int) cs.getMajorSpan(); + childAlloc.height = (int) cs.getMinorSpan(); + } + else + { + childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset(); + childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset(); + childAlloc.height = (int) cs.getMajorSpan(); + childAlloc.width = (int) cs.getMinorSpan(); + } + return childAlloc; + } + + /** + * Sets the current allocation for this view. + * + * @param a the allocation to set + */ + protected void setAllocation(Shape a) + { + if (a instanceof Rectangle) + lastAlloc.setBounds((Rectangle) a); + else + lastAlloc.setBounds(a.getBounds()); + + setSize(lastAlloc.width, lastAlloc.height); + } + + /** + * Returns the index of the view at the specified offset along the major + * layout axis. + * + * @param targetOffset the requested offset + * + * @return the index of the view at the specified offset along the major + * layout axis + */ + protected int getViewIndexAtVisualOffset(float targetOffset) + { + int n = getViewCount(); + if (n > 0) + { + if (lastValidOffset == null) + lastValidOffset = getChildState(0); + if (targetOffset > majorSpan) + return 0; + else if (targetOffset > lastValidOffset.getMajorOffset()) + return updateChildOffsets(targetOffset); + else + { + float offs = 0f; + for (int i = 0; i < n; i++) + { + ChildState cs = getChildState(i); + float nextOffs = offs + cs.getMajorSpan(); + if (targetOffset < nextOffs) + return i; + offs = nextOffs; + } + } + } + return n - 1; + } + + /** + * Updates all the child view offsets up to the specified targetOffset. + * + * @param targetOffset the offset up to which the child view offsets are + * updated + * + * @return the index of the view at the specified offset + */ + private int updateChildOffsets(float targetOffset) + { + int n = getViewCount(); + int targetIndex = n - 1; + int pos = lastValidOffset.getChildView().getStartOffset(); + int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); + float start = lastValidOffset.getMajorOffset(); + float lastOffset = start; + for (int i = startIndex; i < n; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(lastOffset); + lastOffset += cs.getMajorSpan(); + if (targetOffset < lastOffset) + { + targetIndex = i; + lastValidOffset = cs; + break; + } + } + return targetIndex; + } + + /** + * Updates the offsets of the child views up to the specified index. + * + * @param index the index up to which the offsets are updated + */ + private void updateChildOffsetsToIndex(int index) + { + int pos = lastValidOffset.getChildView().getStartOffset(); + int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); + float lastOffset = lastValidOffset.getMajorOffset(); + for (int i = startIndex; i <= index; i++) + { + ChildState cs = getChildState(i); + cs.setMajorOffset(lastOffset); + lastOffset += cs.getMajorSpan(); + } + } + } + + /** + * Represents the layout state of a child view. + */ + public class ChildState + implements Runnable + { + + /** + * The child view for this state record. + */ + private View childView; + + /** + * Indicates if the minor axis requirements of this child view are valid + * or not. + */ + private boolean minorValid; + + /** + * Indicates if the major axis requirements of this child view are valid + * or not. + */ + private boolean majorValid; + + /** + * Indicates if the current child size is valid. This is package private + * to avoid synthetic accessor method. + */ + boolean childSizeValid; + + /** + * The child views minimumSpan. This is package private to avoid accessor + * method. + */ + float minimum; + + /** + * The child views preferredSpan. This is package private to avoid accessor + * method. + */ + float preferred; + + /** + * The current span of the child view along the major axis. + */ + private float majorSpan; + + /** + * The current offset of the child view along the major axis. + */ + private float majorOffset; + + /** + * The current span of the child view along the minor axis. + */ + private float minorSpan; + + /** + * The current offset of the child view along the major axis. + */ + private float minorOffset; + + /** + * The child views maximumSpan. + */ + private float maximum; + + /** + * Creates a new <code>ChildState</code> object for the specified child + * view. + * + * @param view the child view for which to create the state record + */ + public ChildState(View view) + { + childView = view; + } + + /** + * Returns the child view for which this <code>ChildState</code> represents + * the layout state. + * + * @return the child view for this child state object + */ + public View getChildView() + { + return childView; + } + + /** + * Returns <code>true</code> if the current layout information is valid, + * <code>false</code> otherwise. + * + * @return <code>true</code> if the current layout information is valid, + * <code>false</code> otherwise + */ + public boolean isLayoutValid() + { + return minorValid && majorValid && childSizeValid; + } + + /** + * Performs the layout update for the child view managed by this + * <code>ChildState</code>. + */ + public void run() + { + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readLock(); + } + + try + { + + if (!(minorValid && majorValid && childSizeValid) + && childView.getParent() == AsyncBoxView.this) + { + synchronized(AsyncBoxView.this) + { + changing = this; + } + update(); + synchronized(AsyncBoxView.this) + { + changing = null; + } + // Changing the major axis may cause the minor axis + // requirements to have changed, so we need to do this again. + update(); + } + } + finally + { + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readUnlock(); + } + } + } + + /** + * Performs the actual update after the run methods has made its checks + * and locked the document. + */ + private void update() + { + int majorAxis = getMajorAxis(); + boolean minorUpdated = false; + synchronized (this) + { + if (! minorValid) + { + int minorAxis = getMinorAxis(); + minimum = childView.getMinimumSpan(minorAxis); + preferred = childView.getPreferredSpan(minorAxis); + maximum = childView.getMaximumSpan(minorAxis); + minorValid = true; + minorUpdated = true; + } + } + if (minorUpdated) + minorRequirementChange(this); + + boolean majorUpdated = false; + float delta = 0.0F; + synchronized (this) + { + if (! majorValid) + { + float oldSpan = majorSpan; + majorSpan = childView.getPreferredSpan(majorAxis); + delta = majorSpan - oldSpan; + majorValid = true; + majorUpdated = true; + } + } + if (majorUpdated) + { + majorRequirementChange(this, delta); + locator.childChanged(this); + } + + synchronized (this) + { + if (! childSizeValid) + { + float w; + float h; + if (majorAxis == X_AXIS) + { + w = majorSpan; + h = getMinorSpan(); + } + else + { + w = getMinorSpan(); + h = majorSpan; + } + childSizeValid = true; + childView.setSize(w, h); + } + } + } + + /** + * Returns the span of the child view along the minor layout axis. + * + * @return the span of the child view along the minor layout axis + */ + public float getMinorSpan() + { + float retVal; + if (maximum < minorSpan) + retVal = maximum; + else + retVal = Math.max(minimum, minorSpan); + return retVal; + } + + /** + * Returns the offset of the child view along the minor layout axis. + * + * @return the offset of the child view along the minor layout axis + */ + public float getMinorOffset() + { + float retVal; + if (maximum < minorSpan) + { + float align = childView.getAlignment(getMinorAxis()); + retVal = ((minorSpan - maximum) * align); + } + else + retVal = 0f; + + return retVal; + } + + /** + * Returns the span of the child view along the major layout axis. + * + * @return the span of the child view along the major layout axis + */ + + public float getMajorSpan() + { + return majorSpan; + } + + /** + * Returns the offset of the child view along the major layout axis. + * + * @return the offset of the child view along the major layout axis + */ + public float getMajorOffset() + { + return majorOffset; + } + + /** + * Sets the offset of the child view along the major layout axis. This + * should only be called by the ChildLocator of that child view. + * + * @param offset the offset to set + */ + public void setMajorOffset(float offset) + { + majorOffset = offset; + } + + /** + * Mark the preferences changed for that child. This forwards to + * {@link AsyncBoxView#preferenceChanged}. + * + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public void preferenceChanged(boolean width, boolean height) + { + if (getMajorAxis() == X_AXIS) + { + if (width) + majorValid = false; + if (height) + minorValid = false; + } + else + { + if (width) + minorValid = false; + if (height) + majorValid = false; + } + childSizeValid = false; + } + } + + /** + * Flushes the requirements changes upwards asynchronously. + */ + private class FlushTask implements Runnable + { + /** + * Starts the flush task. This obtains a readLock on the document + * and then flushes all the updates using + * {@link AsyncBoxView#flushRequirementChanges()} after updating the + * requirements. + */ + public void run() + { + try + { + // Acquire a lock on the document. + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readLock(); + } + + int n = getViewCount(); + if (minorChanged && (n > 0)) + { + LayoutQueue q = getLayoutQueue(); + ChildState min = getChildState(0); + ChildState pref = getChildState(0); + for (int i = 1; i < n; i++) + { + ChildState cs = getChildState(i); + if (cs.minimum > min.minimum) + min = cs; + if (cs.preferred > pref.preferred) + pref = cs; + } + synchronized (AsyncBoxView.this) + { + minReq = min; + prefReq = pref; + } + } + + flushRequirementChanges(); + } + finally + { + // Release the lock on the document. + Document doc = getDocument(); + if (doc instanceof AbstractDocument) + { + AbstractDocument abstractDoc = (AbstractDocument) doc; + abstractDoc.readUnlock(); + } + } + } + + } + + /** + * The major layout axis. + */ + private int majorAxis; + + /** + * The top inset. + */ + private float topInset; + + /** + * The bottom inset. + */ + private float bottomInset; + + /** + * The left inset. + */ + private float leftInset; + + /** + * Indicates if the major span should be treated as beeing estimated or not. + */ + private boolean estimatedMajorSpan; + + /** + * The right inset. + */ + private float rightInset; + + /** + * The children and their layout statistics. + */ + private ArrayList childStates; + + /** + * The currently changing child state. May be null if there is no child state + * updating at the moment. This is package private to avoid a synthetic + * accessor method inside ChildState. + */ + ChildState changing; + + /** + * Represents the minimum requirements. This is used in + * {@link #getMinimumSpan(int)}. + */ + ChildState minReq; + + /** + * Represents the minimum requirements. This is used in + * {@link #getPreferredSpan(int)}. + */ + ChildState prefReq; + + /** + * Indicates that the major axis requirements have changed. + */ + private boolean majorChanged; + + /** + * Indicates that the minor axis requirements have changed. This is package + * private to avoid synthetic accessor method. + */ + boolean minorChanged; + + /** + * The current span along the major layout axis. This is package private to + * avoid synthetic accessor method. + */ + float majorSpan; + + /** + * The current span along the minor layout axis. This is package private to + * avoid synthetic accessor method. + */ + float minorSpan; + + /** + * This tasked is placed on the layout queue to flush updates up to the + * parent view. + */ + private Runnable flushTask; + + /** + * The child locator for this view. + */ + protected ChildLocator locator; + + /** + * Creates a new <code>AsyncBoxView</code> that represents the specified + * element and layouts its children along the specified axis. + * + * @param elem the element + * @param axis the layout axis + */ + public AsyncBoxView(Element elem, int axis) + { + super(elem); + majorAxis = axis; + childStates = new ArrayList(); + flushTask = new FlushTask(); + locator = new ChildLocator(); + minorSpan = Short.MAX_VALUE; + } + + /** + * Returns the major layout axis. + * + * @return the major layout axis + */ + public int getMajorAxis() + { + return majorAxis; + } + + /** + * Returns the minor layout axis, that is the axis orthogonal to the major + * layout axis. + * + * @return the minor layout axis + */ + public int getMinorAxis() + { + return majorAxis == X_AXIS ? Y_AXIS : X_AXIS; + } + + /** + * Returns the view at the specified <code>index</code>. + * + * @param index the index of the requested child view + * + * @return the view at the specified <code>index</code> + */ + public View getView(int index) + { + View view = null; + synchronized(childStates) + { + if ((index >= 0) && (index < childStates.size())) + { + ChildState cs = (ChildState) childStates.get(index); + view = cs.getChildView(); + } + } + return view; + } + + /** + * Returns the number of child views. + * + * @return the number of child views + */ + public int getViewCount() + { + synchronized(childStates) + { + return childStates.size(); + } + } + + /** + * Returns the view index of the child view that represents the specified + * model position. + * + * @param pos the model position for which we search the view index + * @param bias the bias + * + * @return the view index of the child view that represents the specified + * model position + */ + public int getViewIndex(int pos, Position.Bias bias) + { + int retVal = -1; + + if (bias == Position.Bias.Backward) + pos = Math.max(0, pos - 1); + + // TODO: A possible optimization would be to implement a binary search + // here. + int numChildren = childStates.size(); + if (numChildren > 0) + { + for (int i = 0; i < numChildren; ++i) + { + View child = ((ChildState) childStates.get(i)).getChildView(); + if (child.getStartOffset() <= pos && child.getEndOffset() > pos) + { + retVal = i; + break; + } + } + } + return retVal; + } + + /** + * Returns the top inset. + * + * @return the top inset + */ + public float getTopInset() + { + return topInset; + } + + /** + * Sets the top inset. + * + * @param top the top inset + */ + public void setTopInset(float top) + { + topInset = top; + } + + /** + * Returns the bottom inset. + * + * @return the bottom inset + */ + public float getBottomInset() + { + return bottomInset; + } + + /** + * Sets the bottom inset. + * + * @param bottom the bottom inset + */ + public void setBottomInset(float bottom) + { + bottomInset = bottom; + } + + /** + * Returns the left inset. + * + * @return the left inset + */ + public float getLeftInset() + { + return leftInset; + } + + /** + * Sets the left inset. + * + * @param left the left inset + */ + public void setLeftInset(float left) + { + leftInset = left; + } + + /** + * Returns the right inset. + * + * @return the right inset + */ + public float getRightInset() + { + return rightInset; + } + + /** + * Sets the right inset. + * + * @param right the right inset + */ + public void setRightInset(float right) + { + rightInset = right; + } + + /** + * Loads the child views of this view. This is triggered by + * {@link #setParent(View)}. + * + * @param f the view factory to build child views with + */ + protected void loadChildren(ViewFactory f) + { + 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); + } + } + + /** + * Returns the span along an axis that is taken up by the insets. + * + * @param axis the axis + * + * @return the span along an axis that is taken up by the insets + * + * @since 1.4 + */ + protected float getInsetSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = leftInset + rightInset; + else + span = topInset + bottomInset; + return span; + } + + /** + * Sets the <code>estimatedMajorSpan</code> property that determines if + * the major span should be treated as beeing estimated. + * + * @param estimated if the major span should be treated as estimated or not + * + * @since 1.4 + */ + protected void setEstimatedMajorSpan(boolean estimated) + { + estimatedMajorSpan = estimated; + } + + /** + * Determines whether the major span should be treated as estimated or as + * beeing accurate. + * + * @return <code>true</code> if the major span should be treated as + * estimated, <code>false</code> if the major span should be treated + * as accurate + * + * @since 1.4 + */ + protected boolean getEstimatedMajorSpan() + { + return estimatedMajorSpan; + } + + /** + * Receives notification from the child states that the requirements along + * the minor axis have changed. + * + * @param cs the child state from which this notification is messaged + */ + protected synchronized void minorRequirementChange(ChildState cs) + { + minorChanged = true; + } + + /** + * Receives notification from the child states that the requirements along + * the major axis have changed. + * + * @param cs the child state from which this notification is messaged + */ + protected void majorRequirementChange(ChildState cs, float delta) + { + if (! estimatedMajorSpan) + majorSpan += delta; + majorChanged = true; + } + + /** + * Sets the parent for this view. This calls loadChildren if + * <code>parent</code> is not <code>null</code> and there have not been any + * child views initializes. + * + * @param parent the new parent view; <code>null</code> if this view is + * removed from the view hierarchy + * + * @see View#setParent(View) + */ + public void setParent(View parent) + { + super.setParent(parent); + if ((parent != null) && (getViewCount() == 0)) + { + ViewFactory f = getViewFactory(); + loadChildren(f); + } + } + + /** + * Sets the size of this view. This is ususally called before {@link #paint} + * is called to make sure the view has a valid layout. + * + * This implementation queues layout requests for every child view if the + * minor axis span has changed. (The major axis span is requested to never + * change for this view). + * + * @param width the width of the view + * @param height the height of the view + */ + public void setSize(float width, float height) + { + float targetSpan; + if (majorAxis == X_AXIS) + targetSpan = height - getTopInset() - getBottomInset(); + else + targetSpan = width - getLeftInset() - getRightInset(); + + if (targetSpan != minorSpan) + { + minorSpan = targetSpan; + + int n = getViewCount(); + LayoutQueue q = getLayoutQueue(); + for (int i = 0; i < n; i++) + { + ChildState cs = getChildState(i); + cs.childSizeValid = false; + q.addTask(cs); + } + q.addTask(flushTask); + } + } + + /** + * Replaces child views with new child views. + * + * This creates ChildState objects for all the new views and adds layout + * requests for them to the layout queue. + * + * @param offset the offset at which to remove/insert + * @param length the number of child views to remove + * @param views the new child views to insert + */ + public void replace(int offset, int length, View[] views) + { + synchronized(childStates) + { + LayoutQueue q = getLayoutQueue(); + for (int i = 0; i < length; i++) + childStates.remove(offset); + + for (int i = views.length - 1; i >= 0; i--) + childStates.add(offset, createChildState(views[i])); + + // We need to go through the new child states _after_ they have been + // added to the childStates list, otherwise the layout tasks may find + // an incomplete child list. That means we have to loop through + // them again, but what else can we do? + if (views.length != 0) + { + for (int i = 0; i < views.length; i++) + { + ChildState cs = (ChildState) childStates.get(i + offset); + cs.getChildView().setParent(this); + q.addTask(cs); + } + q.addTask(flushTask); + } + } + } + + /** + * Paints the view. This requests the {@link ChildLocator} to paint the views + * after setting the allocation on it. + * + * @param g the graphics context to use + * @param s the allocation for this view + */ + public void paint(Graphics g, Shape s) + { + synchronized (locator) + { + locator.setAllocation(s); + locator.paintChildren(g); + } + } + + /** + * Returns the preferred span of this view along the specified layout axis. + * + * @return the preferred span of this view along the specified layout axis + */ + public float getPreferredSpan(int axis) + { + float retVal; + if (majorAxis == axis) + retVal = majorSpan; + + else if (prefReq != null) + { + View child = prefReq.getChildView(); + retVal = child.getPreferredSpan(axis); + } + + // If we have no layout information yet, then return insets + 30 as + // an estimation. + else + { + if (axis == X_AXIS) + retVal = getLeftInset() + getRightInset() + 30; + else + retVal = getTopInset() + getBottomInset() + 30; + } + return retVal; + } + + /** + * Maps a model location to view coordinates. + * + * @param pos the model location + * @param a the current allocation of this view + * @param b the bias + * + * @return the view allocation for the specified model location + */ + public Shape modelToView(int pos, Shape a, Bias b) + throws BadLocationException + { + int index = getViewIndexAtPosition(pos, b); + Shape ca = locator.getChildAllocation(index, a); + + ChildState cs = getChildState(index); + synchronized (cs) + { + View cv = cs.getChildView(); + Shape v = cv.modelToView(pos, ca, b); + return v; + } + } + + /** + * Maps view coordinates to a model location. + * + * @param x the x coordinate (relative to <code>a</code>) + * @param y the y coordinate (relative to <code>a</code>) + * @param b holds the bias of the model location on method exit + * + * @return the model location for the specified view location + */ + public int viewToModel(float x, float y, Shape a, Bias[] b) + { + int pos; + int index; + Shape ca; + + synchronized (locator) + { + index = locator.getViewIndexAtPoint(x, y, a); + ca = locator.getChildAllocation(index, a); + } + + ChildState cs = getChildState(index); + synchronized (cs) + { + View v = cs.getChildView(); + pos = v.viewToModel(x, y, ca, b); + } + return pos; + } + + /** + * Returns the child allocation for the child view with the specified + * <code>index</code>. + * + * @param index the index of the child view + * @param a the current allocation of this view + * + * @return the allocation of the child view + */ + public Shape getChildAllocation(int index, Shape a) + { + Shape ca = locator.getChildAllocation(index, a); + return ca; + } + + /** + * Returns the maximum span of this view along the specified axis. + * This is implemented to return the <code>preferredSpan</code> for the + * major axis (that means the box can't be resized along the major axis) and + * {@link Short#MAX_VALUE} for the minor axis. + * + * @param axis the axis + * + * @return the maximum span of this view along the specified axis + */ + public float getMaximumSpan(int axis) + { + float max; + if (axis == majorAxis) + max = getPreferredSpan(axis); + else + max = Short.MAX_VALUE; + return max; + } + + /** + * Returns the minimum span along the specified axis. + */ + public float getMinimumSpan(int axis) + { + float min; + if (axis == majorAxis) + min = getPreferredSpan(axis); + else + { + if (minReq != null) + { + View child = minReq.getChildView(); + min = child.getMinimumSpan(axis); + } + else + { + // No layout information yet. Return insets + 5 as some kind of + // estimation. + if (axis == X_AXIS) + min = getLeftInset() + getRightInset() + 5; + else + min = getTopInset() + getBottomInset() + 5; + } + } + return min; + } + + /** + * Receives notification that one of the child views has changed its + * layout preferences along one or both axis. + * + * This queues a layout request for that child view if necessary. + * + * @param view the view that has changed its preferences + * @param width <code>true</code> if the width preference has changed + * @param height <code>true</code> if the height preference has changed + */ + public synchronized void preferenceChanged(View view, boolean width, + boolean height) + { + if (view == null) + getParent().preferenceChanged(this, width, height); + else + { + if (changing != null) + { + View cv = changing.getChildView(); + if (cv == view) + { + changing.preferenceChanged(width, height); + return; + } + } + int index = getViewIndexAtPosition(view.getStartOffset(), + Position.Bias.Forward); + ChildState cs = getChildState(index); + cs.preferenceChanged(width, height); + LayoutQueue q = getLayoutQueue(); + q.addTask(cs); + q.addTask(flushTask); + } + } + + /** + * Updates the layout for this view. This is implemented to trigger + * {@link ChildLocator#childChanged} for the changed view, if there is + * any. + * + * @param ec the element change, may be <code>null</code> if there were + * no changes to the element of this view + * @param e the document event + * @param a the current allocation of this view + */ + protected void updateLayout(DocumentEvent.ElementChange ec, + DocumentEvent e, Shape a) + { + if (ec != null) + { + int index = Math.max(ec.getIndex() - 1, 0); + ChildState cs = getChildState(index); + locator.childChanged(cs); + } + } + + + /** + * Returns the <code>ChildState</code> object associated with the child view + * at the specified <code>index</code>. + * + * @param index the index of the child view for which to query the state + * + * @return the child state for the specified child view + */ + protected ChildState getChildState(int index) { + synchronized (childStates) + { + return (ChildState) childStates.get(index); + } + } + + /** + * Returns the <code>LayoutQueue</code> used for layouting the box view. + * This simply returns {@link LayoutQueue#getDefaultQueue()}. + * + * @return the <code>LayoutQueue</code> used for layouting the box view + */ + protected LayoutQueue getLayoutQueue() + { + return LayoutQueue.getDefaultQueue(); + } + + /** + * Returns the child view index of the view that represents the specified + * position in the document model. + * + * @param pos the position in the model + * @param b the bias + * + * @return the child view index of the view that represents the specified + * position in the document model + */ + protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) + { + if (b == Position.Bias.Backward) + pos = Math.max(0, pos - 1); + Element elem = getElement(); + return elem.getElementIndex(pos); + } + + /** + * Creates a <code>ChildState</code> object for the specified view. + * + * @param v the view for which to create a child state object + * + * @return the created child state + */ + protected ChildState createChildState(View v) + { + return new ChildState(v); + } + + /** + * Flushes the requirements changes upwards to the parent view. This is + * called from the layout thread. + */ + protected synchronized void flushRequirementChanges() + { + if (majorChanged || minorChanged) + { + View p = getParent(); + if (p != null) + { + boolean horizontal; + boolean vertical; + if (majorAxis == X_AXIS) + { + horizontal = majorChanged; + vertical = minorChanged; + } + else + { + vertical = majorChanged; + horizontal = minorChanged; + } + + p.preferenceChanged(this, horizontal, vertical); + majorChanged = false; + minorChanged = false; + + Component c = getContainer(); + if (c != null) + c.repaint(); + } + } + } +} |