001    /* FlowView.java -- A composite View
002       Copyright (C) 2005  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    
046    import javax.swing.SizeRequirements;
047    import javax.swing.event.DocumentEvent;
048    
049    /**
050     * A <code>View</code> that can flows it's children into it's layout space.
051     *
052     * The <code>FlowView</code> manages a set of logical views (that are
053     * the children of the {@link #layoutPool} field). These are translated
054     * at layout time into a set of physical views. These are the views that
055     * are managed as the real child views. Each of these child views represents
056     * a row and are laid out within a box using the superclasses behaviour.
057     * The concrete implementation of the rows must be provided by subclasses.
058     *
059     * @author Roman Kennke (roman@kennke.org)
060     */
061    public abstract class FlowView extends BoxView
062    {
063      /**
064       * A strategy for translating the logical views of a <code>FlowView</code>
065       * into the real views.
066       */
067      public static class FlowStrategy
068      {
069        /**
070         * Creates a new instance of <code>FlowStragegy</code>.
071         */
072        public FlowStrategy()
073        {
074          // Nothing to do here.
075        }
076    
077        /**
078         * Receives notification from a <code>FlowView</code> that some content
079         * has been inserted into the document at a location that the
080         * <code>FlowView</code> is responsible for.
081         *
082         * The default implementation simply calls {@link #layout}.
083         *
084         * @param fv the flow view that sends the notification
085         * @param e the document event describing the change
086         * @param alloc the current allocation of the flow view
087         */
088        public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
089        {
090          if (alloc == null)
091            {
092              fv.layoutChanged(X_AXIS);
093              fv.layoutChanged(Y_AXIS);
094            }
095          else
096            {
097              Component host = fv.getContainer();
098              if (host != null)
099                host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
100            }
101        }
102    
103        /**
104         * Receives notification from a <code>FlowView</code> that some content
105         * has been removed from the document at a location that the
106         * <code>FlowView</code> is responsible for.
107         *
108         * The default implementation simply calls {@link #layout}.
109         *
110         * @param fv the flow view that sends the notification
111         * @param e the document event describing the change
112         * @param alloc the current allocation of the flow view
113         */
114        public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
115        {
116          if (alloc == null)
117            {
118              fv.layoutChanged(X_AXIS);
119              fv.layoutChanged(Y_AXIS);
120            }
121          else
122            {
123              Component host = fv.getContainer();
124              if (host != null)
125                host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
126            }
127        }
128    
129        /**
130         * Receives notification from a <code>FlowView</code> that some attributes
131         * have changed in the document at a location that the
132         * <code>FlowView</code> is responsible for.
133         *
134         * The default implementation simply calls {@link #layout}.
135         *
136         * @param fv the flow view that sends the notification
137         * @param e the document event describing the change
138         * @param alloc the current allocation of the flow view
139         */
140        public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
141        {
142          if (alloc == null)
143            {
144              fv.layoutChanged(X_AXIS);
145              fv.layoutChanged(Y_AXIS);
146            }
147          else
148            {
149              Component host = fv.getContainer();
150              if (host != null)
151                host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
152            }
153        }
154    
155        /**
156         * Returns the logical view of the managed <code>FlowView</code>.
157         *
158         * @param fv the flow view for which to return the logical view
159         *
160         * @return the logical view of the managed <code>FlowView</code>
161         */
162        protected View getLogicalView(FlowView fv)
163        {
164          return fv.layoutPool;
165        }
166    
167        /**
168         * Performs the layout for the whole view. By default this rebuilds
169         * all the physical views from the logical views of the managed FlowView.
170         *
171         * This is called by {@link FlowView#layout} to update the layout of
172         * the view.
173         *
174         * @param fv the flow view for which we perform the layout
175         */
176        public void layout(FlowView fv)
177        {
178          int start = fv.getStartOffset();
179          int end = fv.getEndOffset();
180    
181          // Preserve the views from the logical view from beeing removed.
182          View lv = getLogicalView(fv);
183          int viewCount = lv.getViewCount();
184          for (int i = 0; i < viewCount; i++)
185            {
186              View v = lv.getView(i);
187              v.setParent(lv);
188            }
189    
190          // Then remove all views from the flow view.
191          fv.removeAll();
192    
193          for (int rowIndex = 0; start < end; rowIndex++)
194            {
195              View row = fv.createRow();
196              fv.append(row);
197              int next = layoutRow(fv, rowIndex, start);
198              if (row.getViewCount() == 0)
199                {
200                  row.append(createView(fv, start, Integer.MAX_VALUE, rowIndex));
201                  next = row.getEndOffset();
202                }
203              if (start < next)
204                start = next;
205              else
206                assert false: "May not happen";
207            }
208        }
209    
210        /**
211         * Lays out one row of the flow view. This is called by {@link #layout} to
212         * fill one row with child views until the available span is exhausted. The
213         * default implementation fills the row by calling
214         * {@link #createView(FlowView, int, int, int)} until the available space is
215         * exhausted, a forced break is encountered or there are no more views in
216         * the logical view. If the available space is exhausted,
217         * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row into
218         * the available span.
219         * 
220         * @param fv the flow view for which we perform the layout
221         * @param rowIndex the index of the row
222         * @param pos the model position for the beginning of the row
223         * @return the start position of the next row
224         */
225        protected int layoutRow(FlowView fv, int rowIndex, int pos)
226        {
227          View row = fv.getView(rowIndex);
228          int axis = fv.getFlowAxis();
229          int span = fv.getFlowSpan(rowIndex);
230          int x = fv.getFlowStart(rowIndex);
231          int end = fv.getEndOffset();
232    
233          // Needed for adjusting indentation in adjustRow().
234          int preX = x;
235          int availableSpan = span;
236    
237          TabExpander tabExp = fv instanceof TabExpander ? (TabExpander) fv : null;
238    
239          boolean forcedBreak = false;
240          while (pos < end && span >= 0)
241            {
242              View view = createView(fv, pos, span, rowIndex);
243              if (view == null
244                  || (span == 0 && view.getPreferredSpan(axis) > 0))
245                break;
246    
247              int viewSpan;
248              if (axis == X_AXIS && view instanceof TabableView)
249                viewSpan = (int) ((TabableView) view).getTabbedSpan(x, tabExp);
250              else
251                viewSpan = (int) view.getPreferredSpan(axis);
252    
253              // Break if the line if the view does not fit in this row or the
254              // line just must be broken.
255              int breakWeight = view.getBreakWeight(axis, pos, span);
256              if (breakWeight >= ForcedBreakWeight)
257                {
258                  int rowViewCount = row.getViewCount();
259                  if (rowViewCount > 0)
260                    {
261                      view = view.breakView(axis, pos, x, span);
262                      if (view != null)
263                        {
264                          if (axis == X_AXIS && view instanceof TabableView)
265                            viewSpan =
266                              (int) ((TabableView) view).getTabbedSpan(x, tabExp);
267                          else
268                            viewSpan = (int) view.getPreferredSpan(axis);
269                        }
270                      else
271                        viewSpan = 0;
272                    }
273                  forcedBreak = true;
274                }
275              span -= viewSpan;
276              x += viewSpan;
277              if (view != null)
278                {
279                  row.append(view);
280                  pos = view.getEndOffset();
281                }
282              if (forcedBreak)
283                break;
284            }
285    
286          if (span < 0)
287            adjustRow(fv, rowIndex, availableSpan, preX);
288          else if (row.getViewCount() == 0)
289            {
290              View view = createView(fv, pos, Integer.MAX_VALUE, rowIndex);
291              row.append(view);
292            }
293          return row.getEndOffset();
294        }
295    
296        /**
297         * Creates physical views that form the rows of the flow view. This
298         * can be an entire view from the logical view (if it fits within the
299         * available span), a fragment of such a view (if it doesn't fit in the
300         * available span and can be broken down) or <code>null</code> (if it does
301         * not fit in the available span and also cannot be broken down).
302         *
303         * The default implementation fetches the logical view at the specified
304         * <code>startOffset</code>. If that view has a different startOffset than
305         * specified in the argument, a fragment is created using
306         * {@link View#createFragment(int, int)} that has the correct startOffset
307         * and the logical view's endOffset.
308         *
309         * @param fv the flow view
310         * @param startOffset the start offset for the view to be created
311         * @param spanLeft the available span
312         * @param rowIndex the index of the row
313         *
314         * @return a view to fill the row with, or <code>null</code> if there
315         *         is no view or view fragment that fits in the available span
316         */
317        protected View createView(FlowView fv, int startOffset, int spanLeft,
318                                  int rowIndex)
319        {
320           View logicalView = getLogicalView(fv);
321           int index = logicalView.getViewIndex(startOffset,
322                                                Position.Bias.Forward);
323           View retVal = logicalView.getView(index);
324           if (retVal.getStartOffset() != startOffset)
325             retVal = retVal.createFragment(startOffset, retVal.getEndOffset());
326           return retVal;
327        }
328    
329        /**
330         * Tries to adjust the specified row to fit within the desired span. The
331         * default implementation iterates through the children of the specified
332         * row to find the view that has the highest break weight and - if there
333         * is more than one view with such a break weight - which is nearest to
334         * the end of the row. If there is such a view that has a break weight >
335         * {@link View#BadBreakWeight}, this view is broken using the
336         * {@link View#breakView(int, int, float, float)} method and this view and
337         * all views after the now broken view are replaced by the broken view.
338         *
339         * @param fv the flow view
340         * @param rowIndex the index of the row to be adjusted
341         * @param desiredSpan the layout span
342         * @param x the X location at which the row starts
343         */
344        protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
345          // Determine the last view that has the highest break weight.
346          int axis = fv.getFlowAxis();
347          View row = fv.getView(rowIndex);
348          int count = row.getViewCount();
349          int breakIndex = -1;
350          int breakWeight = BadBreakWeight;
351          int breakSpan = 0;
352          int currentSpan = 0;
353          for (int i = 0; i < count; ++i)
354            {
355              View view = row.getView(i);
356              int spanLeft = desiredSpan - currentSpan;
357              int weight = view.getBreakWeight(axis, x + currentSpan, spanLeft);
358              if (weight >= breakWeight && weight > BadBreakWeight)
359                {
360                  breakIndex = i;
361                  breakSpan = currentSpan;
362                  breakWeight = weight;
363                  if (weight >= ForcedBreakWeight)
364                    // Don't search further.
365                    break;
366                }
367              currentSpan += view.getPreferredSpan(axis);
368            }
369    
370          // If there is a potential break location found, break the row at
371          // this location.
372          if (breakIndex >= 0)
373            {
374              int spanLeft = desiredSpan - breakSpan;
375              View toBeBroken = row.getView(breakIndex);
376              View brokenView = toBeBroken.breakView(axis,
377                                                     toBeBroken.getStartOffset(),
378                                                     x + breakSpan, spanLeft);
379              View lv = getLogicalView(fv);
380              for (int i = breakIndex; i < count; i++)
381                {
382                  View tmp = row.getView(i);
383                  if (contains(lv, tmp))
384                    tmp.setParent(lv);
385                  else if (tmp.getViewCount() > 0)
386                    reparent(tmp, lv);
387                }
388              row.replace(breakIndex, count - breakIndex,
389                          new View[]{ brokenView });
390            }
391    
392        }
393    
394        /**
395         * Helper method to determine if one view contains another as child.
396         */
397        private boolean contains(View view, View child)
398        {
399          boolean ret = false;
400          int n  = view.getViewCount();
401          for (int i = 0; i < n && ret == false; i++)
402            {
403              if (view.getView(i) == child)
404                ret = true;
405            }
406          return ret;
407        }
408    
409        /**
410         * Helper method that reparents the <code>view</code> and all of its
411         * decendents to the <code>parent</code> (the logical view).
412         *
413         * @param view the view to reparent
414         * @param parent the new parent
415         */
416        private void reparent(View view, View parent)
417        {
418          int n = view.getViewCount();
419          for (int i = 0; i < n; i++)
420            {
421              View tmp = view.getView(i);
422              if (contains(parent, tmp))
423                tmp.setParent(parent);
424              else
425                reparent(tmp, parent);
426            }
427        }
428      }
429    
430      /**
431       * This special subclass of <code>View</code> is used to represent
432       * the logical representation of this view. It does not support any
433       * visual representation, this is handled by the physical view implemented
434       * in the <code>FlowView</code>.
435       */
436      class LogicalView extends CompositeView
437      {
438        /**
439         * Creates a new LogicalView instance.
440         */
441        LogicalView(Element el)
442        {
443          super(el);
444        }
445    
446        /**
447         * Overridden to return the attributes of the parent
448         * (== the FlowView instance).
449         */
450        public AttributeSet getAttributes()
451        {
452          View p = getParent();
453          return p != null ? p.getAttributes() : null;
454        }
455    
456        protected void childAllocation(int index, Rectangle a)
457        {
458          // Nothing to do here (not visual).
459        }
460    
461        protected View getViewAtPoint(int x, int y, Rectangle r)
462        {
463          // Nothing to do here (not visual).
464          return null;
465        }
466    
467        protected boolean isAfter(int x, int y, Rectangle r)
468        {
469          // Nothing to do here (not visual).
470          return false;
471        }
472    
473        protected boolean isBefore(int x, int y, Rectangle r)
474        {
475          // Nothing to do here (not visual).
476          return false;
477        }
478    
479        public float getPreferredSpan(int axis)
480        {
481          float max = 0;
482          float pref = 0;
483          int n = getViewCount();
484          for (int i = 0; i < n; i++)
485            {
486              View v = getView(i);
487              pref += v.getPreferredSpan(axis);
488              if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
489                  >= ForcedBreakWeight)
490                {
491                  max = Math.max(max, pref);
492                  pref = 0;
493                }
494            }
495          max = Math.max(max, pref);
496          return max;
497        }
498    
499        public float getMinimumSpan(int axis)
500        {
501          float max = 0;
502          float min = 0;
503          boolean wrap = true;
504          int n = getViewCount();
505          for (int i = 0; i < n; i++)
506            {
507              View v = getView(i);
508              if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
509                  == BadBreakWeight)
510                {
511                  min += v.getPreferredSpan(axis);
512                  wrap = false;
513                }
514              else if (! wrap)
515                {
516                  max = Math.max(min, max);
517                  wrap = true;
518                  min = 0;
519                }
520            }
521          max = Math.max(max, min);
522          return max;
523        }
524    
525        public void paint(Graphics g, Shape s)
526        {
527          // Nothing to do here (not visual).
528        }
529    
530        /**
531         * Overridden to handle possible leaf elements.
532         */
533        protected void loadChildren(ViewFactory f)
534        {
535          Element el = getElement();
536          if (el.isLeaf())
537            {
538              View v = new LabelView(el);
539              append(v);
540            }
541          else
542            super.loadChildren(f);
543        }
544    
545        /**
546         * Overridden to reparent the children to this logical view, in case
547         * they have been parented by a row.
548         */
549        protected void forwardUpdateToView(View v, DocumentEvent e, Shape a,
550                                           ViewFactory f)
551        {
552          v.setParent(this);
553          super.forwardUpdateToView(v, e, a, f);
554        }
555    
556        /**
557         * Overridden to handle possible leaf element.
558         */
559        protected int getViewIndexAtPosition(int pos)
560        {
561          int index = 0;
562          if (! getElement().isLeaf())
563            index = super.getViewIndexAtPosition(pos);
564          return index;
565        }
566      }
567    
568      /**
569       * The shared instance of FlowStrategy.
570       */
571      static final FlowStrategy sharedStrategy = new FlowStrategy();
572    
573      /**
574       * The span of the <code>FlowView</code> that should be flowed.
575       */
576      protected int layoutSpan;
577    
578      /**
579       * Represents the logical child elements of this view, encapsulated within
580       * one parent view (an instance of a package private <code>LogicalView</code>
581       * class). These will be translated to a set of real views that are then
582       * displayed on screen. This translation is performed by the inner class
583       * {@link FlowStrategy}.
584       */
585      protected View layoutPool;
586    
587      /**
588       * The <code>FlowStrategy</code> to use for translating between the
589       * logical and physical view.
590       */
591      protected FlowStrategy strategy;
592    
593      /**
594       * Creates a new <code>FlowView</code> for the given
595       * <code>Element</code> and <code>axis</code>.
596       *
597       * @param element the element that is rendered by this FlowView
598       * @param axis the axis along which the view is tiled, either
599       *        <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow
600       *        axis is orthogonal to this one
601       */
602      public FlowView(Element element, int axis)
603      {
604        super(element, axis);
605        strategy = sharedStrategy;
606        layoutSpan = Short.MAX_VALUE;
607      }
608    
609      /**
610       * Returns the axis along which the view should be flowed. This is
611       * orthogonal to the axis along which the boxes are tiled.
612       *
613       * @return the axis along which the view should be flowed
614       */
615      public int getFlowAxis()
616      {
617        int axis = getAxis();
618        int flowAxis;
619     
620        if (axis == X_AXIS)
621          flowAxis = Y_AXIS;
622        else
623          flowAxis = X_AXIS;
624    
625        return flowAxis;
626    
627      }
628    
629      /**
630       * Returns the span of the flow for the specified child view. A flow
631       * layout can be shaped by providing different span values for different
632       * child indices. The default implementation returns the entire available
633       * span inside the view.
634       *
635       * @param index the index of the child for which to return the span
636       *
637       * @return the span of the flow for the specified child view
638       */
639      public int getFlowSpan(int index)
640      {
641        return layoutSpan;
642      }
643    
644      /**
645       * Returns the location along the flow axis where the flow span starts
646       * given a child view index. The flow can be shaped by providing
647       * different values here.
648       *
649       * @param index the index of the child for which to return the flow location
650       *
651       * @return the location along the flow axis where the flow span starts
652       */
653      public int getFlowStart(int index)
654      {
655        return 0;
656      }
657    
658      /**
659       * Creates a new view that represents a row within a flow.
660       *
661       * @return a view for a new row
662       */
663      protected abstract View createRow();
664    
665      /**
666       * Loads the children of this view. The <code>FlowView</code> does not
667       * directly load its children. Instead it creates a logical view
668       * ({@link #layoutPool}) which is filled by the logical child views.
669       * The real children are created at layout time and each represent one
670       * row.
671       *
672       * This method is called by {@link View#setParent} in order to initialize
673       * the view.
674       *
675       * @param vf the view factory to use for creating the child views
676       */
677      protected void loadChildren(ViewFactory vf)
678      {
679        if (layoutPool == null)
680          {
681            layoutPool = new LogicalView(getElement());
682          }
683        layoutPool.setParent(this);
684        // Initialize the flow strategy.
685        strategy.insertUpdate(this, null, null);
686      }
687    
688      /**
689       * Performs the layout of this view. If the span along the flow axis changed,
690       * this first calls {@link FlowStrategy#layout} in order to rebuild the
691       * rows of this view. Then the superclass's behaviour is called to arrange
692       * the rows within the box.
693       *
694       * @param width the width of the view
695       * @param height the height of the view
696       */
697      protected void layout(int width, int height)
698      {
699        int flowAxis = getFlowAxis();
700        int span; 
701        if (flowAxis == X_AXIS)
702          span = (int) width;
703        else
704          span = (int) height;
705    
706        if (layoutSpan != span)
707          {
708            layoutChanged(flowAxis);
709            layoutChanged(getAxis());
710            layoutSpan = span;
711          }
712    
713        if (! isLayoutValid(flowAxis))
714          {
715            int axis = getAxis();
716            int oldSpan = axis == X_AXIS ? getWidth() : getHeight();
717            strategy.layout(this);
718            int newSpan = (int) getPreferredSpan(axis);
719            if (oldSpan != newSpan)
720              {
721                View parent = getParent();
722                if (parent != null)
723                  parent.preferenceChanged(this, axis == X_AXIS, axis == Y_AXIS);
724              }
725          }
726    
727        super.layout(width, height);
728      }
729    
730      /**
731       * Receice notification that some content has been inserted in the region
732       * that this view is responsible for. This calls
733       * {@link FlowStrategy#insertUpdate}.
734       *
735       * @param changes the document event describing the changes
736       * @param a the current allocation of the view
737       * @param vf the view factory that is used for creating new child views
738       */
739      public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
740      {
741        // First we must send the insertUpdate to the logical view so it can
742        // be updated accordingly.
743        layoutPool.insertUpdate(changes, a, vf);
744        strategy.insertUpdate(this, changes, getInsideAllocation(a));
745      }
746    
747      /**
748       * Receice notification that some content has been removed from the region
749       * that this view is responsible for. This calls
750       * {@link FlowStrategy#removeUpdate}.
751       *
752       * @param changes the document event describing the changes
753       * @param a the current allocation of the view
754       * @param vf the view factory that is used for creating new child views
755       */
756      public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
757      {
758        layoutPool.removeUpdate(changes, a, vf);
759        strategy.removeUpdate(this, changes, getInsideAllocation(a));
760      }
761    
762      /**
763       * Receice notification that some attributes changed in the region
764       * that this view is responsible for. This calls
765       * {@link FlowStrategy#changedUpdate}.
766       *
767       * @param changes the document event describing the changes
768       * @param a the current allocation of the view
769       * @param vf the view factory that is used for creating new child views
770       */
771      public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
772      {
773        layoutPool.changedUpdate(changes, a, vf);
774        strategy.changedUpdate(this, changes, getInsideAllocation(a));
775      }
776    
777      /**
778       * Returns the index of the child <code>View</code> for the given model
779       * position.
780       *
781       * This is implemented to iterate over the children of this
782       * view (the rows) and return the index of the first view that contains
783       * the given position.
784       *
785       * @param pos the model position for whicht the child <code>View</code> is
786       *        queried
787       *
788       * @return the index of the child <code>View</code> for the given model
789       *         position
790       */
791      protected int getViewIndexAtPosition(int pos)
792      {
793        // First make sure we have a valid layout.
794        if (!isAllocationValid())
795          layout(getWidth(), getHeight());
796    
797        int count = getViewCount();
798        int result = -1;
799    
800        for (int i = 0; i < count; ++i)
801          {
802            View child = getView(i);
803            int start = child.getStartOffset();
804            int end = child.getEndOffset();
805            if (start <= pos && end > pos)
806              {
807                result = i;
808                break;
809              }
810          }
811        return result;
812      }
813    
814      /**
815       * Calculates the size requirements of this <code>BoxView</code> along
816       * its minor axis, that is the axis opposite to the axis specified in the
817       * constructor.
818       *
819       * This is overridden and forwards the request to the logical view.
820       *
821       * @param axis the axis that is examined
822       * @param r the <code>SizeRequirements</code> object to hold the result,
823       *        if <code>null</code>, a new one is created
824       *
825       * @return the size requirements for this <code>BoxView</code> along
826       *         the specified axis
827       */
828      protected SizeRequirements calculateMinorAxisRequirements(int axis,
829                                                                SizeRequirements r)
830      {
831        SizeRequirements res = r;
832        if (res == null)
833          res = new SizeRequirements();
834        res.minimum = (int) layoutPool.getMinimumSpan(axis);
835        res.preferred = Math.max(res.minimum,
836                                 (int) layoutPool.getPreferredSpan(axis));
837        res.maximum = Integer.MAX_VALUE;
838        res.alignment = 0.5F;
839        return res;
840      }
841    }