001 /* BoxView.java -- An composite view
002 Copyright (C) 2005, 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.Container;
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 * An implementation of {@link CompositeView} that arranges its children in
051 * a box along one axis. This is comparable to how the <code>BoxLayout</code>
052 * works, but for <code>View</code> children.
053 *
054 * @author Roman Kennke (roman@kennke.org)
055 */
056 public class BoxView
057 extends CompositeView
058 {
059
060 /**
061 * The axis along which this <code>BoxView</code> is laid out.
062 */
063 private int myAxis;
064
065 /**
066 * Indicates if the layout is valid along X_AXIS or Y_AXIS.
067 */
068 private boolean[] layoutValid = new boolean[2];
069
070 /**
071 * Indicates if the requirements for an axis are valid.
072 */
073 private boolean[] requirementsValid = new boolean[2];
074
075 /**
076 * The spans along the X_AXIS and Y_AXIS.
077 */
078 private int[][] spans = new int[2][];
079
080 /**
081 * The offsets of the children along the X_AXIS and Y_AXIS.
082 */
083 private int[][] offsets = new int[2][];
084
085 /**
086 * The size requirements along the X_AXIS and Y_AXIS.
087 */
088 private SizeRequirements[] requirements = new SizeRequirements[2];
089
090 /**
091 * The current span along X_AXIS or Y_AXIS.
092 */
093 private int[] span = new int[2];
094
095 /**
096 * Creates a new <code>BoxView</code> for the given
097 * <code>Element</code> and axis. Valid values for the axis are
098 * {@link View#X_AXIS} and {@link View#Y_AXIS}.
099 *
100 * @param element the element that is rendered by this BoxView
101 * @param axis the axis along which the box is laid out
102 */
103 public BoxView(Element element, int axis)
104 {
105 super(element);
106 myAxis = axis;
107 layoutValid[0] = false;
108 layoutValid[1] = false;
109 requirementsValid[X_AXIS] = false;
110 requirementsValid[Y_AXIS] = false;
111 span[0] = 0;
112 span[1] = 0;
113 requirements[0] = new SizeRequirements();
114 requirements[1] = new SizeRequirements();
115
116 // Initialize the cache arrays.
117 spans[0] = new int[0];
118 spans[1] = new int[0];
119 offsets[0] = new int[0];
120 offsets[1] = new int[0];
121 }
122
123 /**
124 * Returns the axis along which this <code>BoxView</code> is laid out.
125 *
126 * @return the axis along which this <code>BoxView</code> is laid out
127 *
128 * @since 1.3
129 */
130 public int getAxis()
131 {
132 return myAxis;
133 }
134
135 /**
136 * Sets the axis along which this <code>BoxView</code> is laid out.
137 *
138 * Valid values for the axis are {@link View#X_AXIS} and
139 * {@link View#Y_AXIS}.
140 *
141 * @param axis the axis along which this <code>BoxView</code> is laid out
142 *
143 * @since 1.3
144 */
145 public void setAxis(int axis)
146 {
147 boolean changed = axis != myAxis;
148 myAxis = axis;
149 if (changed)
150 preferenceChanged(null, true, true);
151 }
152
153 /**
154 * Marks the layout along the specified axis as invalid. This is triggered
155 * automatically when any of the child view changes its preferences
156 * via {@link #preferenceChanged(View, boolean, boolean)}.
157 *
158 * The layout will be updated the next time when
159 * {@link #setSize(float, float)} is called, typically from within the
160 * {@link #paint(Graphics, Shape)} method.
161 *
162 * Valid values for the axis are {@link View#X_AXIS} and
163 * {@link View#Y_AXIS}.
164 *
165 * @param axis an <code>int</code> value
166 *
167 * @since 1.3
168 */
169 public void layoutChanged(int axis)
170 {
171 if (axis != X_AXIS && axis != Y_AXIS)
172 throw new IllegalArgumentException("Invalid axis parameter.");
173 layoutValid[axis] = false;
174 }
175
176 /**
177 * Returns <code>true</code> if the layout along the specified
178 * <code>axis</code> is valid, <code>false</code> otherwise.
179 *
180 * Valid values for the axis are {@link View#X_AXIS} and
181 * {@link View#Y_AXIS}.
182 *
183 * @param axis the axis
184 *
185 * @return <code>true</code> if the layout along the specified
186 * <code>axis</code> is valid, <code>false</code> otherwise
187 *
188 * @since 1.4
189 */
190 protected boolean isLayoutValid(int axis)
191 {
192 if (axis != X_AXIS && axis != Y_AXIS)
193 throw new IllegalArgumentException("Invalid axis parameter.");
194 return layoutValid[axis];
195 }
196
197 /**
198 * Paints the child <code>View</code> at the specified <code>index</code>.
199 * This method modifies the actual values in <code>alloc</code> so make
200 * sure you have a copy of the original values if you need them.
201 *
202 * @param g the <code>Graphics</code> context to paint to
203 * @param alloc the allocated region for the child to paint into
204 * @param index the index of the child to be painted
205 *
206 * @see #childAllocation(int, Rectangle)
207 */
208 protected void paintChild(Graphics g, Rectangle alloc, int index)
209 {
210 View child = getView(index);
211 child.paint(g, alloc);
212 }
213
214 /**
215 * Replaces child views by some other child views. If there are no views to
216 * remove (<code>length == 0</code>), the result is a simple insert, if
217 * there are no children to add (<code>view == null</code>) the result
218 * is a simple removal.
219 *
220 * In addition this invalidates the layout and resizes the internal cache
221 * for the child allocations. The old children's cached allocations can
222 * still be accessed (although they are not guaranteed to be valid), and
223 * the new children will have an initial offset and span of 0.
224 *
225 * @param offset the start offset from where to remove children
226 * @param length the number of children to remove
227 * @param views the views that replace the removed children
228 */
229 public void replace(int offset, int length, View[] views)
230 {
231 // Actually perform the replace.
232 super.replace(offset, length, views);
233
234 // Resize and copy data for cache arrays.
235 int newItems = views != null ? views.length : 0;
236 int minor = 1 - myAxis;
237 offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems);
238 spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems);
239 layoutValid[myAxis] = false;
240 requirementsValid[myAxis] = false;
241 offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems);
242 spans[minor] = replaceLayoutArray(spans[minor], offset, newItems);
243 layoutValid[minor] = false;
244 requirementsValid[minor] = false;
245 }
246
247 /**
248 * Helper method. This updates the layout cache arrays in response
249 * to a call to {@link #replace(int, int, View[])}.
250 *
251 * @param oldArray the old array
252 *
253 * @return the replaced array
254 */
255 private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems)
256
257 {
258 int num = getViewCount();
259 int[] newArray = new int[num];
260 System.arraycopy(oldArray, 0, newArray, 0, offset);
261 System.arraycopy(oldArray, offset, newArray, offset + newItems,
262 num - newItems - offset);
263 return newArray;
264 }
265
266 /**
267 * A Rectangle instance to be reused in the paint() method below.
268 */
269 private final Rectangle tmpRect = new Rectangle();
270
271 private Rectangle clipRect = new Rectangle();
272
273 /**
274 * Renders the <code>Element</code> that is associated with this
275 * <code>View</code>.
276 *
277 * @param g the <code>Graphics</code> context to render to
278 * @param a the allocated region for the <code>Element</code>
279 */
280 public void paint(Graphics g, Shape a)
281 {
282 // Try to avoid allocation if possible (almost all cases).
283 Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
284
285 // This returns a cached instance.
286 alloc = getInsideAllocation(alloc);
287
288 int count = getViewCount();
289 for (int i = 0; i < count; i++)
290 {
291 View child = getView(i);
292 tmpRect.setBounds(alloc);
293 childAllocation(i, tmpRect);
294 if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height))
295 paintChild(g, tmpRect, i);
296 }
297 }
298
299 /**
300 * Returns the preferred span of the content managed by this
301 * <code>View</code> along the specified <code>axis</code>.
302 *
303 * @param axis the axis
304 *
305 * @return the preferred span of this <code>View</code>.
306 */
307 public float getPreferredSpan(int axis)
308 {
309 updateRequirements(axis);
310 // Add margin.
311 float margin;
312 if (axis == X_AXIS)
313 margin = getLeftInset() + getRightInset();
314 else
315 margin = getTopInset() + getBottomInset();
316 return requirements[axis].preferred + margin;
317 }
318
319 /**
320 * Returns the maximum span of this view along the specified axis.
321 * This returns <code>Integer.MAX_VALUE</code> for the minor axis
322 * and the preferred span for the major axis.
323 *
324 * @param axis the axis
325 *
326 * @return the maximum span of this view along the specified axis
327 */
328 public float getMaximumSpan(int axis)
329 {
330 updateRequirements(axis);
331 // Add margin.
332 float margin;
333 if (axis == X_AXIS)
334 margin = getLeftInset() + getRightInset();
335 else
336 margin = getTopInset() + getBottomInset();
337 return requirements[axis].maximum + margin;
338 }
339
340 /**
341 * Returns the minimum span of this view along the specified axis.
342 * This calculates the minimum span using
343 * {@link #calculateMajorAxisRequirements} or
344 * {@link #calculateMinorAxisRequirements} (depending on the axis) and
345 * returns the resulting minimum span.
346 *
347 * @param axis the axis
348 *
349 * @return the minimum span of this view along the specified axis
350 */
351 public float getMinimumSpan(int axis)
352 {
353 updateRequirements(axis);
354 // Add margin.
355 float margin;
356 if (axis == X_AXIS)
357 margin = getLeftInset() + getRightInset();
358 else
359 margin = getTopInset() + getBottomInset();
360 return requirements[axis].minimum + margin;
361 }
362
363 /**
364 * Calculates size requirements for a baseline layout. This is not
365 * used by the BoxView itself, but by subclasses that wish to perform
366 * a baseline layout, like the FlowView's rows.
367 *
368 * @param axis the axis that is examined
369 * @param sr the <code>SizeRequirements</code> object to hold the result,
370 * if <code>null</code>, a new one is created
371 *
372 * @return the size requirements for this <code>BoxView</code> along
373 * the specified axis
374 */
375 protected SizeRequirements baselineRequirements(int axis,
376 SizeRequirements sr)
377 {
378 // Create new instance if sr == null.
379 if (sr == null)
380 sr = new SizeRequirements();
381 sr.alignment = 0.5F;
382
383 // Calculate overall ascent and descent.
384 int totalAscentMin = 0;
385 int totalAscentPref = 0;
386 int totalAscentMax = 0;
387 int totalDescentMin = 0;
388 int totalDescentPref = 0;
389 int totalDescentMax = 0;
390
391 int count = getViewCount();
392 for (int i = 0; i < count; i++)
393 {
394 View v = getView(i);
395 float align = v.getAlignment(axis);
396 int span = (int) v.getPreferredSpan(axis);
397 int ascent = (int) (align * span);
398 int descent = span - ascent;
399
400 totalAscentPref = Math.max(ascent, totalAscentPref);
401 totalDescentPref = Math.max(descent, totalDescentPref);
402 if (v.getResizeWeight(axis) > 0)
403 {
404 // If the view is resizable, then use the min and max size
405 // of the view.
406 span = (int) v.getMinimumSpan(axis);
407 ascent = (int) (align * span);
408 descent = span - ascent;
409 totalAscentMin = Math.max(ascent, totalAscentMin);
410 totalDescentMin = Math.max(descent, totalDescentMin);
411
412 span = (int) v.getMaximumSpan(axis);
413 ascent = (int) (align * span);
414 descent = span - ascent;
415 totalAscentMax = Math.max(ascent, totalAscentMax);
416 totalDescentMax = Math.max(descent, totalDescentMax);
417 }
418 else
419 {
420 // If the view is not resizable, use the preferred span.
421 totalAscentMin = Math.max(ascent, totalAscentMin);
422 totalDescentMin = Math.max(descent, totalDescentMin);
423 totalAscentMax = Math.max(ascent, totalAscentMax);
424 totalDescentMax = Math.max(descent, totalDescentMax);
425 }
426 }
427
428 // Preferred overall span is the sum of the preferred ascent and descent.
429 // With overflow check.
430 sr.preferred = (int) Math.min((long) totalAscentPref
431 + (long) totalDescentPref,
432 Integer.MAX_VALUE);
433
434 // Align along the baseline.
435 if (sr.preferred > 0)
436 sr.alignment = (float) totalAscentPref / sr.preferred;
437
438 if (sr.alignment == 0)
439 {
440 // Nothing above the baseline, use the descent.
441 sr.minimum = totalDescentMin;
442 sr.maximum = totalDescentMax;
443 }
444 else if (sr.alignment == 1.0F)
445 {
446 // Nothing below the baseline, use the descent.
447 sr.minimum = totalAscentMin;
448 sr.maximum = totalAscentMax;
449 }
450 else
451 {
452 sr.minimum = Math.max((int) (totalAscentMin / sr.alignment),
453 (int) (totalDescentMin / (1.0F - sr.alignment)));
454 sr.maximum = Math.min((int) (totalAscentMax / sr.alignment),
455 (int) (totalDescentMax / (1.0F - sr.alignment)));
456 }
457 return sr;
458 }
459
460 /**
461 * Calculates the baseline layout of the children of this
462 * <code>BoxView</code> along the specified axis.
463 *
464 * This is not used by the BoxView itself, but by subclasses that wish to
465 * perform a baseline layout, like the FlowView's rows.
466 *
467 * @param span the target span
468 * @param axis the axis that is examined
469 * @param offsets an empty array, filled with the offsets of the children
470 * @param spans an empty array, filled with the spans of the children
471 */
472 protected void baselineLayout(int span, int axis, int[] offsets,
473 int[] spans)
474 {
475 int totalAscent = (int) (span * getAlignment(axis));
476 int totalDescent = span - totalAscent;
477
478 int count = getViewCount();
479 for (int i = 0; i < count; i++)
480 {
481 View v = getView(i);
482 float align = v.getAlignment(axis);
483 int viewSpan;
484 if (v.getResizeWeight(axis) > 0)
485 {
486 // If possible, then resize for best fit.
487 int min = (int) v.getMinimumSpan(axis);
488 int max = (int) v.getMaximumSpan(axis);
489 if (align == 0.0F)
490 viewSpan = Math.max(Math.min(max, totalDescent), min);
491 else if (align == 1.0F)
492 viewSpan = Math.max(Math.min(max, totalAscent), min);
493 else
494 {
495 int fit = (int) Math.min(totalAscent / align,
496 totalDescent / (1.0F - align));
497 viewSpan = Math.max(Math.min(max, fit), min);
498 }
499 }
500 else
501 viewSpan = (int) v.getPreferredSpan(axis);
502 offsets[i] = totalAscent - (int) (viewSpan * align);
503 spans[i] = viewSpan;
504 }
505 }
506
507 /**
508 * Calculates the size requirements of this <code>BoxView</code> along
509 * its major axis, that is the axis specified in the constructor.
510 *
511 * @param axis the axis that is examined
512 * @param sr the <code>SizeRequirements</code> object to hold the result,
513 * if <code>null</code>, a new one is created
514 *
515 * @return the size requirements for this <code>BoxView</code> along
516 * the specified axis
517 */
518 protected SizeRequirements calculateMajorAxisRequirements(int axis,
519 SizeRequirements sr)
520 {
521 SizeRequirements res = sr;
522 if (res == null)
523 res = new SizeRequirements();
524
525 float min = 0;
526 float pref = 0;
527 float max = 0;
528
529 int n = getViewCount();
530 for (int i = 0; i < n; i++)
531 {
532 View child = getView(i);
533 min += child.getMinimumSpan(axis);
534 pref += child.getPreferredSpan(axis);
535 max += child.getMaximumSpan(axis);
536 }
537
538 res.minimum = (int) min;
539 res.preferred = (int) pref;
540 res.maximum = (int) max;
541 res.alignment = 0.5F;
542
543 return res;
544 }
545
546 /**
547 * Calculates the size requirements of this <code>BoxView</code> along
548 * its minor axis, that is the axis opposite to the axis specified in the
549 * constructor.
550 *
551 * @param axis the axis that is examined
552 * @param sr the <code>SizeRequirements</code> object to hold the result,
553 * if <code>null</code>, a new one is created
554 *
555 * @return the size requirements for this <code>BoxView</code> along
556 * the specified axis
557 */
558 protected SizeRequirements calculateMinorAxisRequirements(int axis,
559 SizeRequirements sr)
560 {
561 SizeRequirements res = sr;
562 if (res == null)
563 res = new SizeRequirements();
564
565 res.minimum = 0;
566 res.preferred = 0;
567 res.maximum = Integer.MAX_VALUE;
568 res.alignment = 0.5F;
569 int n = getViewCount();
570 for (int i = 0; i < n; i++)
571 {
572 View child = getView(i);
573 res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum);
574 res.preferred = Math.max((int) child.getPreferredSpan(axis),
575 res.preferred);
576 res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum);
577 }
578
579 return res;
580 }
581
582
583 /**
584 * Returns <code>true</code> if the specified point lies before the
585 * given <code>Rectangle</code>, <code>false</code> otherwise.
586 *
587 * "Before" is typically defined as being to the left or above.
588 *
589 * @param x the X coordinate of the point
590 * @param y the Y coordinate of the point
591 * @param r the rectangle to test the point against
592 *
593 * @return <code>true</code> if the specified point lies before the
594 * given <code>Rectangle</code>, <code>false</code> otherwise
595 */
596 protected boolean isBefore(int x, int y, Rectangle r)
597 {
598 boolean result = false;
599
600 if (myAxis == X_AXIS)
601 result = x < r.x;
602 else
603 result = y < r.y;
604
605 return result;
606 }
607
608 /**
609 * Returns <code>true</code> if the specified point lies after the
610 * given <code>Rectangle</code>, <code>false</code> otherwise.
611 *
612 * "After" is typically defined as being to the right or below.
613 *
614 * @param x the X coordinate of the point
615 * @param y the Y coordinate of the point
616 * @param r the rectangle to test the point against
617 *
618 * @return <code>true</code> if the specified point lies after the
619 * given <code>Rectangle</code>, <code>false</code> otherwise
620 */
621 protected boolean isAfter(int x, int y, Rectangle r)
622 {
623 boolean result = false;
624
625 if (myAxis == X_AXIS)
626 result = x > r.x + r.width;
627 else
628 result = y > r.y + r.height;
629
630 return result;
631 }
632
633 /**
634 * Returns the child <code>View</code> at the specified location.
635 *
636 * @param x the X coordinate
637 * @param y the Y coordinate
638 * @param r the inner allocation of this <code>BoxView</code> on entry,
639 * the allocation of the found child on exit
640 *
641 * @return the child <code>View</code> at the specified location
642 */
643 protected View getViewAtPoint(int x, int y, Rectangle r)
644 {
645 View result = null;
646 int count = getViewCount();
647 if (myAxis == X_AXIS)
648 {
649 // Border case. Requested point is left from the box.
650 if (x < r.x + offsets[X_AXIS][0])
651 {
652 childAllocation(0, r);
653 result = getView(0);
654 }
655 else
656 {
657 // Search views inside box.
658 for (int i = 0; i < count && result == null; i++)
659 {
660 if (x < r.x + offsets[X_AXIS][i])
661 {
662 childAllocation(i - 1, r);
663 result = getView(i - 1);
664 }
665 }
666 }
667 }
668 else // Same algorithm for Y_AXIS.
669 {
670 // Border case. Requested point is above the box.
671 if (y < r.y + offsets[Y_AXIS][0])
672 {
673 childAllocation(0, r);
674 result = getView(0);
675 }
676 else
677 {
678 // Search views inside box.
679 for (int i = 0; i < count && result == null; i++)
680 {
681 if (y < r.y + offsets[Y_AXIS][i])
682 {
683 childAllocation(i - 1, r);
684 result = getView(i - 1);
685 }
686 }
687 }
688 }
689 // Not found, other border case: point is right from or below the box.
690 if (result == null)
691 {
692 childAllocation(count - 1, r);
693 result = getView(count - 1);
694 }
695 return result;
696 }
697
698 /**
699 * Computes the allocation for a child <code>View</code>. The parameter
700 * <code>a</code> stores the allocation of this <code>CompositeView</code>
701 * and is then adjusted to hold the allocation of the child view.
702 *
703 * @param index
704 * the index of the child <code>View</code>
705 * @param a
706 * the allocation of this <code>CompositeView</code> before the
707 * call, the allocation of the child on exit
708 */
709 protected void childAllocation(int index, Rectangle a)
710 {
711 a.x += offsets[X_AXIS][index];
712 a.y += offsets[Y_AXIS][index];
713 a.width = spans[X_AXIS][index];
714 a.height = spans[Y_AXIS][index];
715 }
716
717 /**
718 * Lays out the children of this <code>BoxView</code> with the specified
719 * bounds.
720 *
721 * @param width the width of the allocated region for the children (that
722 * is the inner allocation of this <code>BoxView</code>
723 * @param height the height of the allocated region for the children (that
724 * is the inner allocation of this <code>BoxView</code>
725 */
726 protected void layout(int width, int height)
727 {
728 layoutAxis(X_AXIS, width);
729 layoutAxis(Y_AXIS, height);
730 }
731
732 private void layoutAxis(int axis, int s)
733 {
734 if (span[axis] != s)
735 layoutValid[axis] = false;
736 if (! layoutValid[axis])
737 {
738 span[axis] = s;
739 updateRequirements(axis);
740 if (axis == myAxis)
741 layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]);
742 else
743 layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]);
744 layoutValid[axis] = true;
745
746 // Push out child layout.
747 int viewCount = getViewCount();
748 for (int i = 0; i < viewCount; i++)
749 {
750 View v = getView(i);
751 v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
752 }
753 }
754 }
755
756 /**
757 * Performs the layout along the major axis of a <code>BoxView</code>.
758 *
759 * @param targetSpan the (inner) span of the <code>BoxView</code> in which
760 * to layout the children
761 * @param axis the axis along which the layout is performed
762 * @param offsets the array that holds the offsets of the children on exit
763 * @param spans the array that holds the spans of the children on exit
764 */
765 protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
766 int[] spans)
767 {
768 // Set the spans to the preferred sizes. Determine the space
769 // that we have to adjust the sizes afterwards.
770 long sumPref = 0;
771 int n = getViewCount();
772 for (int i = 0; i < n; i++)
773 {
774 View child = getView(i);
775 spans[i] = (int) child.getPreferredSpan(axis);
776 sumPref += spans[i];
777 }
778
779 // Try to adjust the spans so that we fill the targetSpan.
780 long diff = targetSpan - sumPref;
781 float factor = 0.0F;
782 int[] diffs = null;
783 if (diff != 0)
784 {
785 long total = 0;
786 diffs = new int[n];
787 for (int i = 0; i < n; i++)
788 {
789 View child = getView(i);
790 int span;
791 if (diff < 0)
792 {
793 span = (int) child.getMinimumSpan(axis);
794 diffs[i] = spans[i] - span;
795 }
796 else
797 {
798 span = (int) child.getMaximumSpan(axis);
799 diffs[i] = span - spans[i];
800 }
801 total += span;
802 }
803
804 float maxAdjust = Math.abs(total - sumPref);
805 factor = diff / maxAdjust;
806 factor = Math.min(factor, 1.0F);
807 factor = Math.max(factor, -1.0F);
808 }
809
810 // Actually perform adjustments.
811 int totalOffs = 0;
812 for (int i = 0; i < n; i++)
813 {
814 offsets[i] = totalOffs;
815 if (diff != 0)
816 {
817 float adjust = factor * diffs[i];
818 spans[i] += Math.round(adjust);
819 }
820 // Avoid overflow here.
821 totalOffs = (int) Math.min((long) totalOffs + (long) spans[i],
822 Integer.MAX_VALUE);
823 }
824 }
825
826 /**
827 * Performs the layout along the minor axis of a <code>BoxView</code>.
828 *
829 * @param targetSpan the (inner) span of the <code>BoxView</code> in which
830 * to layout the children
831 * @param axis the axis along which the layout is performed
832 * @param offsets the array that holds the offsets of the children on exit
833 * @param spans the array that holds the spans of the children on exit
834 */
835 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
836 int[] spans)
837 {
838 int count = getViewCount();
839 for (int i = 0; i < count; i++)
840 {
841 View child = getView(i);
842 int max = (int) child.getMaximumSpan(axis);
843 if (max < targetSpan)
844 {
845 // Align child when it can't be made as wide as the target span.
846 float align = child.getAlignment(axis);
847 offsets[i] = (int) ((targetSpan - max) * align);
848 spans[i] = max;
849 }
850 else
851 {
852 // Expand child to target width if possible.
853 int min = (int) child.getMinimumSpan(axis);
854 offsets[i] = 0;
855 spans[i] = Math.max(min, targetSpan);
856 }
857 }
858 }
859
860 /**
861 * Returns <code>true</code> if the cached allocations for the children
862 * are still valid, <code>false</code> otherwise.
863 *
864 * @return <code>true</code> if the cached allocations for the children
865 * are still valid, <code>false</code> otherwise
866 */
867 protected boolean isAllocationValid()
868 {
869 return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS);
870 }
871
872 /**
873 * Return the current width of the box. This is the last allocated width.
874 *
875 * @return the current width of the box
876 */
877 public int getWidth()
878 {
879 // The RI returns the following here, however, I'd think that is a bug.
880 // return span[X_AXIS] + getLeftInset() - getRightInset();
881 return span[X_AXIS] + getLeftInset() + getRightInset();
882 }
883
884 /**
885 * Return the current height of the box. This is the last allocated height.
886 *
887 * @return the current height of the box
888 */
889 public int getHeight()
890 {
891 // The RI returns the following here, however, I'd think that is a bug.
892 // return span[Y_AXIS] + getTopInset() - getBottomInset();
893 return span[Y_AXIS] + getTopInset() + getBottomInset();
894 }
895
896 /**
897 * Sets the size of the view. If the actual size has changed, the layout
898 * is updated accordingly.
899 *
900 * @param width the new width
901 * @param height the new height
902 */
903 public void setSize(float width, float height)
904 {
905 layout((int) (width - getLeftInset() - getRightInset()),
906 (int) (height - getTopInset() - getBottomInset()));
907 }
908
909 /**
910 * Returns the span for the child view with the given index for the specified
911 * axis.
912 *
913 * @param axis the axis to examine, either <code>X_AXIS</code> or
914 * <code>Y_AXIS</code>
915 * @param childIndex the index of the child for for which to return the span
916 *
917 * @return the span for the child view with the given index for the specified
918 * axis
919 */
920 protected int getSpan(int axis, int childIndex)
921 {
922 if (axis != X_AXIS && axis != Y_AXIS)
923 throw new IllegalArgumentException("Illegal axis argument");
924 return spans[axis][childIndex];
925 }
926
927 /**
928 * Returns the offset for the child view with the given index for the
929 * specified axis.
930 *
931 * @param axis the axis to examine, either <code>X_AXIS</code> or
932 * <code>Y_AXIS</code>
933 * @param childIndex the index of the child for for which to return the span
934 *
935 * @return the offset for the child view with the given index for the
936 * specified axis
937 */
938 protected int getOffset(int axis, int childIndex)
939 {
940 if (axis != X_AXIS && axis != Y_AXIS)
941 throw new IllegalArgumentException("Illegal axis argument");
942 return offsets[axis][childIndex];
943 }
944
945 /**
946 * Returns the alignment for this box view for the specified axis. The
947 * axis that is tiled (the major axis) will be requested to be aligned
948 * centered (0.5F). The minor axis alignment depends on the child view's
949 * total alignment.
950 *
951 * @param axis the axis which is examined
952 *
953 * @return the alignment for this box view for the specified axis
954 */
955 public float getAlignment(int axis)
956 {
957 updateRequirements(axis);
958 return requirements[axis].alignment;
959 }
960
961 /**
962 * Called by a child View when its preferred span has changed.
963 *
964 * @param width indicates that the preferred width of the child changed.
965 * @param height indicates that the preferred height of the child changed.
966 * @param child the child View.
967 */
968 public void preferenceChanged(View child, boolean width, boolean height)
969 {
970 if (width)
971 {
972 layoutValid[X_AXIS] = false;
973 requirementsValid[X_AXIS] = false;
974 }
975 if (height)
976 {
977 layoutValid[Y_AXIS] = false;
978 requirementsValid[Y_AXIS] = false;
979 }
980 super.preferenceChanged(child, width, height);
981 }
982
983 /**
984 * Maps the document model position <code>pos</code> to a Shape
985 * in the view coordinate space. This method overrides CompositeView's
986 * method to make sure the children are allocated properly before
987 * calling the super's behaviour.
988 */
989 public Shape modelToView(int pos, Shape a, Position.Bias bias)
990 throws BadLocationException
991 {
992 // Make sure everything is allocated properly and then call super
993 if (! isAllocationValid())
994 {
995 Rectangle bounds = a.getBounds();
996 setSize(bounds.width, bounds.height);
997 }
998 return super.modelToView(pos, a, bias);
999 }
1000
1001 /**
1002 * Returns the resize weight of this view. A value of <code>0</code> or less
1003 * means this view is not resizeable. Positive values make the view
1004 * resizeable. This implementation returns <code>0</code> for the major
1005 * axis and <code>1</code> for the minor axis of this box view.
1006 *
1007 * @param axis the axis
1008 *
1009 * @return the resizability of this view along the specified axis
1010 *
1011 * @throws IllegalArgumentException if <code>axis</code> is invalid
1012 */
1013 public int getResizeWeight(int axis)
1014 {
1015 if (axis != X_AXIS && axis != Y_AXIS)
1016 throw new IllegalArgumentException("Illegal axis argument");
1017 updateRequirements(axis);
1018 int weight = 0;
1019 if ((requirements[axis].preferred != requirements[axis].minimum)
1020 || (requirements[axis].preferred != requirements[axis].maximum))
1021 weight = 1;
1022 return weight;
1023 }
1024
1025 /**
1026 * Returns the child allocation for the child view with the specified
1027 * <code>index</code>. If the layout is invalid, this returns
1028 * <code>null</code>.
1029 *
1030 * @param index the child view index
1031 * @param a the allocation to this view
1032 *
1033 * @return the child allocation for the child view with the specified
1034 * <code>index</code> or <code>null</code> if the layout is invalid
1035 * or <code>a</code> is null
1036 */
1037 public Shape getChildAllocation(int index, Shape a)
1038 {
1039 Shape ret = null;
1040 if (isAllocationValid() && a != null)
1041 ret = super.getChildAllocation(index, a);
1042 return ret;
1043 }
1044
1045 protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
1046 Shape a, ViewFactory vf)
1047 {
1048 boolean wasValid = isLayoutValid(myAxis);
1049 super.forwardUpdate(ec, e, a, vf);
1050 // Trigger repaint when one of the children changed the major axis.
1051 if (wasValid && ! isLayoutValid(myAxis))
1052 {
1053 Container c = getContainer();
1054 if (a != null && c != null)
1055 {
1056 int pos = e.getOffset();
1057 int index = getViewIndexAtPosition(pos);
1058 Rectangle r = getInsideAllocation(a);
1059 if (myAxis == X_AXIS)
1060 {
1061 r.x += offsets[myAxis][index];
1062 r.width -= offsets[myAxis][index];
1063 }
1064 else
1065 {
1066 r.y += offsets[myAxis][index];
1067 r.height -= offsets[myAxis][index];
1068 }
1069 c.repaint(r.x, r.y, r.width, r.height);
1070 }
1071 }
1072 }
1073
1074 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
1075 {
1076 if (! isAllocationValid())
1077 {
1078 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
1079 setSize(r.width, r.height);
1080 }
1081 return super.viewToModel(x, y, a, bias);
1082 }
1083
1084 protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias)
1085 {
1086 // FIXME: What to do here?
1087 return super.flipEastAndWestAtEnds(position, bias);
1088 }
1089
1090 /**
1091 * Updates the view's cached requirements along the specified axis if
1092 * necessary. The requirements are only updated if the layout for the
1093 * specified axis is marked as invalid.
1094 *
1095 * @param axis the axis
1096 */
1097 private void updateRequirements(int axis)
1098 {
1099 if (axis != Y_AXIS && axis != X_AXIS)
1100 throw new IllegalArgumentException("Illegal axis: " + axis);
1101 if (! requirementsValid[axis])
1102 {
1103 if (axis == myAxis)
1104 requirements[axis] = calculateMajorAxisRequirements(axis,
1105 requirements[axis]);
1106 else
1107 requirements[axis] = calculateMinorAxisRequirements(axis,
1108 requirements[axis]);
1109 requirementsValid[axis] = true;
1110 }
1111 }
1112 }