001    /* CompositeView.java -- An abstract view that manages child views
002       Copyright (C) 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.Rectangle;
042    import java.awt.Shape;
043    
044    import javax.swing.SwingConstants;
045    
046    /**
047     * An abstract base implementation of {@link View} that manages child
048     * <code>View</code>s.
049     *
050     * @author Roman Kennke (roman@kennke.org)
051     */
052    public abstract class CompositeView
053      extends View
054    {
055    
056      /**
057       * The child views of this <code>CompositeView</code>.
058       */
059      private View[] children;
060    
061      /**
062       * The number of child views.
063       */
064      private int numChildren;
065    
066      /**
067       * The allocation of this <code>View</code> minus its insets. This is
068       * initialized in {@link #getInsideAllocation} and reused and modified in
069       * {@link #childAllocation(int, Rectangle)}.
070       */
071      private final Rectangle insideAllocation = new Rectangle();
072    
073      /**
074       * The insets of this <code>CompositeView</code>. This is initialized
075       * in {@link #setInsets}.
076       */
077      private short top;
078      private short bottom;
079      private short left;
080      private short right;
081    
082      /**
083       * Creates a new <code>CompositeView</code> for the given
084       * <code>Element</code>.
085       *
086       * @param element the element that is rendered by this CompositeView
087       */
088      public CompositeView(Element element)
089      {
090        super(element);
091        children = new View[0];
092        top = 0;
093        bottom = 0;
094        left = 0;
095        right = 0;
096      }
097    
098      /**
099       * Loads the child views of this <code>CompositeView</code>. This method
100       * is called from {@link #setParent} to initialize the child views of
101       * this composite view.
102       *
103       * @param f the view factory to use for creating new child views
104       *
105       * @see #setParent
106       */
107      protected void loadChildren(ViewFactory f)
108      {
109        if (f != null)
110          {
111            Element el = getElement();
112            int count = el.getElementCount();
113            View[] newChildren = new View[count];
114            for (int i = 0; i < count; ++i)
115              {
116                Element child = el.getElement(i);
117                View view = f.create(child);
118                newChildren[i] = view;
119              }
120            // I'd have called replace(0, getViewCount(), newChildren) here
121            // in order to replace all existing views. However according to
122            // Harmony's tests this is not what the RI does.
123            replace(0, 0, newChildren);
124          }
125      }
126    
127      /**
128       * Sets the parent of this <code>View</code>.
129       * In addition to setting the parent, this calls {@link #loadChildren}, if
130       * this <code>View</code> does not already have its children initialized.
131       *
132       * @param parent the parent to set
133       */
134      public void setParent(View parent)
135      {
136        super.setParent(parent);
137        if (parent != null && numChildren == 0)
138          loadChildren(getViewFactory());
139      }
140    
141      /**
142       * Returns the number of child views.
143       *
144       * @return the number of child views
145       */
146      public int getViewCount()
147      {
148        return numChildren;
149      }
150    
151      /**
152       * Returns the child view at index <code>n</code>.
153       *
154       * @param n the index of the requested child view
155       *
156       * @return the child view at index <code>n</code>
157       */
158      public View getView(int n)
159      {
160        return children[n];
161      }
162    
163      /**
164       * Replaces child views by some other child views. If there are no views to
165       * remove (<code>length == 0</code>), the result is a simple insert, if
166       * there are no children to add (<code>view == null</code>) the result
167       * is a simple removal.
168       *
169       * @param offset the start offset from where to remove children
170       * @param length the number of children to remove
171       * @param views the views that replace the removed children
172       */
173      public void replace(int offset, int length, View[] views)
174      {
175        // Make sure we have an array. The Harmony testsuite indicates that we
176        // have to do something like this.
177        if (views == null)
178          views = new View[0];
179    
180        // First we set the parent of the removed children to null.
181        int endOffset = offset + length;
182        for (int i = offset; i < endOffset; ++i)
183          {
184            if (children[i].getParent() == this)
185              children[i].setParent(null);
186            children[i] = null;
187          }
188    
189        // Update the children array.
190        int delta = views.length - length;
191        int src = offset + length;
192        int numMove = numChildren - src;
193        int dst = src + delta;
194        if (numChildren + delta > children.length)
195          {
196            // Grow array.
197            int newLength = Math.max(2 * children.length, numChildren + delta);
198            View[] newChildren = new View[newLength];
199            System.arraycopy(children, 0, newChildren, 0, offset);
200            System.arraycopy(views, 0, newChildren, offset, views.length);
201            System.arraycopy(children, src, newChildren, dst, numMove);
202            children = newChildren;
203          }
204        else
205          {
206            // Patch existing array.
207            System.arraycopy(children, src, children, dst, numMove);
208            System.arraycopy(views, 0, children, offset, views.length);
209          }
210        numChildren += delta;
211    
212        // Finally we set the parent of the added children to this.
213        for (int i = 0; i < views.length; ++i)
214          views[i].setParent(this);
215      }
216    
217      /**
218       * Returns the allocation for the specified child <code>View</code>.
219       *
220       * @param index the index of the child view
221       * @param a the allocation for this view
222       *
223       * @return the allocation for the specified child <code>View</code>
224       */
225      public Shape getChildAllocation(int index, Shape a)
226      {
227        Rectangle r = getInsideAllocation(a);
228        childAllocation(index, r);
229        return r;
230      }
231    
232      /**
233       * Maps a position in the document into the coordinate space of the View.
234       * The output rectangle usually reflects the font height but has a width
235       * of zero.
236       *
237       * @param pos the position of the character in the model
238       * @param a the area that is occupied by the view
239       * @param bias either {@link Position.Bias#Forward} or
240       *        {@link Position.Bias#Backward} depending on the preferred
241       *        direction bias. If <code>null</code> this defaults to
242       *        <code>Position.Bias.Forward</code>
243       *
244       * @return a rectangle that gives the location of the document position
245       *         inside the view coordinate space
246       *
247       * @throws BadLocationException if <code>pos</code> is invalid
248       * @throws IllegalArgumentException if b is not one of the above listed
249       *         valid values
250       */
251      public Shape modelToView(int pos, Shape a, Position.Bias bias)
252        throws BadLocationException
253      {
254        boolean backward = bias == Position.Bias.Backward;
255        int testpos = backward ? Math.max(0, pos - 1) : pos;
256    
257        Shape ret = null;
258        if (! backward || testpos >= getStartOffset())
259          {
260            int childIndex = getViewIndexAtPosition(testpos);
261            if (childIndex != -1 && childIndex < getViewCount())
262              {
263                View child = getView(childIndex);
264                if (child != null && testpos >= child.getStartOffset()
265                    && testpos < child.getEndOffset())
266                  {
267                    Shape childAlloc = getChildAllocation(childIndex, a);
268                    if (childAlloc != null)
269                      {
270                        ret = child.modelToView(pos, childAlloc, bias);
271                        // Handle corner case.
272                        if (ret == null && child.getEndOffset() == pos)
273                          {
274                            childIndex++;
275                            if (childIndex < getViewCount())
276                              {
277                                child = getView(childIndex);
278                                childAlloc = getChildAllocation(childIndex, a);
279                                ret = child.modelToView(pos, childAlloc, bias);
280                              }
281                          }
282                      }
283                  }
284              }
285          }
286    
287        if (ret == null)
288          throw new BadLocationException("Position " + pos
289                                         + " is not represented by view.", pos);
290    
291        return ret;
292      }
293    
294      /**
295       * Maps a region in the document into the coordinate space of the View.
296       *
297       * @param p1 the beginning position inside the document
298       * @param b1 the direction bias for the beginning position
299       * @param p2 the end position inside the document
300       * @param b2 the direction bias for the end position
301       * @param a the area that is occupied by the view
302       *
303       * @return a rectangle that gives the span of the document region
304       *         inside the view coordinate space
305       *
306       * @throws BadLocationException if <code>p1</code> or <code>p2</code> are
307       *         invalid
308       * @throws IllegalArgumentException if b1 or b2 is not one of the above
309       *         listed valid values
310       */
311      public Shape modelToView(int p1, Position.Bias b1,
312                               int p2, Position.Bias b2, Shape a)
313        throws BadLocationException
314      {
315        // TODO: This is most likely not 100% ok, figure out what else is to
316        // do here.
317        return super.modelToView(p1, b1, p2, b2, a);
318      }
319    
320      /**
321       * Maps coordinates from the <code>View</code>'s space into a position
322       * in the document model.
323       *
324       * @param x the x coordinate in the view space, x >= 0
325       * @param y the y coordinate in the view space, y >= 0
326       * @param a the allocation of this <code>View</code>
327       * @param b the bias to use
328       *
329       * @return the position in the document that corresponds to the screen
330       *         coordinates <code>x, y</code> >= 0
331       */
332      public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
333      {
334        if (x >= 0 && y >= 0)
335          {
336            Rectangle r = getInsideAllocation(a);
337            View view = getViewAtPoint((int) x, (int) y, r);
338            return view.viewToModel(x, y, r, b);
339          }
340        return 0;
341      }
342    
343      /**
344       * Returns the next model location that is visible in eiter north / south
345       * direction or east / west direction. This is used to determine the placement
346       * of the caret when navigating around the document with the arrow keys. This
347       * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom}
348       * and {@link #getNextEastWestVisualPositionFrom}.
349       * 
350       * @param pos
351       *          the model position to start search from
352       * @param b
353       *          the bias for <code>pos</code>
354       * @param a
355       *          the allocated region for this view
356       * @param direction
357       *          the direction from the current position, can be one of the
358       *          following:
359       *          <ul>
360       *          <li>{@link SwingConstants#WEST}</li>
361       *          <li>{@link SwingConstants#EAST}</li>
362       *          <li>{@link SwingConstants#NORTH}</li>
363       *          <li>{@link SwingConstants#SOUTH}</li>
364       *          </ul>
365       * @param biasRet
366       *          the bias of the return value gets stored here
367       * @return the position inside the model that represents the next visual
368       *         location
369       * @throws BadLocationException
370       *           if <code>pos</code> is not a valid location inside the document
371       *           model
372       * @throws IllegalArgumentException
373       *           if <code>direction</code> is invalid
374       */
375      public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
376                                           int direction, Position.Bias[] biasRet)
377        throws BadLocationException
378      {
379        int retVal = -1;
380        switch (direction)
381          {
382          case SwingConstants.WEST:
383          case SwingConstants.EAST:
384            retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction,
385                                                       biasRet);
386            break;
387          case SwingConstants.NORTH:
388          case SwingConstants.SOUTH:
389            retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction,
390                                                         biasRet);
391            break;
392          default:
393            throw new IllegalArgumentException("Illegal value for direction.");
394          }
395        return retVal;
396      }
397    
398      /**
399       * Returns the index of the child view that represents the specified
400       * model location.
401       *
402       * @param pos the model location for which to determine the child view index
403       * @param b the bias to be applied to <code>pos</code>
404       *
405       * @return the index of the child view that represents the specified
406       *         model location
407       */
408      public int getViewIndex(int pos, Position.Bias b)
409      {
410        if (b == Position.Bias.Backward)
411          pos -= 1;
412        int i = -1;
413        if (pos >= getStartOffset() && pos < getEndOffset())
414          i = getViewIndexAtPosition(pos);
415        return i;
416      }
417    
418      /**
419       * Returns <code>true</code> if the specified point lies before the
420       * given <code>Rectangle</code>, <code>false</code> otherwise.
421       *
422       * &quot;Before&quot; is typically defined as being to the left or above.
423       *
424       * @param x the X coordinate of the point
425       * @param y the Y coordinate of the point
426       * @param r the rectangle to test the point against
427       *
428       * @return <code>true</code> if the specified point lies before the
429       *         given <code>Rectangle</code>, <code>false</code> otherwise
430       */
431      protected abstract boolean isBefore(int x, int y, Rectangle r);
432    
433      /**
434       * Returns <code>true</code> if the specified point lies after the
435       * given <code>Rectangle</code>, <code>false</code> otherwise.
436       *
437       * &quot;After&quot; is typically defined as being to the right or below.
438       *
439       * @param x the X coordinate of the point
440       * @param y the Y coordinate of the point
441       * @param r the rectangle to test the point against
442       *
443       * @return <code>true</code> if the specified point lies after the
444       *         given <code>Rectangle</code>, <code>false</code> otherwise
445       */
446      protected abstract boolean isAfter(int x, int y, Rectangle r);
447    
448      /**
449       * Returns the child <code>View</code> at the specified location.
450       *
451       * @param x the X coordinate
452       * @param y the Y coordinate
453       * @param r the inner allocation of this <code>BoxView</code> on entry,
454       *        the allocation of the found child on exit
455       *
456       * @return the child <code>View</code> at the specified location
457       */
458      protected abstract View getViewAtPoint(int x, int y, Rectangle r);
459    
460      /**
461       * Computes the allocation for a child <code>View</code>. The parameter
462       * <code>a</code> stores the allocation of this <code>CompositeView</code>
463       * and is then adjusted to hold the allocation of the child view.
464       *
465       * @param index the index of the child <code>View</code>
466       * @param a the allocation of this <code>CompositeView</code> before the
467       *        call, the allocation of the child on exit
468       */
469      protected abstract void childAllocation(int index, Rectangle a);
470    
471      /**
472       * Returns the child <code>View</code> that contains the given model
473       * position. The given <code>Rectangle</code> gives the parent's allocation
474       * and is changed to the child's allocation on exit.
475       *
476       * @param pos the model position to query the child <code>View</code> for
477       * @param a the parent allocation on entry and the child allocation on exit
478       *
479       * @return the child view at the given model position
480       */
481      protected View getViewAtPosition(int pos, Rectangle a)
482      {
483        View view = null;
484        int i = getViewIndexAtPosition(pos);
485        if (i >= 0 && i < getViewCount() && a != null)
486          {
487            view = getView(i);
488            childAllocation(i, a);
489          }
490        return view;
491      }
492    
493      /**
494       * Returns the index of the child <code>View</code> for the given model
495       * position.
496       *
497       * @param pos the model position for whicht the child <code>View</code> is
498       *        queried
499       *
500       * @return the index of the child <code>View</code> for the given model
501       *         position
502       */
503      protected int getViewIndexAtPosition(int pos)
504      {
505        // We have a 1:1 mapping of elements to views here, so we forward
506        // this to the element.
507        Element el = getElement();
508        return el.getElementIndex(pos);
509      }
510    
511      /**
512       * Returns the allocation that is given to this <code>CompositeView</code>
513       * minus this <code>CompositeView</code>'s insets.
514       *
515       * Also this translates from an immutable allocation to a mutable allocation
516       * that is typically reused and further narrowed, like in
517       * {@link #childAllocation}.
518       *
519       * @param a the allocation given to this <code>CompositeView</code>
520       *
521       * @return the allocation that is given to this <code>CompositeView</code>
522       *         minus this <code>CompositeView</code>'s insets or
523       *         <code>null</code> if a was <code>null</code>
524       */
525      protected Rectangle getInsideAllocation(Shape a)
526      {
527        if (a == null)
528          return null;
529    
530        // Try to avoid allocation of Rectangle here.
531        Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
532    
533        // Initialize the inside allocation rectangle. This is done inside
534        // a synchronized block in order to avoid multiple threads creating
535        // this instance simultanously.
536        Rectangle inside = insideAllocation;
537        inside.x = alloc.x + getLeftInset();
538        inside.y = alloc.y + getTopInset();
539        inside.width = alloc.width - getLeftInset() - getRightInset();
540        inside.height = alloc.height - getTopInset() - getBottomInset();
541        return inside;
542      }
543    
544      /**
545       * Sets the insets defined by attributes in <code>attributes</code>. This
546       * queries the attribute keys {@link StyleConstants#SpaceAbove},
547       * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and
548       * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to
549       * actually set the insets on this <code>CompositeView</code>.
550       *
551       * @param attributes the attributes from which to query the insets
552       */
553      protected void setParagraphInsets(AttributeSet attributes)
554      {
555        top = (short) StyleConstants.getSpaceAbove(attributes);
556        bottom = (short) StyleConstants.getSpaceBelow(attributes);
557        left = (short) StyleConstants.getLeftIndent(attributes);
558        right = (short) StyleConstants.getRightIndent(attributes);
559      }
560    
561      /**
562       * Sets the insets of this <code>CompositeView</code>.
563       *
564       * @param t the top inset
565       * @param l the left inset
566       * @param b the bottom inset
567       * @param r the right inset
568       */
569      protected void setInsets(short t, short l, short b, short r)
570      {
571        top = t;
572        left = l;
573        bottom = b;
574        right = r;
575      }
576    
577      /**
578       * Returns the left inset of this <code>CompositeView</code>.
579       *
580       * @return the left inset of this <code>CompositeView</code>
581       */
582      protected short getLeftInset()
583      {
584        return left;
585      }
586    
587      /**
588       * Returns the right inset of this <code>CompositeView</code>.
589       *
590       * @return the right inset of this <code>CompositeView</code>
591       */
592      protected short getRightInset()
593      {
594        return right;
595      }
596    
597      /**
598       * Returns the top inset of this <code>CompositeView</code>.
599       *
600       * @return the top inset of this <code>CompositeView</code>
601       */
602      protected short getTopInset()
603      {
604        return top;
605      }
606    
607      /**
608       * Returns the bottom inset of this <code>CompositeView</code>.
609       *
610       * @return the bottom inset of this <code>CompositeView</code>
611       */
612      protected short getBottomInset()
613      {
614        return bottom;
615      }
616    
617      /**
618       * Returns the next model location that is visible in north or south
619       * direction.
620       * This is used to determine the
621       * placement of the caret when navigating around the document with
622       * the arrow keys.
623       *
624       * @param pos the model position to start search from
625       * @param b the bias for <code>pos</code>
626       * @param a the allocated region for this view
627       * @param direction the direction from the current position, can be one of
628       *        the following:
629       *        <ul>
630       *        <li>{@link SwingConstants#NORTH}</li>
631       *        <li>{@link SwingConstants#SOUTH}</li>
632       *        </ul>
633       * @param biasRet the bias of the return value gets stored here
634       *
635       * @return the position inside the model that represents the next visual
636       *         location
637       *
638       * @throws BadLocationException if <code>pos</code> is not a valid location
639       *         inside the document model
640       * @throws IllegalArgumentException if <code>direction</code> is invalid
641       */
642      protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
643                                                        Shape a, int direction,
644                                                        Position.Bias[] biasRet)
645        throws BadLocationException
646      {
647        // TODO: It is unknown to me how this method has to be implemented and
648        // there is no specification telling me how to do it properly. Therefore
649        // the implementation was done for cases that are known.
650        //
651        // If this method ever happens to act silly for your particular case then
652        // it is likely that it is a cause of not knowing about your case when it
653        // was implemented first. You are free to fix the behavior.
654        //
655        // Here are the assumptions that lead to the implementation:
656        // If direction is NORTH chose the View preceding the one that contains the
657        // offset 'pos' (imagine the views are stacked on top of each other where
658        // the top is 0 and the bottom is getViewCount()-1.
659        // Consecutively when the direction is SOUTH the View following the one
660        // the offset 'pos' lies in is questioned.
661        //
662        // This limitation is described as PR 27345.
663        int index = getViewIndex(pos, b);
664        View v = null;
665        
666        if (index == -1)
667          return pos;
668    
669        switch (direction)
670        {
671          case NORTH:
672            // If we cannot calculate a proper offset return the one that was
673            // provided.
674            if (index <= 0)
675              return pos;
676            
677            v = getView(index - 1);
678            break;
679          case SOUTH:
680            // If we cannot calculate a proper offset return the one that was
681            // provided.
682            if (index >= getViewCount() - 1)
683              return pos;
684            
685            v = getView(index + 1);
686            break;
687          default:
688              throw new IllegalArgumentException();
689        }
690        
691        return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet);
692      }
693    
694      /**
695       * Returns the next model location that is visible in east or west
696       * direction.
697       * This is used to determine the
698       * placement of the caret when navigating around the document with
699       * the arrow keys.
700       *
701       * @param pos the model position to start search from
702       * @param b the bias for <code>pos</code>
703       * @param a the allocated region for this view
704       * @param direction the direction from the current position, can be one of
705       *        the following:
706       *        <ul>
707       *        <li>{@link SwingConstants#EAST}</li>
708       *        <li>{@link SwingConstants#WEST}</li>
709       *        </ul>
710       * @param biasRet the bias of the return value gets stored here
711       *
712       * @return the position inside the model that represents the next visual
713       *         location
714       *
715       * @throws BadLocationException if <code>pos</code> is not a valid location
716       *         inside the document model
717       * @throws IllegalArgumentException if <code>direction</code> is invalid
718       */
719      protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b,
720                                                      Shape a, int direction,
721                                                      Position.Bias[] biasRet)
722        throws BadLocationException
723      {
724        // TODO: It is unknown to me how this method has to be implemented and
725        // there is no specification telling me how to do it properly. Therefore
726        // the implementation was done for cases that are known.
727        //
728        // If this method ever happens to act silly for your particular case then
729        // it is likely that it is a cause of not knowing about your case when it
730        // was implemented first. You are free to fix the behavior.
731        //
732        // Here are the assumptions that lead to the implementation:
733        // If direction is EAST increase the offset by one and ask the View to
734        // which that index belong to calculate the 'next visual position'.
735        // If the direction is WEST do the same with offset 'pos' being decreased
736        // by one.
737        // This behavior will fail in a right-to-left or bidi environment!
738        //
739        // This limitation is described as PR 27346.
740        int index;
741        
742        View v = null;
743        
744        switch (direction)
745        {
746          case EAST:
747            index = getViewIndex(pos + 1, b);
748            // If we cannot calculate a proper offset return the one that was
749            // provided.
750            if (index == -1)
751              return pos;
752            
753            v  = getView(index);
754            break;
755          case WEST:
756            index = getViewIndex(pos - 1, b);
757            // If we cannot calculate a proper offset return the one that was
758            // provided.
759            if (index == -1)
760              return pos;
761            
762            v  = getView(index);
763            break;
764          default:
765            throw new IllegalArgumentException();
766        }
767        
768        return v.getNextVisualPositionFrom(pos,
769                                           b,
770                                           a,
771                                           direction,
772                                           biasRet);
773      }
774    
775      /**
776       * Determines if the next view in horinzontal direction is located to
777       * the east or west of the view at position <code>pos</code>. Usually
778       * the <code>View</code>s are laid out from the east to the west, so
779       * we unconditionally return <code>false</code> here. Subclasses that
780       * support bidirectional text may wish to override this method.
781       *
782       * @param pos the position in the document
783       * @param bias the bias to be applied to <code>pos</code>
784       *
785       * @return <code>true</code> if the next <code>View</code> is located
786       *         to the EAST, <code>false</code> otherwise
787       */
788      protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias)
789      {
790        return false;
791      }
792    }