History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/AsyncBoxView.java (1 of 18352 files). Show all

Unified · Split

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