Source for javax.swing.text.CompositeView

   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.Rectangle;
  42: import java.awt.Shape;
  43: 
  44: import javax.swing.SwingConstants;
  45: 
  46: /**
  47:  * An abstract base implementation of {@link View} that manages child
  48:  * <code>View</code>s.
  49:  *
  50:  * @author Roman Kennke (roman@kennke.org)
  51:  */
  52: public abstract class CompositeView
  53:   extends View
  54: {
  55: 
  56:   /**
  57:    * The child views of this <code>CompositeView</code>.
  58:    */
  59:   private View[] children;
  60: 
  61:   /**
  62:    * The number of child views.
  63:    */
  64:   private int numChildren;
  65: 
  66:   /**
  67:    * The allocation of this <code>View</code> minus its insets. This is
  68:    * initialized in {@link #getInsideAllocation} and reused and modified in
  69:    * {@link #childAllocation(int, Rectangle)}.
  70:    */
  71:   private final Rectangle insideAllocation = new Rectangle();
  72: 
  73:   /**
  74:    * The insets of this <code>CompositeView</code>. This is initialized
  75:    * in {@link #setInsets}.
  76:    */
  77:   private short top;
  78:   private short bottom;
  79:   private short left;
  80:   private short right;
  81: 
  82:   /**
  83:    * Creates a new <code>CompositeView</code> for the given
  84:    * <code>Element</code>.
  85:    *
  86:    * @param element the element that is rendered by this CompositeView
  87:    */
  88:   public CompositeView(Element element)
  89:   {
  90:     super(element);
  91:     children = new View[0];
  92:     top = 0;
  93:     bottom = 0;
  94:     left = 0;
  95:     right = 0;
  96:   }
  97: 
  98:   /**
  99:    * 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: }