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    }