001/* AsyncBoxView.java -- A box view that performs layout asynchronously
002   Copyright (C) 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Component;
042import java.awt.Graphics;
043import java.awt.Rectangle;
044import java.awt.Shape;
045import java.util.ArrayList;
046
047import javax.swing.event.DocumentEvent;
048import 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 */
079public 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}