1 /*
2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing.text;
26 
27 import java.io.PrintStream;
28 import java.util.Vector;
29 import java.awt.*;
30 import javax.swing.event.DocumentEvent;
31 import javax.swing.SizeRequirements;
32 
33 /**
34  * A view that arranges its children into a box shape by tiling
35  * its children along an axis.  The box is somewhat like that
36  * found in TeX where there is alignment of the
37  * children, flexibility of the children is considered, etc.
38  * This is a building block that might be useful to represent
39  * things like a collection of lines, paragraphs,
40  * lists, columns, pages, etc.  The axis along which the children are tiled is
41  * considered the major axis.  The orthogonal axis is the minor axis.
42  * <p>
43  * Layout for each axis is handled separately by the methods
44  * <code>layoutMajorAxis</code> and <code>layoutMinorAxis</code>.
45  * Subclasses can change the layout algorithm by
46  * reimplementing these methods.    These methods will be called
47  * as necessary depending upon whether or not there is cached
48  * layout information and the cache is considered
49  * valid.  These methods are typically called if the given size
50  * along the axis changes, or if <code>layoutChanged</code> is
51  * called to force an updated layout.  The <code>layoutChanged</code>
52  * method invalidates cached layout information, if there is any.
53  * The requirements published to the parent view are calculated by
54  * the methods <code>calculateMajorAxisRequirements</code>
55  * and  <code>calculateMinorAxisRequirements</code>.
56  * If the layout algorithm is changed, these methods will
57  * likely need to be reimplemented.
58  *
59  * @author  Timothy Prinzing
60  */
61 public class BoxView extends CompositeView {
62 
63     /**
64      * Constructs a <code>BoxView</code>.
65      *
66      * @param elem the element this view is responsible for
67      * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
68      */
BoxView(Element elem, int axis)69     public BoxView(Element elem, int axis) {
70         super(elem);
71         tempRect = new Rectangle();
72         this.majorAxis = axis;
73 
74         majorOffsets = new int[0];
75         majorSpans = new int[0];
76         majorReqValid = false;
77         majorAllocValid = false;
78         minorOffsets = new int[0];
79         minorSpans = new int[0];
80         minorReqValid = false;
81         minorAllocValid = false;
82     }
83 
84     /**
85      * Fetches the tile axis property.  This is the axis along which
86      * the child views are tiled.
87      *
88      * @return the major axis of the box, either
89      *  <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
90      *
91      * @since 1.3
92      */
getAxis()93     public int getAxis() {
94         return majorAxis;
95     }
96 
97     /**
98      * Sets the tile axis property.  This is the axis along which
99      * the child views are tiled.
100      *
101      * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
102      *
103      * @since 1.3
104      */
setAxis(int axis)105     public void setAxis(int axis) {
106         boolean axisChanged = (axis != majorAxis);
107         majorAxis = axis;
108         if (axisChanged) {
109             preferenceChanged(null, true, true);
110         }
111     }
112 
113     /**
114      * Invalidates the layout along an axis.  This happens
115      * automatically if the preferences have changed for
116      * any of the child views.  In some cases the layout
117      * may need to be recalculated when the preferences
118      * have not changed.  The layout can be marked as
119      * invalid by calling this method.  The layout will
120      * be updated the next time the <code>setSize</code> method
121      * is called on this view (typically in paint).
122      *
123      * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
124      *
125      * @since 1.3
126      */
layoutChanged(int axis)127     public void layoutChanged(int axis) {
128         if (axis == majorAxis) {
129             majorAllocValid = false;
130         } else {
131             minorAllocValid = false;
132         }
133     }
134 
135     /**
136      * Determines if the layout is valid along the given axis.
137      * @return if the layout is valid along the given axis
138      *
139      * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
140      *
141      * @since 1.4
142      */
isLayoutValid(int axis)143     protected boolean isLayoutValid(int axis) {
144         if (axis == majorAxis) {
145             return majorAllocValid;
146         } else {
147             return minorAllocValid;
148         }
149     }
150 
151     /**
152      * Paints a child.  By default
153      * that is all it does, but a subclass can use this to paint
154      * things relative to the child.
155      *
156      * @param g the graphics context
157      * @param alloc the allocated region to paint into
158      * @param index the child index, &gt;= 0 &amp;&amp; &lt; getViewCount()
159      */
paintChild(Graphics g, Rectangle alloc, int index)160     protected void paintChild(Graphics g, Rectangle alloc, int index) {
161         View child = getView(index);
162         child.paint(g, alloc);
163     }
164 
165     // --- View methods ---------------------------------------------
166 
167     /**
168      * Invalidates the layout and resizes the cache of
169      * requests/allocations.  The child allocations can still
170      * be accessed for the old layout, but the new children
171      * will have an offset and span of 0.
172      *
173      * @param index the starting index into the child views to insert
174      *   the new views; this should be a value &gt;= 0 and &lt;= getViewCount
175      * @param length the number of existing child views to remove;
176      *   This should be a value &gt;= 0 and &lt;= (getViewCount() - offset)
177      * @param elems the child views to add; this value can be
178      *   <code>null</code>to indicate no children are being added
179      *   (useful to remove)
180      */
replace(int index, int length, View[] elems)181     public void replace(int index, int length, View[] elems) {
182         super.replace(index, length, elems);
183 
184         // invalidate cache
185         int nInserted = (elems != null) ? elems.length : 0;
186         majorOffsets = updateLayoutArray(majorOffsets, index, nInserted);
187         majorSpans = updateLayoutArray(majorSpans, index, nInserted);
188         majorReqValid = false;
189         majorAllocValid = false;
190         minorOffsets = updateLayoutArray(minorOffsets, index, nInserted);
191         minorSpans = updateLayoutArray(minorSpans, index, nInserted);
192         minorReqValid = false;
193         minorAllocValid = false;
194     }
195 
196     /**
197      * Resizes the given layout array to match the new number of
198      * child views.  The current number of child views are used to
199      * produce the new array.  The contents of the old array are
200      * inserted into the new array at the appropriate places so that
201      * the old layout information is transferred to the new array.
202      *
203      * @param oldArray the original layout array
204      * @param offset location where new views will be inserted
205      * @param nInserted the number of child views being inserted;
206      *          therefore the number of blank spaces to leave in the
207      *          new array at location <code>offset</code>
208      * @return the new layout array
209      */
updateLayoutArray(int[] oldArray, int offset, int nInserted)210     int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) {
211         int n = getViewCount();
212         int[] newArray = new int[n];
213 
214         System.arraycopy(oldArray, 0, newArray, 0, offset);
215         System.arraycopy(oldArray, offset,
216                          newArray, offset + nInserted, n - nInserted - offset);
217         return newArray;
218     }
219 
220     /**
221      * Forwards the given <code>DocumentEvent</code> to the child views
222      * that need to be notified of the change to the model.
223      * If a child changed its requirements and the allocation
224      * was valid prior to forwarding the portion of the box
225      * from the starting child to the end of the box will
226      * be repainted.
227      *
228      * @param ec changes to the element this view is responsible
229      *  for (may be <code>null</code> if there were no changes)
230      * @param e the change information from the associated document
231      * @param a the current allocation of the view
232      * @param f the factory to use to rebuild if the view has children
233      * @see #insertUpdate
234      * @see #removeUpdate
235      * @see #changedUpdate
236      * @since 1.3
237      */
forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, Shape a, ViewFactory f)238     protected void forwardUpdate(DocumentEvent.ElementChange ec,
239                                  DocumentEvent e, Shape a, ViewFactory f) {
240         boolean wasValid = isLayoutValid(majorAxis);
241         super.forwardUpdate(ec, e, a, f);
242 
243         // determine if a repaint is needed
244         if (wasValid && (! isLayoutValid(majorAxis))) {
245             // Repaint is needed because one of the tiled children
246             // have changed their span along the major axis.  If there
247             // is a hosting component and an allocated shape we repaint.
248             Component c = getContainer();
249             if ((a != null) && (c != null)) {
250                 int pos = e.getOffset();
251                 int index = getViewIndexAtPosition(pos);
252                 Rectangle alloc = getInsideAllocation(a);
253                 if (majorAxis == X_AXIS) {
254                     alloc.x += majorOffsets[index];
255                     alloc.width -= majorOffsets[index];
256                 } else {
257                     alloc.y += minorOffsets[index];
258                     alloc.height -= minorOffsets[index];
259                 }
260                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
261             }
262         }
263     }
264 
265     /**
266      * This is called by a child to indicate its
267      * preferred span has changed.  This is implemented to
268      * throw away cached layout information so that new
269      * calculations will be done the next time the children
270      * need an allocation.
271      *
272      * @param child the child view
273      * @param width true if the width preference should change
274      * @param height true if the height preference should change
275      */
preferenceChanged(View child, boolean width, boolean height)276     public void preferenceChanged(View child, boolean width, boolean height) {
277         boolean majorChanged = (majorAxis == X_AXIS) ? width : height;
278         boolean minorChanged = (majorAxis == X_AXIS) ? height : width;
279         if (majorChanged) {
280             majorReqValid = false;
281             majorAllocValid = false;
282         }
283         if (minorChanged) {
284             minorReqValid = false;
285             minorAllocValid = false;
286         }
287         super.preferenceChanged(child, width, height);
288     }
289 
290     /**
291      * Gets the resize weight.  A value of 0 or less is not resizable.
292      *
293      * @param axis may be either <code>View.X_AXIS</code> or
294      *          <code>View.Y_AXIS</code>
295      * @return the weight
296      * @exception IllegalArgumentException for an invalid axis
297      */
getResizeWeight(int axis)298     public int getResizeWeight(int axis) {
299         checkRequests(axis);
300         if (axis == majorAxis) {
301             if ((majorRequest.preferred != majorRequest.minimum) ||
302                 (majorRequest.preferred != majorRequest.maximum)) {
303                 return 1;
304             }
305         } else {
306             if ((minorRequest.preferred != minorRequest.minimum) ||
307                 (minorRequest.preferred != minorRequest.maximum)) {
308                 return 1;
309             }
310         }
311         return 0;
312     }
313 
314     /**
315      * Sets the size of the view along an axis.  This should cause
316      * layout of the view along the given axis.
317      *
318      * @param axis may be either <code>View.X_AXIS</code> or
319      *          <code>View.Y_AXIS</code>
320      * @param span the span to layout to >= 0
321      */
setSpanOnAxis(int axis, float span)322     void setSpanOnAxis(int axis, float span) {
323         if (axis == majorAxis) {
324             if (majorSpan != (int) span) {
325                 majorAllocValid = false;
326             }
327             if (! majorAllocValid) {
328                 // layout the major axis
329                 majorSpan = (int) span;
330                 checkRequests(majorAxis);
331                 layoutMajorAxis(majorSpan, axis, majorOffsets, majorSpans);
332                 majorAllocValid = true;
333 
334                 // flush changes to the children
335                 updateChildSizes();
336             }
337         } else {
338             if (((int) span) != minorSpan) {
339                 minorAllocValid = false;
340             }
341             if (! minorAllocValid) {
342                 // layout the minor axis
343                 minorSpan = (int) span;
344                 checkRequests(axis);
345                 layoutMinorAxis(minorSpan, axis, minorOffsets, minorSpans);
346                 minorAllocValid = true;
347 
348                 // flush changes to the children
349                 updateChildSizes();
350             }
351         }
352     }
353 
354     /**
355      * Propagates the current allocations to the child views.
356      */
updateChildSizes()357     void updateChildSizes() {
358         int n = getViewCount();
359         if (majorAxis == X_AXIS) {
360             for (int i = 0; i < n; i++) {
361                 View v = getView(i);
362                 v.setSize((float) majorSpans[i], (float) minorSpans[i]);
363             }
364         } else {
365             for (int i = 0; i < n; i++) {
366                 View v = getView(i);
367                 v.setSize((float) minorSpans[i], (float) majorSpans[i]);
368             }
369         }
370     }
371 
372     /**
373      * Returns the size of the view along an axis.  This is implemented
374      * to return zero.
375      *
376      * @param axis may be either <code>View.X_AXIS</code> or
377      *          <code>View.Y_AXIS</code>
378      * @return the current span of the view along the given axis, >= 0
379      */
getSpanOnAxis(int axis)380     float getSpanOnAxis(int axis) {
381         if (axis == majorAxis) {
382             return majorSpan;
383         } else {
384             return minorSpan;
385         }
386     }
387 
388     /**
389      * Sets the size of the view.  This should cause
390      * layout of the view if the view caches any layout
391      * information.  This is implemented to call the
392      * layout method with the sizes inside of the insets.
393      *
394      * @param width the width &gt;= 0
395      * @param height the height &gt;= 0
396      */
setSize(float width, float height)397     public void setSize(float width, float height) {
398         layout(Math.max(0, (int)(width - getLeftInset() - getRightInset())),
399                Math.max(0, (int)(height - getTopInset() - getBottomInset())));
400     }
401 
402     /**
403      * Renders the <code>BoxView</code> using the given
404      * rendering surface and area
405      * on that surface.  Only the children that intersect
406      * the clip bounds of the given <code>Graphics</code>
407      * will be rendered.
408      *
409      * @param g the rendering surface to use
410      * @param allocation the allocated region to render into
411      * @see View#paint
412      */
paint(Graphics g, Shape allocation)413     public void paint(Graphics g, Shape allocation) {
414         Rectangle alloc = (allocation instanceof Rectangle) ?
415                            (Rectangle)allocation : allocation.getBounds();
416         int n = getViewCount();
417         int x = alloc.x + getLeftInset();
418         int y = alloc.y + getTopInset();
419         Rectangle clip = g.getClipBounds();
420         for (int i = 0; i < n; i++) {
421             tempRect.x = x + getOffset(X_AXIS, i);
422             tempRect.y = y + getOffset(Y_AXIS, i);
423             tempRect.width = getSpan(X_AXIS, i);
424             tempRect.height = getSpan(Y_AXIS, i);
425             int trx0 = tempRect.x, trx1 = trx0 + tempRect.width;
426             int try0 = tempRect.y, try1 = try0 + tempRect.height;
427             int crx0 = clip.x, crx1 = crx0 + clip.width;
428             int cry0 = clip.y, cry1 = cry0 + clip.height;
429             // We should paint views that intersect with clipping region
430             // even if the intersection has no inside points (is a line).
431             // This is needed for supporting views that have zero width, like
432             // views that contain only combining marks.
433             if ((trx1 >= crx0) && (try1 >= cry0) && (crx1 >= trx0) && (cry1 >= try0)) {
434                 paintChild(g, tempRect, i);
435             }
436         }
437     }
438 
439     /**
440      * Fetches the allocation for the given child view.
441      * This enables finding out where various views
442      * are located.  This is implemented to return
443      * <code>null</code> if the layout is invalid,
444      * otherwise the superclass behavior is executed.
445      *
446      * @param index the index of the child, &gt;= 0 &amp;&amp; &gt; getViewCount()
447      * @param a  the allocation to this view
448      * @return the allocation to the child; or <code>null</code>
449      *          if <code>a</code> is <code>null</code>;
450      *          or <code>null</code> if the layout is invalid
451      */
getChildAllocation(int index, Shape a)452     public Shape getChildAllocation(int index, Shape a) {
453         if (a != null) {
454             Shape ca = super.getChildAllocation(index, a);
455             if ((ca != null) && (! isAllocationValid())) {
456                 // The child allocation may not have been set yet.
457                 Rectangle r = (ca instanceof Rectangle) ?
458                     (Rectangle) ca : ca.getBounds();
459                 if ((r.width == 0) && (r.height == 0)) {
460                     return null;
461                 }
462             }
463             return ca;
464         }
465         return null;
466     }
467 
468     /**
469      * Provides a mapping from the document model coordinate space
470      * to the coordinate space of the view mapped to it.  This makes
471      * sure the allocation is valid before calling the superclass.
472      *
473      * @param pos the position to convert &gt;= 0
474      * @param a the allocated region to render into
475      * @return the bounding box of the given position
476      * @exception BadLocationException  if the given position does
477      *  not represent a valid location in the associated document
478      * @see View#modelToView
479      */
modelToView(int pos, Shape a, Position.Bias b)480     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
481         if (! isAllocationValid()) {
482             Rectangle alloc = a.getBounds();
483             setSize(alloc.width, alloc.height);
484         }
485         return super.modelToView(pos, a, b);
486     }
487 
488     /**
489      * Provides a mapping from the view coordinate space to the logical
490      * coordinate space of the model.
491      *
492      * @param x   x coordinate of the view location to convert &gt;= 0
493      * @param y   y coordinate of the view location to convert &gt;= 0
494      * @param a the allocated region to render into
495      * @return the location within the model that best represents the
496      *  given point in the view &gt;= 0
497      * @see View#viewToModel
498      */
viewToModel(float x, float y, Shape a, Position.Bias[] bias)499     public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
500         if (! isAllocationValid()) {
501             Rectangle alloc = a.getBounds();
502             setSize(alloc.width, alloc.height);
503         }
504         return super.viewToModel(x, y, a, bias);
505     }
506 
507     /**
508      * Determines the desired alignment for this view along an
509      * axis.  This is implemented to give the total alignment
510      * needed to position the children with the alignment points
511      * lined up along the axis orthogonal to the axis that is
512      * being tiled.  The axis being tiled will request to be
513      * centered (i.e. 0.5f).
514      *
515      * @param axis may be either <code>View.X_AXIS</code>
516      *   or <code>View.Y_AXIS</code>
517      * @return the desired alignment &gt;= 0.0f &amp;&amp; &lt;= 1.0f; this should
518      *   be a value between 0.0 and 1.0 where 0 indicates alignment at the
519      *   origin and 1.0 indicates alignment to the full span
520      *   away from the origin; an alignment of 0.5 would be the
521      *   center of the view
522      * @exception IllegalArgumentException for an invalid axis
523      */
getAlignment(int axis)524     public float getAlignment(int axis) {
525         checkRequests(axis);
526         if (axis == majorAxis) {
527             return majorRequest.alignment;
528         } else {
529             return minorRequest.alignment;
530         }
531     }
532 
533     /**
534      * Determines the preferred span for this view along an
535      * axis.
536      *
537      * @param axis may be either <code>View.X_AXIS</code>
538      *           or <code>View.Y_AXIS</code>
539      * @return   the span the view would like to be rendered into &gt;= 0;
540      *           typically the view is told to render into the span
541      *           that is returned, although there is no guarantee;
542      *           the parent may choose to resize or break the view
543      * @exception IllegalArgumentException for an invalid axis type
544      */
getPreferredSpan(int axis)545     public float getPreferredSpan(int axis) {
546         checkRequests(axis);
547         float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
548             getTopInset() + getBottomInset();
549         if (axis == majorAxis) {
550             return ((float)majorRequest.preferred) + marginSpan;
551         } else {
552             return ((float)minorRequest.preferred) + marginSpan;
553         }
554     }
555 
556     /**
557      * Determines the minimum span for this view along an
558      * axis.
559      *
560      * @param axis may be either <code>View.X_AXIS</code>
561      *           or <code>View.Y_AXIS</code>
562      * @return  the span the view would like to be rendered into &gt;= 0;
563      *           typically the view is told to render into the span
564      *           that is returned, although there is no guarantee;
565      *           the parent may choose to resize or break the view
566      * @exception IllegalArgumentException for an invalid axis type
567      */
getMinimumSpan(int axis)568     public float getMinimumSpan(int axis) {
569         checkRequests(axis);
570         float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
571             getTopInset() + getBottomInset();
572         if (axis == majorAxis) {
573             return ((float)majorRequest.minimum) + marginSpan;
574         } else {
575             return ((float)minorRequest.minimum) + marginSpan;
576         }
577     }
578 
579     /**
580      * Determines the maximum span for this view along an
581      * axis.
582      *
583      * @param axis may be either <code>View.X_AXIS</code>
584      *           or <code>View.Y_AXIS</code>
585      * @return   the span the view would like to be rendered into &gt;= 0;
586      *           typically the view is told to render into the span
587      *           that is returned, although there is no guarantee;
588      *           the parent may choose to resize or break the view
589      * @exception IllegalArgumentException for an invalid axis type
590      */
getMaximumSpan(int axis)591     public float getMaximumSpan(int axis) {
592         checkRequests(axis);
593         float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
594             getTopInset() + getBottomInset();
595         if (axis == majorAxis) {
596             return ((float)majorRequest.maximum) + marginSpan;
597         } else {
598             return ((float)minorRequest.maximum) + marginSpan;
599         }
600     }
601 
602     // --- local methods ----------------------------------------------------
603 
604     /**
605      * Are the allocations for the children still
606      * valid?
607      *
608      * @return true if allocations still valid
609      */
isAllocationValid()610     protected boolean isAllocationValid() {
611         return (majorAllocValid && minorAllocValid);
612     }
613 
614     /**
615      * Determines if a point falls before an allocated region.
616      *
617      * @param x the X coordinate &gt;= 0
618      * @param y the Y coordinate &gt;= 0
619      * @param innerAlloc the allocated region; this is the area
620      *   inside of the insets
621      * @return true if the point lies before the region else false
622      */
isBefore(int x, int y, Rectangle innerAlloc)623     protected boolean isBefore(int x, int y, Rectangle innerAlloc) {
624         if (majorAxis == View.X_AXIS) {
625             return (x < innerAlloc.x);
626         } else {
627             return (y < innerAlloc.y);
628         }
629     }
630 
631     /**
632      * Determines if a point falls after an allocated region.
633      *
634      * @param x the X coordinate &gt;= 0
635      * @param y the Y coordinate &gt;= 0
636      * @param innerAlloc the allocated region; this is the area
637      *   inside of the insets
638      * @return true if the point lies after the region else false
639      */
isAfter(int x, int y, Rectangle innerAlloc)640     protected boolean isAfter(int x, int y, Rectangle innerAlloc) {
641         if (majorAxis == View.X_AXIS) {
642             return (x > (innerAlloc.width + innerAlloc.x));
643         } else {
644             return (y > (innerAlloc.height + innerAlloc.y));
645         }
646     }
647 
648     /**
649      * Fetches the child view at the given coordinates.
650      *
651      * @param x the X coordinate &gt;= 0
652      * @param y the Y coordinate &gt;= 0
653      * @param alloc the parents inner allocation on entry, which should
654      *   be changed to the child's allocation on exit
655      * @return the view
656      */
getViewAtPoint(int x, int y, Rectangle alloc)657     protected View getViewAtPoint(int x, int y, Rectangle alloc) {
658         int n = getViewCount();
659         if (majorAxis == View.X_AXIS) {
660             if (x < (alloc.x + majorOffsets[0])) {
661                 childAllocation(0, alloc);
662                 return getView(0);
663             }
664             for (int i = 0; i < n; i++) {
665                 if (x < (alloc.x + majorOffsets[i])) {
666                     childAllocation(i - 1, alloc);
667                     return getView(i - 1);
668                 }
669             }
670             childAllocation(n - 1, alloc);
671             return getView(n - 1);
672         } else {
673             if (y < (alloc.y + majorOffsets[0])) {
674                 childAllocation(0, alloc);
675                 return getView(0);
676             }
677             for (int i = 0; i < n; i++) {
678                 if (y < (alloc.y + majorOffsets[i])) {
679                     childAllocation(i - 1, alloc);
680                     return getView(i - 1);
681                 }
682             }
683             childAllocation(n - 1, alloc);
684             return getView(n - 1);
685         }
686     }
687 
688     /**
689      * Allocates a region for a child view.
690      *
691      * @param index the index of the child view to
692      *   allocate, &gt;= 0 &amp;&amp; &lt; getViewCount()
693      * @param alloc the allocated region
694      */
childAllocation(int index, Rectangle alloc)695     protected void childAllocation(int index, Rectangle alloc) {
696         alloc.x += getOffset(X_AXIS, index);
697         alloc.y += getOffset(Y_AXIS, index);
698         alloc.width = getSpan(X_AXIS, index);
699         alloc.height = getSpan(Y_AXIS, index);
700     }
701 
702     /**
703      * Perform layout on the box
704      *
705      * @param width the width (inside of the insets) &gt;= 0
706      * @param height the height (inside of the insets) &gt;= 0
707      */
layout(int width, int height)708     protected void layout(int width, int height) {
709         setSpanOnAxis(X_AXIS, width);
710         setSpanOnAxis(Y_AXIS, height);
711     }
712 
713     /**
714      * Returns the current width of the box.  This is the width that
715      * it was last allocated.
716      * @return the current width of the box
717      */
getWidth()718     public int getWidth() {
719         int span;
720         if (majorAxis == X_AXIS) {
721             span = majorSpan;
722         } else {
723             span = minorSpan;
724         }
725         span += getLeftInset() - getRightInset();
726         return span;
727     }
728 
729     /**
730      * Returns the current height of the box.  This is the height that
731      * it was last allocated.
732      * @return the current height of the box
733      */
getHeight()734     public int getHeight() {
735         int span;
736         if (majorAxis == Y_AXIS) {
737             span = majorSpan;
738         } else {
739             span = minorSpan;
740         }
741         span += getTopInset() - getBottomInset();
742         return span;
743     }
744 
745     /**
746      * Performs layout for the major axis of the box (i.e. the
747      * axis that it represents). The results of the layout (the
748      * offset and span for each children) are placed in the given
749      * arrays which represent the allocations to the children
750      * along the major axis.
751      *
752      * @param targetSpan the total span given to the view, which
753      *  would be used to layout the children
754      * @param axis the axis being layed out
755      * @param offsets the offsets from the origin of the view for
756      *  each of the child views; this is a return value and is
757      *  filled in by the implementation of this method
758      * @param spans the span of each child view; this is a return
759      *  value and is filled in by the implementation of this method
760      */
layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans)761     protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
762         /*
763          * first pass, calculate the preferred sizes
764          * and the flexibility to adjust the sizes.
765          */
766         long preferred = 0;
767         int n = getViewCount();
768         for (int i = 0; i < n; i++) {
769             View v = getView(i);
770             spans[i] = (int) v.getPreferredSpan(axis);
771             preferred += spans[i];
772         }
773 
774         /*
775          * Second pass, expand or contract by as much as possible to reach
776          * the target span.
777          */
778 
779         // determine the adjustment to be made
780         long desiredAdjustment = targetSpan - preferred;
781         float adjustmentFactor = 0.0f;
782         int[] diffs = null;
783 
784         if (desiredAdjustment != 0) {
785             long totalSpan = 0;
786             diffs = new int[n];
787             for (int i = 0; i < n; i++) {
788                 View v = getView(i);
789                 int tmp;
790                 if (desiredAdjustment < 0) {
791                     tmp = (int)v.getMinimumSpan(axis);
792                     diffs[i] = spans[i] - tmp;
793                 } else {
794                     tmp = (int)v.getMaximumSpan(axis);
795                     diffs[i] = tmp - spans[i];
796                 }
797                 totalSpan += tmp;
798             }
799 
800             float maximumAdjustment = Math.abs(totalSpan - preferred);
801                 adjustmentFactor = desiredAdjustment / maximumAdjustment;
802                 adjustmentFactor = Math.min(adjustmentFactor, 1.0f);
803                 adjustmentFactor = Math.max(adjustmentFactor, -1.0f);
804             }
805 
806         // make the adjustments
807         int totalOffset = 0;
808         for (int i = 0; i < n; i++) {
809             offsets[i] = totalOffset;
810             if (desiredAdjustment != 0) {
811                 float adjF = adjustmentFactor * diffs[i];
812                 spans[i] += Math.round(adjF);
813             }
814             totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
815         }
816     }
817 
818     /**
819      * Performs layout for the minor axis of the box (i.e. the
820      * axis orthogonal to the axis that it represents). The results
821      * of the layout (the offset and span for each children) are
822      * placed in the given arrays which represent the allocations to
823      * the children along the minor axis.
824      *
825      * @param targetSpan the total span given to the view, which
826      *  would be used to layout the children
827      * @param axis the axis being layed out
828      * @param offsets the offsets from the origin of the view for
829      *  each of the child views; this is a return value and is
830      *  filled in by the implementation of this method
831      * @param spans the span of each child view; this is a return
832      *  value and is filled in by the implementation of this method
833      */
layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans)834     protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
835         int n = getViewCount();
836         for (int i = 0; i < n; i++) {
837             View v = getView(i);
838             int max = (int) v.getMaximumSpan(axis);
839             if (max < targetSpan) {
840                 // can't make the child this wide, align it
841                 float align = v.getAlignment(axis);
842                 offsets[i] = (int) ((targetSpan - max) * align);
843                 spans[i] = max;
844             } else {
845                 // make it the target width, or as small as it can get.
846                 int min = (int)v.getMinimumSpan(axis);
847                 offsets[i] = 0;
848                 spans[i] = Math.max(min, targetSpan);
849             }
850         }
851     }
852 
853     /**
854      * Calculates the size requirements for the major axis
855      * <code>axis</code>.
856      *
857      * @param axis the axis being studied
858      * @param r the <code>SizeRequirements</code> object;
859      *          if <code>null</code> one will be created
860      * @return the newly initialized <code>SizeRequirements</code> object
861      * @see javax.swing.SizeRequirements
862      */
calculateMajorAxisRequirements(int axis, SizeRequirements r)863     protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
864         // calculate tiled request
865         float min = 0;
866         float pref = 0;
867         float max = 0;
868 
869         int n = getViewCount();
870         for (int i = 0; i < n; i++) {
871             View v = getView(i);
872             min += v.getMinimumSpan(axis);
873             pref += v.getPreferredSpan(axis);
874             max += v.getMaximumSpan(axis);
875         }
876 
877         if (r == null) {
878             r = new SizeRequirements();
879         }
880         r.alignment = 0.5f;
881         r.minimum = (int) min;
882         r.preferred = (int) pref;
883         r.maximum = (int) max;
884         return r;
885     }
886 
887     /**
888      * Calculates the size requirements for the minor axis
889      * <code>axis</code>.
890      *
891      * @param axis the axis being studied
892      * @param r the <code>SizeRequirements</code> object;
893      *          if <code>null</code> one will be created
894      * @return the newly initialized <code>SizeRequirements</code> object
895      * @see javax.swing.SizeRequirements
896      */
calculateMinorAxisRequirements(int axis, SizeRequirements r)897     protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
898         int min = 0;
899         long pref = 0;
900         int max = Integer.MAX_VALUE;
901         int n = getViewCount();
902         for (int i = 0; i < n; i++) {
903             View v = getView(i);
904             min = Math.max((int) v.getMinimumSpan(axis), min);
905             pref = Math.max((int) v.getPreferredSpan(axis), pref);
906             max = Math.max((int) v.getMaximumSpan(axis), max);
907         }
908 
909         if (r == null) {
910             r = new SizeRequirements();
911             r.alignment = 0.5f;
912         }
913         r.preferred = (int) pref;
914         r.minimum = min;
915         r.maximum = max;
916         return r;
917     }
918 
919     /**
920      * Checks the request cache and update if needed.
921      * @param axis the axis being studied
922      * @exception IllegalArgumentException if <code>axis</code> is
923      *  neither <code>View.X_AXIS</code> nor <code>View.Y_AXIS</code>
924      */
checkRequests(int axis)925     void checkRequests(int axis) {
926         if ((axis != X_AXIS) && (axis != Y_AXIS)) {
927             throw new IllegalArgumentException("Invalid axis: " + axis);
928         }
929         if (axis == majorAxis) {
930             if (!majorReqValid) {
931                 majorRequest = calculateMajorAxisRequirements(axis,
932                                                               majorRequest);
933                 majorReqValid = true;
934             }
935         } else if (! minorReqValid) {
936             minorRequest = calculateMinorAxisRequirements(axis, minorRequest);
937             minorReqValid = true;
938         }
939     }
940 
941     /**
942      * Computes the location and extent of each child view
943      * in this <code>BoxView</code> given the <code>targetSpan</code>,
944      * which is the width (or height) of the region we have to
945      * work with.
946      *
947      * @param targetSpan the total span given to the view, which
948      *  would be used to layout the children
949      * @param axis the axis being studied, either
950      *          <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
951      * @param offsets an empty array filled by this method with
952      *          values specifying the location  of each child view
953      * @param spans  an empty array filled by this method with
954      *          values specifying the extent of each child view
955      */
baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans)956     protected void baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans) {
957         int totalAscent = (int)(targetSpan * getAlignment(axis));
958         int totalDescent = targetSpan - totalAscent;
959 
960         int n = getViewCount();
961 
962         for (int i = 0; i < n; i++) {
963             View v = getView(i);
964             float align = v.getAlignment(axis);
965             float viewSpan;
966 
967             if (v.getResizeWeight(axis) > 0) {
968                 // if resizable then resize to the best fit
969 
970                 // the smallest span possible
971                 float minSpan = v.getMinimumSpan(axis);
972                 // the largest span possible
973                 float maxSpan = v.getMaximumSpan(axis);
974 
975                 if (align == 0.0f) {
976                     // if the alignment is 0 then we need to fit into the descent
977                     viewSpan = Math.max(Math.min(maxSpan, totalDescent), minSpan);
978                 } else if (align == 1.0f) {
979                     // if the alignment is 1 then we need to fit into the ascent
980                     viewSpan = Math.max(Math.min(maxSpan, totalAscent), minSpan);
981                 } else {
982                     // figure out the span that we must fit into
983                     float fitSpan = Math.min(totalAscent / align,
984                                              totalDescent / (1.0f - align));
985                     // fit into the calculated span
986                     viewSpan = Math.max(Math.min(maxSpan, fitSpan), minSpan);
987                 }
988             } else {
989                 // otherwise use the preferred spans
990                 viewSpan = v.getPreferredSpan(axis);
991             }
992 
993             offsets[i] = totalAscent - (int)(viewSpan * align);
994             spans[i] = (int)viewSpan;
995         }
996     }
997 
998     /**
999      * Calculates the size requirements for this <code>BoxView</code>
1000      * by examining the size of each child view.
1001      *
1002      * @param axis the axis being studied
1003      * @param r the <code>SizeRequirements</code> object;
1004      *          if <code>null</code> one will be created
1005      * @return the newly initialized <code>SizeRequirements</code> object
1006      */
baselineRequirements(int axis, SizeRequirements r)1007     protected SizeRequirements baselineRequirements(int axis, SizeRequirements r) {
1008         SizeRequirements totalAscent = new SizeRequirements();
1009         SizeRequirements totalDescent = new SizeRequirements();
1010 
1011         if (r == null) {
1012             r = new SizeRequirements();
1013         }
1014 
1015         r.alignment = 0.5f;
1016 
1017         int n = getViewCount();
1018 
1019         // loop through all children calculating the max of all their ascents and
1020         // descents at minimum, preferred, and maximum sizes
1021         for (int i = 0; i < n; i++) {
1022             View v = getView(i);
1023             float align = v.getAlignment(axis);
1024             float span;
1025             int ascent;
1026             int descent;
1027 
1028             // find the maximum of the preferred ascents and descents
1029             span = v.getPreferredSpan(axis);
1030             ascent = (int)(align * span);
1031             descent = (int)(span - ascent);
1032             totalAscent.preferred = Math.max(ascent, totalAscent.preferred);
1033             totalDescent.preferred = Math.max(descent, totalDescent.preferred);
1034 
1035             if (v.getResizeWeight(axis) > 0) {
1036                 // if the view is resizable then do the same for the minimum and
1037                 // maximum ascents and descents
1038                 span = v.getMinimumSpan(axis);
1039                 ascent = (int)(align * span);
1040                 descent = (int)(span - ascent);
1041                 totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
1042                 totalDescent.minimum = Math.max(descent, totalDescent.minimum);
1043 
1044                 span = v.getMaximumSpan(axis);
1045                 ascent = (int)(align * span);
1046                 descent = (int)(span - ascent);
1047                 totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
1048                 totalDescent.maximum = Math.max(descent, totalDescent.maximum);
1049             } else {
1050                 // otherwise use the preferred
1051                 totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
1052                 totalDescent.minimum = Math.max(descent, totalDescent.minimum);
1053                 totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
1054                 totalDescent.maximum = Math.max(descent, totalDescent.maximum);
1055             }
1056         }
1057 
1058         // we now have an overall preferred, minimum, and maximum ascent and descent
1059 
1060         // calculate the preferred span as the sum of the preferred ascent and preferred descent
1061         r.preferred = (int)Math.min((long)totalAscent.preferred + (long)totalDescent.preferred,
1062                                     Integer.MAX_VALUE);
1063 
1064         // calculate the preferred alignment as the preferred ascent divided by the preferred span
1065         if (r.preferred > 0) {
1066             r.alignment = (float)totalAscent.preferred / r.preferred;
1067         }
1068 
1069 
1070         if (r.alignment == 0.0f) {
1071             // if the preferred alignment is 0 then the minimum and maximum spans are simply
1072             // the minimum and maximum descents since there's nothing above the baseline
1073             r.minimum = totalDescent.minimum;
1074             r.maximum = totalDescent.maximum;
1075         } else if (r.alignment == 1.0f) {
1076             // if the preferred alignment is 1 then the minimum and maximum spans are simply
1077             // the minimum and maximum ascents since there's nothing below the baseline
1078             r.minimum = totalAscent.minimum;
1079             r.maximum = totalAscent.maximum;
1080         } else {
1081             // we want to honor the preferred alignment so we calculate two possible minimum
1082             // span values using 1) the minimum ascent and the alignment, and 2) the minimum
1083             // descent and the alignment. We'll choose the larger of these two numbers.
1084             r.minimum = Math.round(Math.max(totalAscent.minimum / r.alignment,
1085                                           totalDescent.minimum / (1.0f - r.alignment)));
1086             // a similar calculation is made for the maximum but we choose the smaller number.
1087             r.maximum = Math.round(Math.min(totalAscent.maximum / r.alignment,
1088                                           totalDescent.maximum / (1.0f - r.alignment)));
1089         }
1090 
1091         return r;
1092     }
1093 
1094     /**
1095      * Fetches the offset of a particular child's current layout.
1096      * @param axis the axis being studied
1097      * @param childIndex the index of the requested child
1098      * @return the offset (location) for the specified child
1099      */
getOffset(int axis, int childIndex)1100     protected int getOffset(int axis, int childIndex) {
1101         int[] offsets = (axis == majorAxis) ? majorOffsets : minorOffsets;
1102         return offsets[childIndex];
1103     }
1104 
1105     /**
1106      * Fetches the span of a particular child's current layout.
1107      * @param axis the axis being studied
1108      * @param childIndex the index of the requested child
1109      * @return the span (width or height) of the specified child
1110      */
getSpan(int axis, int childIndex)1111     protected int getSpan(int axis, int childIndex) {
1112         int[] spans = (axis == majorAxis) ? majorSpans : minorSpans;
1113         return spans[childIndex];
1114     }
1115 
1116     /**
1117      * Determines in which direction the next view lays.
1118      * Consider the View at index n. Typically the <code>View</code>s
1119      * are layed out from left to right, so that the <code>View</code>
1120      * to the EAST will be at index n + 1, and the <code>View</code>
1121      * to the WEST will be at index n - 1. In certain situations,
1122      * such as with bidirectional text, it is possible
1123      * that the <code>View</code> to EAST is not at index n + 1,
1124      * but rather at index n - 1, or that the <code>View</code>
1125      * to the WEST is not at index n - 1, but index n + 1.
1126      * In this case this method would return true,
1127      * indicating the <code>View</code>s are layed out in
1128      * descending order. Otherwise the method would return false
1129      * indicating the <code>View</code>s are layed out in ascending order.
1130      * <p>
1131      * If the receiver is laying its <code>View</code>s along the
1132      * <code>Y_AXIS</code>, this will return the value from
1133      * invoking the same method on the <code>View</code>
1134      * responsible for rendering <code>position</code> and
1135      * <code>bias</code>. Otherwise this will return false.
1136      *
1137      * @param position position into the model
1138      * @param bias either <code>Position.Bias.Forward</code> or
1139      *          <code>Position.Bias.Backward</code>
1140      * @return true if the <code>View</code>s surrounding the
1141      *          <code>View</code> responding for rendering
1142      *          <code>position</code> and <code>bias</code>
1143      *          are layed out in descending order; otherwise false
1144      */
flipEastAndWestAtEnds(int position, Position.Bias bias)1145     protected boolean flipEastAndWestAtEnds(int position,
1146                                             Position.Bias bias) {
1147         if(majorAxis == Y_AXIS) {
1148             int testPos = (bias == Position.Bias.Backward) ?
1149                           Math.max(0, position - 1) : position;
1150             int index = getViewIndexAtPosition(testPos);
1151             if(index != -1) {
1152                 View v = getView(index);
1153                 if(v != null && v instanceof CompositeView) {
1154                     return ((CompositeView)v).flipEastAndWestAtEnds(position,
1155                                                                     bias);
1156                 }
1157             }
1158         }
1159         return false;
1160     }
1161 
1162     // --- variables ------------------------------------------------
1163 
1164     int majorAxis;
1165 
1166     int majorSpan;
1167     int minorSpan;
1168 
1169     /*
1170      * Request cache
1171      */
1172     boolean majorReqValid;
1173     boolean minorReqValid;
1174     SizeRequirements majorRequest;
1175     SizeRequirements minorRequest;
1176 
1177     /*
1178      * Allocation cache
1179      */
1180     boolean majorAllocValid;
1181     int[] majorOffsets;
1182     int[] majorSpans;
1183     boolean minorAllocValid;
1184     int[] minorOffsets;
1185     int[] minorSpans;
1186 
1187     /** used in paint. */
1188     Rectangle tempRect;
1189 }
1190