1 /* CompositeView.java -- An abstract view that manages child views
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
.Insets
;
42 import java
.awt
.Rectangle
;
43 import java
.awt
.Shape
;
45 import javax
.swing
.SwingConstants
;
48 * An abstract base implementation of {@link View} that manages child
51 * @author Roman Kennke (roman@kennke.org)
53 public abstract class CompositeView
58 * The child views of this <code>CompositeView</code>.
63 * The allocation of this <code>View</code> minus its insets. This is
64 * initialized in {@link #getInsideAllocation} and reused and modified in
65 * {@link #childAllocation(int, Rectangle)}.
67 Rectangle insideAllocation
;
70 * The insets of this <code>CompositeView</code>. This is initialized
71 * in {@link #setInsets}.
76 * Creates a new <code>CompositeView</code> for the given
77 * <code>Element</code>.
79 * @param element the element that is rendered by this CompositeView
81 public CompositeView(Element element
)
84 children
= new View
[0];
85 insets
= new Insets(0, 0, 0, 0);
89 * Loads the child views of this <code>CompositeView</code>. This method
90 * is called from {@link #setParent} to initialize the child views of
91 * this composite view.
93 * @param f the view factory to use for creating new child views
97 protected void loadChildren(ViewFactory f
)
99 Element el
= getElement();
100 int count
= el
.getElementCount();
101 View
[] newChildren
= new View
[count
];
102 for (int i
= 0; i
< count
; ++i
)
104 Element child
= el
.getElement(i
);
105 View view
= f
.create(child
);
106 newChildren
[i
] = view
;
108 replace(0, getViewCount(), newChildren
);
112 * Sets the parent of this <code>View</code>.
113 * In addition to setting the parent, this calls {@link #loadChildren}, if
114 * this <code>View</code> does not already have its children initialized.
116 * @param parent the parent to set
118 public void setParent(View parent
)
120 super.setParent(parent
);
121 if (parent
!= null && ((children
== null) || children
.length
== 0))
122 loadChildren(getViewFactory());
126 * Returns the number of child views.
128 * @return the number of child views
130 public int getViewCount()
132 return children
.length
;
136 * Returns the child view at index <code>n</code>.
138 * @param n the index of the requested child view
140 * @return the child view at index <code>n</code>
142 public View
getView(int n
)
148 * Replaces child views by some other child views. If there are no views to
149 * remove (<code>length == 0</code>), the result is a simple insert, if
150 * there are no children to add (<code>view == null</code>) the result
151 * is a simple removal.
153 * @param offset the start offset from where to remove children
154 * @param length the number of children to remove
155 * @param views the views that replace the removed children
157 public void replace(int offset
, int length
, View
[] views
)
159 // Check for null views to add.
160 for (int i
= 0; i
< views
.length
; ++i
)
161 if (views
[i
] == null)
162 throw new NullPointerException("Added views must not be null");
164 int endOffset
= offset
+ length
;
166 // First we set the parent of the removed children to null.
167 for (int i
= offset
; i
< endOffset
; ++i
)
168 children
[i
].setParent(null);
170 View
[] newChildren
= new View
[children
.length
- length
+ views
.length
];
171 System
.arraycopy(children
, 0, newChildren
, 0, offset
);
172 System
.arraycopy(views
, 0, newChildren
, offset
, views
.length
);
173 System
.arraycopy(children
, offset
+ length
, newChildren
,
174 offset
+ views
.length
,
175 children
.length
- (offset
+ length
));
176 children
= newChildren
;
178 // Finally we set the parent of the added children to this.
179 for (int i
= 0; i
< views
.length
; ++i
)
180 views
[i
].setParent(this);
184 * Returns the allocation for the specified child <code>View</code>.
186 * @param index the index of the child view
187 * @param a the allocation for this view
189 * @return the allocation for the specified child <code>View</code>
191 public Shape
getChildAllocation(int index
, Shape a
)
193 Rectangle r
= getInsideAllocation(a
);
194 childAllocation(index
, r
);
199 * Maps a position in the document into the coordinate space of the View.
200 * The output rectangle usually reflects the font height but has a width
203 * @param pos the position of the character in the model
204 * @param a the area that is occupied by the view
205 * @param bias either {@link Position.Bias#Forward} or
206 * {@link Position.Bias#Backward} depending on the preferred
207 * direction bias. If <code>null</code> this defaults to
208 * <code>Position.Bias.Forward</code>
210 * @return a rectangle that gives the location of the document position
211 * inside the view coordinate space
213 * @throws BadLocationException if <code>pos</code> is invalid
214 * @throws IllegalArgumentException if b is not one of the above listed
217 public Shape
modelToView(int pos
, Shape a
, Position
.Bias bias
)
218 throws BadLocationException
220 int childIndex
= getViewIndex(pos
, bias
);
221 if (childIndex
!= -1)
223 View child
= getView(childIndex
);
224 Rectangle r
= a
.getBounds();
225 childAllocation(childIndex
, r
);
226 Shape result
= child
.modelToView(pos
, r
, bias
);
228 throw new AssertionError("" + child
.getClass().getName()
229 + ".modelToView() must not return null");
233 throw new BadLocationException("No child view for the specified location",
238 * Maps a region in the document into the coordinate space of the View.
240 * @param p1 the beginning position inside the document
241 * @param b1 the direction bias for the beginning position
242 * @param p2 the end position inside the document
243 * @param b2 the direction bias for the end position
244 * @param a the area that is occupied by the view
246 * @return a rectangle that gives the span of the document region
247 * inside the view coordinate space
249 * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
251 * @throws IllegalArgumentException if b1 or b2 is not one of the above
252 * listed valid values
254 public Shape
modelToView(int p1
, Position
.Bias b1
,
255 int p2
, Position
.Bias b2
, Shape a
)
256 throws BadLocationException
258 // TODO: This is most likely not 100% ok, figure out what else is to
260 return super.modelToView(p1
, b1
, p2
, b2
, a
);
264 * Maps coordinates from the <code>View</code>'s space into a position
265 * in the document model.
267 * @param x the x coordinate in the view space, x >= 0
268 * @param y the y coordinate in the view space, y >= 0
269 * @param a the allocation of this <code>View</code>
270 * @param b the bias to use
272 * @return the position in the document that corresponds to the screen
273 * coordinates <code>x, y</code> >= 0
275 public int viewToModel(float x
, float y
, Shape a
, Position
.Bias
[] b
)
277 if (x
>= 0 && y
>= 0)
279 Rectangle r
= getInsideAllocation(a
);
280 View view
= getViewAtPoint((int) x
, (int) y
, r
);
281 return view
.viewToModel(x
, y
, a
, b
);
287 * Returns the next model location that is visible in eiter north / south
288 * direction or east / west direction. This is used to determine the placement
289 * of the caret when navigating around the document with the arrow keys. This
290 * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
291 * and {@link #getNextEastWestVisualPositionFrom}.
294 * the model position to start search from
296 * the bias for <code>pos</code>
298 * the allocated region for this view
300 * the direction from the current position, can be one of the
303 * <li>{@link SwingConstants#WEST}</li>
304 * <li>{@link SwingConstants#EAST}</li>
305 * <li>{@link SwingConstants#NORTH}</li>
306 * <li>{@link SwingConstants#SOUTH}</li>
309 * the bias of the return value gets stored here
310 * @return the position inside the model that represents the next visual
312 * @throws BadLocationException
313 * if <code>pos</code> is not a valid location inside the document
315 * @throws IllegalArgumentException
316 * if <code>direction</code> is invalid
318 public int getNextVisualPositionFrom(int pos
, Position
.Bias b
, Shape a
,
319 int direction
, Position
.Bias
[] biasRet
)
320 throws BadLocationException
325 case SwingConstants
.WEST
:
326 case SwingConstants
.EAST
:
327 retVal
= getNextEastWestVisualPositionFrom(pos
, b
, a
, direction
,
330 case SwingConstants
.NORTH
:
331 case SwingConstants
.SOUTH
:
332 retVal
= getNextNorthSouthVisualPositionFrom(pos
, b
, a
, direction
,
336 throw new IllegalArgumentException("Illegal value for direction.");
342 * Returns the index of the child view that represents the specified
345 * @param pos the model location for which to determine the child view index
346 * @param b the bias to be applied to <code>pos</code>
348 * @return the index of the child view that represents the specified
351 public int getViewIndex(int pos
, Position
.Bias b
)
353 // FIXME: Handle bias somehow.
354 return getViewIndexAtPosition(pos
);
358 * Returns <code>true</code> if the specified point lies before the
359 * given <code>Rectangle</code>, <code>false</code> otherwise.
361 * "Before" is typically defined as being to the left or above.
363 * @param x the X coordinate of the point
364 * @param y the Y coordinate of the point
365 * @param r the rectangle to test the point against
367 * @return <code>true</code> if the specified point lies before the
368 * given <code>Rectangle</code>, <code>false</code> otherwise
370 protected abstract boolean isBefore(int x
, int y
, Rectangle r
);
373 * Returns <code>true</code> if the specified point lies after the
374 * given <code>Rectangle</code>, <code>false</code> otherwise.
376 * "After" is typically defined as being to the right or below.
378 * @param x the X coordinate of the point
379 * @param y the Y coordinate of the point
380 * @param r the rectangle to test the point against
382 * @return <code>true</code> if the specified point lies after the
383 * given <code>Rectangle</code>, <code>false</code> otherwise
385 protected abstract boolean isAfter(int x
, int y
, Rectangle r
);
388 * Returns the child <code>View</code> at the specified location.
390 * @param x the X coordinate
391 * @param y the Y coordinate
392 * @param r the inner allocation of this <code>BoxView</code> on entry,
393 * the allocation of the found child on exit
395 * @return the child <code>View</code> at the specified location
397 protected abstract View
getViewAtPoint(int x
, int y
, Rectangle r
);
400 * Computes the allocation for a child <code>View</code>. The parameter
401 * <code>a</code> stores the allocation of this <code>CompositeView</code>
402 * and is then adjusted to hold the allocation of the child view.
404 * @param index the index of the child <code>View</code>
405 * @param a the allocation of this <code>CompositeView</code> before the
406 * call, the allocation of the child on exit
408 protected abstract void childAllocation(int index
, Rectangle a
);
411 * Returns the child <code>View</code> that contains the given model
412 * position. The given <code>Rectangle</code> gives the parent's allocation
413 * and is changed to the child's allocation on exit.
415 * @param pos the model position to query the child <code>View</code> for
416 * @param a the parent allocation on entry and the child allocation on exit
418 * @return the child view at the given model position
420 protected View
getViewAtPosition(int pos
, Rectangle a
)
422 int i
= getViewIndexAtPosition(pos
);
423 View view
= children
[i
];
424 childAllocation(i
, a
);
429 * Returns the index of the child <code>View</code> for the given model
432 * @param pos the model position for whicht the child <code>View</code> is
435 * @return the index of the child <code>View</code> for the given model
438 protected int getViewIndexAtPosition(int pos
)
441 for (int i
= 0; i
< children
.length
; i
++)
443 if (children
[i
].getStartOffset() <= pos
444 && children
[i
].getEndOffset() > pos
)
454 * Returns the allocation that is given to this <code>CompositeView</code>
455 * minus this <code>CompositeView</code>'s insets.
457 * Also this translates from an immutable allocation to a mutable allocation
458 * that is typically reused and further narrowed, like in
459 * {@link #childAllocation}.
461 * @param a the allocation given to this <code>CompositeView</code>
463 * @return the allocation that is given to this <code>CompositeView</code>
464 * minus this <code>CompositeView</code>'s insets or
465 * <code>null</code> if a was <code>null</code>
467 protected Rectangle
getInsideAllocation(Shape a
)
472 Rectangle alloc
= a
.getBounds();
473 // Initialize the inside allocation rectangle. This is done inside
474 // a synchronized block in order to avoid multiple threads creating
475 // this instance simultanously.
479 inside
= insideAllocation
;
482 inside
= new Rectangle();
483 insideAllocation
= inside
;
486 inside
.x
= alloc
.x
+ insets
.left
;
487 inside
.y
= alloc
.y
+ insets
.top
;
488 inside
.width
= alloc
.width
- insets
.left
- insets
.right
;
489 inside
.height
= alloc
.height
- insets
.top
- insets
.bottom
;
494 * Sets the insets defined by attributes in <code>attributes</code>. This
495 * queries the attribute keys {@link StyleConstants#SpaceAbove},
496 * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and
497 * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to
498 * actually set the insets on this <code>CompositeView</code>.
500 * @param attributes the attributes from which to query the insets
502 protected void setParagraphInsets(AttributeSet attributes
)
504 Float l
= (Float
) attributes
.getAttribute(StyleConstants
.LeftIndent
);
507 left
= l
.shortValue();
508 Float r
= (Float
) attributes
.getAttribute(StyleConstants
.RightIndent
);
511 right
= r
.shortValue();
512 Float t
= (Float
) attributes
.getAttribute(StyleConstants
.SpaceAbove
);
515 top
= t
.shortValue();
516 Float b
= (Float
) attributes
.getAttribute(StyleConstants
.SpaceBelow
);
519 bottom
= b
.shortValue();
520 setInsets(top
, left
, bottom
, right
);
524 * Sets the insets of this <code>CompositeView</code>.
526 * @param top the top inset
527 * @param left the left inset
528 * @param bottom the bottom inset
529 * @param right the right inset
531 protected void setInsets(short top
, short left
, short bottom
, short right
)
535 insets
.bottom
= bottom
;
536 insets
.right
= right
;
540 * Returns the left inset of this <code>CompositeView</code>.
542 * @return the left inset of this <code>CompositeView</code>
544 protected short getLeftInset()
546 return (short) insets
.left
;
550 * Returns the right inset of this <code>CompositeView</code>.
552 * @return the right inset of this <code>CompositeView</code>
554 protected short getRightInset()
556 return (short) insets
.right
;
560 * Returns the top inset of this <code>CompositeView</code>.
562 * @return the top inset of this <code>CompositeView</code>
564 protected short getTopInset()
566 return (short) insets
.top
;
570 * Returns the bottom inset of this <code>CompositeView</code>.
572 * @return the bottom inset of this <code>CompositeView</code>
574 protected short getBottomInset()
576 return (short) insets
.bottom
;
580 * Returns the next model location that is visible in north or south
582 * This is used to determine the
583 * placement of the caret when navigating around the document with
586 * @param pos the model position to start search from
587 * @param b the bias for <code>pos</code>
588 * @param a the allocated region for this view
589 * @param direction the direction from the current position, can be one of
592 * <li>{@link SwingConstants#NORTH}</li>
593 * <li>{@link SwingConstants#SOUTH}</li>
595 * @param biasRet the bias of the return value gets stored here
597 * @return the position inside the model that represents the next visual
600 * @throws BadLocationException if <code>pos</code> is not a valid location
601 * inside the document model
602 * @throws IllegalArgumentException if <code>direction</code> is invalid
604 protected int getNextNorthSouthVisualPositionFrom(int pos
, Position
.Bias b
,
605 Shape a
, int direction
,
606 Position
.Bias
[] biasRet
)
607 throws BadLocationException
609 // FIXME: Implement this correctly.
614 * Returns the next model location that is visible in east or west
616 * This is used to determine the
617 * placement of the caret when navigating around the document with
620 * @param pos the model position to start search from
621 * @param b the bias for <code>pos</code>
622 * @param a the allocated region for this view
623 * @param direction the direction from the current position, can be one of
626 * <li>{@link SwingConstants#EAST}</li>
627 * <li>{@link SwingConstants#WEST}</li>
629 * @param biasRet the bias of the return value gets stored here
631 * @return the position inside the model that represents the next visual
634 * @throws BadLocationException if <code>pos</code> is not a valid location
635 * inside the document model
636 * @throws IllegalArgumentException if <code>direction</code> is invalid
638 protected int getNextEastWestVisualPositionFrom(int pos
, Position
.Bias b
,
639 Shape a
, int direction
,
640 Position
.Bias
[] biasRet
)
641 throws BadLocationException
643 // FIXME: Implement this correctly.
648 * Determines if the next view in horinzontal direction is located to
649 * the east or west of the view at position <code>pos</code>. Usually
650 * the <code>View</code>s are laid out from the east to the west, so
651 * we unconditionally return <code>false</code> here. Subclasses that
652 * support bidirectional text may wish to override this method.
654 * @param pos the position in the document
655 * @param bias the bias to be applied to <code>pos</code>
657 * @return <code>true</code> if the next <code>View</code> is located
658 * to the EAST, <code>false</code> otherwise
660 protected boolean flipEastAndWestAtEnds(int pos
, Position
.Bias bias
)