001 /* View.java --
002 Copyright (C) 2002, 2004, 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.SwingConstants;
047 import javax.swing.SwingUtilities;
048 import javax.swing.event.DocumentEvent;
049
050 public abstract class View implements SwingConstants
051 {
052 public static final int BadBreakWeight = 0;
053 public static final int ExcellentBreakWeight = 2000;
054 public static final int ForcedBreakWeight = 3000;
055 public static final int GoodBreakWeight = 1000;
056
057 public static final int X_AXIS = 0;
058 public static final int Y_AXIS = 1;
059
060 private Element elt;
061 private View parent;
062
063 /**
064 * Creates a new <code>View</code> instance.
065 *
066 * @param elem an <code>Element</code> value
067 */
068 public View(Element elem)
069 {
070 elt = elem;
071 }
072
073 public abstract void paint(Graphics g, Shape s);
074
075 /**
076 * Sets the parent for this view. This is the first method that is beeing
077 * called on a view to setup the view hierarchy. This is also the last method
078 * beeing called when the view is disconnected from the view hierarchy, in
079 * this case <code>parent</code> is null.
080 *
081 * If <code>parent</code> is <code>null</code>, a call to this method also
082 * calls <code>setParent</code> on the children, thus disconnecting them from
083 * the view hierarchy. That means that super must be called when this method
084 * is overridden.
085 *
086 * @param parent the parent to set, <code>null</code> when this view is
087 * beeing disconnected from the view hierarchy
088 */
089 public void setParent(View parent)
090 {
091 if (parent == null)
092 {
093 int numChildren = getViewCount();
094 for (int i = 0; i < numChildren; i++)
095 {
096 View child = getView(i);
097 // It is important that we only reset the parent on views that
098 // actually belong to us. In FlowView the child may already be
099 // reparented.
100 if (child.getParent() == this)
101 child.setParent(null);
102 }
103 }
104
105 this.parent = parent;
106 }
107
108 public View getParent()
109 {
110 return parent;
111 }
112
113 public Container getContainer()
114 {
115 View parent = getParent();
116 if (parent == null)
117 return null;
118 else
119 return parent.getContainer();
120 }
121
122 public Document getDocument()
123 {
124 return getElement().getDocument();
125 }
126
127 public Element getElement()
128 {
129 return elt;
130 }
131
132 /**
133 * Returns the preferred span along the specified axis. Normally the view is
134 * rendered with the span returned here if that is possible.
135 *
136 * @param axis the axis
137 *
138 * @return the preferred span along the specified axis
139 */
140 public abstract float getPreferredSpan(int axis);
141
142 /**
143 * Returns the resize weight of this view. A value of <code>0</code> or less
144 * means this view is not resizeable. Positive values make the view
145 * resizeable. The default implementation returns <code>0</code>
146 * unconditionally.
147 *
148 * @param axis the axis
149 *
150 * @return the resizability of this view along the specified axis
151 */
152 public int getResizeWeight(int axis)
153 {
154 return 0;
155 }
156
157 /**
158 * Returns the maximum span along the specified axis. The default
159 * implementation will forward to
160 * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
161 * returns a value > 0, in which case this returns {@link Integer#MIN_VALUE}.
162 *
163 * @param axis the axis
164 *
165 * @return the maximum span along the specified axis
166 */
167 public float getMaximumSpan(int axis)
168 {
169 float max = Integer.MAX_VALUE;
170 if (getResizeWeight(axis) <= 0)
171 max = getPreferredSpan(axis);
172 return max;
173 }
174
175 /**
176 * Returns the minimum span along the specified axis. The default
177 * implementation will forward to
178 * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)}
179 * returns a value > 0, in which case this returns <code>0</code>.
180 *
181 * @param axis the axis
182 *
183 * @return the minimum span along the specified axis
184 */
185 public float getMinimumSpan(int axis)
186 {
187 float min = 0;
188 if (getResizeWeight(axis) <= 0)
189 min = getPreferredSpan(axis);
190 return min;
191 }
192
193 public void setSize(float width, float height)
194 {
195 // The default implementation does nothing.
196 }
197
198 /**
199 * Returns the alignment of this view along the baseline of the parent view.
200 * An alignment of <code>0.0</code> will align this view with the left edge
201 * along the baseline, an alignment of <code>0.5</code> will align it
202 * centered to the baseline, an alignment of <code>1.0</code> will align
203 * the right edge along the baseline.
204 *
205 * The default implementation returns 0.5 unconditionally.
206 *
207 * @param axis the axis
208 *
209 * @return the alignment of this view along the parents baseline for the
210 * specified axis
211 */
212 public float getAlignment(int axis)
213 {
214 return 0.5f;
215 }
216
217 public AttributeSet getAttributes()
218 {
219 return getElement().getAttributes();
220 }
221
222 public boolean isVisible()
223 {
224 return true;
225 }
226
227 public int getViewCount()
228 {
229 return 0;
230 }
231
232 public View getView(int index)
233 {
234 return null;
235 }
236
237 public ViewFactory getViewFactory()
238 {
239 View parent = getParent();
240 return parent != null ? parent.getViewFactory() : null;
241 }
242
243 /**
244 * Replaces a couple of child views with new child views. If
245 * <code>length == 0</code> then this is a simple insertion, if
246 * <code>views == null</code> this only removes some child views.
247 *
248 * @param offset the offset at which to replace
249 * @param length the number of child views to be removed
250 * @param views the new views to be inserted, may be <code>null</code>
251 */
252 public void replace(int offset, int length, View[] views)
253 {
254 // Default implementation does nothing.
255 }
256
257 public void insert(int offset, View view)
258 {
259 View[] array = { view };
260 replace(offset, 1, array);
261 }
262
263 public void append(View view)
264 {
265 View[] array = { view };
266 int offset = getViewCount();
267 replace(offset, 0, array);
268 }
269
270 public void removeAll()
271 {
272 replace(0, getViewCount(), null);
273 }
274
275 public void remove(int index)
276 {
277 replace(index, 1, null);
278 }
279
280 public View createFragment(int p0, int p1)
281 {
282 // The default implementation doesn't support fragmentation.
283 return this;
284 }
285
286 public int getStartOffset()
287 {
288 return getElement().getStartOffset();
289 }
290
291 public int getEndOffset()
292 {
293 return getElement().getEndOffset();
294 }
295
296 public Shape getChildAllocation(int index, Shape a)
297 {
298 return null;
299 }
300
301 /**
302 * @since 1.4
303 */
304 public int getViewIndex(float x, float y, Shape allocation)
305 {
306 return -1;
307 }
308
309 /**
310 * @since 1.4
311 */
312 public String getToolTipText(float x, float y, Shape allocation)
313 {
314 int index = getViewIndex(x, y, allocation);
315
316 String text = null;
317 if (index >= 0)
318 {
319 allocation = getChildAllocation(index, allocation);
320 Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
321 : allocation.getBounds();
322 if (r.contains(x, y))
323 text = getView(index).getToolTipText(x, y, allocation);
324 }
325 return text;
326 }
327
328 /**
329 * @since 1.3
330 */
331 public Graphics getGraphics()
332 {
333 return getContainer().getGraphics();
334 }
335
336 public void preferenceChanged(View child, boolean width, boolean height)
337 {
338 View p = getParent();
339 if (p != null)
340 p.preferenceChanged(this, width, height);
341 }
342
343 public int getBreakWeight(int axis, float pos, float len)
344 {
345 int weight = BadBreakWeight;
346 if (len > getPreferredSpan(axis))
347 weight = GoodBreakWeight;
348 return weight;
349 }
350
351 public View breakView(int axis, int offset, float pos, float len)
352 {
353 return this;
354 }
355
356 /**
357 * @since 1.3
358 */
359 public int getViewIndex(int pos, Position.Bias b)
360 {
361 return -1;
362 }
363
364 /**
365 * Receive notification about an insert update to the text model.
366 *
367 * The default implementation of this method does the following:
368 * <ul>
369 * <li>Call {@link #updateChildren} if the element that this view is
370 * responsible for has changed. This makes sure that the children can
371 * correctly represent the model.<li>
372 * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
373 * the child views.<li>
374 * <li>Call {@link #updateLayout}. Gives the view a chance to either
375 * repair its layout, reschedule layout or do nothing at all.</li>
376 * </ul>
377 *
378 * @param ev the DocumentEvent that describes the change
379 * @param shape the shape of the view
380 * @param vf the ViewFactory for creating child views
381 */
382 public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
383 {
384 if (getViewCount() > 0)
385 {
386 Element el = getElement();
387 DocumentEvent.ElementChange ec = ev.getChange(el);
388 if (ec != null)
389 {
390 if (! updateChildren(ec, ev, vf))
391 ec = null;
392 }
393 forwardUpdate(ec, ev, shape, vf);
394 updateLayout(ec, ev, shape);
395 }
396 }
397
398 /**
399 * Receive notification about a remove update to the text model.
400 *
401 * The default implementation of this method does the following:
402 * <ul>
403 * <li>Call {@link #updateChildren} if the element that this view is
404 * responsible for has changed. This makes sure that the children can
405 * correctly represent the model.<li>
406 * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
407 * the child views.<li>
408 * <li>Call {@link #updateLayout}. Gives the view a chance to either
409 * repair its layout, reschedule layout or do nothing at all.</li>
410 * </ul>
411 *
412 * @param ev the DocumentEvent that describes the change
413 * @param shape the shape of the view
414 * @param vf the ViewFactory for creating child views
415 */
416 public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
417 {
418 Element el = getElement();
419 DocumentEvent.ElementChange ec = ev.getChange(el);
420 if (ec != null)
421 {
422 if (! updateChildren(ec, ev, vf))
423 ec = null;
424 }
425 forwardUpdate(ec, ev, shape, vf);
426 updateLayout(ec, ev, shape);
427 }
428
429 /**
430 * Receive notification about a change update to the text model.
431 *
432 * The default implementation of this method does the following:
433 * <ul>
434 * <li>Call {@link #updateChildren} if the element that this view is
435 * responsible for has changed. This makes sure that the children can
436 * correctly represent the model.<li>
437 * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to
438 * the child views.<li>
439 * <li>Call {@link #updateLayout}. Gives the view a chance to either
440 * repair its layout, reschedule layout or do nothing at all.</li>
441 * </ul>
442 *
443 * @param ev the DocumentEvent that describes the change
444 * @param shape the shape of the view
445 * @param vf the ViewFactory for creating child views
446 */
447 public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
448 {
449 if (getViewCount() > 0)
450 {
451 Element el = getElement();
452 DocumentEvent.ElementChange ec = ev.getChange(el);
453 if (ec != null)
454 {
455 if (! updateChildren(ec, ev, vf))
456 ec = null;
457 }
458 forwardUpdate(ec, ev, shape, vf);
459 updateLayout(ec, ev, shape);
460 }
461 }
462
463 /**
464 * Updates the list of children that is returned by {@link #getView}
465 * and {@link #getViewCount}.
466 *
467 * Element that are specified as beeing added in the ElementChange record are
468 * assigned a view for using the ViewFactory. Views of Elements that
469 * are specified as beeing removed are removed from the list.
470 *
471 * @param ec the ElementChange record that describes the change of the
472 * element
473 * @param ev the DocumentEvent describing the change of the document model
474 * @param vf the ViewFactory to use for creating new views
475 *
476 * @return whether or not the child views represent the child elements of
477 * the element that this view is responsible for. Some views may
478 * create views that are responsible only for parts of the element
479 * that they are responsible for and should then return false.
480 *
481 * @since 1.3
482 */
483 protected boolean updateChildren(DocumentEvent.ElementChange ec,
484 DocumentEvent ev,
485 ViewFactory vf)
486 {
487 Element[] added = ec.getChildrenAdded();
488 Element[] removed = ec.getChildrenRemoved();
489 int index = ec.getIndex();
490
491 View[] newChildren = null;
492 if (added != null)
493 {
494 newChildren = new View[added.length];
495 for (int i = 0; i < added.length; ++i)
496 newChildren[i] = vf.create(added[i]);
497 }
498 int numRemoved = removed != null ? removed.length : 0;
499 replace(index, numRemoved, newChildren);
500
501 return true;
502 }
503
504 /**
505 * Forwards the DocumentEvent to child views that need to get notified
506 * of the change to the model. This calles {@link #forwardUpdateToView}
507 * for each View that must be forwarded to.
508 *
509 * If <code>ec</code> is not <code>null</code> (this means there have been
510 * structural changes to the element that this view is responsible for) this
511 * method should recognize this and don't notify newly added child views.
512 *
513 * @param ec the ElementChange describing the element changes (may be
514 * <code>null</code> if there were no changes)
515 * @param ev the DocumentEvent describing the changes to the model
516 * @param shape the current allocation of the view
517 * @param vf the ViewFactory used to create new Views
518 *
519 * @since 1.3
520 */
521 protected void forwardUpdate(DocumentEvent.ElementChange ec,
522 DocumentEvent ev, Shape shape, ViewFactory vf)
523 {
524 int count = getViewCount();
525 if (count > 0)
526 {
527 // Determine start index.
528 int startOffset = ev.getOffset();
529 int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
530
531 // For REMOVE events we have to forward the event to the last element,
532 // for the case that an Element has been removed that represente
533 // the offset.
534 if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE
535 && startOffset >= getEndOffset())
536 {
537 startIndex = getViewCount() - 1;
538 }
539
540 // When startIndex is on a view boundary, forward event to the
541 // previous view too.
542 if (startIndex >= 0)
543 {
544 View v = getView(startIndex);
545 if (v != null)
546 {
547 if (v.getStartOffset() == startOffset && startOffset > 0)
548 startIndex = Math.max(0, startIndex - 1);
549 }
550 }
551 startIndex = Math.max(0, startIndex);
552
553 // Determine end index.
554 int endIndex = startIndex;
555 if (ev.getType() != DocumentEvent.EventType.REMOVE)
556 {
557 endIndex = getViewIndex(startOffset + ev.getLength(),
558 Position.Bias.Forward);
559 if (endIndex < 0)
560 endIndex = getViewCount() - 1;
561 }
562
563 // Determine hole that comes from added elements (we don't forward
564 // the event to newly added views.
565 int startAdded = endIndex + 1;
566 int endAdded = startAdded;
567 Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
568 if (added != null && added.length > 0)
569 {
570 startAdded = ec.getIndex();
571 endAdded = startAdded + added.length - 1;
572 }
573
574 // Forward event to all views between startIndex and endIndex,
575 // and leave out all views in the hole.
576 for (int i = startIndex; i <= endIndex; i++)
577 {
578 // Skip newly added child views.
579 if (! (i >= startAdded && i <= endAdded))
580 {
581 View child = getView(i);
582 if (child != null)
583 {
584 Shape childAlloc = getChildAllocation(i, shape);
585 forwardUpdateToView(child, ev, childAlloc, vf);
586 }
587 }
588 }
589 }
590 }
591
592 /**
593 * Forwards an update event to the given child view. This calls
594 * {@link #insertUpdate}, {@link #removeUpdate} or {@link #changedUpdate},
595 * depending on the type of document event.
596 *
597 * @param view the View to forward the event to
598 * @param ev the DocumentEvent to forward
599 * @param shape the current allocation of the View
600 * @param vf the ViewFactory used to create new Views
601 *
602 * @since 1.3
603 */
604 protected void forwardUpdateToView(View view, DocumentEvent ev, Shape shape,
605 ViewFactory vf)
606 {
607 DocumentEvent.EventType type = ev.getType();
608 if (type == DocumentEvent.EventType.INSERT)
609 view.insertUpdate(ev, shape, vf);
610 else if (type == DocumentEvent.EventType.REMOVE)
611 view.removeUpdate(ev, shape, vf);
612 else if (type == DocumentEvent.EventType.CHANGE)
613 view.changedUpdate(ev, shape, vf);
614 }
615
616 /**
617 * Updates the layout.
618 *
619 * @param ec the ElementChange that describes the changes to the element
620 * @param ev the DocumentEvent that describes the changes to the model
621 * @param shape the current allocation for this view
622 *
623 * @since 1.3
624 */
625 protected void updateLayout(DocumentEvent.ElementChange ec,
626 DocumentEvent ev, Shape shape)
627 {
628 if (ec != null && shape != null)
629 {
630 preferenceChanged(null, true, true);
631 Container c = getContainer();
632 if (c != null)
633 c.repaint();
634 }
635 }
636
637 /**
638 * Maps a position in the document into the coordinate space of the View.
639 * The output rectangle usually reflects the font height but has a width
640 * of zero.
641 *
642 * @param pos the position of the character in the model
643 * @param a the area that is occupied by the view
644 * @param b either {@link Position.Bias#Forward} or
645 * {@link Position.Bias#Backward} depending on the preferred
646 * direction bias. If <code>null</code> this defaults to
647 * <code>Position.Bias.Forward</code>
648 *
649 * @return a rectangle that gives the location of the document position
650 * inside the view coordinate space
651 *
652 * @throws BadLocationException if <code>pos</code> is invalid
653 * @throws IllegalArgumentException if b is not one of the above listed
654 * valid values
655 */
656 public abstract Shape modelToView(int pos, Shape a, Position.Bias b)
657 throws BadLocationException;
658
659 /**
660 * Maps a region in the document into the coordinate space of the View.
661 *
662 * @param p1 the beginning position inside the document
663 * @param b1 the direction bias for the beginning position
664 * @param p2 the end position inside the document
665 * @param b2 the direction bias for the end position
666 * @param a the area that is occupied by the view
667 *
668 * @return a rectangle that gives the span of the document region
669 * inside the view coordinate space
670 *
671 * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
672 * invalid
673 * @throws IllegalArgumentException if b1 or b2 is not one of the above
674 * listed valid values
675 */
676 public Shape modelToView(int p1, Position.Bias b1,
677 int p2, Position.Bias b2, Shape a)
678 throws BadLocationException
679 {
680 if (b1 != Position.Bias.Forward && b1 != Position.Bias.Backward)
681 throw new IllegalArgumentException
682 ("b1 must be either Position.Bias.Forward or Position.Bias.Backward");
683 if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward)
684 throw new IllegalArgumentException
685 ("b2 must be either Position.Bias.Forward or Position.Bias.Backward");
686
687 Shape s1 = modelToView(p1, a, b1);
688 // Special case for p2 == end index.
689 Shape s2;
690 if (p2 != getEndOffset())
691 {
692 s2 = modelToView(p2, a, b2);
693 }
694 else
695 {
696 try
697 {
698 s2 = modelToView(p2, a, b2);
699 }
700 catch (BadLocationException ex)
701 {
702 // Assume the end rectangle to be at the right edge of the
703 // view.
704 Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
705 : a.getBounds();
706 s2 = new Rectangle(aRect.x + aRect.width - 1, aRect.y, 1,
707 aRect.height);
708 }
709 }
710
711 // Need to modify the rectangle, so we create a copy in all cases.
712 Rectangle r1 = s1.getBounds();
713 Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2
714 : s2.getBounds();
715
716 // For multiline view, let the resulting rectangle span the whole view.
717 if (r1.y != r2.y)
718 {
719 Rectangle aRect = a instanceof Rectangle ? (Rectangle) a
720 : a.getBounds();
721 r1.x = aRect.x;
722 r1.width = aRect.width;
723 }
724
725 return SwingUtilities.computeUnion(r2.x, r2.y, r2.width, r2.height, r1);
726 }
727
728 /**
729 * Maps a position in the document into the coordinate space of the View.
730 * The output rectangle usually reflects the font height but has a width
731 * of zero.
732 *
733 * This method is deprecated and calls
734 * {@link #modelToView(int, Position.Bias, int, Position.Bias, Shape)} with
735 * a bias of {@link Position.Bias#Forward}.
736 *
737 * @param pos the position of the character in the model
738 * @param a the area that is occupied by the view
739 *
740 * @return a rectangle that gives the location of the document position
741 * inside the view coordinate space
742 *
743 * @throws BadLocationException if <code>pos</code> is invalid
744 *
745 * @deprecated Use {@link #modelToView(int, Shape, Position.Bias)} instead.
746 */
747 public Shape modelToView(int pos, Shape a) throws BadLocationException
748 {
749 return modelToView(pos, a, Position.Bias.Forward);
750 }
751
752 /**
753 * Maps coordinates from the <code>View</code>'s space into a position
754 * in the document model.
755 *
756 * @param x the x coordinate in the view space
757 * @param y the y coordinate in the view space
758 * @param a the allocation of this <code>View</code>
759 * @param b the bias to use
760 *
761 * @return the position in the document that corresponds to the screen
762 * coordinates <code>x, y</code>
763 */
764 public abstract int viewToModel(float x, float y, Shape a, Position.Bias[] b);
765
766 /**
767 * Maps coordinates from the <code>View</code>'s space into a position
768 * in the document model. This method is deprecated and only there for
769 * compatibility.
770 *
771 * @param x the x coordinate in the view space
772 * @param y the y coordinate in the view space
773 * @param a the allocation of this <code>View</code>
774 *
775 * @return the position in the document that corresponds to the screen
776 * coordinates <code>x, y</code>
777 *
778 * @deprecated Use {@link #viewToModel(float, float, Shape, Position.Bias[])}
779 * instead.
780 */
781 public int viewToModel(float x, float y, Shape a)
782 {
783 Position.Bias[] biasRet = new Position.Bias[1];
784 biasRet[0] = Position.Bias.Forward;
785 return viewToModel(x, y, a, biasRet);
786 }
787
788 /**
789 * Dumps the complete View hierarchy. This method can be used for debugging
790 * purposes.
791 */
792 protected void dump()
793 {
794 // Climb up the hierarchy to the parent.
795 View parent = getParent();
796 if (parent != null)
797 parent.dump();
798 else
799 dump(0);
800 }
801
802 /**
803 * Dumps the view hierarchy below this View with the specified indentation
804 * level.
805 *
806 * @param indent the indentation level to be used for this view
807 */
808 void dump(int indent)
809 {
810 for (int i = 0; i < indent; ++i)
811 System.out.print('.');
812 System.out.println(this + "(" + getStartOffset() + "," + getEndOffset() + ": " + getElement());
813
814 int count = getViewCount();
815 for (int i = 0; i < count; ++i)
816 getView(i).dump(indent + 1);
817 }
818
819 /**
820 * Returns the document position that is (visually) nearest to the given
821 * document position <code>pos</code> in the given direction <code>d</code>.
822 *
823 * @param pos the document position
824 * @param b the bias for <code>pos</code>
825 * @param a the allocation for this view
826 * @param d the direction, must be either {@link SwingConstants#NORTH},
827 * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
828 * {@link SwingConstants#EAST}
829 * @param biasRet an array of {@link Position.Bias} that can hold at least
830 * one element, which is filled with the bias of the return position
831 * on method exit
832 *
833 * @return the document position that is (visually) nearest to the given
834 * document position <code>pos</code> in the given direction
835 * <code>d</code>
836 *
837 * @throws BadLocationException if <code>pos</code> is not a valid offset in
838 * the document model
839 * @throws IllegalArgumentException if <code>d</code> is not a valid direction
840 */
841 public int getNextVisualPositionFrom(int pos, Position.Bias b,
842 Shape a, int d,
843 Position.Bias[] biasRet)
844 throws BadLocationException
845 {
846 int ret = pos;
847 Rectangle r;
848 View parent;
849
850 switch (d)
851 {
852 case EAST:
853 // TODO: take component orientation into account?
854 // Note: If pos is below zero the implementation will return
855 // pos + 1 regardless of whether that value is a correct offset
856 // in the document model. However this is what the RI does.
857 ret = Math.min(pos + 1, getEndOffset());
858 break;
859 case WEST:
860 // TODO: take component orientation into account?
861 ret = Math.max(pos - 1, getStartOffset());
862 break;
863 case NORTH:
864 // Try to find a suitable offset by examining the area above.
865 parent = getParent();
866 r = parent.modelToView(pos, a, b).getBounds();
867 ret = parent.viewToModel(r.x, r.y - 1, a, biasRet);
868 break;
869 case SOUTH:
870 // Try to find a suitable offset by examining the area below.
871 parent = getParent();
872 r = parent.modelToView(pos, a, b).getBounds();
873 ret = parent.viewToModel(r.x + r.width, r.y + r.height, a, biasRet);
874 break;
875 default:
876 throw new IllegalArgumentException("Illegal value for d");
877 }
878
879 return ret;
880 }
881 }