KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > AsyncBoxView


1 /*
2  * @(#)AsyncBoxView.java 1.16 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.util.*;
10 import java.awt.*;
11 import javax.swing.SwingUtilities JavaDoc;
12 import javax.swing.event.DocumentEvent JavaDoc;
13
14 /**
15  * A box that does layout asynchronously. This
16  * is useful to keep the GUI event thread moving by
17  * not doing any layout on it. The layout is done
18  * on a granularity of operations on the child views.
19  * After each child view is accessed for some part
20  * of layout (a potentially time consuming operation)
21  * the remaining tasks can be abandoned or a new higher
22  * priority task (i.e. to service a synchronous request
23  * or a visible area) can be taken on.
24  * <p>
25  * While the child view is being accessed
26  * a read lock is aquired on the associated document
27  * so that the model is stable while being accessed.
28  *
29  * @author Timothy Prinzing
30  * @version 1.16 12/19/03
31  * @since 1.3
32  */

33 public class AsyncBoxView extends View JavaDoc {
34
35     /**
36      * Construct a box view that does asynchronous layout.
37      *
38      * @param elem the element of the model to represent
39      * @param axis the axis to tile along. This can be
40      * either X_AXIS or Y_AXIS.
41      */

42     public AsyncBoxView(Element JavaDoc elem, int axis) {
43     super(elem);
44     stats = new ArrayList();
45     this.axis = axis;
46     locator = new ChildLocator();
47     flushTask = new FlushTask();
48     minorSpan = Short.MAX_VALUE;
49     estimatedMajorSpan = false;
50     }
51
52     /**
53      * Fetch the major axis (the axis the children
54      * are tiled along). This will have a value of
55      * either X_AXIS or Y_AXIS.
56      */

57     public int getMajorAxis() {
58     return axis;
59     }
60
61     /**
62      * Fetch the minor axis (the axis orthoginal
63      * to the tiled axis). This will have a value of
64      * either X_AXIS or Y_AXIS.
65      */

66     public int getMinorAxis() {
67     return (axis == X_AXIS) ? Y_AXIS : X_AXIS;
68     }
69
70     /**
71      * Get the top part of the margin around the view.
72      */

73     public float getTopInset() {
74     return topInset;
75     }
76
77     /**
78      * Set the top part of the margin around the view.
79      *
80      * @param i the value of the inset
81      */

82     public void setTopInset(float i) {
83     topInset = i;
84     }
85
86     /**
87      * Get the bottom part of the margin around the view.
88      */

89     public float getBottomInset() {
90     return bottomInset;
91     }
92
93     /**
94      * Set the bottom part of the margin around the view.
95      *
96      * @param i the value of the inset
97      */

98     public void setBottomInset(float i) {
99     bottomInset = i;
100     }
101     
102     /**
103      * Get the left part of the margin around the view.
104      */

105     public float getLeftInset() {
106     return leftInset;
107     }
108
109     /**
110      * Set the left part of the margin around the view.
111      *
112      * @param i the value of the inset
113      */

114     public void setLeftInset(float i) {
115     leftInset = i;
116     }
117
118     /**
119      * Get the right part of the margin around the view.
120      */

121     public float getRightInset() {
122     return rightInset;
123     }
124
125     /**
126      * Set the right part of the margin around the view.
127      *
128      * @param i the value of the inset
129      */

130     public void setRightInset(float i) {
131     rightInset = i;
132     }
133     
134     /**
135      * Fetch the span along an axis that is taken up by the insets.
136      *
137      * @param axis the axis to determine the total insets along,
138      * either X_AXIS or Y_AXIS.
139      * @since 1.4
140      */

141     protected float getInsetSpan(int axis) {
142     float margin = (axis == X_AXIS) ?
143         getLeftInset() + getRightInset() : getTopInset() + getBottomInset();
144     return margin;
145     }
146
147     /**
148      * Set the estimatedMajorSpan property that determines if the
149      * major span should be treated as being estimated. If this
150      * property is true, the value of setSize along the major axis
151      * will change the requirements along the major axis and incremental
152      * changes will be ignored until all of the children have been updated
153      * (which will cause the property to automatically be set to false).
154      * If the property is false the value of the majorSpan will be
155      * considered to be accurate and incremental changes will be
156      * added into the total as they are calculated.
157      *
158      * @since 1.4
159      */

160     protected void setEstimatedMajorSpan(boolean isEstimated) {
161     estimatedMajorSpan = isEstimated;
162     }
163
164     /**
165      * Is the major span currently estimated?
166      *
167      * @since 1.4
168      */

169     protected boolean getEstimatedMajorSpan() {
170     return estimatedMajorSpan;
171     }
172
173     /**
174      * Fetch the object representing the layout state of
175      * of the child at the given index.
176      *
177      * @param index the child index. This should be a
178      * value >= 0 and < getViewCount().
179      */

180     protected ChildState getChildState(int index) {
181     synchronized(stats) {
182         if ((index >= 0) && (index < stats.size())) {
183         return (ChildState) stats.get(index);
184         }
185         return null;
186     }
187     }
188
189     /**
190      * Fetch the queue to use for layout.
191      */

192     protected LayoutQueue JavaDoc getLayoutQueue() {
193     return LayoutQueue.getDefaultQueue();
194     }
195
196     /**
197      * New ChildState records are created through
198      * this method to allow subclasses the extend
199      * the ChildState records to do/hold more
200      */

201     protected ChildState createChildState(View JavaDoc v) {
202     return new ChildState(v);
203     }
204
205     /**
206      * Requirements changed along the major axis.
207      * This is called by the thread doing layout for
208      * the given ChildState object when it has completed
209      * fetching the child views new preferences.
210      * Typically this would be the layout thread, but
211      * might be the event thread if it is trying to update
212      * something immediately (such as to perform a
213      * model/view translation).
214      * <p>
215      * This is implemented to mark the major axis as having
216      * changed so that a future check to see if the requirements
217      * need to be published to the parent view will consider
218      * the major axis. If the span along the major axis is
219      * not estimated, it is updated by the given delta to reflect
220      * the incremental change. The delta is ignored if the
221      * major span is estimated.
222      */

223     protected synchronized void majorRequirementChange(ChildState cs, float delta) {
224     if (estimatedMajorSpan == false) {
225         majorSpan += delta;
226     }
227     majorChanged = true;
228     }
229
230     /**
231      * Requirements changed along the minor axis.
232      * This is called by the thread doing layout for
233      * the given ChildState object when it has completed
234      * fetching the child views new preferences.
235      * Typically this would be the layout thread, but
236      * might be the GUI thread if it is trying to update
237      * something immediately (such as to perform a
238      * model/view translation).
239      */

240     protected synchronized void minorRequirementChange(ChildState cs) {
241     minorChanged = true;
242     }
243
244     /**
245      * Publish the changes in preferences upward to the parent
246      * view. This is normally called by the layout thread.
247      */

248     protected void flushRequirementChanges() {
249     AbstractDocument JavaDoc doc = (AbstractDocument JavaDoc) getDocument();
250     try {
251         doc.readLock();
252
253         View JavaDoc parent = null;
254         boolean horizontal = false;
255         boolean vertical = false;
256
257         synchronized(this) {
258         // perform tasks that iterate over the children while
259
// preventing the collection from changing.
260
synchronized(stats) {
261             int n = getViewCount();
262             if ((n > 0) && (minorChanged || estimatedMajorSpan)) {
263             LayoutQueue JavaDoc q = getLayoutQueue();
264             ChildState min = getChildState(0);
265             ChildState pref = getChildState(0);
266             float span = 0f;
267             for (int i = 1; i < n; i++) {
268                 ChildState cs = getChildState(i);
269                 if (minorChanged) {
270                 if (cs.min > min.min) {
271                     min = cs;
272                 }
273                 if (cs.pref > pref.pref) {
274                     pref = cs;
275                 }
276                 }
277                 if (estimatedMajorSpan) {
278                 span += cs.getMajorSpan();
279                 }
280             }
281
282             if (minorChanged) {
283                 minRequest = min;
284                 prefRequest = pref;
285             }
286             if (estimatedMajorSpan) {
287                 majorSpan = span;
288                 estimatedMajorSpan = false;
289                 majorChanged = true;
290             }
291             }
292         }
293
294         // message preferenceChanged
295
if (majorChanged || minorChanged) {
296             parent = getParent();
297             if (parent != null) {
298             if (axis == X_AXIS) {
299                 horizontal = majorChanged;
300                 vertical = minorChanged;
301             } else {
302                 vertical = majorChanged;
303                 horizontal = minorChanged;
304             }
305             }
306             majorChanged = false;
307             minorChanged = false;
308         }
309         }
310
311         // propagate a preferenceChanged, using the
312
// layout thread.
313
if (parent != null) {
314         parent.preferenceChanged(this, horizontal, vertical);
315             
316         // probably want to change this to be more exact.
317
Component c = getContainer();
318         if (c != null) {
319             c.repaint();
320         }
321         }
322     } finally {
323         doc.readUnlock();
324     }
325     }
326
327     /**
328      * Calls the superclass to update the child views, and
329      * updates the status records for the children. This
330      * is expected to be called while a write lock is held
331      * on the model so that interaction with the layout
332      * thread will not happen (i.e. the layout thread
333      * acquires a read lock before doing anything).
334      *
335      * @param offset the starting offset into the child views >= 0
336      * @param length the number of existing views to replace >= 0
337      * @param views the child views to insert
338      */

339     public void replace(int offset, int length, View JavaDoc[] views) {
340     synchronized(stats) {
341         // remove the replaced state records
342
for (int i = 0; i < length; i++) {
343                 ChildState cs = (ChildState)stats.remove(offset);
344                 float csSpan = cs.getMajorSpan();
345
346                 cs.getChildView().setParent(null);
347                 if (csSpan != 0) {
348                     majorRequirementChange(cs, -csSpan);
349                 }
350         }
351
352         // insert the state records for the new children
353
LayoutQueue JavaDoc q = getLayoutQueue();
354         if (views != null) {
355         for (int i = 0; i < views.length; i++) {
356             ChildState s = createChildState(views[i]);
357             stats.add(offset + i, s);
358             q.addTask(s);
359         }
360         }
361
362         // notify that the size changed
363
q.addTask(flushTask);
364     }
365     }
366
367     /**
368      * Loads all of the children to initialize the view.
369      * This is called by the <a HREF="#setParent">setParent</a>
370      * method. Subclasses can reimplement this to initialize
371      * their child views in a different manner. The default
372      * implementation creates a child view for each
373      * child element.
374      * <p>
375      * Normally a write-lock is held on the Document while
376      * the children are being changed, which keeps the rendering
377      * and layout threads safe. The exception to this is when
378      * the view is initialized to represent an existing element
379      * (via this method), so it is synchronized to exclude
380      * preferenceChanged while we are initializing.
381      *
382      * @param f the view factory
383      * @see #setParent
384      */

385     protected void loadChildren(ViewFactory JavaDoc f) {
386     Element JavaDoc e = getElement();
387     int n = e.getElementCount();
388     if (n > 0) {
389         View JavaDoc[] added = new View JavaDoc[n];
390         for (int i = 0; i < n; i++) {
391         added[i] = f.create(e.getElement(i));
392         }
393         replace(0, 0, added);
394     }
395     }
396
397     /**
398      * Fetches the child view index representing the given position in
399      * the model. This is implemented to fetch the view in the case
400      * where there is a child view for each child element.
401      *
402      * @param pos the position >= 0
403      * @return index of the view representing the given position, or
404      * -1 if no view represents that position
405      */

406     protected synchronized int getViewIndexAtPosition(int pos, Position.Bias JavaDoc b) {
407     boolean isBackward = (b == Position.Bias.Backward);
408     pos = (isBackward) ? Math.max(0, pos - 1) : pos;
409     Element JavaDoc elem = getElement();
410     return elem.getElementIndex(pos);
411     }
412
413     /**
414      * Update the layout in response to receiving notification of
415      * change from the model. This is implemented to note the
416      * change on the ChildLocator so that offsets of the children
417      * will be correctly computed.
418      *
419      * @param ec changes to the element this view is responsible
420      * for (may be null if there were no changes).
421      * @param e the change information from the associated document
422      * @param a the current allocation of the view
423      * @see #insertUpdate
424      * @see #removeUpdate
425      * @see #changedUpdate
426      */

427     protected void updateLayout(DocumentEvent.ElementChange JavaDoc ec,
428                     DocumentEvent JavaDoc e, Shape a) {
429     if (ec != null) {
430         // the newly inserted children don't have a valid
431
// offset so the child locator needs to be messaged
432
// that the child prior to the new children has
433
// changed size.
434
int index = Math.max(ec.getIndex() - 1, 0);
435         ChildState cs = getChildState(index);
436         locator.childChanged(cs);
437     }
438     }
439
440     // --- View methods ------------------------------------
441

442     /**
443      * Sets the parent of the view.
444      * This is reimplemented to provide the superclass
445      * behavior as well as calling the <code>loadChildren</code>
446      * method if this view does not already have children.
447      * The children should not be loaded in the
448      * constructor because the act of setting the parent
449      * may cause them to try to search up the hierarchy
450      * (to get the hosting Container for example).
451      * If this view has children (the view is being moved
452      * from one place in the view hierarchy to another),
453      * the <code>loadChildren</code> method will not be called.
454      *
455      * @param parent the parent of the view, null if none
456      */

457     public void setParent(View JavaDoc parent) {
458     super.setParent(parent);
459     if ((parent != null) && (getViewCount() == 0)) {
460         ViewFactory JavaDoc f = getViewFactory();
461         loadChildren(f);
462     }
463     }
464
465     /**
466      * Child views can call this on the parent to indicate that
467      * the preference has changed and should be reconsidered
468      * for layout. This is reimplemented to queue new work
469      * on the layout thread. This method gets messaged from
470      * multiple threads via the children.
471      *
472      * @param child the child view
473      * @param width true if the width preference has changed
474      * @param height true if the height preference has changed
475      * @see javax.swing.JComponent#revalidate
476      */

477     public synchronized void preferenceChanged(View JavaDoc child, boolean width, boolean height) {
478     if (child == null) {
479         getParent().preferenceChanged(this, width, height);
480     } else {
481         if (changing != null) {
482         View JavaDoc cv = changing.getChildView();
483         if (cv == child) {
484             // size was being changed on the child, no need to
485
// queue work for it.
486
changing.preferenceChanged(width, height);
487             return;
488         }
489         }
490         int index = getViewIndex(child.getStartOffset(),
491                      Position.Bias.Forward);
492         ChildState cs = getChildState(index);
493         cs.preferenceChanged(width, height);
494         LayoutQueue JavaDoc q = getLayoutQueue();
495         q.addTask(cs);
496         q.addTask(flushTask);
497     }
498     }
499
500     /**
501      * Sets the size of the view. This should cause
502      * layout of the view if the view caches any layout
503      * information.
504      * <p>
505      * Since the major axis is updated asynchronously and should be
506      * the sum of the tiled children the call is ignored for the major
507      * axis. Since the minor axis is flexible, work is queued to resize
508      * the children if the minor span changes.
509      *
510      * @param width the width >= 0
511      * @param height the height >= 0
512      */

513     public void setSize(float width, float height) {
514     setSpanOnAxis(X_AXIS, width);
515     setSpanOnAxis(Y_AXIS, height);
516     }
517
518     /**
519      * Retrieves the size of the view along an axis.
520      *
521      * @param axis may be either <code>View.X_AXIS</code> or
522      * <code>View.Y_AXIS</code>
523      * @return the current span of the view along the given axis, >= 0
524      */

525     float getSpanOnAxis(int axis) {
526     if (axis == getMajorAxis()) {
527         return majorSpan;
528     }
529     return minorSpan;
530     }
531
532     /**
533      * Sets the size of the view along an axis. Since the major
534      * axis is updated asynchronously and should be the sum of the
535      * tiled children the call is ignored for the major axis. Since
536      * the minor axis is flexible, work is queued to resize the
537      * children if the minor span changes.
538      *
539      * @param axis may be either <code>View.X_AXIS</code> or
540      * <code>View.Y_AXIS</code>
541      * @param span the span to layout to >= 0
542      */

543     void setSpanOnAxis(int axis, float span) {
544     float margin = getInsetSpan(axis);
545     if (axis == getMinorAxis()) {
546         float targetSpan = span - margin;
547         if (targetSpan != minorSpan) {
548         minorSpan = targetSpan;
549
550         // mark all of the ChildState instances as needing to
551
// resize the child, and queue up work to fix them.
552
int n = getViewCount();
553         if (n != 0) {
554             LayoutQueue JavaDoc q = getLayoutQueue();
555             for (int i = 0; i < n; i++) {
556             ChildState cs = getChildState(i);
557             cs.childSizeValid = false;
558             q.addTask(cs);
559             }
560             q.addTask(flushTask);
561         }
562         }
563     } else {
564         // along the major axis the value is ignored
565
// unless the estimatedMajorSpan property is
566
// true.
567
if (estimatedMajorSpan) {
568         majorSpan = span - margin;
569         }
570     }
571     }
572
573     /**
574      * Render the view using the given allocation and
575      * rendering surface.
576      * <p>
577      * This is implemented to determine whether or not the
578      * desired region to be rendered (i.e. the unclipped
579      * area) is up to date or not. If up-to-date the children
580      * are rendered. If not up-to-date, a task to build
581      * the desired area is placed on the layout queue as
582      * a high priority task. This keeps by event thread
583      * moving by rendering if ready, and postponing until
584      * a later time if not ready (since paint requests
585      * can be rescheduled).
586      *
587      * @param g the rendering surface to use
588      * @param alloc the allocated region to render into
589      * @see View#paint
590      */

591     public void paint(Graphics g, Shape alloc) {
592     synchronized (locator) {
593         locator.setAllocation(alloc);
594         locator.paintChildren(g);
595     }
596     }
597
598     /**
599      * Determines the preferred span for this view along an
600      * axis.
601      *
602      * @param axis may be either View.X_AXIS or View.Y_AXIS
603      * @return the span the view would like to be rendered into >= 0.
604      * Typically the view is told to render into the span
605      * that is returned, although there is no guarantee.
606      * The parent may choose to resize or break the view.
607      * @exception IllegalArgumentException for an invalid axis type
608      */

609     public float getPreferredSpan(int axis) {
610     float margin = getInsetSpan(axis);
611     if (axis == this.axis) {
612         return majorSpan + margin;
613     }
614     if (prefRequest != null) {
615         View JavaDoc child = prefRequest.getChildView();
616         return child.getPreferredSpan(axis) + margin;
617     }
618
619     // nothing is known about the children yet
620
return margin + 30;
621     }
622
623     /**
624      * Determines the minimum span for this view along an
625      * axis.
626      *
627      * @param axis may be either View.X_AXIS or View.Y_AXIS
628      * @return the span the view would like to be rendered into >= 0.
629      * Typically the view is told to render into the span
630      * that is returned, although there is no guarantee.
631      * The parent may choose to resize or break the view.
632      * @exception IllegalArgumentException for an invalid axis type
633      */

634     public float getMinimumSpan(int axis) {
635     if (axis == this.axis) {
636         return getPreferredSpan(axis);
637     }
638     if (minRequest != null) {
639         View JavaDoc child = minRequest.getChildView();
640         return child.getMinimumSpan(axis);
641     }
642
643     // nothing is known about the children yet
644
if (axis == X_AXIS) {
645         return getLeftInset() + getRightInset() + 5;
646     } else {
647         return getTopInset() + getBottomInset() + 5;
648     }
649     }
650
651     /**
652      * Determines the maximum span for this view along an
653      * axis.
654      *
655      * @param axis may be either View.X_AXIS or View.Y_AXIS
656      * @return the span the view would like to be rendered into >= 0.
657      * Typically the view is told to render into the span
658      * that is returned, although there is no guarantee.
659      * The parent may choose to resize or break the view.
660      * @exception IllegalArgumentException for an invalid axis type
661      */

662     public float getMaximumSpan(int axis) {
663     if (axis == this.axis) {
664         return getPreferredSpan(axis);
665     }
666     return Integer.MAX_VALUE;
667     }
668
669
670     /**
671      * Returns the number of views in this view. Since
672      * the default is to not be a composite view this
673      * returns 0.
674      *
675      * @return the number of views >= 0
676      * @see View#getViewCount
677      */

678     public int getViewCount() {
679     synchronized(stats) {
680         return stats.size();
681     }
682     }
683
684     /**
685      * Gets the nth child view. Since there are no
686      * children by default, this returns null.
687      *
688      * @param n the number of the view to get, >= 0 && < getViewCount()
689      * @return the view
690      */

691     public View JavaDoc getView(int n) {
692     ChildState cs = getChildState(n);
693     if (cs != null) {
694         return cs.getChildView();
695     }
696     return null;
697     }
698
699     /**
700      * Fetches the allocation for the given child view.
701      * This enables finding out where various views
702      * are located, without assuming the views store
703      * their location. This returns null since the
704      * default is to not have any child views.
705      *
706      * @param index the index of the child, >= 0 && < getViewCount()
707      * @param a the allocation to this view.
708      * @return the allocation to the child
709      */

710     public Shape getChildAllocation(int index, Shape a) {
711     Shape ca = locator.getChildAllocation(index, a);
712     return ca;
713     }
714
715     /**
716      * Returns the child view index representing the given position in
717      * the model. By default a view has no children so this is implemented
718      * to return -1 to indicate there is no valid child index for any
719      * position.
720      *
721      * @param pos the position >= 0
722      * @return index of the view representing the given position, or
723      * -1 if no view represents that position
724      * @since 1.3
725      */

726     public int getViewIndex(int pos, Position.Bias JavaDoc b) {
727         return getViewIndexAtPosition(pos, b);
728     }
729
730     /**
731      * Provides a mapping from the document model coordinate space
732      * to the coordinate space of the view mapped to it.
733      *
734      * @param pos the position to convert >= 0
735      * @param a the allocated region to render into
736      * @param b the bias toward the previous character or the
737      * next character represented by the offset, in case the
738      * position is a boundary of two views.
739      * @return the bounding box of the given position is returned
740      * @exception BadLocationException if the given position does
741      * not represent a valid location in the associated document
742      * @exception IllegalArgumentException for an invalid bias argument
743      * @see View#viewToModel
744      */

745     public Shape modelToView(int pos, Shape a, Position.Bias JavaDoc b) throws BadLocationException JavaDoc {
746     int index = getViewIndex(pos, b);
747     Shape ca = locator.getChildAllocation(index, a);
748
749     // forward to the child view, and make sure we don't
750
// interact with the layout thread by synchronizing
751
// on the child state.
752
ChildState cs = getChildState(index);
753     synchronized (cs) {
754         View JavaDoc cv = cs.getChildView();
755         Shape v = cv.modelToView(pos, ca, b);
756         return v;
757     }
758     }
759
760     /**
761      * Provides a mapping from the view coordinate space to the logical
762      * coordinate space of the model. The biasReturn argument will be
763      * filled in to indicate that the point given is closer to the next
764      * character in the model or the previous character in the model.
765      * <p>
766      * This is expected to be called by the GUI thread, holding a
767      * read-lock on the associated model. It is implemented to
768      * locate the child view and determine it's allocation with a
769      * lock on the ChildLocator object, and to call viewToModel
770      * on the child view with a lock on the ChildState object
771      * to avoid interaction with the layout thread.
772      *
773      * @param x the X coordinate >= 0
774      * @param y the Y coordinate >= 0
775      * @param a the allocated region to render into
776      * @return the location within the model that best represents the
777      * given point in the view >= 0. The biasReturn argument will be
778      * filled in to indicate that the point given is closer to the next
779      * character in the model or the previous character in the model.
780      */

781     public int viewToModel(float x, float y, Shape a, Position.Bias JavaDoc[] biasReturn) {
782     int pos; // return position
783
int index; // child index to forward to
784
Shape ca; // child allocation
785

786     // locate the child view and it's allocation so that
787
// we can forward to it. Make sure the layout thread
788
// doesn't change anything by trying to flush changes
789
// to the parent while the GUI thread is trying to
790
// find the child and it's allocation.
791
synchronized (locator) {
792         index = locator.getViewIndexAtPoint(x, y, a);
793         ca = locator.getChildAllocation(index, a);
794     }
795
796     // forward to the child view, and make sure we don't
797
// interact with the layout thread by synchronizing
798
// on the child state.
799
ChildState cs = getChildState(index);
800     synchronized (cs) {
801         View JavaDoc v = cs.getChildView();
802         pos = v.viewToModel(x, y, ca, biasReturn);
803     }
804     return pos;
805     }
806
807     /**
808      * Provides a way to determine the next visually represented model
809      * location that one might place a caret. Some views may not be visible,
810      * they might not be in the same order found in the model, or they just
811      * might not allow access to some of the locations in the model.
812      *
813      * @param pos the position to convert >= 0
814      * @param a the allocated region to render into
815      * @param direction the direction from the current position that can
816      * be thought of as the arrow keys typically found on a keyboard;
817      * this may be one of the following:
818      * <ul>
819      * <code>SwingConstants.WEST</code>
820      * <code>SwingConstants.EAST</code>
821      * <code>SwingConstants.NORTH</code>
822      * <code>SwingConstants.SOUTH</code>
823      * </ul>
824      * @param biasRet an array contain the bias that was checked
825      * @return the location within the model that best represents the next
826      * location visual position
827      * @exception BadLocationException
828      * @exception IllegalArgumentException if <code>direction</code> is invalid
829      */

830     public int getNextVisualPositionFrom(int pos, Position.Bias JavaDoc b, Shape a,
831                      int direction,
832                                          Position.Bias JavaDoc[] biasRet)
833                                                   throws BadLocationException JavaDoc {
834         return Utilities.getNextVisualPositionFrom(
835                             this, pos, b, a, direction, biasRet);
836     }
837
838     // --- variables -----------------------------------------
839

840     /**
841      * The major axis against which the children are
842      * tiled.
843      */

844     int axis;
845
846     /**
847      * The children and their layout statistics.
848      */

849     java.util.List JavaDoc stats;
850
851     /**
852      * Current span along the major axis. This
853      * is also the value returned by getMinimumSize,
854      * getPreferredSize, and getMaximumSize along
855      * the major axis.
856      */

857     float majorSpan;
858
859     /**
860      * Is the span along the major axis estimated?
861      */

862     boolean estimatedMajorSpan;
863
864     /**
865      * Current span along the minor axis. This
866      * is what layout was done against (i.e. things
867      * are flexible in this direction).
868      */

869     float minorSpan;
870
871     /**
872      * Object that manages the offsets of the
873      * children. All locking for management of
874      * child locations is on this object.
875      */

876     protected ChildLocator locator;
877
878     float topInset;
879     float bottomInset;
880     float leftInset;
881     float rightInset;
882
883     ChildState minRequest;
884     ChildState prefRequest;
885     boolean majorChanged;
886     boolean minorChanged;
887     Runnable JavaDoc flushTask;
888
889     /**
890      * Child that is actively changing size. This often
891      * causes a preferenceChanged, so this is a cache to
892      * possibly speed up the marking the state. It also
893      * helps flag an opportunity to avoid adding to flush
894      * task to the layout queue.
895      */

896     ChildState changing;
897
898     /**
899      * A class to manage the effective position of the
900      * child views in a localized area while changes are
901      * being made around the localized area. The AsyncBoxView
902      * may be continuously changing, but the visible area
903      * needs to remain fairly stable until the layout thread
904      * decides to publish an update to the parent.
905      */

906     public class ChildLocator {
907
908     /**
909      * construct a child locator.
910      */

911     public ChildLocator() {
912         lastAlloc = new Rectangle();
913         childAlloc = new Rectangle();
914     }
915
916     /**
917      * Notification that a child changed. This can effect
918      * whether or not new offset calculations are needed.
919      * This is called by a ChildState object that has
920      * changed it's major span. This can therefore be
921      * called by multiple threads.
922      */

923         public synchronized void childChanged(ChildState cs) {
924         if (lastValidOffset == null) {
925         lastValidOffset = cs;
926         } else if (cs.getChildView().getStartOffset() <
927                lastValidOffset.getChildView().getStartOffset()) {
928         lastValidOffset = cs;
929         }
930     }
931
932     /**
933      * Paint the children that intersect the clip area.
934      */

935         public synchronized void paintChildren(Graphics g) {
936         Rectangle clip = g.getClipBounds();
937         float targetOffset = (axis == X_AXIS) ?
938         clip.x - lastAlloc.x : clip.y - lastAlloc.y;
939         int index = getViewIndexAtVisualOffset(targetOffset);
940         int n = getViewCount();
941         float offs = getChildState(index).getMajorOffset();
942         for (int i = index; i < n; i++) {
943         ChildState cs = getChildState(i);
944         cs.setMajorOffset(offs);
945         Shape ca = getChildAllocation(i);
946         if (intersectsClip(ca, clip)) {
947             synchronized (cs) {
948             View JavaDoc v = cs.getChildView();
949             v.paint(g, ca);
950             }
951         } else {
952             // done painting intersection
953
break;
954         }
955         offs += cs.getMajorSpan();
956         }
957     }
958
959     /**
960      * Fetch the allocation to use for a child view.
961      * This will update the offsets for all children
962      * not yet updated before the given index.
963      */

964     public synchronized Shape getChildAllocation(int index, Shape a) {
965         if (a == null) {
966         return null;
967         }
968         setAllocation(a);
969         ChildState cs = getChildState(index);
970         if (lastValidOffset == null) {
971         lastValidOffset = getChildState(0);
972         }
973         if (cs.getChildView().getStartOffset() >
974         lastValidOffset.getChildView().getStartOffset()) {
975         // offsets need to be updated
976
updateChildOffsetsToIndex(index);
977         }
978         Shape ca = getChildAllocation(index);
979         return ca;
980     }
981
982     /**
983      * Fetches the child view index at the given point.
984      * This is called by the various View methods that
985      * need to calculate which child to forward a message
986      * to. This should be called by a block synchronized
987      * on this object, and would typically be followed
988      * with one or more calls to getChildAllocation that
989      * should also be in the synchronized block.
990      *
991      * @param x the X coordinate >= 0
992      * @param y the Y coordinate >= 0
993      * @param a the allocation to the View
994      * @return the nearest child index
995      */

996         public int getViewIndexAtPoint(float x, float y, Shape a) {
997         setAllocation(a);
998         float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y;
999         int index = getViewIndexAtVisualOffset(targetOffset);
1000        return index;
1001    }
1002
1003    /**
1004     * Fetch the allocation to use for a child view.
1005     * <em>This does not update the offsets in the ChildState
1006     * records.</em>
1007     */

1008    protected Shape getChildAllocation(int index) {
1009        ChildState cs = getChildState(index);
1010        if (! cs.isLayoutValid()) {
1011        cs.run();
1012        }
1013        if (axis == X_AXIS) {
1014        childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
1015        childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
1016        childAlloc.width = (int) cs.getMajorSpan();
1017        childAlloc.height = (int) cs.getMinorSpan();
1018        } else {
1019        childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
1020        childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
1021        childAlloc.height = (int) cs.getMajorSpan();
1022        childAlloc.width = (int) cs.getMinorSpan();
1023        }
1024            childAlloc.x += (int)getLeftInset();
1025            childAlloc.y += (int)getRightInset();
1026        return childAlloc;
1027    }
1028
1029    /**
1030     * Copy the currently allocated shape into the Rectangle
1031     * used to store the current allocation. This would be
1032     * a floating point rectangle in a Java2D-specific implmentation.
1033     */

1034    protected void setAllocation(Shape a) {
1035        if (a instanceof Rectangle) {
1036        lastAlloc.setBounds((Rectangle) a);
1037        } else {
1038        lastAlloc.setBounds(a.getBounds());
1039        }
1040        setSize(lastAlloc.width, lastAlloc.height);
1041    }
1042
1043    /**
1044     * Locate the view responsible for an offset into the box
1045     * along the major axis. Make sure that offsets are set
1046     * on the ChildState objects up to the given target span
1047     * past the desired offset.
1048     *
1049     * @return index of the view representing the given visual
1050     * location (targetOffset), or -1 if no view represents
1051     * that location
1052     */

1053        protected int getViewIndexAtVisualOffset(float targetOffset) {
1054        int n = getViewCount();
1055        if (n > 0) {
1056                boolean lastValid = (lastValidOffset != null);
1057
1058        if (lastValidOffset == null) {
1059            lastValidOffset = getChildState(0);
1060        }
1061        if (targetOffset > majorSpan) {
1062            // should only get here on the first time display.
1063
if (!lastValid) {
1064                        return 0;
1065                    }
1066                    int pos = lastValidOffset.getChildView().getStartOffset();
1067                    int index = getViewIndex(pos, Position.Bias.Forward);
1068                    return index;
1069        } else if (targetOffset > lastValidOffset.getMajorOffset()) {
1070            // roll offset calculations forward
1071
return updateChildOffsets(targetOffset);
1072        } else {
1073            // no changes prior to the needed offset
1074
// this should be a binary search
1075
float offs = 0f;
1076            for (int i = 0; i < n; i++) {
1077            ChildState cs = getChildState(i);
1078            float nextOffs = offs + cs.getMajorSpan();
1079            if (targetOffset < nextOffs) {
1080                return i;
1081            }
1082            offs = nextOffs;
1083            }
1084        }
1085        }
1086        return n - 1;
1087    }
1088
1089    /**
1090     * Move the location of the last offset calculation forward
1091     * to the desired offset.
1092     */

1093    int updateChildOffsets(float targetOffset) {
1094        int n = getViewCount();
1095        int targetIndex = n - 1;;
1096        int pos = lastValidOffset.getChildView().getStartOffset();
1097        int startIndex = getViewIndex(pos, Position.Bias.Forward);
1098        float start = lastValidOffset.getMajorOffset();
1099        float lastOffset = start;
1100        for (int i = startIndex; i < n; i++) {
1101        ChildState cs = getChildState(i);
1102        cs.setMajorOffset(lastOffset);
1103        lastOffset += cs.getMajorSpan();
1104        if (targetOffset < lastOffset) {
1105            targetIndex = i;
1106            lastValidOffset = cs;
1107            break;
1108        }
1109        }
1110
1111        return targetIndex;
1112    }
1113
1114    /**
1115     * Move the location of the last offset calculation forward
1116     * to the desired index.
1117     */

1118    void updateChildOffsetsToIndex(int index) {
1119        int pos = lastValidOffset.getChildView().getStartOffset();
1120        int startIndex = getViewIndex(pos, Position.Bias.Forward);
1121        float lastOffset = lastValidOffset.getMajorOffset();
1122        for (int i = startIndex; i <= index; i++) {
1123        ChildState cs = getChildState(i);
1124        cs.setMajorOffset(lastOffset);
1125        lastOffset += cs.getMajorSpan();
1126        }
1127    }
1128
1129    boolean intersectsClip(Shape childAlloc, Rectangle clip) {
1130        Rectangle cs = (childAlloc instanceof Rectangle) ?
1131        (Rectangle) childAlloc : childAlloc.getBounds();
1132        if (cs.intersects(clip)) {
1133                // Make sure that lastAlloc also contains childAlloc,
1134
// this will be false if haven't yet flushed changes.
1135
return lastAlloc.intersects(cs);
1136            }
1137            return false;
1138    }
1139
1140    /**
1141     * The location of the last offset calculation
1142     * that is valid.
1143     */

1144    protected ChildState lastValidOffset;
1145
1146    /**
1147     * The last seen allocation (for repainting when changes
1148     * are flushed upward).
1149     */

1150    protected Rectangle lastAlloc;
1151
1152    /**
1153     * A shape to use for the child allocation to avoid
1154     * creating a lot of garbage.
1155     */

1156    protected Rectangle childAlloc;
1157    }
1158
1159    /**
1160     * A record representing the layout state of a
1161     * child view. It is runnable as a task on another
1162     * thread. All access to the child view that is
1163     * based upon a read-lock on the model should synchronize
1164     * on this object (i.e. The layout thread and the GUI
1165     * thread can both have a read lock on the model at the
1166     * same time and are not protected from each other).
1167     * Access to a child view hierarchy is serialized via
1168     * synchronization on the ChildState instance.
1169     */

1170    public class ChildState implements Runnable JavaDoc {
1171
1172    /**
1173     * Construct a child status. This needs to start
1174     * out as fairly large so we don't falsely begin with
1175     * the idea that all of the children are visible.
1176     */

1177    public ChildState(View JavaDoc v) {
1178        child = v;
1179        minorValid = false;
1180        majorValid = false;
1181        childSizeValid = false;
1182        child.setParent(AsyncBoxView.this);
1183    }
1184
1185    /**
1186     * Fetch the child view this record represents
1187     */

1188        public View JavaDoc getChildView() {
1189        return child;
1190    }
1191
1192    /**
1193     * Update the child state. This should be
1194     * called by the thread that desires to spend
1195     * time updating the child state (intended to
1196     * be the layout thread).
1197     * <p>
1198     * This aquires a read lock on the associated
1199     * document for the duration of the update to
1200     * ensure the model is not changed while it is
1201     * operating. The first thing to do would be
1202     * to see if any work actually needs to be done.
1203     * The following could have conceivably happened
1204     * while the state was waiting to be updated:
1205     * <ol>
1206     * <li>The child may have been removed from the
1207     * view hierarchy.
1208     * <li>The child may have been updated by a
1209     * higher priority operation (i.e. the child
1210     * may have become visible).
1211     * </ol>
1212     */

1213    public void run () {
1214        AbstractDocument JavaDoc doc = (AbstractDocument JavaDoc) getDocument();
1215        try {
1216        doc.readLock();
1217        if (minorValid && majorValid && childSizeValid) {
1218            // nothing to do
1219
return;
1220        }
1221        if (child.getParent() == AsyncBoxView.this) {
1222            // this may overwrite anothers threads cached
1223
// value for actively changing... but that just
1224
// means it won't use the cache if there is an
1225
// overwrite.
1226
synchronized(AsyncBoxView.this) {
1227            changing = this;
1228            }
1229            updateChild();
1230            synchronized(AsyncBoxView.this) {
1231            changing = null;
1232            }
1233
1234            // setting the child size on the minor axis
1235
// may have caused it to change it's preference
1236
// along the major axis.
1237
updateChild();
1238        }
1239        } finally {
1240        doc.readUnlock();
1241        }
1242    }
1243
1244    void updateChild() {
1245        boolean minorUpdated = false;
1246        synchronized(this) {
1247        if (! minorValid) {
1248            int minorAxis = getMinorAxis();
1249            min = child.getMinimumSpan(minorAxis);
1250            pref = child.getPreferredSpan(minorAxis);
1251            max = child.getMaximumSpan(minorAxis);
1252            minorValid = true;
1253            minorUpdated = true;
1254        }
1255        }
1256        if (minorUpdated) {
1257        minorRequirementChange(this);
1258        }
1259
1260        boolean majorUpdated = false;
1261        float delta = 0.0f;
1262        synchronized(this) {
1263        if (! majorValid) {
1264            float old = span;
1265            span = child.getPreferredSpan(axis);
1266            delta = span - old;
1267            majorValid = true;
1268            majorUpdated = true;
1269        }
1270        }
1271        if (majorUpdated) {
1272        majorRequirementChange(this, delta);
1273        locator.childChanged(this);
1274        }
1275
1276        synchronized(this) {
1277        if (! childSizeValid) {
1278            float w;
1279            float h;
1280            if (axis == X_AXIS) {
1281            w = span;
1282            h = getMinorSpan();
1283            } else {
1284            w = getMinorSpan();
1285            h = span;
1286            }
1287            childSizeValid = true;
1288            child.setSize(w, h);
1289        }
1290        }
1291        
1292    }
1293    
1294    /**
1295     * What is the span along the minor axis.
1296     */

1297    public float getMinorSpan() {
1298        if (max < minorSpan) {
1299        return max;
1300        }
1301        // make it the target width, or as small as it can get.
1302
return Math.max(min, minorSpan);
1303    }
1304
1305    /**
1306     * What is the offset along the minor axis
1307     */

1308    public float getMinorOffset() {
1309        if (max < minorSpan) {
1310        // can't make the child this wide, align it
1311
float align = child.getAlignment(getMinorAxis());
1312        return ((minorSpan - max) * align);
1313        }
1314        return 0f;
1315    }
1316
1317    /**
1318     * What is the span along the major axis.
1319     */

1320    public float getMajorSpan() {
1321        return span;
1322    }
1323
1324    /**
1325     * Get the offset along the major axis
1326     */

1327    public float getMajorOffset() {
1328        return offset;
1329    }
1330
1331    /**
1332     * This method should only be called by the ChildLocator,
1333     * it is simply a convenient place to hold the cached
1334     * location.
1335     */

1336    public void setMajorOffset(float offs) {
1337        offset = offs;
1338    }
1339
1340    /**
1341     * Mark preferences changed for this child.
1342     *
1343     * @param width true if the width preference has changed
1344     * @param height true if the height preference has changed
1345     * @see javax.swing.JComponent#revalidate
1346     */

1347        public void preferenceChanged(boolean width, boolean height) {
1348        if (axis == X_AXIS) {
1349        if (width) {
1350            majorValid = false;
1351        }
1352        if (height) {
1353            minorValid = false;
1354        }
1355        } else {
1356        if (width) {
1357            minorValid = false;
1358        }
1359        if (height) {
1360            majorValid = false;
1361        }
1362        }
1363        childSizeValid = false;
1364    }
1365
1366    /**
1367     * Has the child view been laid out.
1368     */

1369    public boolean isLayoutValid() {
1370        return (minorValid && majorValid && childSizeValid);
1371    }
1372
1373    // minor axis
1374
private float min;
1375    private float pref;
1376    private float max;
1377    private float align;
1378    private boolean minorValid;
1379    
1380    // major axis
1381
private float span;
1382    private float offset;
1383    private boolean majorValid;
1384    
1385    private View JavaDoc child;
1386    private boolean childSizeValid;
1387    }
1388
1389    /**
1390     * Task to flush requirement changes upward
1391     */

1392    class FlushTask implements Runnable JavaDoc {
1393    
1394    public void run() {
1395        flushRequirementChanges();
1396    }
1397
1398    }
1399
1400}
1401
Popular Tags