001 /* AsyncBoxView.java -- A box view that performs layout asynchronously
002 Copyright (C) 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import java.awt.Component;
042 import java.awt.Graphics;
043 import java.awt.Rectangle;
044 import java.awt.Shape;
045 import java.util.ArrayList;
046
047 import javax.swing.event.DocumentEvent;
048 import javax.swing.text.Position.Bias;
049
050 /**
051 * A {@link View} implementation that lays out its child views in a box, either
052 * vertically or horizontally. The difference to {@link BoxView} is that the
053 * layout is performed in an asynchronous manner. This helps to keep the
054 * eventqueue free from non-GUI related tasks.
055 *
056 * This view is currently not used in standard text components. In order to
057 * use it you would have to implement a special {@link EditorKit} with a
058 * {@link ViewFactory} that returns this view. For example:
059 *
060 * <pre>
061 * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory
062 * {
063 * public View create(Element el)
064 * {
065 * if (el.getName().equals(AbstractDocument.SectionElementName))
066 * return new AsyncBoxView(el, View.Y_AXIS);
067 * return super.getViewFactory().create(el);
068 * }
069 * public ViewFactory getViewFactory() {
070 * return this;
071 * }
072 * }
073 * </pre>
074 *
075 * @author Roman Kennke (kennke@aicas.com)
076 *
077 * @since 1.3
078 */
079 public class AsyncBoxView
080 extends View
081 {
082
083 /**
084 * Manages the effective position of child views. That keeps the visible
085 * layout stable while the AsyncBoxView might be changing until the layout
086 * thread decides to publish the new layout.
087 */
088 public class ChildLocator
089 {
090
091 /**
092 * The last valid location.
093 */
094 protected ChildState lastValidOffset;
095
096 /**
097 * The last allocation.
098 */
099 protected Rectangle lastAlloc;
100
101 /**
102 * A Rectangle used for child allocation calculation to avoid creation
103 * of lots of garbage Rectangle objects.
104 */
105 protected Rectangle childAlloc;
106
107 /**
108 * Creates a new ChildLocator.
109 */
110 public ChildLocator()
111 {
112 lastAlloc = new Rectangle();
113 childAlloc = new Rectangle();
114 }
115
116 /**
117 * Receives notification that a child has changed. This is called by
118 * child state objects that have changed it's major span.
119 *
120 * This sets the {@link #lastValidOffset} field to <code>cs</code> if
121 * the new child state's view start offset is smaller than the start offset
122 * of the current child state's view or when <code>lastValidOffset</code>
123 * is <code>null</code>.
124 *
125 * @param cs the child state object that has changed
126 */
127 public synchronized void childChanged(ChildState cs)
128 {
129 if (lastValidOffset == null
130 || cs.getChildView().getStartOffset()
131 < lastValidOffset.getChildView().getStartOffset())
132 {
133 lastValidOffset = cs;
134 }
135 }
136
137 /**
138 * Returns the view index of the view that occupies the specified area, or
139 * <code>-1</code> if there is no such child view.
140 *
141 * @param x the x coordinate (relative to <code>a</code>)
142 * @param y the y coordinate (relative to <code>a</code>)
143 * @param a the current allocation of this view
144 *
145 * @return the view index of the view that occupies the specified area, or
146 * <code>-1</code> if there is no such child view
147 */
148 public int getViewIndexAtPoint(float x, float y, Shape a)
149 {
150 setAllocation(a);
151 float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x
152 : y - lastAlloc.y;
153 int index = getViewIndexAtVisualOffset(targetOffset);
154 return index;
155 }
156
157 /**
158 * Returns the current allocation for a child view. This updates the
159 * offsets for all children <em>before</em> the requested child view.
160 *
161 * @param index the index of the child view
162 * @param a the current allocation of this view
163 *
164 * @return the current allocation for a child view
165 */
166 public synchronized Shape getChildAllocation(int index, Shape a)
167 {
168 if (a == null)
169 return null;
170 setAllocation(a);
171 ChildState cs = getChildState(index);
172 if (cs.getChildView().getStartOffset()
173 > lastValidOffset.getChildView().getStartOffset())
174 {
175 updateChildOffsetsToIndex(index);
176 }
177 Shape ca = getChildAllocation(index);
178 return ca;
179 }
180
181 /**
182 * Paints all child views.
183 *
184 * @param g the graphics context to use
185 */
186 public synchronized void paintChildren(Graphics g)
187 {
188 Rectangle clip = g.getClipBounds();
189 float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x
190 : clip.y - lastAlloc.y;
191 int index = getViewIndexAtVisualOffset(targetOffset);
192 int n = getViewCount();
193 float offs = getChildState(index).getMajorOffset();
194 for (int i = index; i < n; i++)
195 {
196 ChildState cs = getChildState(i);
197 cs.setMajorOffset(offs);
198 Shape ca = getChildAllocation(i);
199 if (ca.intersects(clip))
200 {
201 synchronized (cs)
202 {
203 View v = cs.getChildView();
204 v.paint(g, ca);
205 }
206 }
207 else
208 {
209 // done painting intersection
210 break;
211 }
212 offs += cs.getMajorSpan();
213 }
214 }
215
216 /**
217 * Returns the current allocation of the child view with the specified
218 * index. Note that this will <em>not</em> update any location information.
219 *
220 * @param index the index of the requested child view
221 *
222 * @return the current allocation of the child view with the specified
223 * index
224 */
225 protected Shape getChildAllocation(int index)
226 {
227 ChildState cs = getChildState(index);
228 if (! cs.isLayoutValid())
229 cs.run();
230
231 if (getMajorAxis() == X_AXIS)
232 {
233 childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
234 childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
235 childAlloc.width = (int) cs.getMajorSpan();
236 childAlloc.height = (int) cs.getMinorSpan();
237 }
238 else
239 {
240 childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
241 childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
242 childAlloc.height = (int) cs.getMajorSpan();
243 childAlloc.width = (int) cs.getMinorSpan();
244 }
245 return childAlloc;
246 }
247
248 /**
249 * Sets the current allocation for this view.
250 *
251 * @param a the allocation to set
252 */
253 protected void setAllocation(Shape a)
254 {
255 if (a instanceof Rectangle)
256 lastAlloc.setBounds((Rectangle) a);
257 else
258 lastAlloc.setBounds(a.getBounds());
259
260 setSize(lastAlloc.width, lastAlloc.height);
261 }
262
263 /**
264 * Returns the index of the view at the specified offset along the major
265 * layout axis.
266 *
267 * @param targetOffset the requested offset
268 *
269 * @return the index of the view at the specified offset along the major
270 * layout axis
271 */
272 protected int getViewIndexAtVisualOffset(float targetOffset)
273 {
274 int n = getViewCount();
275 if (n > 0)
276 {
277 if (lastValidOffset == null)
278 lastValidOffset = getChildState(0);
279 if (targetOffset > majorSpan)
280 return 0;
281 else if (targetOffset > lastValidOffset.getMajorOffset())
282 return updateChildOffsets(targetOffset);
283 else
284 {
285 float offs = 0f;
286 for (int i = 0; i < n; i++)
287 {
288 ChildState cs = getChildState(i);
289 float nextOffs = offs + cs.getMajorSpan();
290 if (targetOffset < nextOffs)
291 return i;
292 offs = nextOffs;
293 }
294 }
295 }
296 return n - 1;
297 }
298
299 /**
300 * Updates all the child view offsets up to the specified targetOffset.
301 *
302 * @param targetOffset the offset up to which the child view offsets are
303 * updated
304 *
305 * @return the index of the view at the specified offset
306 */
307 private int updateChildOffsets(float targetOffset)
308 {
309 int n = getViewCount();
310 int targetIndex = n - 1;
311 int pos = lastValidOffset.getChildView().getStartOffset();
312 int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
313 float start = lastValidOffset.getMajorOffset();
314 float lastOffset = start;
315 for (int i = startIndex; i < n; i++)
316 {
317 ChildState cs = getChildState(i);
318 cs.setMajorOffset(lastOffset);
319 lastOffset += cs.getMajorSpan();
320 if (targetOffset < lastOffset)
321 {
322 targetIndex = i;
323 lastValidOffset = cs;
324 break;
325 }
326 }
327 return targetIndex;
328 }
329
330 /**
331 * Updates the offsets of the child views up to the specified index.
332 *
333 * @param index the index up to which the offsets are updated
334 */
335 private void updateChildOffsetsToIndex(int index)
336 {
337 int pos = lastValidOffset.getChildView().getStartOffset();
338 int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
339 float lastOffset = lastValidOffset.getMajorOffset();
340 for (int i = startIndex; i <= index; i++)
341 {
342 ChildState cs = getChildState(i);
343 cs.setMajorOffset(lastOffset);
344 lastOffset += cs.getMajorSpan();
345 }
346 }
347 }
348
349 /**
350 * Represents the layout state of a child view.
351 */
352 public class ChildState
353 implements Runnable
354 {
355
356 /**
357 * The child view for this state record.
358 */
359 private View childView;
360
361 /**
362 * Indicates if the minor axis requirements of this child view are valid
363 * or not.
364 */
365 private boolean minorValid;
366
367 /**
368 * Indicates if the major axis requirements of this child view are valid
369 * or not.
370 */
371 private boolean majorValid;
372
373 /**
374 * Indicates if the current child size is valid. This is package private
375 * to avoid synthetic accessor method.
376 */
377 boolean childSizeValid;
378
379 /**
380 * The child views minimumSpan. This is package private to avoid accessor
381 * method.
382 */
383 float minimum;
384
385 /**
386 * The child views preferredSpan. This is package private to avoid accessor
387 * method.
388 */
389 float preferred;
390
391 /**
392 * The current span of the child view along the major axis.
393 */
394 private float majorSpan;
395
396 /**
397 * The current offset of the child view along the major axis.
398 */
399 private float majorOffset;
400
401 /**
402 * The current span of the child view along the minor axis.
403 */
404 private float minorSpan;
405
406 /**
407 * The current offset of the child view along the major axis.
408 */
409 private float minorOffset;
410
411 /**
412 * The child views maximumSpan.
413 */
414 private float maximum;
415
416 /**
417 * Creates a new <code>ChildState</code> object for the specified child
418 * view.
419 *
420 * @param view the child view for which to create the state record
421 */
422 public ChildState(View view)
423 {
424 childView = view;
425 }
426
427 /**
428 * Returns the child view for which this <code>ChildState</code> represents
429 * the layout state.
430 *
431 * @return the child view for this child state object
432 */
433 public View getChildView()
434 {
435 return childView;
436 }
437
438 /**
439 * Returns <code>true</code> if the current layout information is valid,
440 * <code>false</code> otherwise.
441 *
442 * @return <code>true</code> if the current layout information is valid,
443 * <code>false</code> otherwise
444 */
445 public boolean isLayoutValid()
446 {
447 return minorValid && majorValid && childSizeValid;
448 }
449
450 /**
451 * Performs the layout update for the child view managed by this
452 * <code>ChildState</code>.
453 */
454 public void run()
455 {
456 Document doc = getDocument();
457 if (doc instanceof AbstractDocument)
458 {
459 AbstractDocument abstractDoc = (AbstractDocument) doc;
460 abstractDoc.readLock();
461 }
462
463 try
464 {
465
466 if (!(minorValid && majorValid && childSizeValid)
467 && childView.getParent() == AsyncBoxView.this)
468 {
469 synchronized(AsyncBoxView.this)
470 {
471 changing = this;
472 }
473 update();
474 synchronized(AsyncBoxView.this)
475 {
476 changing = null;
477 }
478 // Changing the major axis may cause the minor axis
479 // requirements to have changed, so we need to do this again.
480 update();
481 }
482 }
483 finally
484 {
485 if (doc instanceof AbstractDocument)
486 {
487 AbstractDocument abstractDoc = (AbstractDocument) doc;
488 abstractDoc.readUnlock();
489 }
490 }
491 }
492
493 /**
494 * Performs the actual update after the run methods has made its checks
495 * and locked the document.
496 */
497 private void update()
498 {
499 int majorAxis = getMajorAxis();
500 boolean minorUpdated = false;
501 synchronized (this)
502 {
503 if (! minorValid)
504 {
505 int minorAxis = getMinorAxis();
506 minimum = childView.getMinimumSpan(minorAxis);
507 preferred = childView.getPreferredSpan(minorAxis);
508 maximum = childView.getMaximumSpan(minorAxis);
509 minorValid = true;
510 minorUpdated = true;
511 }
512 }
513 if (minorUpdated)
514 minorRequirementChange(this);
515
516 boolean majorUpdated = false;
517 float delta = 0.0F;
518 synchronized (this)
519 {
520 if (! majorValid)
521 {
522 float oldSpan = majorSpan;
523 majorSpan = childView.getPreferredSpan(majorAxis);
524 delta = majorSpan - oldSpan;
525 majorValid = true;
526 majorUpdated = true;
527 }
528 }
529 if (majorUpdated)
530 {
531 majorRequirementChange(this, delta);
532 locator.childChanged(this);
533 }
534
535 synchronized (this)
536 {
537 if (! childSizeValid)
538 {
539 float w;
540 float h;
541 if (majorAxis == X_AXIS)
542 {
543 w = majorSpan;
544 h = getMinorSpan();
545 }
546 else
547 {
548 w = getMinorSpan();
549 h = majorSpan;
550 }
551 childSizeValid = true;
552 childView.setSize(w, h);
553 }
554 }
555 }
556
557 /**
558 * Returns the span of the child view along the minor layout axis.
559 *
560 * @return the span of the child view along the minor layout axis
561 */
562 public float getMinorSpan()
563 {
564 float retVal;
565 if (maximum < minorSpan)
566 retVal = maximum;
567 else
568 retVal = Math.max(minimum, minorSpan);
569 return retVal;
570 }
571
572 /**
573 * Returns the offset of the child view along the minor layout axis.
574 *
575 * @return the offset of the child view along the minor layout axis
576 */
577 public float getMinorOffset()
578 {
579 float retVal;
580 if (maximum < minorSpan)
581 {
582 float align = childView.getAlignment(getMinorAxis());
583 retVal = ((minorSpan - maximum) * align);
584 }
585 else
586 retVal = 0f;
587
588 return retVal;
589 }
590
591 /**
592 * Returns the span of the child view along the major layout axis.
593 *
594 * @return the span of the child view along the major layout axis
595 */
596
597 public float getMajorSpan()
598 {
599 return majorSpan;
600 }
601
602 /**
603 * Returns the offset of the child view along the major layout axis.
604 *
605 * @return the offset of the child view along the major layout axis
606 */
607 public float getMajorOffset()
608 {
609 return majorOffset;
610 }
611
612 /**
613 * Sets the offset of the child view along the major layout axis. This
614 * should only be called by the ChildLocator of that child view.
615 *
616 * @param offset the offset to set
617 */
618 public void setMajorOffset(float offset)
619 {
620 majorOffset = offset;
621 }
622
623 /**
624 * Mark the preferences changed for that child. This forwards to
625 * {@link AsyncBoxView#preferenceChanged}.
626 *
627 * @param width <code>true</code> if the width preference has changed
628 * @param height <code>true</code> if the height preference has changed
629 */
630 public void preferenceChanged(boolean width, boolean height)
631 {
632 if (getMajorAxis() == X_AXIS)
633 {
634 if (width)
635 majorValid = false;
636 if (height)
637 minorValid = false;
638 }
639 else
640 {
641 if (width)
642 minorValid = false;
643 if (height)
644 majorValid = false;
645 }
646 childSizeValid = false;
647 }
648 }
649
650 /**
651 * Flushes the requirements changes upwards asynchronously.
652 */
653 private class FlushTask implements Runnable
654 {
655 /**
656 * Starts the flush task. This obtains a readLock on the document
657 * and then flushes all the updates using
658 * {@link AsyncBoxView#flushRequirementChanges()} after updating the
659 * requirements.
660 */
661 public void run()
662 {
663 try
664 {
665 // Acquire a lock on the document.
666 Document doc = getDocument();
667 if (doc instanceof AbstractDocument)
668 {
669 AbstractDocument abstractDoc = (AbstractDocument) doc;
670 abstractDoc.readLock();
671 }
672
673 int n = getViewCount();
674 if (minorChanged && (n > 0))
675 {
676 LayoutQueue q = getLayoutQueue();
677 ChildState min = getChildState(0);
678 ChildState pref = getChildState(0);
679 for (int i = 1; i < n; i++)
680 {
681 ChildState cs = getChildState(i);
682 if (cs.minimum > min.minimum)
683 min = cs;
684 if (cs.preferred > pref.preferred)
685 pref = cs;
686 }
687 synchronized (AsyncBoxView.this)
688 {
689 minReq = min;
690 prefReq = pref;
691 }
692 }
693
694 flushRequirementChanges();
695 }
696 finally
697 {
698 // Release the lock on the document.
699 Document doc = getDocument();
700 if (doc instanceof AbstractDocument)
701 {
702 AbstractDocument abstractDoc = (AbstractDocument) doc;
703 abstractDoc.readUnlock();
704 }
705 }
706 }
707
708 }
709
710 /**
711 * The major layout axis.
712 */
713 private int majorAxis;
714
715 /**
716 * The top inset.
717 */
718 private float topInset;
719
720 /**
721 * The bottom inset.
722 */
723 private float bottomInset;
724
725 /**
726 * The left inset.
727 */
728 private float leftInset;
729
730 /**
731 * Indicates if the major span should be treated as beeing estimated or not.
732 */
733 private boolean estimatedMajorSpan;
734
735 /**
736 * The right inset.
737 */
738 private float rightInset;
739
740 /**
741 * The children and their layout statistics.
742 */
743 private ArrayList childStates;
744
745 /**
746 * The currently changing child state. May be null if there is no child state
747 * updating at the moment. This is package private to avoid a synthetic
748 * accessor method inside ChildState.
749 */
750 ChildState changing;
751
752 /**
753 * Represents the minimum requirements. This is used in
754 * {@link #getMinimumSpan(int)}.
755 */
756 ChildState minReq;
757
758 /**
759 * Represents the minimum requirements. This is used in
760 * {@link #getPreferredSpan(int)}.
761 */
762 ChildState prefReq;
763
764 /**
765 * Indicates that the major axis requirements have changed.
766 */
767 private boolean majorChanged;
768
769 /**
770 * Indicates that the minor axis requirements have changed. This is package
771 * private to avoid synthetic accessor method.
772 */
773 boolean minorChanged;
774
775 /**
776 * The current span along the major layout axis. This is package private to
777 * avoid synthetic accessor method.
778 */
779 float majorSpan;
780
781 /**
782 * The current span along the minor layout axis. This is package private to
783 * avoid synthetic accessor method.
784 */
785 float minorSpan;
786
787 /**
788 * This tasked is placed on the layout queue to flush updates up to the
789 * parent view.
790 */
791 private Runnable flushTask;
792
793 /**
794 * The child locator for this view.
795 */
796 protected ChildLocator locator;
797
798 /**
799 * Creates a new <code>AsyncBoxView</code> that represents the specified
800 * element and layouts its children along the specified axis.
801 *
802 * @param elem the element
803 * @param axis the layout axis
804 */
805 public AsyncBoxView(Element elem, int axis)
806 {
807 super(elem);
808 majorAxis = axis;
809 childStates = new ArrayList();
810 flushTask = new FlushTask();
811 locator = new ChildLocator();
812 minorSpan = Short.MAX_VALUE;
813 }
814
815 /**
816 * Returns the major layout axis.
817 *
818 * @return the major layout axis
819 */
820 public int getMajorAxis()
821 {
822 return majorAxis;
823 }
824
825 /**
826 * Returns the minor layout axis, that is the axis orthogonal to the major
827 * layout axis.
828 *
829 * @return the minor layout axis
830 */
831 public int getMinorAxis()
832 {
833 return majorAxis == X_AXIS ? Y_AXIS : X_AXIS;
834 }
835
836 /**
837 * Returns the view at the specified <code>index</code>.
838 *
839 * @param index the index of the requested child view
840 *
841 * @return the view at the specified <code>index</code>
842 */
843 public View getView(int index)
844 {
845 View view = null;
846 synchronized(childStates)
847 {
848 if ((index >= 0) && (index < childStates.size()))
849 {
850 ChildState cs = (ChildState) childStates.get(index);
851 view = cs.getChildView();
852 }
853 }
854 return view;
855 }
856
857 /**
858 * Returns the number of child views.
859 *
860 * @return the number of child views
861 */
862 public int getViewCount()
863 {
864 synchronized(childStates)
865 {
866 return childStates.size();
867 }
868 }
869
870 /**
871 * Returns the view index of the child view that represents the specified
872 * model position.
873 *
874 * @param pos the model position for which we search the view index
875 * @param bias the bias
876 *
877 * @return the view index of the child view that represents the specified
878 * model position
879 */
880 public int getViewIndex(int pos, Position.Bias bias)
881 {
882 int retVal = -1;
883
884 if (bias == Position.Bias.Backward)
885 pos = Math.max(0, pos - 1);
886
887 // TODO: A possible optimization would be to implement a binary search
888 // here.
889 int numChildren = childStates.size();
890 if (numChildren > 0)
891 {
892 for (int i = 0; i < numChildren; ++i)
893 {
894 View child = ((ChildState) childStates.get(i)).getChildView();
895 if (child.getStartOffset() <= pos && child.getEndOffset() > pos)
896 {
897 retVal = i;
898 break;
899 }
900 }
901 }
902 return retVal;
903 }
904
905 /**
906 * Returns the top inset.
907 *
908 * @return the top inset
909 */
910 public float getTopInset()
911 {
912 return topInset;
913 }
914
915 /**
916 * Sets the top inset.
917 *
918 * @param top the top inset
919 */
920 public void setTopInset(float top)
921 {
922 topInset = top;
923 }
924
925 /**
926 * Returns the bottom inset.
927 *
928 * @return the bottom inset
929 */
930 public float getBottomInset()
931 {
932 return bottomInset;
933 }
934
935 /**
936 * Sets the bottom inset.
937 *
938 * @param bottom the bottom inset
939 */
940 public void setBottomInset(float bottom)
941 {
942 bottomInset = bottom;
943 }
944
945 /**
946 * Returns the left inset.
947 *
948 * @return the left inset
949 */
950 public float getLeftInset()
951 {
952 return leftInset;
953 }
954
955 /**
956 * Sets the left inset.
957 *
958 * @param left the left inset
959 */
960 public void setLeftInset(float left)
961 {
962 leftInset = left;
963 }
964
965 /**
966 * Returns the right inset.
967 *
968 * @return the right inset
969 */
970 public float getRightInset()
971 {
972 return rightInset;
973 }
974
975 /**
976 * Sets the right inset.
977 *
978 * @param right the right inset
979 */
980 public void setRightInset(float right)
981 {
982 rightInset = right;
983 }
984
985 /**
986 * Loads the child views of this view. This is triggered by
987 * {@link #setParent(View)}.
988 *
989 * @param f the view factory to build child views with
990 */
991 protected void loadChildren(ViewFactory f)
992 {
993 Element e = getElement();
994 int n = e.getElementCount();
995 if (n > 0)
996 {
997 View[] added = new View[n];
998 for (int i = 0; i < n; i++)
999 {
1000 added[i] = f.create(e.getElement(i));
1001 }
1002 replace(0, 0, added);
1003 }
1004 }
1005
1006 /**
1007 * Returns the span along an axis that is taken up by the insets.
1008 *
1009 * @param axis the axis
1010 *
1011 * @return the span along an axis that is taken up by the insets
1012 *
1013 * @since 1.4
1014 */
1015 protected float getInsetSpan(int axis)
1016 {
1017 float span;
1018 if (axis == X_AXIS)
1019 span = leftInset + rightInset;
1020 else
1021 span = topInset + bottomInset;
1022 return span;
1023 }
1024
1025 /**
1026 * Sets the <code>estimatedMajorSpan</code> property that determines if
1027 * the major span should be treated as beeing estimated.
1028 *
1029 * @param estimated if the major span should be treated as estimated or not
1030 *
1031 * @since 1.4
1032 */
1033 protected void setEstimatedMajorSpan(boolean estimated)
1034 {
1035 estimatedMajorSpan = estimated;
1036 }
1037
1038 /**
1039 * Determines whether the major span should be treated as estimated or as
1040 * beeing accurate.
1041 *
1042 * @return <code>true</code> if the major span should be treated as
1043 * estimated, <code>false</code> if the major span should be treated
1044 * as accurate
1045 *
1046 * @since 1.4
1047 */
1048 protected boolean getEstimatedMajorSpan()
1049 {
1050 return estimatedMajorSpan;
1051 }
1052
1053 /**
1054 * Receives notification from the child states that the requirements along
1055 * the minor axis have changed.
1056 *
1057 * @param cs the child state from which this notification is messaged
1058 */
1059 protected synchronized void minorRequirementChange(ChildState cs)
1060 {
1061 minorChanged = true;
1062 }
1063
1064 /**
1065 * Receives notification from the child states that the requirements along
1066 * the major axis have changed.
1067 *
1068 * @param cs the child state from which this notification is messaged
1069 */
1070 protected void majorRequirementChange(ChildState cs, float delta)
1071 {
1072 if (! estimatedMajorSpan)
1073 majorSpan += delta;
1074 majorChanged = true;
1075 }
1076
1077 /**
1078 * Sets the parent for this view. This calls loadChildren if
1079 * <code>parent</code> is not <code>null</code> and there have not been any
1080 * child views initializes.
1081 *
1082 * @param parent the new parent view; <code>null</code> if this view is
1083 * removed from the view hierarchy
1084 *
1085 * @see View#setParent(View)
1086 */
1087 public void setParent(View parent)
1088 {
1089 super.setParent(parent);
1090 if ((parent != null) && (getViewCount() == 0))
1091 {
1092 ViewFactory f = getViewFactory();
1093 loadChildren(f);
1094 }
1095 }
1096
1097 /**
1098 * Sets the size of this view. This is ususally called before {@link #paint}
1099 * is called to make sure the view has a valid layout.
1100 *
1101 * This implementation queues layout requests for every child view if the
1102 * minor axis span has changed. (The major axis span is requested to never
1103 * change for this view).
1104 *
1105 * @param width the width of the view
1106 * @param height the height of the view
1107 */
1108 public void setSize(float width, float height)
1109 {
1110 float targetSpan;
1111 if (majorAxis == X_AXIS)
1112 targetSpan = height - getTopInset() - getBottomInset();
1113 else
1114 targetSpan = width - getLeftInset() - getRightInset();
1115
1116 if (targetSpan != minorSpan)
1117 {
1118 minorSpan = targetSpan;
1119
1120 int n = getViewCount();
1121 LayoutQueue q = getLayoutQueue();
1122 for (int i = 0; i < n; i++)
1123 {
1124 ChildState cs = getChildState(i);
1125 cs.childSizeValid = false;
1126 q.addTask(cs);
1127 }
1128 q.addTask(flushTask);
1129 }
1130 }
1131
1132 /**
1133 * Replaces child views with new child views.
1134 *
1135 * This creates ChildState objects for all the new views and adds layout
1136 * requests for them to the layout queue.
1137 *
1138 * @param offset the offset at which to remove/insert
1139 * @param length the number of child views to remove
1140 * @param views the new child views to insert
1141 */
1142 public void replace(int offset, int length, View[] views)
1143 {
1144 synchronized(childStates)
1145 {
1146 LayoutQueue q = getLayoutQueue();
1147 for (int i = 0; i < length; i++)
1148 childStates.remove(offset);
1149
1150 for (int i = views.length - 1; i >= 0; i--)
1151 childStates.add(offset, createChildState(views[i]));
1152
1153 // We need to go through the new child states _after_ they have been
1154 // added to the childStates list, otherwise the layout tasks may find
1155 // an incomplete child list. That means we have to loop through
1156 // them again, but what else can we do?
1157 if (views.length != 0)
1158 {
1159 for (int i = 0; i < views.length; i++)
1160 {
1161 ChildState cs = (ChildState) childStates.get(i + offset);
1162 cs.getChildView().setParent(this);
1163 q.addTask(cs);
1164 }
1165 q.addTask(flushTask);
1166 }
1167 }
1168 }
1169
1170 /**
1171 * Paints the view. This requests the {@link ChildLocator} to paint the views
1172 * after setting the allocation on it.
1173 *
1174 * @param g the graphics context to use
1175 * @param s the allocation for this view
1176 */
1177 public void paint(Graphics g, Shape s)
1178 {
1179 synchronized (locator)
1180 {
1181 locator.setAllocation(s);
1182 locator.paintChildren(g);
1183 }
1184 }
1185
1186 /**
1187 * Returns the preferred span of this view along the specified layout axis.
1188 *
1189 * @return the preferred span of this view along the specified layout axis
1190 */
1191 public float getPreferredSpan(int axis)
1192 {
1193 float retVal;
1194 if (majorAxis == axis)
1195 retVal = majorSpan;
1196
1197 else if (prefReq != null)
1198 {
1199 View child = prefReq.getChildView();
1200 retVal = child.getPreferredSpan(axis);
1201 }
1202
1203 // If we have no layout information yet, then return insets + 30 as
1204 // an estimation.
1205 else
1206 {
1207 if (axis == X_AXIS)
1208 retVal = getLeftInset() + getRightInset() + 30;
1209 else
1210 retVal = getTopInset() + getBottomInset() + 30;
1211 }
1212 return retVal;
1213 }
1214
1215 /**
1216 * Maps a model location to view coordinates.
1217 *
1218 * @param pos the model location
1219 * @param a the current allocation of this view
1220 * @param b the bias
1221 *
1222 * @return the view allocation for the specified model location
1223 */
1224 public Shape modelToView(int pos, Shape a, Bias b)
1225 throws BadLocationException
1226 {
1227 int index = getViewIndexAtPosition(pos, b);
1228 Shape ca = locator.getChildAllocation(index, a);
1229
1230 ChildState cs = getChildState(index);
1231 synchronized (cs)
1232 {
1233 View cv = cs.getChildView();
1234 Shape v = cv.modelToView(pos, ca, b);
1235 return v;
1236 }
1237 }
1238
1239 /**
1240 * Maps view coordinates to a model location.
1241 *
1242 * @param x the x coordinate (relative to <code>a</code>)
1243 * @param y the y coordinate (relative to <code>a</code>)
1244 * @param b holds the bias of the model location on method exit
1245 *
1246 * @return the model location for the specified view location
1247 */
1248 public int viewToModel(float x, float y, Shape a, Bias[] b)
1249 {
1250 int pos;
1251 int index;
1252 Shape ca;
1253
1254 synchronized (locator)
1255 {
1256 index = locator.getViewIndexAtPoint(x, y, a);
1257 ca = locator.getChildAllocation(index, a);
1258 }
1259
1260 ChildState cs = getChildState(index);
1261 synchronized (cs)
1262 {
1263 View v = cs.getChildView();
1264 pos = v.viewToModel(x, y, ca, b);
1265 }
1266 return pos;
1267 }
1268
1269 /**
1270 * Returns the child allocation for the child view with the specified
1271 * <code>index</code>.
1272 *
1273 * @param index the index of the child view
1274 * @param a the current allocation of this view
1275 *
1276 * @return the allocation of the child view
1277 */
1278 public Shape getChildAllocation(int index, Shape a)
1279 {
1280 Shape ca = locator.getChildAllocation(index, a);
1281 return ca;
1282 }
1283
1284 /**
1285 * Returns the maximum span of this view along the specified axis.
1286 * This is implemented to return the <code>preferredSpan</code> for the
1287 * major axis (that means the box can't be resized along the major axis) and
1288 * {@link Short#MAX_VALUE} for the minor axis.
1289 *
1290 * @param axis the axis
1291 *
1292 * @return the maximum span of this view along the specified axis
1293 */
1294 public float getMaximumSpan(int axis)
1295 {
1296 float max;
1297 if (axis == majorAxis)
1298 max = getPreferredSpan(axis);
1299 else
1300 max = Short.MAX_VALUE;
1301 return max;
1302 }
1303
1304 /**
1305 * Returns the minimum span along the specified axis.
1306 */
1307 public float getMinimumSpan(int axis)
1308 {
1309 float min;
1310 if (axis == majorAxis)
1311 min = getPreferredSpan(axis);
1312 else
1313 {
1314 if (minReq != null)
1315 {
1316 View child = minReq.getChildView();
1317 min = child.getMinimumSpan(axis);
1318 }
1319 else
1320 {
1321 // No layout information yet. Return insets + 5 as some kind of
1322 // estimation.
1323 if (axis == X_AXIS)
1324 min = getLeftInset() + getRightInset() + 5;
1325 else
1326 min = getTopInset() + getBottomInset() + 5;
1327 }
1328 }
1329 return min;
1330 }
1331
1332 /**
1333 * Receives notification that one of the child views has changed its
1334 * layout preferences along one or both axis.
1335 *
1336 * This queues a layout request for that child view if necessary.
1337 *
1338 * @param view the view that has changed its preferences
1339 * @param width <code>true</code> if the width preference has changed
1340 * @param height <code>true</code> if the height preference has changed
1341 */
1342 public synchronized void preferenceChanged(View view, boolean width,
1343 boolean height)
1344 {
1345 if (view == null)
1346 getParent().preferenceChanged(this, width, height);
1347 else
1348 {
1349 if (changing != null)
1350 {
1351 View cv = changing.getChildView();
1352 if (cv == view)
1353 {
1354 changing.preferenceChanged(width, height);
1355 return;
1356 }
1357 }
1358 int index = getViewIndexAtPosition(view.getStartOffset(),
1359 Position.Bias.Forward);
1360 ChildState cs = getChildState(index);
1361 cs.preferenceChanged(width, height);
1362 LayoutQueue q = getLayoutQueue();
1363 q.addTask(cs);
1364 q.addTask(flushTask);
1365 }
1366 }
1367
1368 /**
1369 * Updates the layout for this view. This is implemented to trigger
1370 * {@link ChildLocator#childChanged} for the changed view, if there is
1371 * any.
1372 *
1373 * @param ec the element change, may be <code>null</code> if there were
1374 * no changes to the element of this view
1375 * @param e the document event
1376 * @param a the current allocation of this view
1377 */
1378 protected void updateLayout(DocumentEvent.ElementChange ec,
1379 DocumentEvent e, Shape a)
1380 {
1381 if (ec != null)
1382 {
1383 int index = Math.max(ec.getIndex() - 1, 0);
1384 ChildState cs = getChildState(index);
1385 locator.childChanged(cs);
1386 }
1387 }
1388
1389
1390 /**
1391 * Returns the <code>ChildState</code> object associated with the child view
1392 * at the specified <code>index</code>.
1393 *
1394 * @param index the index of the child view for which to query the state
1395 *
1396 * @return the child state for the specified child view
1397 */
1398 protected ChildState getChildState(int index) {
1399 synchronized (childStates)
1400 {
1401 return (ChildState) childStates.get(index);
1402 }
1403 }
1404
1405 /**
1406 * Returns the <code>LayoutQueue</code> used for layouting the box view.
1407 * This simply returns {@link LayoutQueue#getDefaultQueue()}.
1408 *
1409 * @return the <code>LayoutQueue</code> used for layouting the box view
1410 */
1411 protected LayoutQueue getLayoutQueue()
1412 {
1413 return LayoutQueue.getDefaultQueue();
1414 }
1415
1416 /**
1417 * Returns the child view index of the view that represents the specified
1418 * position in the document model.
1419 *
1420 * @param pos the position in the model
1421 * @param b the bias
1422 *
1423 * @return the child view index of the view that represents the specified
1424 * position in the document model
1425 */
1426 protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b)
1427 {
1428 if (b == Position.Bias.Backward)
1429 pos = Math.max(0, pos - 1);
1430 Element elem = getElement();
1431 return elem.getElementIndex(pos);
1432 }
1433
1434 /**
1435 * Creates a <code>ChildState</code> object for the specified view.
1436 *
1437 * @param v the view for which to create a child state object
1438 *
1439 * @return the created child state
1440 */
1441 protected ChildState createChildState(View v)
1442 {
1443 return new ChildState(v);
1444 }
1445
1446 /**
1447 * Flushes the requirements changes upwards to the parent view. This is
1448 * called from the layout thread.
1449 */
1450 protected synchronized void flushRequirementChanges()
1451 {
1452 if (majorChanged || minorChanged)
1453 {
1454 View p = getParent();
1455 if (p != null)
1456 {
1457 boolean horizontal;
1458 boolean vertical;
1459 if (majorAxis == X_AXIS)
1460 {
1461 horizontal = majorChanged;
1462 vertical = minorChanged;
1463 }
1464 else
1465 {
1466 vertical = majorChanged;
1467 horizontal = minorChanged;
1468 }
1469
1470 p.preferenceChanged(this, horizontal, vertical);
1471 majorChanged = false;
1472 minorChanged = false;
1473
1474 Component c = getContainer();
1475 if (c != null)
1476 c.repaint();
1477 }
1478 }
1479 }
1480 }