OSDN Git Service

2006-08-14 Mark Wielaard <mark@klomp.org>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / CompositeView.java
1 /* CompositeView.java -- An abstract view that manages child views
2    Copyright (C) 2005, 2006  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10
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.
15
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.
20
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.
25
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. */
37
38
39 package javax.swing.text;
40
41 import java.awt.Insets;
42 import java.awt.Rectangle;
43 import java.awt.Shape;
44
45 import javax.swing.SwingConstants;
46
47 /**
48  * An abstract base implementation of {@link View} that manages child
49  * <code>View</code>s.
50  *
51  * @author Roman Kennke (roman@kennke.org)
52  */
53 public abstract class CompositeView
54   extends View
55 {
56
57   /**
58    * The child views of this <code>CompositeView</code>.
59    */
60   View[] children;
61
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)}.
66    */
67   Rectangle insideAllocation;
68
69   /**
70    * The insets of this <code>CompositeView</code>. This is initialized
71    * in {@link #setInsets}.
72    */
73   Insets insets;
74
75   /**
76    * Creates a new <code>CompositeView</code> for the given
77    * <code>Element</code>.
78    *
79    * @param element the element that is rendered by this CompositeView
80    */
81   public CompositeView(Element element)
82   {
83     super(element);
84     children = new View[0];
85     insets = new Insets(0, 0, 0, 0);
86   }
87
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.
92    *
93    * @param f the view factory to use for creating new child views
94    *
95    * @see #setParent
96    */
97   protected void loadChildren(ViewFactory f)
98   {
99     Element el = getElement();
100     int count = el.getElementCount();
101     View[] newChildren = new View[count];
102     for (int i = 0; i < count; ++i)
103       {
104         Element child = el.getElement(i);
105         View view = f.create(child);
106         newChildren[i] = view;
107       }
108     replace(0, getViewCount(), newChildren);
109   }
110
111   /**
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.
115    *
116    * @param parent the parent to set
117    */
118   public void setParent(View parent)
119   {
120     super.setParent(parent);
121     if (parent != null && ((children == null) || children.length == 0))
122       loadChildren(getViewFactory());
123   }
124
125   /**
126    * Returns the number of child views.
127    *
128    * @return the number of child views
129    */
130   public int getViewCount()
131   {
132     return children.length;
133   }
134
135   /**
136    * Returns the child view at index <code>n</code>.
137    *
138    * @param n the index of the requested child view
139    *
140    * @return the child view at index <code>n</code>
141    */
142   public View getView(int n)
143   {
144     return children[n];
145   }
146
147   /**
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.
152    *
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
156    */
157   public void replace(int offset, int length, View[] views)
158   {
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");
163
164     int endOffset = offset + length;
165
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);
169
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;
177
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);
181   }
182
183   /**
184    * Returns the allocation for the specified child <code>View</code>.
185    *
186    * @param index the index of the child view
187    * @param a the allocation for this view
188    *
189    * @return the allocation for the specified child <code>View</code>
190    */
191   public Shape getChildAllocation(int index, Shape a)
192   {
193     Rectangle r = getInsideAllocation(a);
194     childAllocation(index, r);
195     return r;
196   }
197
198   /**
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.
202    *
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>
209    *
210    * @return a rectangle that gives the location of the document position
211    *         inside the view coordinate space
212    *
213    * @throws BadLocationException if <code>pos</code> is invalid
214    * @throws IllegalArgumentException if b is not one of the above listed
215    *         valid values
216    */
217   public Shape modelToView(int pos, Shape a, Position.Bias bias)
218     throws BadLocationException
219   {
220     boolean backward = bias == Position.Bias.Backward;
221     int testpos = backward ? Math.max(0, pos - 1) : pos;
222
223     Shape ret = null;
224     if (! backward || testpos >= getStartOffset())
225       {
226         int childIndex = getViewIndexAtPosition(testpos);
227         if (childIndex != -1 && childIndex < getViewCount())
228           {
229             View child = getView(childIndex);
230             if (child != null && testpos >= child.getStartOffset()
231                 && testpos < child.getEndOffset())
232               {
233                 Shape childAlloc = getChildAllocation(childIndex, a);
234                 if (childAlloc != null)
235                   {
236                     ret = child.modelToView(pos, childAlloc, bias);
237                     // Handle corner case.
238                     if (ret == null && child.getEndOffset() == pos)
239                       {
240                         childIndex++;
241                         if (childIndex < getViewCount())
242                           {
243                             child = getView(childIndex);
244                             childAlloc = getChildAllocation(childIndex, a);
245                             ret = child.modelToView(pos, childAlloc, bias);
246                           }
247                       }
248                   }
249               }
250           }
251         else
252           {
253             throw new BadLocationException("Position " + pos
254                                            + " is not represented by view.", pos);
255           }    
256       }
257     return ret;
258   }
259
260   /**
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.
266    *
267    * @param a the allocation for this view
268    * @param bias the bias
269    *
270    * @return a default location
271    */
272   private Shape createDefaultLocation(Shape a, Position.Bias bias)
273   {
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;
279   }
280
281   /**
282    * Maps a region in the document into the coordinate space of the View.
283    *
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
289    *
290    * @return a rectangle that gives the span of the document region
291    *         inside the view coordinate space
292    *
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
297    */
298   public Shape modelToView(int p1, Position.Bias b1,
299                            int p2, Position.Bias b2, Shape a)
300     throws BadLocationException
301   {
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);
305   }
306
307   /**
308    * Maps coordinates from the <code>View</code>'s space into a position
309    * in the document model.
310    *
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
315    *
316    * @return the position in the document that corresponds to the screen
317    *         coordinates <code>x, y</code> >= 0
318    */
319   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
320   {
321     if (x >= 0 && y >= 0)
322       {
323         Rectangle r = getInsideAllocation(a);
324         View view = getViewAtPoint((int) x, (int) y, r);
325         return view.viewToModel(x, y, r, b);
326       }
327     return 0;
328   }
329
330   /**
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}.
336    * 
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
361    */
362   public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
363                                        int direction, Position.Bias[] biasRet)
364     throws BadLocationException
365   {
366     int retVal = -1;
367     switch (direction)
368       {
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.");
381       }
382     return retVal;
383   }
384
385   /**
386    * Returns the index of the child view that represents the specified
387    * model location.
388    *
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>
391    *
392    * @return the index of the child view that represents the specified
393    *         model location
394    */
395   public int getViewIndex(int pos, Position.Bias b)
396   {
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;
403   }
404
405   /**
406    * Returns <code>true</code> if the specified point lies before the
407    * given <code>Rectangle</code>, <code>false</code> otherwise.
408    *
409    * &quot;Before&quot; is typically defined as being to the left or above.
410    *
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
414    *
415    * @return <code>true</code> if the specified point lies before the
416    *         given <code>Rectangle</code>, <code>false</code> otherwise
417    */
418   protected abstract boolean isBefore(int x, int y, Rectangle r);
419
420   /**
421    * Returns <code>true</code> if the specified point lies after the
422    * given <code>Rectangle</code>, <code>false</code> otherwise.
423    *
424    * &quot;After&quot; is typically defined as being to the right or below.
425    *
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
429    *
430    * @return <code>true</code> if the specified point lies after the
431    *         given <code>Rectangle</code>, <code>false</code> otherwise
432    */
433   protected abstract boolean isAfter(int x, int y, Rectangle r);
434
435   /**
436    * Returns the child <code>View</code> at the specified location.
437    *
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
442    *
443    * @return the child <code>View</code> at the specified location
444    */
445   protected abstract View getViewAtPoint(int x, int y, Rectangle r);
446
447   /**
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.
451    *
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
455    */
456   protected abstract void childAllocation(int index, Rectangle a);
457
458   /**
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.
462    *
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
465    *
466    * @return the child view at the given model position
467    */
468   protected View getViewAtPosition(int pos, Rectangle a)
469   {
470     View view = null;
471     int i = getViewIndexAtPosition(pos);
472     if (i >= 0 && i < getViewCount() && a != null)
473       {
474         view = getView(i);
475         childAllocation(i, a);
476       }
477     return view;
478   }
479
480   /**
481    * Returns the index of the child <code>View</code> for the given model
482    * position.
483    *
484    * @param pos the model position for whicht the child <code>View</code> is
485    *        queried
486    *
487    * @return the index of the child <code>View</code> for the given model
488    *         position
489    */
490   protected int getViewIndexAtPosition(int pos)
491   {
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);
496   }
497
498   /**
499    * Returns the allocation that is given to this <code>CompositeView</code>
500    * minus this <code>CompositeView</code>'s insets.
501    *
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}.
505    *
506    * @param a the allocation given to this <code>CompositeView</code>
507    *
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>
511    */
512   protected Rectangle getInsideAllocation(Shape a)
513   {
514     if (a == null)
515       return null;
516
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)
523       {
524         inside = insideAllocation;
525         if (inside == null)
526           {
527             inside = new Rectangle();
528             insideAllocation = inside;
529           }
530       }
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;
536   }
537
538   /**
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>.
544    *
545    * @param attributes the attributes from which to query the insets
546    */
547   protected void setParagraphInsets(AttributeSet attributes)
548   {
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);
566   }
567
568   /**
569    * Sets the insets of this <code>CompositeView</code>.
570    *
571    * @param top the top inset
572    * @param left the left inset
573    * @param bottom the bottom inset
574    * @param right the right inset
575    */
576   protected void setInsets(short top, short left, short bottom, short right)
577   {
578     insets.top = top;
579     insets.left = left;
580     insets.bottom = bottom;
581     insets.right = right;
582   }
583
584   /**
585    * Returns the left inset of this <code>CompositeView</code>.
586    *
587    * @return the left inset of this <code>CompositeView</code>
588    */
589   protected short getLeftInset()
590   {
591     return (short) insets.left;
592   }
593
594   /**
595    * Returns the right inset of this <code>CompositeView</code>.
596    *
597    * @return the right inset of this <code>CompositeView</code>
598    */
599   protected short getRightInset()
600   {
601     return (short) insets.right;
602   }
603
604   /**
605    * Returns the top inset of this <code>CompositeView</code>.
606    *
607    * @return the top inset of this <code>CompositeView</code>
608    */
609   protected short getTopInset()
610   {
611     return (short) insets.top;
612   }
613
614   /**
615    * Returns the bottom inset of this <code>CompositeView</code>.
616    *
617    * @return the bottom inset of this <code>CompositeView</code>
618    */
619   protected short getBottomInset()
620   {
621     return (short) insets.bottom;
622   }
623
624   /**
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.
630    *
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
641    *
642    * @return the position inside the model that represents the next visual
643    *         location
644    *
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
648    */
649   protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
650                                                     Shape a, int direction,
651                                                     Position.Bias[] biasRet)
652     throws BadLocationException
653   {
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.
657     //
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.
661     //
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.
668     //
669     // This limitation is described as PR 27345.
670     int index = getViewIndex(pos, b);
671     View v = null;
672     
673     if (index == -1)
674       return pos;
675
676     switch (direction)
677     {
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;
683         
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;
691         
692         v = getView(index + 1);
693         break;
694       default:
695           throw new IllegalArgumentException();
696     }
697     
698     return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet);
699   }
700
701   /**
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.
707    *
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
718    *
719    * @return the position inside the model that represents the next visual
720    *         location
721    *
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
725    */
726   protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
727                                                   Shape a, int direction,
728                                                   Position.Bias[] biasRet)
729     throws BadLocationException
730   {
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.
734     //
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.
738     //
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!
745     //
746     // This limitation is described as PR 27346.
747     int index;
748     
749     View v = null;
750     
751     switch (direction)
752     {
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;
759         
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;
768         
769         v  = getView(index);
770         break;
771       default:
772         throw new IllegalArgumentException();
773     }
774     
775     return v.getNextVisualPositionFrom(pos,
776                                        b,
777                                        a,
778                                        direction,
779                                        biasRet);
780   }
781
782   /**
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.
788    *
789    * @param pos the position in the document
790    * @param bias the bias to be applied to <code>pos</code>
791    *
792    * @return <code>true</code> if the next <code>View</code> is located
793    *         to the EAST, <code>false</code> otherwise
794    */
795   protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias)
796   {
797     return false;
798   }
799 }