jdk/src/share/classes/javax/swing/text/AsyncBoxView.java
changeset 2 90ce3da70b43
child 1287 a04aca99c77a
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 1999-2006 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 package javax.swing.text;
       
    26 
       
    27 import java.util.*;
       
    28 import java.awt.*;
       
    29 import javax.swing.SwingUtilities;
       
    30 import javax.swing.event.DocumentEvent;
       
    31 
       
    32 /**
       
    33  * A box that does layout asynchronously.  This
       
    34  * is useful to keep the GUI event thread moving by
       
    35  * not doing any layout on it.  The layout is done
       
    36  * on a granularity of operations on the child views.
       
    37  * After each child view is accessed for some part
       
    38  * of layout (a potentially time consuming operation)
       
    39  * the remaining tasks can be abandoned or a new higher
       
    40  * priority task (i.e. to service a synchronous request
       
    41  * or a visible area) can be taken on.
       
    42  * <p>
       
    43  * While the child view is being accessed
       
    44  * a read lock is aquired on the associated document
       
    45  * so that the model is stable while being accessed.
       
    46  *
       
    47  * @author  Timothy Prinzing
       
    48  * @since   1.3
       
    49  */
       
    50 public class AsyncBoxView extends View {
       
    51 
       
    52     /**
       
    53      * Construct a box view that does asynchronous layout.
       
    54      *
       
    55      * @param elem the element of the model to represent
       
    56      * @param axis the axis to tile along.  This can be
       
    57      *  either X_AXIS or Y_AXIS.
       
    58      */
       
    59     public AsyncBoxView(Element elem, int axis) {
       
    60         super(elem);
       
    61         stats = new ArrayList();
       
    62         this.axis = axis;
       
    63         locator = new ChildLocator();
       
    64         flushTask = new FlushTask();
       
    65         minorSpan = Short.MAX_VALUE;
       
    66         estimatedMajorSpan = false;
       
    67     }
       
    68 
       
    69     /**
       
    70      * Fetch the major axis (the axis the children
       
    71      * are tiled along).  This will have a value of
       
    72      * either X_AXIS or Y_AXIS.
       
    73      */
       
    74     public int getMajorAxis() {
       
    75         return axis;
       
    76     }
       
    77 
       
    78     /**
       
    79      * Fetch the minor axis (the axis orthoginal
       
    80      * to the tiled axis).  This will have a value of
       
    81      * either X_AXIS or Y_AXIS.
       
    82      */
       
    83     public int getMinorAxis() {
       
    84         return (axis == X_AXIS) ? Y_AXIS : X_AXIS;
       
    85     }
       
    86 
       
    87     /**
       
    88      * Get the top part of the margin around the view.
       
    89      */
       
    90     public float getTopInset() {
       
    91         return topInset;
       
    92     }
       
    93 
       
    94     /**
       
    95      * Set the top part of the margin around the view.
       
    96      *
       
    97      * @param i the value of the inset
       
    98      */
       
    99     public void setTopInset(float i) {
       
   100         topInset = i;
       
   101     }
       
   102 
       
   103     /**
       
   104      * Get the bottom part of the margin around the view.
       
   105      */
       
   106     public float getBottomInset() {
       
   107         return bottomInset;
       
   108     }
       
   109 
       
   110     /**
       
   111      * Set the bottom part of the margin around the view.
       
   112      *
       
   113      * @param i the value of the inset
       
   114      */
       
   115     public void setBottomInset(float i) {
       
   116         bottomInset = i;
       
   117     }
       
   118 
       
   119     /**
       
   120      * Get the left part of the margin around the view.
       
   121      */
       
   122     public float getLeftInset() {
       
   123         return leftInset;
       
   124     }
       
   125 
       
   126     /**
       
   127      * Set the left part of the margin around the view.
       
   128      *
       
   129      * @param i the value of the inset
       
   130      */
       
   131     public void setLeftInset(float i) {
       
   132         leftInset = i;
       
   133     }
       
   134 
       
   135     /**
       
   136      * Get the right part of the margin around the view.
       
   137      */
       
   138     public float getRightInset() {
       
   139         return rightInset;
       
   140     }
       
   141 
       
   142     /**
       
   143      * Set the right part of the margin around the view.
       
   144      *
       
   145      * @param i the value of the inset
       
   146      */
       
   147     public void setRightInset(float i) {
       
   148         rightInset = i;
       
   149     }
       
   150 
       
   151     /**
       
   152      * Fetch the span along an axis that is taken up by the insets.
       
   153      *
       
   154      * @param axis the axis to determine the total insets along,
       
   155      *  either X_AXIS or Y_AXIS.
       
   156      * @since 1.4
       
   157      */
       
   158     protected float getInsetSpan(int axis) {
       
   159         float margin = (axis == X_AXIS) ?
       
   160             getLeftInset() + getRightInset() : getTopInset() + getBottomInset();
       
   161         return margin;
       
   162     }
       
   163 
       
   164     /**
       
   165      * Set the estimatedMajorSpan property that determines if the
       
   166      * major span should be treated as being estimated.  If this
       
   167      * property is true, the value of setSize along the major axis
       
   168      * will change the requirements along the major axis and incremental
       
   169      * changes will be ignored until all of the children have been updated
       
   170      * (which will cause the property to automatically be set to false).
       
   171      * If the property is false the value of the majorSpan will be
       
   172      * considered to be accurate and incremental changes will be
       
   173      * added into the total as they are calculated.
       
   174      *
       
   175      * @since 1.4
       
   176      */
       
   177     protected void setEstimatedMajorSpan(boolean isEstimated) {
       
   178         estimatedMajorSpan = isEstimated;
       
   179     }
       
   180 
       
   181     /**
       
   182      * Is the major span currently estimated?
       
   183      *
       
   184      * @since 1.4
       
   185      */
       
   186     protected boolean getEstimatedMajorSpan() {
       
   187         return estimatedMajorSpan;
       
   188     }
       
   189 
       
   190     /**
       
   191      * Fetch the object representing the layout state of
       
   192      * of the child at the given index.
       
   193      *
       
   194      * @param index the child index.  This should be a
       
   195      *   value >= 0 and < getViewCount().
       
   196      */
       
   197     protected ChildState getChildState(int index) {
       
   198         synchronized(stats) {
       
   199             if ((index >= 0) && (index < stats.size())) {
       
   200                 return (ChildState) stats.get(index);
       
   201             }
       
   202             return null;
       
   203         }
       
   204     }
       
   205 
       
   206     /**
       
   207      * Fetch the queue to use for layout.
       
   208      */
       
   209     protected LayoutQueue getLayoutQueue() {
       
   210         return LayoutQueue.getDefaultQueue();
       
   211     }
       
   212 
       
   213     /**
       
   214      * New ChildState records are created through
       
   215      * this method to allow subclasses the extend
       
   216      * the ChildState records to do/hold more
       
   217      */
       
   218     protected ChildState createChildState(View v) {
       
   219         return new ChildState(v);
       
   220     }
       
   221 
       
   222     /**
       
   223      * Requirements changed along the major axis.
       
   224      * This is called by the thread doing layout for
       
   225      * the given ChildState object when it has completed
       
   226      * fetching the child views new preferences.
       
   227      * Typically this would be the layout thread, but
       
   228      * might be the event thread if it is trying to update
       
   229      * something immediately (such as to perform a
       
   230      * model/view translation).
       
   231      * <p>
       
   232      * This is implemented to mark the major axis as having
       
   233      * changed so that a future check to see if the requirements
       
   234      * need to be published to the parent view will consider
       
   235      * the major axis.  If the span along the major axis is
       
   236      * not estimated, it is updated by the given delta to reflect
       
   237      * the incremental change.  The delta is ignored if the
       
   238      * major span is estimated.
       
   239      */
       
   240     protected synchronized void majorRequirementChange(ChildState cs, float delta) {
       
   241         if (estimatedMajorSpan == false) {
       
   242             majorSpan += delta;
       
   243         }
       
   244         majorChanged = true;
       
   245     }
       
   246 
       
   247     /**
       
   248      * Requirements changed along the minor axis.
       
   249      * This is called by the thread doing layout for
       
   250      * the given ChildState object when it has completed
       
   251      * fetching the child views new preferences.
       
   252      * Typically this would be the layout thread, but
       
   253      * might be the GUI thread if it is trying to update
       
   254      * something immediately (such as to perform a
       
   255      * model/view translation).
       
   256      */
       
   257     protected synchronized void minorRequirementChange(ChildState cs) {
       
   258         minorChanged = true;
       
   259     }
       
   260 
       
   261     /**
       
   262      * Publish the changes in preferences upward to the parent
       
   263      * view.  This is normally called by the layout thread.
       
   264      */
       
   265     protected void flushRequirementChanges() {
       
   266         AbstractDocument doc = (AbstractDocument) getDocument();
       
   267         try {
       
   268             doc.readLock();
       
   269 
       
   270             View parent = null;
       
   271             boolean horizontal = false;
       
   272             boolean vertical = false;
       
   273 
       
   274             synchronized(this) {
       
   275                 // perform tasks that iterate over the children while
       
   276                 // preventing the collection from changing.
       
   277                 synchronized(stats) {
       
   278                     int n = getViewCount();
       
   279                     if ((n > 0) && (minorChanged || estimatedMajorSpan)) {
       
   280                         LayoutQueue q = getLayoutQueue();
       
   281                         ChildState min = getChildState(0);
       
   282                         ChildState pref = getChildState(0);
       
   283                         float span = 0f;
       
   284                         for (int i = 1; i < n; i++) {
       
   285                             ChildState cs = getChildState(i);
       
   286                             if (minorChanged) {
       
   287                                 if (cs.min > min.min) {
       
   288                                     min = cs;
       
   289                                 }
       
   290                                 if (cs.pref > pref.pref) {
       
   291                                     pref = cs;
       
   292                                 }
       
   293                             }
       
   294                             if (estimatedMajorSpan) {
       
   295                                 span += cs.getMajorSpan();
       
   296                             }
       
   297                         }
       
   298 
       
   299                         if (minorChanged) {
       
   300                             minRequest = min;
       
   301                             prefRequest = pref;
       
   302                         }
       
   303                         if (estimatedMajorSpan) {
       
   304                             majorSpan = span;
       
   305                             estimatedMajorSpan = false;
       
   306                             majorChanged = true;
       
   307                         }
       
   308                     }
       
   309                 }
       
   310 
       
   311                 // message preferenceChanged
       
   312                 if (majorChanged || minorChanged) {
       
   313                     parent = getParent();
       
   314                     if (parent != null) {
       
   315                         if (axis == X_AXIS) {
       
   316                             horizontal = majorChanged;
       
   317                             vertical = minorChanged;
       
   318                         } else {
       
   319                             vertical = majorChanged;
       
   320                             horizontal = minorChanged;
       
   321                         }
       
   322                     }
       
   323                     majorChanged = false;
       
   324                     minorChanged = false;
       
   325                 }
       
   326             }
       
   327 
       
   328             // propagate a preferenceChanged, using the
       
   329             // layout thread.
       
   330             if (parent != null) {
       
   331                 parent.preferenceChanged(this, horizontal, vertical);
       
   332 
       
   333                 // probably want to change this to be more exact.
       
   334                 Component c = getContainer();
       
   335                 if (c != null) {
       
   336                     c.repaint();
       
   337                 }
       
   338             }
       
   339         } finally {
       
   340             doc.readUnlock();
       
   341         }
       
   342     }
       
   343 
       
   344     /**
       
   345      * Calls the superclass to update the child views, and
       
   346      * updates the status records for the children.  This
       
   347      * is expected to be called while a write lock is held
       
   348      * on the model so that interaction with the layout
       
   349      * thread will not happen (i.e. the layout thread
       
   350      * acquires a read lock before doing anything).
       
   351      *
       
   352      * @param offset the starting offset into the child views >= 0
       
   353      * @param length the number of existing views to replace >= 0
       
   354      * @param views the child views to insert
       
   355      */
       
   356     public void replace(int offset, int length, View[] views) {
       
   357         synchronized(stats) {
       
   358             // remove the replaced state records
       
   359             for (int i = 0; i < length; i++) {
       
   360                 ChildState cs = (ChildState)stats.remove(offset);
       
   361                 float csSpan = cs.getMajorSpan();
       
   362 
       
   363                 cs.getChildView().setParent(null);
       
   364                 if (csSpan != 0) {
       
   365                     majorRequirementChange(cs, -csSpan);
       
   366                 }
       
   367             }
       
   368 
       
   369             // insert the state records for the new children
       
   370             LayoutQueue q = getLayoutQueue();
       
   371             if (views != null) {
       
   372                 for (int i = 0; i < views.length; i++) {
       
   373                     ChildState s = createChildState(views[i]);
       
   374                     stats.add(offset + i, s);
       
   375                     q.addTask(s);
       
   376                 }
       
   377             }
       
   378 
       
   379             // notify that the size changed
       
   380             q.addTask(flushTask);
       
   381         }
       
   382     }
       
   383 
       
   384     /**
       
   385      * Loads all of the children to initialize the view.
       
   386      * This is called by the <a href="#setParent">setParent</a>
       
   387      * method.  Subclasses can reimplement this to initialize
       
   388      * their child views in a different manner.  The default
       
   389      * implementation creates a child view for each
       
   390      * child element.
       
   391      * <p>
       
   392      * Normally a write-lock is held on the Document while
       
   393      * the children are being changed, which keeps the rendering
       
   394      * and layout threads safe.  The exception to this is when
       
   395      * the view is initialized to represent an existing element
       
   396      * (via this method), so it is synchronized to exclude
       
   397      * preferenceChanged while we are initializing.
       
   398      *
       
   399      * @param f the view factory
       
   400      * @see #setParent
       
   401      */
       
   402     protected void loadChildren(ViewFactory f) {
       
   403         Element e = getElement();
       
   404         int n = e.getElementCount();
       
   405         if (n > 0) {
       
   406             View[] added = new View[n];
       
   407             for (int i = 0; i < n; i++) {
       
   408                 added[i] = f.create(e.getElement(i));
       
   409             }
       
   410             replace(0, 0, added);
       
   411         }
       
   412     }
       
   413 
       
   414     /**
       
   415      * Fetches the child view index representing the given position in
       
   416      * the model.  This is implemented to fetch the view in the case
       
   417      * where there is a child view for each child element.
       
   418      *
       
   419      * @param pos the position >= 0
       
   420      * @return  index of the view representing the given position, or
       
   421      *   -1 if no view represents that position
       
   422      */
       
   423     protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) {
       
   424         boolean isBackward = (b == Position.Bias.Backward);
       
   425         pos = (isBackward) ? Math.max(0, pos - 1) : pos;
       
   426         Element elem = getElement();
       
   427         return elem.getElementIndex(pos);
       
   428     }
       
   429 
       
   430     /**
       
   431      * Update the layout in response to receiving notification of
       
   432      * change from the model.  This is implemented to note the
       
   433      * change on the ChildLocator so that offsets of the children
       
   434      * will be correctly computed.
       
   435      *
       
   436      * @param ec changes to the element this view is responsible
       
   437      *  for (may be null if there were no changes).
       
   438      * @param e the change information from the associated document
       
   439      * @param a the current allocation of the view
       
   440      * @see #insertUpdate
       
   441      * @see #removeUpdate
       
   442      * @see #changedUpdate
       
   443      */
       
   444     protected void updateLayout(DocumentEvent.ElementChange ec,
       
   445                                     DocumentEvent e, Shape a) {
       
   446         if (ec != null) {
       
   447             // the newly inserted children don't have a valid
       
   448             // offset so the child locator needs to be messaged
       
   449             // that the child prior to the new children has
       
   450             // changed size.
       
   451             int index = Math.max(ec.getIndex() - 1, 0);
       
   452             ChildState cs = getChildState(index);
       
   453             locator.childChanged(cs);
       
   454         }
       
   455     }
       
   456 
       
   457     // --- View methods ------------------------------------
       
   458 
       
   459     /**
       
   460      * Sets the parent of the view.
       
   461      * This is reimplemented to provide the superclass
       
   462      * behavior as well as calling the <code>loadChildren</code>
       
   463      * method if this view does not already have children.
       
   464      * The children should not be loaded in the
       
   465      * constructor because the act of setting the parent
       
   466      * may cause them to try to search up the hierarchy
       
   467      * (to get the hosting Container for example).
       
   468      * If this view has children (the view is being moved
       
   469      * from one place in the view hierarchy to another),
       
   470      * the <code>loadChildren</code> method will not be called.
       
   471      *
       
   472      * @param parent the parent of the view, null if none
       
   473      */
       
   474     public void setParent(View parent) {
       
   475         super.setParent(parent);
       
   476         if ((parent != null) && (getViewCount() == 0)) {
       
   477             ViewFactory f = getViewFactory();
       
   478             loadChildren(f);
       
   479         }
       
   480     }
       
   481 
       
   482     /**
       
   483      * Child views can call this on the parent to indicate that
       
   484      * the preference has changed and should be reconsidered
       
   485      * for layout.  This is reimplemented to queue new work
       
   486      * on the layout thread.  This method gets messaged from
       
   487      * multiple threads via the children.
       
   488      *
       
   489      * @param child the child view
       
   490      * @param width true if the width preference has changed
       
   491      * @param height true if the height preference has changed
       
   492      * @see javax.swing.JComponent#revalidate
       
   493      */
       
   494     public synchronized void preferenceChanged(View child, boolean width, boolean height) {
       
   495         if (child == null) {
       
   496             getParent().preferenceChanged(this, width, height);
       
   497         } else {
       
   498             if (changing != null) {
       
   499                 View cv = changing.getChildView();
       
   500                 if (cv == child) {
       
   501                     // size was being changed on the child, no need to
       
   502                     // queue work for it.
       
   503                     changing.preferenceChanged(width, height);
       
   504                     return;
       
   505                 }
       
   506             }
       
   507             int index = getViewIndex(child.getStartOffset(),
       
   508                                      Position.Bias.Forward);
       
   509             ChildState cs = getChildState(index);
       
   510             cs.preferenceChanged(width, height);
       
   511             LayoutQueue q = getLayoutQueue();
       
   512             q.addTask(cs);
       
   513             q.addTask(flushTask);
       
   514         }
       
   515     }
       
   516 
       
   517     /**
       
   518      * Sets the size of the view.  This should cause
       
   519      * layout of the view if the view caches any layout
       
   520      * information.
       
   521      * <p>
       
   522      * Since the major axis is updated asynchronously and should be
       
   523      * the sum of the tiled children the call is ignored for the major
       
   524      * axis.  Since the minor axis is flexible, work is queued to resize
       
   525      * the children if the minor span changes.
       
   526      *
       
   527      * @param width the width >= 0
       
   528      * @param height the height >= 0
       
   529      */
       
   530     public void setSize(float width, float height) {
       
   531         setSpanOnAxis(X_AXIS, width);
       
   532         setSpanOnAxis(Y_AXIS, height);
       
   533     }
       
   534 
       
   535     /**
       
   536      * Retrieves the size of the view along an axis.
       
   537      *
       
   538      * @param axis may be either <code>View.X_AXIS</code> or
       
   539      *          <code>View.Y_AXIS</code>
       
   540      * @return the current span of the view along the given axis, >= 0
       
   541      */
       
   542     float getSpanOnAxis(int axis) {
       
   543         if (axis == getMajorAxis()) {
       
   544             return majorSpan;
       
   545         }
       
   546         return minorSpan;
       
   547     }
       
   548 
       
   549     /**
       
   550      * Sets the size of the view along an axis.  Since the major
       
   551      * axis is updated asynchronously and should be the sum of the
       
   552      * tiled children the call is ignored for the major axis.  Since
       
   553      * the minor axis is flexible, work is queued to resize the
       
   554      * children if the minor span changes.
       
   555      *
       
   556      * @param axis may be either <code>View.X_AXIS</code> or
       
   557      *          <code>View.Y_AXIS</code>
       
   558      * @param span the span to layout to >= 0
       
   559      */
       
   560     void setSpanOnAxis(int axis, float span) {
       
   561         float margin = getInsetSpan(axis);
       
   562         if (axis == getMinorAxis()) {
       
   563             float targetSpan = span - margin;
       
   564             if (targetSpan != minorSpan) {
       
   565                 minorSpan = targetSpan;
       
   566 
       
   567                 // mark all of the ChildState instances as needing to
       
   568                 // resize the child, and queue up work to fix them.
       
   569                 int n = getViewCount();
       
   570                 if (n != 0) {
       
   571                     LayoutQueue q = getLayoutQueue();
       
   572                     for (int i = 0; i < n; i++) {
       
   573                         ChildState cs = getChildState(i);
       
   574                         cs.childSizeValid = false;
       
   575                         q.addTask(cs);
       
   576                     }
       
   577                     q.addTask(flushTask);
       
   578                 }
       
   579             }
       
   580         } else {
       
   581             // along the major axis the value is ignored
       
   582             // unless the estimatedMajorSpan property is
       
   583             // true.
       
   584             if (estimatedMajorSpan) {
       
   585                 majorSpan = span - margin;
       
   586             }
       
   587         }
       
   588     }
       
   589 
       
   590     /**
       
   591      * Render the view using the given allocation and
       
   592      * rendering surface.
       
   593      * <p>
       
   594      * This is implemented to determine whether or not the
       
   595      * desired region to be rendered (i.e. the unclipped
       
   596      * area) is up to date or not.  If up-to-date the children
       
   597      * are rendered.  If not up-to-date, a task to build
       
   598      * the desired area is placed on the layout queue as
       
   599      * a high priority task.  This keeps by event thread
       
   600      * moving by rendering if ready, and postponing until
       
   601      * a later time if not ready (since paint requests
       
   602      * can be rescheduled).
       
   603      *
       
   604      * @param g the rendering surface to use
       
   605      * @param alloc the allocated region to render into
       
   606      * @see View#paint
       
   607      */
       
   608     public void paint(Graphics g, Shape alloc) {
       
   609         synchronized (locator) {
       
   610             locator.setAllocation(alloc);
       
   611             locator.paintChildren(g);
       
   612         }
       
   613     }
       
   614 
       
   615     /**
       
   616      * Determines the preferred span for this view along an
       
   617      * axis.
       
   618      *
       
   619      * @param axis may be either View.X_AXIS or View.Y_AXIS
       
   620      * @return   the span the view would like to be rendered into >= 0.
       
   621      *           Typically the view is told to render into the span
       
   622      *           that is returned, although there is no guarantee.
       
   623      *           The parent may choose to resize or break the view.
       
   624      * @exception IllegalArgumentException for an invalid axis type
       
   625      */
       
   626     public float getPreferredSpan(int axis) {
       
   627         float margin = getInsetSpan(axis);
       
   628         if (axis == this.axis) {
       
   629             return majorSpan + margin;
       
   630         }
       
   631         if (prefRequest != null) {
       
   632             View child = prefRequest.getChildView();
       
   633             return child.getPreferredSpan(axis) + margin;
       
   634         }
       
   635 
       
   636         // nothing is known about the children yet
       
   637         return margin + 30;
       
   638     }
       
   639 
       
   640     /**
       
   641      * Determines the minimum span for this view along an
       
   642      * axis.
       
   643      *
       
   644      * @param axis may be either View.X_AXIS or View.Y_AXIS
       
   645      * @return  the span the view would like to be rendered into >= 0.
       
   646      *           Typically the view is told to render into the span
       
   647      *           that is returned, although there is no guarantee.
       
   648      *           The parent may choose to resize or break the view.
       
   649      * @exception IllegalArgumentException for an invalid axis type
       
   650      */
       
   651     public float getMinimumSpan(int axis) {
       
   652         if (axis == this.axis) {
       
   653             return getPreferredSpan(axis);
       
   654         }
       
   655         if (minRequest != null) {
       
   656             View child = minRequest.getChildView();
       
   657             return child.getMinimumSpan(axis);
       
   658         }
       
   659 
       
   660         // nothing is known about the children yet
       
   661         if (axis == X_AXIS) {
       
   662             return getLeftInset() + getRightInset() + 5;
       
   663         } else {
       
   664             return getTopInset() + getBottomInset() + 5;
       
   665         }
       
   666     }
       
   667 
       
   668     /**
       
   669      * Determines the maximum span for this view along an
       
   670      * axis.
       
   671      *
       
   672      * @param axis may be either View.X_AXIS or View.Y_AXIS
       
   673      * @return   the span the view would like to be rendered into >= 0.
       
   674      *           Typically the view is told to render into the span
       
   675      *           that is returned, although there is no guarantee.
       
   676      *           The parent may choose to resize or break the view.
       
   677      * @exception IllegalArgumentException for an invalid axis type
       
   678      */
       
   679     public float getMaximumSpan(int axis) {
       
   680         if (axis == this.axis) {
       
   681             return getPreferredSpan(axis);
       
   682         }
       
   683         return Integer.MAX_VALUE;
       
   684     }
       
   685 
       
   686 
       
   687     /**
       
   688      * Returns the number of views in this view.  Since
       
   689      * the default is to not be a composite view this
       
   690      * returns 0.
       
   691      *
       
   692      * @return the number of views >= 0
       
   693      * @see View#getViewCount
       
   694      */
       
   695     public int getViewCount() {
       
   696         synchronized(stats) {
       
   697             return stats.size();
       
   698         }
       
   699     }
       
   700 
       
   701     /**
       
   702      * Gets the nth child view.  Since there are no
       
   703      * children by default, this returns null.
       
   704      *
       
   705      * @param n the number of the view to get, >= 0 && < getViewCount()
       
   706      * @return the view
       
   707      */
       
   708     public View getView(int n) {
       
   709         ChildState cs = getChildState(n);
       
   710         if (cs != null) {
       
   711             return cs.getChildView();
       
   712         }
       
   713         return null;
       
   714     }
       
   715 
       
   716     /**
       
   717      * Fetches the allocation for the given child view.
       
   718      * This enables finding out where various views
       
   719      * are located, without assuming the views store
       
   720      * their location.  This returns null since the
       
   721      * default is to not have any child views.
       
   722      *
       
   723      * @param index the index of the child, >= 0 && < getViewCount()
       
   724      * @param a  the allocation to this view.
       
   725      * @return the allocation to the child
       
   726      */
       
   727     public Shape getChildAllocation(int index, Shape a) {
       
   728         Shape ca = locator.getChildAllocation(index, a);
       
   729         return ca;
       
   730     }
       
   731 
       
   732     /**
       
   733      * Returns the child view index representing the given position in
       
   734      * the model.  By default a view has no children so this is implemented
       
   735      * to return -1 to indicate there is no valid child index for any
       
   736      * position.
       
   737      *
       
   738      * @param pos the position >= 0
       
   739      * @return  index of the view representing the given position, or
       
   740      *   -1 if no view represents that position
       
   741      * @since 1.3
       
   742      */
       
   743     public int getViewIndex(int pos, Position.Bias b) {
       
   744         return getViewIndexAtPosition(pos, b);
       
   745     }
       
   746 
       
   747     /**
       
   748      * Provides a mapping from the document model coordinate space
       
   749      * to the coordinate space of the view mapped to it.
       
   750      *
       
   751      * @param pos the position to convert >= 0
       
   752      * @param a the allocated region to render into
       
   753      * @param b the bias toward the previous character or the
       
   754      *  next character represented by the offset, in case the
       
   755      *  position is a boundary of two views.
       
   756      * @return the bounding box of the given position is returned
       
   757      * @exception BadLocationException  if the given position does
       
   758      *   not represent a valid location in the associated document
       
   759      * @exception IllegalArgumentException for an invalid bias argument
       
   760      * @see View#viewToModel
       
   761      */
       
   762     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
       
   763         int index = getViewIndex(pos, b);
       
   764         Shape ca = locator.getChildAllocation(index, a);
       
   765 
       
   766         // forward to the child view, and make sure we don't
       
   767         // interact with the layout thread by synchronizing
       
   768         // on the child state.
       
   769         ChildState cs = getChildState(index);
       
   770         synchronized (cs) {
       
   771             View cv = cs.getChildView();
       
   772             Shape v = cv.modelToView(pos, ca, b);
       
   773             return v;
       
   774         }
       
   775     }
       
   776 
       
   777     /**
       
   778      * Provides a mapping from the view coordinate space to the logical
       
   779      * coordinate space of the model.  The biasReturn argument will be
       
   780      * filled in to indicate that the point given is closer to the next
       
   781      * character in the model or the previous character in the model.
       
   782      * <p>
       
   783      * This is expected to be called by the GUI thread, holding a
       
   784      * read-lock on the associated model.  It is implemented to
       
   785      * locate the child view and determine it's allocation with a
       
   786      * lock on the ChildLocator object, and to call viewToModel
       
   787      * on the child view with a lock on the ChildState object
       
   788      * to avoid interaction with the layout thread.
       
   789      *
       
   790      * @param x the X coordinate >= 0
       
   791      * @param y the Y coordinate >= 0
       
   792      * @param a the allocated region to render into
       
   793      * @return the location within the model that best represents the
       
   794      *  given point in the view >= 0.  The biasReturn argument will be
       
   795      * filled in to indicate that the point given is closer to the next
       
   796      * character in the model or the previous character in the model.
       
   797      */
       
   798     public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
       
   799         int pos;    // return position
       
   800         int index;  // child index to forward to
       
   801         Shape ca;   // child allocation
       
   802 
       
   803         // locate the child view and it's allocation so that
       
   804         // we can forward to it.  Make sure the layout thread
       
   805         // doesn't change anything by trying to flush changes
       
   806         // to the parent while the GUI thread is trying to
       
   807         // find the child and it's allocation.
       
   808         synchronized (locator) {
       
   809             index = locator.getViewIndexAtPoint(x, y, a);
       
   810             ca = locator.getChildAllocation(index, a);
       
   811         }
       
   812 
       
   813         // forward to the child view, and make sure we don't
       
   814         // interact with the layout thread by synchronizing
       
   815         // on the child state.
       
   816         ChildState cs = getChildState(index);
       
   817         synchronized (cs) {
       
   818             View v = cs.getChildView();
       
   819             pos = v.viewToModel(x, y, ca, biasReturn);
       
   820         }
       
   821         return pos;
       
   822     }
       
   823 
       
   824     /**
       
   825      * Provides a way to determine the next visually represented model
       
   826      * location that one might place a caret.  Some views may not be visible,
       
   827      * they might not be in the same order found in the model, or they just
       
   828      * might not allow access to some of the locations in the model.
       
   829      *
       
   830      * @param pos the position to convert >= 0
       
   831      * @param a the allocated region to render into
       
   832      * @param direction the direction from the current position that can
       
   833      *  be thought of as the arrow keys typically found on a keyboard;
       
   834      *  this may be one of the following:
       
   835      *  <ul>
       
   836      *  <code>SwingConstants.WEST</code>
       
   837      *  <code>SwingConstants.EAST</code>
       
   838      *  <code>SwingConstants.NORTH</code>
       
   839      *  <code>SwingConstants.SOUTH</code>
       
   840      *  </ul>
       
   841      * @param biasRet an array contain the bias that was checked
       
   842      * @return the location within the model that best represents the next
       
   843      *  location visual position
       
   844      * @exception BadLocationException
       
   845      * @exception IllegalArgumentException if <code>direction</code> is invalid
       
   846      */
       
   847     public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
       
   848                                          int direction,
       
   849                                          Position.Bias[] biasRet)
       
   850                                                   throws BadLocationException {
       
   851         return Utilities.getNextVisualPositionFrom(
       
   852                             this, pos, b, a, direction, biasRet);
       
   853     }
       
   854 
       
   855     // --- variables -----------------------------------------
       
   856 
       
   857     /**
       
   858      * The major axis against which the children are
       
   859      * tiled.
       
   860      */
       
   861     int axis;
       
   862 
       
   863     /**
       
   864      * The children and their layout statistics.
       
   865      */
       
   866     java.util.List stats;
       
   867 
       
   868     /**
       
   869      * Current span along the major axis.  This
       
   870      * is also the value returned by getMinimumSize,
       
   871      * getPreferredSize, and getMaximumSize along
       
   872      * the major axis.
       
   873      */
       
   874     float majorSpan;
       
   875 
       
   876     /**
       
   877      * Is the span along the major axis estimated?
       
   878      */
       
   879     boolean estimatedMajorSpan;
       
   880 
       
   881     /**
       
   882      * Current span along the minor axis.  This
       
   883      * is what layout was done against (i.e. things
       
   884      * are flexible in this direction).
       
   885      */
       
   886     float minorSpan;
       
   887 
       
   888     /**
       
   889      * Object that manages the offsets of the
       
   890      * children.  All locking for management of
       
   891      * child locations is on this object.
       
   892      */
       
   893     protected ChildLocator locator;
       
   894 
       
   895     float topInset;
       
   896     float bottomInset;
       
   897     float leftInset;
       
   898     float rightInset;
       
   899 
       
   900     ChildState minRequest;
       
   901     ChildState prefRequest;
       
   902     boolean majorChanged;
       
   903     boolean minorChanged;
       
   904     Runnable flushTask;
       
   905 
       
   906     /**
       
   907      * Child that is actively changing size.  This often
       
   908      * causes a preferenceChanged, so this is a cache to
       
   909      * possibly speed up the marking the state.  It also
       
   910      * helps flag an opportunity to avoid adding to flush
       
   911      * task to the layout queue.
       
   912      */
       
   913     ChildState changing;
       
   914 
       
   915     /**
       
   916      * A class to manage the effective position of the
       
   917      * child views in a localized area while changes are
       
   918      * being made around the localized area.  The AsyncBoxView
       
   919      * may be continuously changing, but the visible area
       
   920      * needs to remain fairly stable until the layout thread
       
   921      * decides to publish an update to the parent.
       
   922      * @since 1.3
       
   923      */
       
   924     public class ChildLocator {
       
   925 
       
   926         /**
       
   927          * construct a child locator.
       
   928          */
       
   929         public ChildLocator() {
       
   930             lastAlloc = new Rectangle();
       
   931             childAlloc = new Rectangle();
       
   932         }
       
   933 
       
   934         /**
       
   935          * Notification that a child changed.  This can effect
       
   936          * whether or not new offset calculations are needed.
       
   937          * This is called by a ChildState object that has
       
   938          * changed it's major span.  This can therefore be
       
   939          * called by multiple threads.
       
   940          */
       
   941         public synchronized void childChanged(ChildState cs) {
       
   942             if (lastValidOffset == null) {
       
   943                 lastValidOffset = cs;
       
   944             } else if (cs.getChildView().getStartOffset() <
       
   945                        lastValidOffset.getChildView().getStartOffset()) {
       
   946                 lastValidOffset = cs;
       
   947             }
       
   948         }
       
   949 
       
   950         /**
       
   951          * Paint the children that intersect the clip area.
       
   952          */
       
   953         public synchronized void paintChildren(Graphics g) {
       
   954             Rectangle clip = g.getClipBounds();
       
   955             float targetOffset = (axis == X_AXIS) ?
       
   956                 clip.x - lastAlloc.x : clip.y - lastAlloc.y;
       
   957             int index = getViewIndexAtVisualOffset(targetOffset);
       
   958             int n = getViewCount();
       
   959             float offs = getChildState(index).getMajorOffset();
       
   960             for (int i = index; i < n; i++) {
       
   961                 ChildState cs = getChildState(i);
       
   962                 cs.setMajorOffset(offs);
       
   963                 Shape ca = getChildAllocation(i);
       
   964                 if (intersectsClip(ca, clip)) {
       
   965                     synchronized (cs) {
       
   966                         View v = cs.getChildView();
       
   967                         v.paint(g, ca);
       
   968                     }
       
   969                 } else {
       
   970                     // done painting intersection
       
   971                     break;
       
   972                 }
       
   973                 offs += cs.getMajorSpan();
       
   974             }
       
   975         }
       
   976 
       
   977         /**
       
   978          * Fetch the allocation to use for a child view.
       
   979          * This will update the offsets for all children
       
   980          * not yet updated before the given index.
       
   981          */
       
   982         public synchronized Shape getChildAllocation(int index, Shape a) {
       
   983             if (a == null) {
       
   984                 return null;
       
   985             }
       
   986             setAllocation(a);
       
   987             ChildState cs = getChildState(index);
       
   988             if (lastValidOffset == null) {
       
   989                 lastValidOffset = getChildState(0);
       
   990             }
       
   991             if (cs.getChildView().getStartOffset() >
       
   992                 lastValidOffset.getChildView().getStartOffset()) {
       
   993                 // offsets need to be updated
       
   994                 updateChildOffsetsToIndex(index);
       
   995             }
       
   996             Shape ca = getChildAllocation(index);
       
   997             return ca;
       
   998         }
       
   999 
       
  1000         /**
       
  1001          * Fetches the child view index at the given point.
       
  1002          * This is called by the various View methods that
       
  1003          * need to calculate which child to forward a message
       
  1004          * to.  This should be called by a block synchronized
       
  1005          * on this object, and would typically be followed
       
  1006          * with one or more calls to getChildAllocation that
       
  1007          * should also be in the synchronized block.
       
  1008          *
       
  1009          * @param x the X coordinate >= 0
       
  1010          * @param y the Y coordinate >= 0
       
  1011          * @param a the allocation to the View
       
  1012          * @return the nearest child index
       
  1013          */
       
  1014         public int getViewIndexAtPoint(float x, float y, Shape a) {
       
  1015             setAllocation(a);
       
  1016             float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y;
       
  1017             int index = getViewIndexAtVisualOffset(targetOffset);
       
  1018             return index;
       
  1019         }
       
  1020 
       
  1021         /**
       
  1022          * Fetch the allocation to use for a child view.
       
  1023          * <em>This does not update the offsets in the ChildState
       
  1024          * records.</em>
       
  1025          */
       
  1026         protected Shape getChildAllocation(int index) {
       
  1027             ChildState cs = getChildState(index);
       
  1028             if (! cs.isLayoutValid()) {
       
  1029                 cs.run();
       
  1030             }
       
  1031             if (axis == X_AXIS) {
       
  1032                 childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
       
  1033                 childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
       
  1034                 childAlloc.width = (int) cs.getMajorSpan();
       
  1035                 childAlloc.height = (int) cs.getMinorSpan();
       
  1036             } else {
       
  1037                 childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
       
  1038                 childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
       
  1039                 childAlloc.height = (int) cs.getMajorSpan();
       
  1040                 childAlloc.width = (int) cs.getMinorSpan();
       
  1041             }
       
  1042             childAlloc.x += (int)getLeftInset();
       
  1043             childAlloc.y += (int)getRightInset();
       
  1044             return childAlloc;
       
  1045         }
       
  1046 
       
  1047         /**
       
  1048          * Copy the currently allocated shape into the Rectangle
       
  1049          * used to store the current allocation.  This would be
       
  1050          * a floating point rectangle in a Java2D-specific implmentation.
       
  1051          */
       
  1052         protected void setAllocation(Shape a) {
       
  1053             if (a instanceof Rectangle) {
       
  1054                 lastAlloc.setBounds((Rectangle) a);
       
  1055             } else {
       
  1056                 lastAlloc.setBounds(a.getBounds());
       
  1057             }
       
  1058             setSize(lastAlloc.width, lastAlloc.height);
       
  1059         }
       
  1060 
       
  1061         /**
       
  1062          * Locate the view responsible for an offset into the box
       
  1063          * along the major axis.  Make sure that offsets are set
       
  1064          * on the ChildState objects up to the given target span
       
  1065          * past the desired offset.
       
  1066          *
       
  1067          * @return   index of the view representing the given visual
       
  1068          *   location (targetOffset), or -1 if no view represents
       
  1069          *   that location
       
  1070          */
       
  1071         protected int getViewIndexAtVisualOffset(float targetOffset) {
       
  1072             int n = getViewCount();
       
  1073             if (n > 0) {
       
  1074                 boolean lastValid = (lastValidOffset != null);
       
  1075 
       
  1076                 if (lastValidOffset == null) {
       
  1077                     lastValidOffset = getChildState(0);
       
  1078                 }
       
  1079                 if (targetOffset > majorSpan) {
       
  1080                     // should only get here on the first time display.
       
  1081                     if (!lastValid) {
       
  1082                         return 0;
       
  1083                     }
       
  1084                     int pos = lastValidOffset.getChildView().getStartOffset();
       
  1085                     int index = getViewIndex(pos, Position.Bias.Forward);
       
  1086                     return index;
       
  1087                 } else if (targetOffset > lastValidOffset.getMajorOffset()) {
       
  1088                     // roll offset calculations forward
       
  1089                     return updateChildOffsets(targetOffset);
       
  1090                 } else {
       
  1091                     // no changes prior to the needed offset
       
  1092                     // this should be a binary search
       
  1093                     float offs = 0f;
       
  1094                     for (int i = 0; i < n; i++) {
       
  1095                         ChildState cs = getChildState(i);
       
  1096                         float nextOffs = offs + cs.getMajorSpan();
       
  1097                         if (targetOffset < nextOffs) {
       
  1098                             return i;
       
  1099                         }
       
  1100                         offs = nextOffs;
       
  1101                     }
       
  1102                 }
       
  1103             }
       
  1104             return n - 1;
       
  1105         }
       
  1106 
       
  1107         /**
       
  1108          * Move the location of the last offset calculation forward
       
  1109          * to the desired offset.
       
  1110          */
       
  1111         int updateChildOffsets(float targetOffset) {
       
  1112             int n = getViewCount();
       
  1113             int targetIndex = n - 1;;
       
  1114             int pos = lastValidOffset.getChildView().getStartOffset();
       
  1115             int startIndex = getViewIndex(pos, Position.Bias.Forward);
       
  1116             float start = lastValidOffset.getMajorOffset();
       
  1117             float lastOffset = start;
       
  1118             for (int i = startIndex; i < n; i++) {
       
  1119                 ChildState cs = getChildState(i);
       
  1120                 cs.setMajorOffset(lastOffset);
       
  1121                 lastOffset += cs.getMajorSpan();
       
  1122                 if (targetOffset < lastOffset) {
       
  1123                     targetIndex = i;
       
  1124                     lastValidOffset = cs;
       
  1125                     break;
       
  1126                 }
       
  1127             }
       
  1128 
       
  1129             return targetIndex;
       
  1130         }
       
  1131 
       
  1132         /**
       
  1133          * Move the location of the last offset calculation forward
       
  1134          * to the desired index.
       
  1135          */
       
  1136         void updateChildOffsetsToIndex(int index) {
       
  1137             int pos = lastValidOffset.getChildView().getStartOffset();
       
  1138             int startIndex = getViewIndex(pos, Position.Bias.Forward);
       
  1139             float lastOffset = lastValidOffset.getMajorOffset();
       
  1140             for (int i = startIndex; i <= index; i++) {
       
  1141                 ChildState cs = getChildState(i);
       
  1142                 cs.setMajorOffset(lastOffset);
       
  1143                 lastOffset += cs.getMajorSpan();
       
  1144             }
       
  1145         }
       
  1146 
       
  1147         boolean intersectsClip(Shape childAlloc, Rectangle clip) {
       
  1148             Rectangle cs = (childAlloc instanceof Rectangle) ?
       
  1149                 (Rectangle) childAlloc : childAlloc.getBounds();
       
  1150             if (cs.intersects(clip)) {
       
  1151                 // Make sure that lastAlloc also contains childAlloc,
       
  1152                 // this will be false if haven't yet flushed changes.
       
  1153                 return lastAlloc.intersects(cs);
       
  1154             }
       
  1155             return false;
       
  1156         }
       
  1157 
       
  1158         /**
       
  1159          * The location of the last offset calculation
       
  1160          * that is valid.
       
  1161          */
       
  1162         protected ChildState lastValidOffset;
       
  1163 
       
  1164         /**
       
  1165          * The last seen allocation (for repainting when changes
       
  1166          * are flushed upward).
       
  1167          */
       
  1168         protected Rectangle lastAlloc;
       
  1169 
       
  1170         /**
       
  1171          * A shape to use for the child allocation to avoid
       
  1172          * creating a lot of garbage.
       
  1173          */
       
  1174         protected Rectangle childAlloc;
       
  1175     }
       
  1176 
       
  1177     /**
       
  1178      * A record representing the layout state of a
       
  1179      * child view.  It is runnable as a task on another
       
  1180      * thread.  All access to the child view that is
       
  1181      * based upon a read-lock on the model should synchronize
       
  1182      * on this object (i.e. The layout thread and the GUI
       
  1183      * thread can both have a read lock on the model at the
       
  1184      * same time and are not protected from each other).
       
  1185      * Access to a child view hierarchy is serialized via
       
  1186      * synchronization on the ChildState instance.
       
  1187      * @since 1.3
       
  1188      */
       
  1189     public class ChildState implements Runnable {
       
  1190 
       
  1191         /**
       
  1192          * Construct a child status.  This needs to start
       
  1193          * out as fairly large so we don't falsely begin with
       
  1194          * the idea that all of the children are visible.
       
  1195          * @since 1.4
       
  1196          */
       
  1197         public ChildState(View v) {
       
  1198             child = v;
       
  1199             minorValid = false;
       
  1200             majorValid = false;
       
  1201             childSizeValid = false;
       
  1202             child.setParent(AsyncBoxView.this);
       
  1203         }
       
  1204 
       
  1205         /**
       
  1206          * Fetch the child view this record represents
       
  1207          */
       
  1208         public View getChildView() {
       
  1209             return child;
       
  1210         }
       
  1211 
       
  1212         /**
       
  1213          * Update the child state.  This should be
       
  1214          * called by the thread that desires to spend
       
  1215          * time updating the child state (intended to
       
  1216          * be the layout thread).
       
  1217          * <p>
       
  1218          * This aquires a read lock on the associated
       
  1219          * document for the duration of the update to
       
  1220          * ensure the model is not changed while it is
       
  1221          * operating.  The first thing to do would be
       
  1222          * to see if any work actually needs to be done.
       
  1223          * The following could have conceivably happened
       
  1224          * while the state was waiting to be updated:
       
  1225          * <ol>
       
  1226          * <li>The child may have been removed from the
       
  1227          * view hierarchy.
       
  1228          * <li>The child may have been updated by a
       
  1229          * higher priority operation (i.e. the child
       
  1230          * may have become visible).
       
  1231          * </ol>
       
  1232          */
       
  1233         public void run () {
       
  1234             AbstractDocument doc = (AbstractDocument) getDocument();
       
  1235             try {
       
  1236                 doc.readLock();
       
  1237                 if (minorValid && majorValid && childSizeValid) {
       
  1238                     // nothing to do
       
  1239                     return;
       
  1240                 }
       
  1241                 if (child.getParent() == AsyncBoxView.this) {
       
  1242                     // this may overwrite anothers threads cached
       
  1243                     // value for actively changing... but that just
       
  1244                     // means it won't use the cache if there is an
       
  1245                     // overwrite.
       
  1246                     synchronized(AsyncBoxView.this) {
       
  1247                         changing = this;
       
  1248                     }
       
  1249                     updateChild();
       
  1250                     synchronized(AsyncBoxView.this) {
       
  1251                         changing = null;
       
  1252                     }
       
  1253 
       
  1254                     // setting the child size on the minor axis
       
  1255                     // may have caused it to change it's preference
       
  1256                     // along the major axis.
       
  1257                     updateChild();
       
  1258                 }
       
  1259             } finally {
       
  1260                 doc.readUnlock();
       
  1261             }
       
  1262         }
       
  1263 
       
  1264         void updateChild() {
       
  1265             boolean minorUpdated = false;
       
  1266             synchronized(this) {
       
  1267                 if (! minorValid) {
       
  1268                     int minorAxis = getMinorAxis();
       
  1269                     min = child.getMinimumSpan(minorAxis);
       
  1270                     pref = child.getPreferredSpan(minorAxis);
       
  1271                     max = child.getMaximumSpan(minorAxis);
       
  1272                     minorValid = true;
       
  1273                     minorUpdated = true;
       
  1274                 }
       
  1275             }
       
  1276             if (minorUpdated) {
       
  1277                 minorRequirementChange(this);
       
  1278             }
       
  1279 
       
  1280             boolean majorUpdated = false;
       
  1281             float delta = 0.0f;
       
  1282             synchronized(this) {
       
  1283                 if (! majorValid) {
       
  1284                     float old = span;
       
  1285                     span = child.getPreferredSpan(axis);
       
  1286                     delta = span - old;
       
  1287                     majorValid = true;
       
  1288                     majorUpdated = true;
       
  1289                 }
       
  1290             }
       
  1291             if (majorUpdated) {
       
  1292                 majorRequirementChange(this, delta);
       
  1293                 locator.childChanged(this);
       
  1294             }
       
  1295 
       
  1296             synchronized(this) {
       
  1297                 if (! childSizeValid) {
       
  1298                     float w;
       
  1299                     float h;
       
  1300                     if (axis == X_AXIS) {
       
  1301                         w = span;
       
  1302                         h = getMinorSpan();
       
  1303                     } else {
       
  1304                         w = getMinorSpan();
       
  1305                         h = span;
       
  1306                     }
       
  1307                     childSizeValid = true;
       
  1308                     child.setSize(w, h);
       
  1309                 }
       
  1310             }
       
  1311 
       
  1312         }
       
  1313 
       
  1314         /**
       
  1315          * What is the span along the minor axis.
       
  1316          */
       
  1317         public float getMinorSpan() {
       
  1318             if (max < minorSpan) {
       
  1319                 return max;
       
  1320             }
       
  1321             // make it the target width, or as small as it can get.
       
  1322             return Math.max(min, minorSpan);
       
  1323         }
       
  1324 
       
  1325         /**
       
  1326          * What is the offset along the minor axis
       
  1327          */
       
  1328         public float getMinorOffset() {
       
  1329             if (max < minorSpan) {
       
  1330                 // can't make the child this wide, align it
       
  1331                 float align = child.getAlignment(getMinorAxis());
       
  1332                 return ((minorSpan - max) * align);
       
  1333             }
       
  1334             return 0f;
       
  1335         }
       
  1336 
       
  1337         /**
       
  1338          * What is the span along the major axis.
       
  1339          */
       
  1340         public float getMajorSpan() {
       
  1341             return span;
       
  1342         }
       
  1343 
       
  1344         /**
       
  1345          * Get the offset along the major axis
       
  1346          */
       
  1347         public float getMajorOffset() {
       
  1348             return offset;
       
  1349         }
       
  1350 
       
  1351         /**
       
  1352          * This method should only be called by the ChildLocator,
       
  1353          * it is simply a convenient place to hold the cached
       
  1354          * location.
       
  1355          */
       
  1356         public void setMajorOffset(float offs) {
       
  1357             offset = offs;
       
  1358         }
       
  1359 
       
  1360         /**
       
  1361          * Mark preferences changed for this child.
       
  1362          *
       
  1363          * @param width true if the width preference has changed
       
  1364          * @param height true if the height preference has changed
       
  1365          * @see javax.swing.JComponent#revalidate
       
  1366          */
       
  1367         public void preferenceChanged(boolean width, boolean height) {
       
  1368             if (axis == X_AXIS) {
       
  1369                 if (width) {
       
  1370                     majorValid = false;
       
  1371                 }
       
  1372                 if (height) {
       
  1373                     minorValid = false;
       
  1374                 }
       
  1375             } else {
       
  1376                 if (width) {
       
  1377                     minorValid = false;
       
  1378                 }
       
  1379                 if (height) {
       
  1380                     majorValid = false;
       
  1381                 }
       
  1382             }
       
  1383             childSizeValid = false;
       
  1384         }
       
  1385 
       
  1386         /**
       
  1387          * Has the child view been laid out.
       
  1388          */
       
  1389         public boolean isLayoutValid() {
       
  1390             return (minorValid && majorValid && childSizeValid);
       
  1391         }
       
  1392 
       
  1393         // minor axis
       
  1394         private float min;
       
  1395         private float pref;
       
  1396         private float max;
       
  1397         private float align;
       
  1398         private boolean minorValid;
       
  1399 
       
  1400         // major axis
       
  1401         private float span;
       
  1402         private float offset;
       
  1403         private boolean majorValid;
       
  1404 
       
  1405         private View child;
       
  1406         private boolean childSizeValid;
       
  1407     }
       
  1408 
       
  1409     /**
       
  1410      * Task to flush requirement changes upward
       
  1411      */
       
  1412     class FlushTask implements Runnable {
       
  1413 
       
  1414         public void run() {
       
  1415             flushRequirementChanges();
       
  1416         }
       
  1417 
       
  1418     }
       
  1419 
       
  1420 }