| /* 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(); |
| } |
| } |
| } |
| } |