2006-08-14 Mark Wielaard <mark@klomp.org>
[official-gcc.git] / libjava / classpath / javax / swing / text / CompositeView.java
blob6f487b8981e2ed9456338ce5b84028a73674f0cf
1 /* CompositeView.java -- An abstract view that manages child views
2 Copyright (C) 2005, 2006 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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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;
47 /**
48 * An abstract base implementation of {@link View} that manages child
49 * <code>View</code>s.
51 * @author Roman Kennke (roman@kennke.org)
53 public abstract class CompositeView
54 extends View
57 /**
58 * The child views of this <code>CompositeView</code>.
60 View[] children;
62 /**
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;
69 /**
70 * The insets of this <code>CompositeView</code>. This is initialized
71 * in {@link #setInsets}.
73 Insets insets;
75 /**
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)
83 super(element);
84 children = new View[0];
85 insets = new Insets(0, 0, 0, 0);
88 /**
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
95 * @see #setParent
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)
144 return children[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);
195 return 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
201 * of zero.
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
215 * valid values
217 public Shape modelToView(int pos, Shape a, Position.Bias bias)
218 throws BadLocationException
220 boolean backward = bias == Position.Bias.Backward;
221 int testpos = backward ? Math.max(0, pos - 1) : pos;
223 Shape ret = null;
224 if (! backward || testpos >= getStartOffset())
226 int childIndex = getViewIndexAtPosition(testpos);
227 if (childIndex != -1 && childIndex < getViewCount())
229 View child = getView(childIndex);
230 if (child != null && testpos >= child.getStartOffset()
231 && testpos < child.getEndOffset())
233 Shape childAlloc = getChildAllocation(childIndex, a);
234 if (childAlloc != null)
236 ret = child.modelToView(pos, childAlloc, bias);
237 // Handle corner case.
238 if (ret == null && child.getEndOffset() == pos)
240 childIndex++;
241 if (childIndex < getViewCount())
243 child = getView(childIndex);
244 childAlloc = getChildAllocation(childIndex, a);
245 ret = child.modelToView(pos, childAlloc, bias);
251 else
253 throw new BadLocationException("Position " + pos
254 + " is not represented by view.", pos);
257 return ret;
261 * A helper method for {@link #modelToView(int, Position.Bias, int,
262 * Position.Bias, Shape)}. This creates a default location when there is
263 * no child view that can take responsibility for mapping the position to
264 * view coordinates. Depending on the specified bias this will be the
265 * left or right edge of this view's allocation.
267 * @param a the allocation for this view
268 * @param bias the bias
270 * @return a default location
272 private Shape createDefaultLocation(Shape a, Position.Bias bias)
274 Rectangle alloc = a.getBounds();
275 Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height);
276 if (bias == Position.Bias.Forward)
277 location.x = alloc.x + alloc.width;
278 return location;
282 * Maps a region in the document into the coordinate space of the View.
284 * @param p1 the beginning position inside the document
285 * @param b1 the direction bias for the beginning position
286 * @param p2 the end position inside the document
287 * @param b2 the direction bias for the end position
288 * @param a the area that is occupied by the view
290 * @return a rectangle that gives the span of the document region
291 * inside the view coordinate space
293 * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
294 * invalid
295 * @throws IllegalArgumentException if b1 or b2 is not one of the above
296 * listed valid values
298 public Shape modelToView(int p1, Position.Bias b1,
299 int p2, Position.Bias b2, Shape a)
300 throws BadLocationException
302 // TODO: This is most likely not 100% ok, figure out what else is to
303 // do here.
304 return super.modelToView(p1, b1, p2, b2, a);
308 * Maps coordinates from the <code>View</code>'s space into a position
309 * in the document model.
311 * @param x the x coordinate in the view space, x >= 0
312 * @param y the y coordinate in the view space, y >= 0
313 * @param a the allocation of this <code>View</code>
314 * @param b the bias to use
316 * @return the position in the document that corresponds to the screen
317 * coordinates <code>x, y</code> >= 0
319 public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
321 if (x >= 0 && y >= 0)
323 Rectangle r = getInsideAllocation(a);
324 View view = getViewAtPoint((int) x, (int) y, r);
325 return view.viewToModel(x, y, r, b);
327 return 0;
331 * Returns the next model location that is visible in eiter north / south
332 * direction or east / west direction. This is used to determine the placement
333 * of the caret when navigating around the document with the arrow keys. This
334 * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
335 * and {@link #getNextEastWestVisualPositionFrom}.
337 * @param pos
338 * the model position to start search from
339 * @param b
340 * the bias for <code>pos</code>
341 * @param a
342 * the allocated region for this view
343 * @param direction
344 * the direction from the current position, can be one of the
345 * following:
346 * <ul>
347 * <li>{@link SwingConstants#WEST}</li>
348 * <li>{@link SwingConstants#EAST}</li>
349 * <li>{@link SwingConstants#NORTH}</li>
350 * <li>{@link SwingConstants#SOUTH}</li>
351 * </ul>
352 * @param biasRet
353 * the bias of the return value gets stored here
354 * @return the position inside the model that represents the next visual
355 * location
356 * @throws BadLocationException
357 * if <code>pos</code> is not a valid location inside the document
358 * model
359 * @throws IllegalArgumentException
360 * if <code>direction</code> is invalid
362 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
363 int direction, Position.Bias[] biasRet)
364 throws BadLocationException
366 int retVal = -1;
367 switch (direction)
369 case SwingConstants.WEST:
370 case SwingConstants.EAST:
371 retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction,
372 biasRet);
373 break;
374 case SwingConstants.NORTH:
375 case SwingConstants.SOUTH:
376 retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
377 biasRet);
378 break;
379 default:
380 throw new IllegalArgumentException("Illegal value for direction.");
382 return retVal;
386 * Returns the index of the child view that represents the specified
387 * model location.
389 * @param pos the model location for which to determine the child view index
390 * @param b the bias to be applied to <code>pos</code>
392 * @return the index of the child view that represents the specified
393 * model location
395 public int getViewIndex(int pos, Position.Bias b)
397 if (b == Position.Bias.Backward && pos != 0)
398 pos -= 1;
399 int i = -1;
400 if (pos >= getStartOffset() && pos < getEndOffset())
401 i = getViewIndexAtPosition(pos);
402 return i;
406 * Returns <code>true</code> if the specified point lies before the
407 * given <code>Rectangle</code>, <code>false</code> otherwise.
409 * &quot;Before&quot; is typically defined as being to the left or above.
411 * @param x the X coordinate of the point
412 * @param y the Y coordinate of the point
413 * @param r the rectangle to test the point against
415 * @return <code>true</code> if the specified point lies before the
416 * given <code>Rectangle</code>, <code>false</code> otherwise
418 protected abstract boolean isBefore(int x, int y, Rectangle r);
421 * Returns <code>true</code> if the specified point lies after the
422 * given <code>Rectangle</code>, <code>false</code> otherwise.
424 * &quot;After&quot; is typically defined as being to the right or below.
426 * @param x the X coordinate of the point
427 * @param y the Y coordinate of the point
428 * @param r the rectangle to test the point against
430 * @return <code>true</code> if the specified point lies after the
431 * given <code>Rectangle</code>, <code>false</code> otherwise
433 protected abstract boolean isAfter(int x, int y, Rectangle r);
436 * Returns the child <code>View</code> at the specified location.
438 * @param x the X coordinate
439 * @param y the Y coordinate
440 * @param r the inner allocation of this <code>BoxView</code> on entry,
441 * the allocation of the found child on exit
443 * @return the child <code>View</code> at the specified location
445 protected abstract View getViewAtPoint(int x, int y, Rectangle r);
448 * Computes the allocation for a child <code>View</code>. The parameter
449 * <code>a</code> stores the allocation of this <code>CompositeView</code>
450 * and is then adjusted to hold the allocation of the child view.
452 * @param index the index of the child <code>View</code>
453 * @param a the allocation of this <code>CompositeView</code> before the
454 * call, the allocation of the child on exit
456 protected abstract void childAllocation(int index, Rectangle a);
459 * Returns the child <code>View</code> that contains the given model
460 * position. The given <code>Rectangle</code> gives the parent's allocation
461 * and is changed to the child's allocation on exit.
463 * @param pos the model position to query the child <code>View</code> for
464 * @param a the parent allocation on entry and the child allocation on exit
466 * @return the child view at the given model position
468 protected View getViewAtPosition(int pos, Rectangle a)
470 View view = null;
471 int i = getViewIndexAtPosition(pos);
472 if (i >= 0 && i < getViewCount() && a != null)
474 view = getView(i);
475 childAllocation(i, a);
477 return view;
481 * Returns the index of the child <code>View</code> for the given model
482 * position.
484 * @param pos the model position for whicht the child <code>View</code> is
485 * queried
487 * @return the index of the child <code>View</code> for the given model
488 * position
490 protected int getViewIndexAtPosition(int pos)
492 // We have a 1:1 mapping of elements to views here, so we forward
493 // this to the element.
494 Element el = getElement();
495 return el.getElementIndex(pos);
499 * Returns the allocation that is given to this <code>CompositeView</code>
500 * minus this <code>CompositeView</code>'s insets.
502 * Also this translates from an immutable allocation to a mutable allocation
503 * that is typically reused and further narrowed, like in
504 * {@link #childAllocation}.
506 * @param a the allocation given to this <code>CompositeView</code>
508 * @return the allocation that is given to this <code>CompositeView</code>
509 * minus this <code>CompositeView</code>'s insets or
510 * <code>null</code> if a was <code>null</code>
512 protected Rectangle getInsideAllocation(Shape a)
514 if (a == null)
515 return null;
517 Rectangle alloc = a.getBounds();
518 // Initialize the inside allocation rectangle. This is done inside
519 // a synchronized block in order to avoid multiple threads creating
520 // this instance simultanously.
521 Rectangle inside;
522 synchronized(this)
524 inside = insideAllocation;
525 if (inside == null)
527 inside = new Rectangle();
528 insideAllocation = inside;
531 inside.x = alloc.x + insets.left;
532 inside.y = alloc.y + insets.top;
533 inside.width = alloc.width - insets.left - insets.right;
534 inside.height = alloc.height - insets.top - insets.bottom;
535 return inside;
539 * Sets the insets defined by attributes in <code>attributes</code>. This
540 * queries the attribute keys {@link StyleConstants#SpaceAbove},
541 * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and
542 * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to
543 * actually set the insets on this <code>CompositeView</code>.
545 * @param attributes the attributes from which to query the insets
547 protected void setParagraphInsets(AttributeSet attributes)
549 Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent);
550 short left = 0;
551 if (l != null)
552 left = l.shortValue();
553 Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent);
554 short right = 0;
555 if (r != null)
556 right = r.shortValue();
557 Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove);
558 short top = 0;
559 if (t != null)
560 top = t.shortValue();
561 Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow);
562 short bottom = 0;
563 if (b != null)
564 bottom = b.shortValue();
565 setInsets(top, left, bottom, right);
569 * Sets the insets of this <code>CompositeView</code>.
571 * @param top the top inset
572 * @param left the left inset
573 * @param bottom the bottom inset
574 * @param right the right inset
576 protected void setInsets(short top, short left, short bottom, short right)
578 insets.top = top;
579 insets.left = left;
580 insets.bottom = bottom;
581 insets.right = right;
585 * Returns the left inset of this <code>CompositeView</code>.
587 * @return the left inset of this <code>CompositeView</code>
589 protected short getLeftInset()
591 return (short) insets.left;
595 * Returns the right inset of this <code>CompositeView</code>.
597 * @return the right inset of this <code>CompositeView</code>
599 protected short getRightInset()
601 return (short) insets.right;
605 * Returns the top inset of this <code>CompositeView</code>.
607 * @return the top inset of this <code>CompositeView</code>
609 protected short getTopInset()
611 return (short) insets.top;
615 * Returns the bottom inset of this <code>CompositeView</code>.
617 * @return the bottom inset of this <code>CompositeView</code>
619 protected short getBottomInset()
621 return (short) insets.bottom;
625 * Returns the next model location that is visible in north or south
626 * direction.
627 * This is used to determine the
628 * placement of the caret when navigating around the document with
629 * the arrow keys.
631 * @param pos the model position to start search from
632 * @param b the bias for <code>pos</code>
633 * @param a the allocated region for this view
634 * @param direction the direction from the current position, can be one of
635 * the following:
636 * <ul>
637 * <li>{@link SwingConstants#NORTH}</li>
638 * <li>{@link SwingConstants#SOUTH}</li>
639 * </ul>
640 * @param biasRet the bias of the return value gets stored here
642 * @return the position inside the model that represents the next visual
643 * location
645 * @throws BadLocationException if <code>pos</code> is not a valid location
646 * inside the document model
647 * @throws IllegalArgumentException if <code>direction</code> is invalid
649 protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
650 Shape a, int direction,
651 Position.Bias[] biasRet)
652 throws BadLocationException
654 // TODO: It is unknown to me how this method has to be implemented and
655 // there is no specification telling me how to do it properly. Therefore
656 // the implementation was done for cases that are known.
658 // If this method ever happens to act silly for your particular case then
659 // it is likely that it is a cause of not knowing about your case when it
660 // was implemented first. You are free to fix the behavior.
662 // Here are the assumptions that lead to the implementation:
663 // If direction is NORTH chose the View preceding the one that contains the
664 // offset 'pos' (imagine the views are stacked on top of each other where
665 // the top is 0 and the bottom is getViewCount()-1.
666 // Consecutively when the direction is SOUTH the View following the one
667 // the offset 'pos' lies in is questioned.
669 // This limitation is described as PR 27345.
670 int index = getViewIndex(pos, b);
671 View v = null;
673 if (index == -1)
674 return pos;
676 switch (direction)
678 case NORTH:
679 // If we cannot calculate a proper offset return the one that was
680 // provided.
681 if (index <= 0)
682 return pos;
684 v = getView(index - 1);
685 break;
686 case SOUTH:
687 // If we cannot calculate a proper offset return the one that was
688 // provided.
689 if (index >= getViewCount() - 1)
690 return pos;
692 v = getView(index + 1);
693 break;
694 default:
695 throw new IllegalArgumentException();
698 return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet);
702 * Returns the next model location that is visible in east or west
703 * direction.
704 * This is used to determine the
705 * placement of the caret when navigating around the document with
706 * the arrow keys.
708 * @param pos the model position to start search from
709 * @param b the bias for <code>pos</code>
710 * @param a the allocated region for this view
711 * @param direction the direction from the current position, can be one of
712 * the following:
713 * <ul>
714 * <li>{@link SwingConstants#EAST}</li>
715 * <li>{@link SwingConstants#WEST}</li>
716 * </ul>
717 * @param biasRet the bias of the return value gets stored here
719 * @return the position inside the model that represents the next visual
720 * location
722 * @throws BadLocationException if <code>pos</code> is not a valid location
723 * inside the document model
724 * @throws IllegalArgumentException if <code>direction</code> is invalid
726 protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
727 Shape a, int direction,
728 Position.Bias[] biasRet)
729 throws BadLocationException
731 // TODO: It is unknown to me how this method has to be implemented and
732 // there is no specification telling me how to do it properly. Therefore
733 // the implementation was done for cases that are known.
735 // If this method ever happens to act silly for your particular case then
736 // it is likely that it is a cause of not knowing about your case when it
737 // was implemented first. You are free to fix the behavior.
739 // Here are the assumptions that lead to the implementation:
740 // If direction is EAST increase the offset by one and ask the View to
741 // which that index belong to calculate the 'next visual position'.
742 // If the direction is WEST do the same with offset 'pos' being decreased
743 // by one.
744 // This behavior will fail in a right-to-left or bidi environment!
746 // This limitation is described as PR 27346.
747 int index;
749 View v = null;
751 switch (direction)
753 case EAST:
754 index = getViewIndex(pos + 1, b);
755 // If we cannot calculate a proper offset return the one that was
756 // provided.
757 if (index == -1)
758 return pos;
760 v = getView(index);
761 break;
762 case WEST:
763 index = getViewIndex(pos - 1, b);
764 // If we cannot calculate a proper offset return the one that was
765 // provided.
766 if (index == -1)
767 return pos;
769 v = getView(index);
770 break;
771 default:
772 throw new IllegalArgumentException();
775 return v.getNextVisualPositionFrom(pos,
778 direction,
779 biasRet);
783 * Determines if the next view in horinzontal direction is located to
784 * the east or west of the view at position <code>pos</code>. Usually
785 * the <code>View</code>s are laid out from the east to the west, so
786 * we unconditionally return <code>false</code> here. Subclasses that
787 * support bidirectional text may wish to override this method.
789 * @param pos the position in the document
790 * @param bias the bias to be applied to <code>pos</code>
792 * @return <code>true</code> if the next <code>View</code> is located
793 * to the EAST, <code>false</code> otherwise
795 protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias)
797 return false;