Source for javax.swing.text.View

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