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