/** * Fetches the child view index at the given point. * This is called by the various View methods that * need to calculate which child to forward a message * to. This should be called by a block synchronized * on this object, and would typically be followed * with one or more calls to getChildAllocation that * should also be in the synchronized block. * * @param x the X coordinate >= 0 * @param y the Y coordinate >= 0 * @param a the allocation to the View * @return the nearest child index */ public int getViewIndexAtPoint(float x, float y, Shape a) { setAllocation(a); float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y; int index = getViewIndexAtVisualOffset(targetOffset); return index; } /** * Fetch the allocation to use for a child view. * <em>This does not update the offsets in the ChildState * records.</em> */ protected Shape getChildAllocation(int index) { ChildState cs = getChildState(index); if (! cs.isLayoutValid()) { cs.run(); } if (axis == 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(); } childAlloc.x += (int)getLeftInset(); childAlloc.y += (int)getRightInset(); return childAlloc; } /** * Copy the currently allocated shape into the Rectangle * used to store the current allocation. This would be * a floating point rectangle in a Java2D-specific implmentation. */ protected void setAllocation(Shape a) { if (a instanceof Rectangle) { lastAlloc.setBounds((Rectangle) a); } else { lastAlloc.setBounds(a.getBounds()); } setSize(lastAlloc.width, lastAlloc.height); } /** * Locate the view responsible for an offset into the box * along the major axis. Make sure that offsets are set * on the ChildState objects up to the given target span * past the desired offset. * * @return index of the view representing the given visual * location (targetOffset), or -1 if no view represents * that location */ protected int getViewIndexAtVisualOffset(float targetOffset) { int n = getViewCount(); if (n > 0) { boolean lastValid = (lastValidOffset != null); if (lastValidOffset == null) { lastValidOffset = getChildState(0); } if (targetOffset > majorSpan) { // should only get here on the first time display. if (!lastValid) { return 0; } int pos = lastValidOffset.getChildView().getStartOffset(); int index = getViewIndex(pos, Position.Bias.Forward); return index; } else if (targetOffset > lastValidOffset.getMajorOffset()) { // roll offset calculations forward return updateChildOffsets(targetOffset); } else { // no changes prior to the needed offset // this should be a binary search 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; } /** * Move the location of the last offset calculation forward * to the desired offset. */ int updateChildOffsets(float targetOffset) { int n = getViewCount(); int targetIndex = n - 1; int pos = lastValidOffset.getChildView().getStartOffset(); int startIndex = getViewIndex(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; } /** * Move the location of the last offset calculation forward * to the desired index. */ void updateChildOffsetsToIndex(int index) { int pos = lastValidOffset.getChildView().getStartOffset(); int startIndex = getViewIndex(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(); } } boolean intersectsClip(Shape childAlloc, Rectangle clip) { Rectangle cs = (childAlloc instanceof Rectangle) ? (Rectangle) childAlloc : childAlloc.getBounds(); if (cs.intersects(clip)) { // Make sure that lastAlloc also contains childAlloc, // this will be false if haven't yet flushed changes. return lastAlloc.intersects(cs); } return false; } /** * The location of the last offset calculation * that is valid. */ protected ChildState lastValidOffset; /** * The last seen allocation (for repainting when changes * are flushed upward). */ protected Rectangle lastAlloc; /** * A shape to use for the child allocation to avoid * creating a lot of garbage. */ protected Rectangle childAlloc; } /** * A record representing the layout state of a * child view. It is runnable as a task on another * thread. All access to the child view that is * based upon a read-lock on the model should synchronize * on this object (i.e. The layout thread and the GUI * thread can both have a read lock on the model at the * same time and are not protected from each other). * Access to a child view hierarchy is serialized via * synchronization on the ChildState instance. * @since 1.3 */ public class ChildState implements Runnable { /** * Construct a child status. This needs to start * out as fairly large so we don't falsely begin with * the idea that all of the children are visible. * @since 1.4 */ public ChildState(View v) { child = v; minorValid = false; majorValid = false; childSizeValid = false; child.setParent(AsyncBoxView.this); } /** * Fetch the child view this record represents */ public View getChildView() { return child; } /** * Update the child state. This should be * called by the thread that desires to spend * time updating the child state (intended to * be the layout thread). * <p> * This aquires a read lock on the associated * document for the duration of the update to * ensure the model is not changed while it is * operating. The first thing to do would be * to see if any work actually needs to be done. * The following could have conceivably happened * while the state was waiting to be updated: * <ol> * <li>The child may have been removed from the * view hierarchy. * <li>The child may have been updated by a * higher priority operation (i.e. the child * may have become visible). * </ol> */ public void run () { AbstractDocument doc = (AbstractDocument) getDocument(); try { doc.readLock(); if (minorValid && majorValid && childSizeValid) { // nothing to do return; } if (child.getParent() == AsyncBoxView.this) { // this may overwrite anothers threads cached // value for actively changing... but that just // means it won't use the cache if there is an // overwrite. synchronized(AsyncBoxView.this) { changing = this; } updateChild(); synchronized(AsyncBoxView.this) { changing = null; } // setting the child size on the minor axis // may have caused it to change it's preference // along the major axis. updateChild(); } } finally { doc.readUnlock(); } } void updateChild() { boolean minorUpdated = false; synchronized(this) { if (! minorValid) { int minorAxis = getMinorAxis(); min = child.getMinimumSpan(minorAxis); pref = child.getPreferredSpan(minorAxis); max = child.getMaximumSpan(minorAxis); minorValid = true; minorUpdated = true; } } if (minorUpdated) { minorRequirementChange(this); } boolean majorUpdated = false; float delta = 0.0f; synchronized(this) { if (! majorValid) { float old = span; span = child.getPreferredSpan(axis); delta = span - old; majorValid = true; majorUpdated = true; } } if (majorUpdated) { majorRequirementChange(this, delta); locator.childChanged(this); } synchronized(this) { if (! childSizeValid) { float w; float h; if (axis == X_AXIS) { w = span; h = getMinorSpan(); } else { w = getMinorSpan(); h = span; } childSizeValid = true; child.setSize(w, h); } } } /** * What is the span along the minor axis. */ public float getMinorSpan() { if (max < minorSpan) { return max; } // make it the target width, or as small as it can get. return Math.max(min, minorSpan); } /** * What is the offset along the minor axis */ public float getMinorOffset() { if (max < minorSpan) { // can't make the child this wide, align it float align = child.getAlignment(getMinorAxis()); return ((minorSpan - max) * align); } return 0f; } /** * What is the span along the major axis. */ public float getMajorSpan() { return span; } /** * Get the offset along the major axis */ public float getMajorOffset() { return offset; } /** * This method should only be called by the ChildLocator, * it is simply a convenient place to hold the cached * location. */ public void setMajorOffset(float offs) { offset = offs; } /** * Mark preferences changed for this child. * * @param width true if the width preference has changed * @param height true if the height preference has changed * @see javax.swing.JComponent#revalidate */ public void preferenceChanged(boolean width, boolean height) { if (axis == X_AXIS) { if (width) { majorValid = false; } if (height) { minorValid = false; } } else { if (width) { minorValid = false; } if (height) { majorValid = false; } } childSizeValid = false; } /** * Has the child view been laid out. */ public boolean isLayoutValid() { return (minorValid && majorValid && childSizeValid); } // minor axis private float min; private float pref; private float max; private boolean minorValid; // major axis private float span; private float offset; private boolean majorValid; private View child; private boolean childSizeValid; } /** * Task to flush requirement changes upward */ class FlushTask implements Runnable { public void run() { flushRequirementChanges(); } } }Lines 1001–1420 of 1420. Previous lines
/** * Fetches the child view index at the given point. * This is called by the various View methods that * need to calculate which child to forward a message * to. This should be called by a block synchronized * on this object, and would typically be followed * with one or more calls to getChildAllocation that * should also be in the synchronized block. * * @param x the X coordinate >= 0 * @param y the Y coordinate >= 0 * @param a the allocation to the View * @return the nearest child index */ public int getViewIndexAtPoint(float x, float y, Shape a) { setAllocation(a); float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y; int index = getViewIndexAtVisualOffset(targetOffset); return index; } /** * Fetch the allocation to use for a child view. * <em>This does not update the offsets in the ChildState * records.</em> */ protected Shape getChildAllocation(int index) { ChildState cs = getChildState(index); if (! cs.isLayoutValid()) { cs.run(); } if (axis == 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(); } childAlloc.x += (int)getLeftInset(); childAlloc.y += (int)getRightInset(); return childAlloc; } /** * Copy the currently allocated shape into the Rectangle * used to store the current allocation. This would be * a floating point rectangle in a Java2D-specific implmentation. */ protected void setAllocation(Shape a) { if (a instanceof Rectangle) { lastAlloc.setBounds((Rectangle) a); } else { lastAlloc.setBounds(a.getBounds()); } setSize(lastAlloc.width, lastAlloc.height); } /** * Locate the view responsible for an offset into the box * along the major axis. Make sure that offsets are set * on the ChildState objects up to the given target span * past the desired offset. * * @return index of the view representing the given visual * location (targetOffset), or -1 if no view represents * that location */ protected int getViewIndexAtVisualOffset(float targetOffset) { int n = getViewCount(); if (n > 0) { boolean lastValid = (lastValidOffset != null); if (lastValidOffset == null) { lastValidOffset = getChildState(0); } if (targetOffset > majorSpan) { // should only get here on the first time display. if (!lastValid) { return 0; } int pos = lastValidOffset.getChildView().getStartOffset(); int index = getViewIndex(pos, Position.Bias.Forward); return index; } else if (targetOffset > lastValidOffset.getMajorOffset()) { // roll offset calculations forward return updateChildOffsets(targetOffset); } else { // no changes prior to the needed offset // this should be a binary search 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; } /** * Move the location of the last offset calculation forward * to the desired offset. */ int updateChildOffsets(float targetOffset) { int n = getViewCount(); int targetIndex = n - 1; int pos = lastValidOffset.getChildView().getStartOffset(); int startIndex = getViewIndex(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; } /** * Move the location of the last offset calculation forward * to the desired index. */ void updateChildOffsetsToIndex(int index) { int pos = lastValidOffset.getChildView().getStartOffset(); int startIndex = getViewIndex(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(); } } boolean intersectsClip(Shape childAlloc, Rectangle clip) { Rectangle cs = (childAlloc instanceof Rectangle) ? (Rectangle) childAlloc : childAlloc.getBounds(); if (cs.intersects(clip)) { // Make sure that lastAlloc also contains childAlloc, // this will be false if haven't yet flushed changes. return lastAlloc.intersects(cs); } return false; } /** * The location of the last offset calculation * that is valid. */ protected ChildState lastValidOffset; /** * The last seen allocation (for repainting when changes * are flushed upward). */ protected Rectangle lastAlloc; /** * A shape to use for the child allocation to avoid * creating a lot of garbage. */ protected Rectangle childAlloc; } /** * A record representing the layout state of a * child view. It is runnable as a task on another * thread. All access to the child view that is * based upon a read-lock on the model should synchronize * on this object (i.e. The layout thread and the GUI * thread can both have a read lock on the model at the * same time and are not protected from each other). * Access to a child view hierarchy is serialized via * synchronization on the ChildState instance. * @since 1.3 */ public class ChildState implements Runnable { /** * Construct a child status. This needs to start * out as fairly large so we don't falsely begin with * the idea that all of the children are visible. * @since 1.4 */ public ChildState(View v) { child = v; minorValid = false; majorValid = false; childSizeValid = false; child.setParent(AsyncBoxView.this); } /** * Fetch the child view this record represents */ public View getChildView() { return child; } /** * Update the child state. This should be * called by the thread that desires to spend * time updating the child state (intended to * be the layout thread). * <p> * This aquires a read lock on the associated * document for the duration of the update to * ensure the model is not changed while it is * operating. The first thing to do would be * to see if any work actually needs to be done. * The following could have conceivably happened * while the state was waiting to be updated: * <ol> * <li>The child may have been removed from the * view hierarchy. * <li>The child may have been updated by a * higher priority operation (i.e. the child * may have become visible). * </ol> */ public void run () { AbstractDocument doc = (AbstractDocument) getDocument(); try { doc.readLock(); if (minorValid && majorValid && childSizeValid) { // nothing to do return; } if (child.getParent() == AsyncBoxView.this) { // this may overwrite anothers threads cached // value for actively changing... but that just // means it won't use the cache if there is an // overwrite. synchronized(AsyncBoxView.this) { changing = this; } updateChild(); synchronized(AsyncBoxView.this) { changing = null; } // setting the child size on the minor axis // may have caused it to change it's preference // along the major axis. updateChild(); } } finally { doc.readUnlock(); } } void updateChild() { boolean minorUpdated = false; synchronized(this) { if (! minorValid) { int minorAxis = getMinorAxis(); min = child.getMinimumSpan(minorAxis); pref = child.getPreferredSpan(minorAxis); max = child.getMaximumSpan(minorAxis); minorValid = true; minorUpdated = true; } } if (minorUpdated) { minorRequirementChange(this); } boolean majorUpdated = false; float delta = 0.0f; synchronized(this) { if (! majorValid) { float old = span; span = child.getPreferredSpan(axis); delta = span - old; majorValid = true; majorUpdated = true; } } if (majorUpdated) { majorRequirementChange(this, delta); locator.childChanged(this); } synchronized(this) { if (! childSizeValid) { float w; float h; if (axis == X_AXIS) { w = span; h = getMinorSpan(); } else { w = getMinorSpan(); h = span; } childSizeValid = true; child.setSize(w, h); } } } /** * What is the span along the minor axis. */ public float getMinorSpan() { if (max < minorSpan) { return max; } // make it the target width, or as small as it can get. return Math.max(min, minorSpan); } /** * What is the offset along the minor axis */ public float getMinorOffset() { if (max < minorSpan) { // can't make the child this wide, align it float align = child.getAlignment(getMinorAxis()); return ((minorSpan - max) * align); } return 0f; } /** * What is the span along the major axis. */ public float getMajorSpan() { return span; } /** * Get the offset along the major axis */ public float getMajorOffset() { return offset; } /** * This method should only be called by the ChildLocator, * it is simply a convenient place to hold the cached * location. */ public void setMajorOffset(float offs) { offset = offs; } /** * Mark preferences changed for this child. * * @param width true if the width preference has changed * @param height true if the height preference has changed * @see javax.swing.JComponent#revalidate */ public void preferenceChanged(boolean width, boolean height) { if (axis == X_AXIS) { if (width) { majorValid = false; } if (height) { minorValid = false; } } else { if (width) { minorValid = false; } if (height) { majorValid = false; } } childSizeValid = false; } /** * Has the child view been laid out. */ public boolean isLayoutValid() { return (minorValid && majorValid && childSizeValid); } // minor axis private float min; private float pref; private float max; private boolean minorValid; // major axis private float span; private float offset; private boolean majorValid; private View child; private boolean childSizeValid; } /** * Task to flush requirement changes upward */ class FlushTask implements Runnable { public void run() { flushRequirementChanges(); } } }