1 /* BoxView.java -- An composite view
2 Copyright (C) 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax
.swing
.text
;
41 import java
.awt
.Graphics
;
42 import java
.awt
.Rectangle
;
43 import java
.awt
.Shape
;
45 import javax
.swing
.SizeRequirements
;
48 * An implementation of {@link CompositeView} that arranges its children in
49 * a box along one axis. This is comparable to how the <code>BoxLayout</code>
50 * works, but for <code>View</code> children.
52 * @author Roman Kennke (roman@kennke.org)
59 * The axis along which this <code>BoxView</code> is laid out.
64 * Indicates wether the layout in X_AXIS is valid.
69 * Indicates whether the layout in Y_AXIS is valid.
74 * The spans in X direction of the children.
79 * The spans in Y direction of the children.
84 * The offsets of the children in X direction relative to this BoxView's
90 * The offsets of the children in Y direction relative to this BoxView's
101 * The current height.
106 * Creates a new <code>BoxView</code> for the given
107 * <code>Element</code> and axis. Valid values for the axis are
108 * {@link View#X_AXIS} and {@link View#Y_AXIS}.
110 * @param element the element that is rendered by this BoxView
111 * @param axis the axis along which the box is laid out
113 public BoxView(Element element
, int axis
)
117 xLayoutValid
= false;
118 yLayoutValid
= false;
120 // Initialize the cache arrays.
123 offsetsX
= new int[0];
124 offsetsY
= new int[0];
131 * Returns the axis along which this <code>BoxView</code> is laid out.
133 * @return the axis along which this <code>BoxView</code> is laid out
141 * Sets the axis along which this <code>BoxView</code> is laid out.
143 * Valid values for the axis are {@link View#X_AXIS} and
144 * {@link View#Y_AXIS}.
146 * @param axis the axis along which this <code>BoxView</code> is laid out
148 public void setAxis(int axis
)
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)}.
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.
162 * Valid values for the axis are {@link View#X_AXIS} and
163 * {@link View#Y_AXIS}.
165 * @param axis an <code>int</code> value
167 public void layoutChanged(int axis
)
172 xLayoutValid
= false;
175 yLayoutValid
= false;
178 throw new IllegalArgumentException("Invalid axis parameter.");
183 * Returns <code>true</code> if the layout along the specified
184 * <code>axis</code> is valid, <code>false</code> otherwise.
186 * Valid values for the axis are {@link View#X_AXIS} and
187 * {@link View#Y_AXIS}.
189 * @param axis the axis
191 * @return <code>true</code> if the layout along the specified
192 * <code>axis</code> is valid, <code>false</code> otherwise
194 protected boolean isLayoutValid(int axis
)
196 boolean valid
= false;
200 valid
= xLayoutValid
;
203 valid
= yLayoutValid
;
206 throw new IllegalArgumentException("Invalid axis parameter.");
212 * Paints the child <code>View</code> at the specified <code>index</code>.
213 * This method modifies the actual values in <code>alloc</code> so make
214 * sure you have a copy of the original values if you need them.
216 * @param g the <code>Graphics</code> context to paint to
217 * @param alloc the allocated region for the child to paint into
218 * @param index the index of the child to be painted
220 * @see #childAllocation(int, Rectangle)
222 protected void paintChild(Graphics g
, Rectangle alloc
, int index
)
224 View child
= getView(index
);
225 child
.paint(g
, alloc
);
229 * Replaces child views by some other child views. If there are no views to
230 * remove (<code>length == 0</code>), the result is a simple insert, if
231 * there are no children to add (<code>view == null</code>) the result
232 * is a simple removal.
234 * In addition this invalidates the layout and resizes the internal cache
235 * for the child allocations. The old children's cached allocations can
236 * still be accessed (although they are not guaranteed to be valid), and
237 * the new children will have an initial offset and span of 0.
239 * @param offset the start offset from where to remove children
240 * @param length the number of children to remove
241 * @param views the views that replace the removed children
243 public void replace(int offset
, int length
, View
[] views
)
245 // Resize and copy data for cache arrays.
247 int oldSize
= getViewCount();
249 int[] newSpansX
= new int[oldSize
- length
+ views
.length
];
250 System
.arraycopy(spansX
, 0, newSpansX
, 0, offset
);
251 System
.arraycopy(spansX
, offset
+ length
, newSpansX
,
252 offset
+ views
.length
,
253 oldSize
- (offset
+ length
));
257 int[] newSpansY
= new int[oldSize
- length
+ views
.length
];
258 System
.arraycopy(spansY
, 0, newSpansY
, 0, offset
);
259 System
.arraycopy(spansY
, offset
+ length
, newSpansY
,
260 offset
+ views
.length
,
261 oldSize
- (offset
+ length
));
264 // The offsetsX cache.
265 int[] newOffsetsX
= new int[oldSize
- length
+ views
.length
];
266 System
.arraycopy(offsetsX
, 0, newOffsetsX
, 0, offset
);
267 System
.arraycopy(offsetsX
, offset
+ length
, newOffsetsX
,
268 offset
+ views
.length
,
269 oldSize
- (offset
+ length
));
270 offsetsX
= newOffsetsX
;
272 // The offsetsY cache.
273 int[] newOffsetsY
= new int[oldSize
- length
+ views
.length
];
274 System
.arraycopy(offsetsY
, 0, newOffsetsY
, 0, offset
);
275 System
.arraycopy(offsetsY
, offset
+ length
, newOffsetsY
,
276 offset
+ views
.length
,
277 oldSize
- (offset
+ length
));
278 offsetsY
= newOffsetsY
;
280 // Actually perform the replace.
281 super.replace(offset
, length
, views
);
283 // Invalidate layout information.
284 layoutChanged(X_AXIS
);
285 layoutChanged(Y_AXIS
);
289 * Renders the <code>Element</code> that is associated with this
292 * @param g the <code>Graphics</code> context to render to
293 * @param a the allocated region for the <code>Element</code>
295 public void paint(Graphics g
, Shape a
)
297 // Adjust size if the size is changed.
298 Rectangle bounds
= a
.getBounds();
300 if (bounds
.width
!= getWidth() || bounds
.height
!= getHeight())
301 setSize(bounds
.width
, bounds
.height
);
303 Rectangle inside
= getInsideAllocation(a
);
304 Rectangle copy
= new Rectangle(inside
);
305 int count
= getViewCount();
306 for (int i
= 0; i
< count
; ++i
)
308 copy
.setBounds(inside
);
309 childAllocation(i
, copy
);
311 && g
.hitClip(copy
.x
, copy
.y
, copy
.width
, copy
.height
))
312 paintChild(g
, copy
, i
);
317 * Returns the preferred span of the content managed by this
318 * <code>View</code> along the specified <code>axis</code>.
320 * @param axis the axis
322 * @return the preferred span of this <code>View</code>.
324 public float getPreferredSpan(int axis
)
326 SizeRequirements sr
= new SizeRequirements();
327 int pref
= baselineRequirements(axis
, sr
).preferred
;
331 public float getMaximumSpan(int axis
)
333 if (axis
== getAxis())
334 return getPreferredSpan(axis
);
336 return Integer
.MAX_VALUE
;
340 * Calculates the size requirements for this <code>BoxView</code> along
341 * the specified axis.
343 * @param axis the axis that is examined
344 * @param sr the <code>SizeRequirements</code> object to hold the result,
345 * if <code>null</code>, a new one is created
347 * @return the size requirements for this <code>BoxView</code> along
350 protected SizeRequirements
baselineRequirements(int axis
,
353 SizeRequirements result
;
355 result
= calculateMajorAxisRequirements(axis
, sr
);
357 result
= calculateMinorAxisRequirements(axis
, sr
);
362 * Calculates the layout of the children of this <code>BoxView</code> along
363 * the specified axis.
365 * @param span the target span
366 * @param axis the axis that is examined
367 * @param offsets an empty array, filled with the offsets of the children
368 * @param spans an empty array, filled with the spans of the children
370 protected void baselineLayout(int span
, int axis
, int[] offsets
,
374 layoutMajorAxis(span
, axis
, offsets
, spans
);
376 layoutMinorAxis(span
, axis
, offsets
, spans
);
380 * Calculates the size requirements of this <code>BoxView</code> along
381 * its major axis, that is the axis specified in the constructor.
383 * @param axis the axis that is examined
384 * @param sr the <code>SizeRequirements</code> object to hold the result,
385 * if <code>null</code>, a new one is created
387 * @return the size requirements for this <code>BoxView</code> along
390 protected SizeRequirements
calculateMajorAxisRequirements(int axis
,
393 SizeRequirements
[] childReqs
= getChildRequirements(axis
);
394 return SizeRequirements
.getTiledSizeRequirements(childReqs
);
398 * Calculates the size requirements of this <code>BoxView</code> along
399 * its minor axis, that is the axis opposite to the axis specified in the
402 * @param axis the axis that is examined
403 * @param sr the <code>SizeRequirements</code> object to hold the result,
404 * if <code>null</code>, a new one is created
406 * @return the size requirements for this <code>BoxView</code> along
409 protected SizeRequirements
calculateMinorAxisRequirements(int axis
,
412 SizeRequirements
[] childReqs
= getChildRequirements(axis
);
413 return SizeRequirements
.getAlignedSizeRequirements(childReqs
);
417 * Returns <code>true</code> if the specified point lies before the
418 * given <code>Rectangle</code>, <code>false</code> otherwise.
420 * "Before" is typically defined as being to the left or above.
422 * @param x the X coordinate of the point
423 * @param y the Y coordinate of the point
424 * @param r the rectangle to test the point against
426 * @return <code>true</code> if the specified point lies before the
427 * given <code>Rectangle</code>, <code>false</code> otherwise
429 protected boolean isBefore(int x
, int y
, Rectangle r
)
431 boolean result
= false;
433 if (myAxis
== X_AXIS
)
442 * Returns <code>true</code> if the specified point lies after the
443 * given <code>Rectangle</code>, <code>false</code> otherwise.
445 * "After" is typically defined as being to the right or below.
447 * @param x the X coordinate of the point
448 * @param y the Y coordinate of the point
449 * @param r the rectangle to test the point against
451 * @return <code>true</code> if the specified point lies after the
452 * given <code>Rectangle</code>, <code>false</code> otherwise
454 protected boolean isAfter(int x
, int y
, Rectangle r
)
456 boolean result
= false;
458 if (myAxis
== X_AXIS
)
467 * Returns the child <code>View</code> at the specified location.
469 * @param x the X coordinate
470 * @param y the Y coordinate
471 * @param r the inner allocation of this <code>BoxView</code> on entry,
472 * the allocation of the found child on exit
474 * @return the child <code>View</code> at the specified location
476 protected View
getViewAtPoint(int x
, int y
, Rectangle r
)
479 int count
= getViewCount();
480 Rectangle copy
= new Rectangle(r
);
482 for (int i
= 0; i
< count
; ++i
)
485 childAllocation(i
, r
);
486 if (copy
.contains(x
, y
))
493 if (result
== null && count
> 0)
494 return getView(count
- 1);
499 * Computes the allocation for a child <code>View</code>. The parameter
500 * <code>a</code> stores the allocation of this <code>CompositeView</code>
501 * and is then adjusted to hold the allocation of the child view.
504 * the index of the child <code>View</code>
506 * the allocation of this <code>CompositeView</code> before the
507 * call, the allocation of the child on exit
509 protected void childAllocation(int index
, Rectangle a
)
511 if (! isAllocationValid())
512 layout(a
.width
, a
.height
);
514 a
.x
+= offsetsX
[index
];
515 a
.y
+= offsetsY
[index
];
516 a
.width
= spansX
[index
];
517 a
.height
= spansY
[index
];
521 * Lays out the children of this <code>BoxView</code> with the specified
524 * @param width the width of the allocated region for the children (that
525 * is the inner allocation of this <code>BoxView</code>
526 * @param height the height of the allocated region for the children (that
527 * is the inner allocation of this <code>BoxView</code>
529 protected void layout(int width
, int height
)
531 baselineLayout(width
, X_AXIS
, offsetsX
, spansX
);
532 baselineLayout(height
, Y_AXIS
, offsetsY
, spansY
);
536 * Performs the layout along the major axis of a <code>BoxView</code>.
538 * @param targetSpan the (inner) span of the <code>BoxView</code> in which
539 * to layout the children
540 * @param axis the axis along which the layout is performed
541 * @param offsets the array that holds the offsets of the children on exit
542 * @param spans the array that holds the spans of the children on exit
544 protected void layoutMajorAxis(int targetSpan
, int axis
, int[] offsets
,
547 SizeRequirements
[] childReqs
= getChildRequirements(axis
);
548 // Calculate the spans and offsets using the SizeRequirements uility
550 SizeRequirements
.calculateTiledPositions(targetSpan
, null, childReqs
,
552 validateLayout(axis
);
556 * Performs the layout along the minor axis of a <code>BoxView</code>.
558 * @param targetSpan the (inner) span of the <code>BoxView</code> in which
559 * to layout the children
560 * @param axis the axis along which the layout is performed
561 * @param offsets the array that holds the offsets of the children on exit
562 * @param spans the array that holds the spans of the children on exit
564 protected void layoutMinorAxis(int targetSpan
, int axis
, int[] offsets
,
567 SizeRequirements
[] childReqs
= getChildRequirements(axis
);
568 // Calculate the spans and offsets using the SizeRequirements uility
570 // TODO: This might be an opportunity for performance optimization. Here
571 // we could use a cached instance of SizeRequirements instead of passing
572 // null to baselineRequirements. However, this would involve rewriting
573 // the baselineRequirements() method to not use the SizeRequirements
574 // utility method, since they cannot reuse a cached instance.
575 SizeRequirements total
= baselineRequirements(axis
, null);
576 SizeRequirements
.calculateAlignedPositions(targetSpan
, total
, childReqs
,
578 validateLayout(axis
);
582 * Returns <code>true</code> if the cached allocations for the children
583 * are still valid, <code>false</code> otherwise.
585 * @return <code>true</code> if the cached allocations for the children
586 * are still valid, <code>false</code> otherwise
588 protected boolean isAllocationValid()
590 return isLayoutValid(X_AXIS
) && isLayoutValid(Y_AXIS
);
594 * Return the current width of the box. This is the last allocated width.
596 * @return the current width of the box
598 public int getWidth()
604 * Return the current height of the box. This is the last allocated height.
606 * @return the current height of the box
608 public int getHeight()
614 * Sets the size of the view. If the actual size has changed, the layout
615 * is updated accordingly.
617 * @param width the new width
618 * @param height the new height
620 public void setSize(float width
, float height
)
622 if (this.width
!= (int) width
)
623 layoutChanged(X_AXIS
);
624 if (this.height
!= (int) height
)
625 layoutChanged(Y_AXIS
);
627 this.width
= (int) width
;
628 this.height
= (int) height
;
630 Rectangle outside
= new Rectangle(0, 0, this.width
, this.height
);
631 Rectangle inside
= getInsideAllocation(outside
);
632 if (!isAllocationValid())
633 layout(inside
.width
, inside
.height
);
637 * Sets the layout to valid for a specific axis.
639 * @param axis the axis for which to validate the layout
641 void validateLayout(int axis
)
650 * Returns the size requirements of this view's children for the major
653 * @return the size requirements of this view's children for the major
656 SizeRequirements
[] getChildRequirements(int axis
)
658 // Allocate SizeRequirements for each child view.
659 int count
= getViewCount();
660 SizeRequirements
[] childReqs
= new SizeRequirements
[count
];
661 for (int i
= 0; i
< count
; ++i
)
663 View view
= getView(i
);
664 childReqs
[i
] = new SizeRequirements((int) view
.getMinimumSpan(axis
),
665 (int) view
.getPreferredSpan(axis
),
666 (int) view
.getMaximumSpan(axis
),
667 view
.getAlignment(axis
));
673 * Returns the span for the child view with the given index for the specified
676 * @param axis the axis to examine, either <code>X_AXIS</code> or
677 * <code>Y_AXIS</code>
678 * @param childIndex the index of the child for for which to return the span
680 * @return the span for the child view with the given index for the specified
683 protected int getSpan(int axis
, int childIndex
)
686 return spansX
[childIndex
];
688 return spansY
[childIndex
];
692 * Returns the offset for the child view with the given index for the
695 * @param axis the axis to examine, either <code>X_AXIS</code> or
696 * <code>Y_AXIS</code>
697 * @param childIndex the index of the child for for which to return the span
699 * @return the offset for the child view with the given index for the
702 protected int getOffset(int axis
, int childIndex
)
705 return offsetsX
[childIndex
];
707 return offsetsY
[childIndex
];
711 * Returns the alignment for this box view for the specified axis. The
712 * axis that is tiled (the major axis) will be requested to be aligned
713 * centered (0.5F). The minor axis alignment depends on the child view's
716 * @param axis the axis which is examined
718 * @return the alignment for this box view for the specified axis
720 public float getAlignment(int axis
)
725 return baselineRequirements(axis
, null).alignment
;
729 * Called by a child View when its preferred span has changed.
731 * @param width indicates that the preferred width of the child changed.
732 * @param height indicates that the preferred height of the child changed.
733 * @param child the child View.
735 public void preferenceChanged (View child
, boolean width
, boolean height
)
738 xLayoutValid
= false;
740 yLayoutValid
= false;
741 super.preferenceChanged(child
, width
, height
);
745 * Maps the document model position <code>pos</code> to a Shape
746 * in the view coordinate space. This method overrides CompositeView's
747 * method to make sure the children are allocated properly before
748 * calling the super's behaviour.
750 public Shape
modelToView(int pos
, Shape a
, Position
.Bias bias
)
751 throws BadLocationException
753 // Make sure everything is allocated properly and then call super
754 if (!isAllocationValid())
756 Rectangle bounds
= a
.getBounds();
757 setSize(bounds
.width
, bounds
.height
);
759 return super.modelToView(pos
, a
, bias
);