< prev index next >

src/java.desktop/share/classes/javax/swing/text/ParagraphView.java

Print this page




  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.util.Arrays;
  28 import java.awt.*;
  29 import java.awt.font.TextAttribute;
  30 import javax.swing.event.*;
  31 import javax.swing.SizeRequirements;
  32 
  33 /**
  34  * View of a simple line-wrapping paragraph that supports
  35  * multiple fonts, colors, components, icons, etc.  It is
  36  * basically a vertical box with a margin around it.  The
  37  * contents of the box are a bunch of rows which are special
  38  * horizontal boxes.  This view creates a collection of
  39  * views that represent the child elements of the paragraph
  40  * element.  Each of these views are placed into a row
  41  * directly if they will fit, otherwise the <code>breakView</code>
  42  * method is called to try and carve the view into pieces
  43  * that fit.
  44  *
  45  * @author  Timothy Prinzing
  46  * @author  Scott Violet
  47  * @author  Igor Kushnirskiy
  48  * @see     View
  49  */
  50 public class ParagraphView extends FlowView implements TabExpander {
  51 
  52     /**
  53      * Constructs a <code>ParagraphView</code> for the given element.
  54      *
  55      * @param elem the element that this view is responsible for
  56      */
  57     public ParagraphView(Element elem) {
  58         super(elem, View.Y_AXIS);
  59         setPropertiesFromAttributes();
  60         Document doc = elem.getDocument();
  61         Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
  62         if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
  63             try {
  64                 if (i18nStrategy == null) {
  65                     // the classname should probably come from a property file.
  66                     String classname = "javax.swing.text.TextLayoutStrategy";
  67                     ClassLoader loader = getClass().getClassLoader();
  68                     if (loader != null) {
  69                         i18nStrategy = loader.loadClass(classname);
  70                     } else {
  71                         i18nStrategy = Class.forName(classname);
  72                     }
  73                 }
  74                 Object o = i18nStrategy.newInstance();
  75                 if (o instanceof FlowStrategy) {
  76                     strategy = (FlowStrategy) o;
  77                 }
  78             } catch (Throwable e) {
  79                 throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
  80                                               + e.getMessage());
  81             }
  82         }
  83     }
  84 
  85     /**
  86      * Sets the type of justification.
  87      *
  88      * @param j one of the following values:
  89      * <ul>
  90      * <li><code>StyleConstants.ALIGN_LEFT</code>
  91      * <li><code>StyleConstants.ALIGN_CENTER</code>
  92      * <li><code>StyleConstants.ALIGN_RIGHT</code>
  93      * </ul>
  94      */
  95     protected void setJustification(int j) {
  96         justification = j;
  97     }
  98 
  99     /**
 100      * Sets the line spacing.
 101      *
 102      * @param ls the value is a factor of the line hight
 103      */
 104     protected void setLineSpacing(float ls) {
 105         lineSpacing = ls;
 106     }
 107 
 108     /**
 109      * Sets the indent on the first line.
 110      *
 111      * @param fi the value in points
 112      */


 127                 Document doc = getElement().getDocument();
 128                 Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
 129                 if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
 130                     alignment = StyleConstants.ALIGN_RIGHT;
 131                 } else {
 132                     alignment = StyleConstants.ALIGN_LEFT;
 133                 }
 134             } else {
 135                 alignment = a.intValue();
 136             }
 137             setJustification(alignment);
 138             setLineSpacing(StyleConstants.getLineSpacing(attr));
 139             setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
 140         }
 141     }
 142 
 143     /**
 144      * Returns the number of views that this view is
 145      * responsible for.
 146      * The child views of the paragraph are rows which
 147      * have been used to arrange pieces of the <code>View</code>s
 148      * that represent the child elements.  This is the number
 149      * of views that have been tiled in two dimensions,
 150      * and should be equivalent to the number of child elements
 151      * to the element this view is responsible for.
 152      *
 153      * @return the number of views that this <code>ParagraphView</code>
 154      *          is responsible for
 155      */
 156     protected int getLayoutViewCount() {
 157         return layoutPool.getViewCount();
 158     }
 159 
 160     /**
 161      * Returns the view at a given <code>index</code>.
 162      * The child views of the paragraph are rows which
 163      * have been used to arrange pieces of the <code>Views</code>
 164      * that represent the child elements.  This methods returns
 165      * the view responsible for the child element index
 166      * (prior to breaking).  These are the Views that were
 167      * produced from a factory (to represent the child
 168      * elements) and used for layout.
 169      *
 170      * @param index the <code>index</code> of the desired view
 171      * @return the view at <code>index</code>
 172      */
 173     protected View getLayoutView(int index) {
 174         return layoutPool.getView(index);
 175     }
 176 
 177     /**
 178      * Returns the next visual position for the cursor, in
 179      * either the east or west direction.
 180      * Overridden from <code>CompositeView</code>.
 181      * @param pos position into the model
 182      * @param b either <code>Position.Bias.Forward</code> or
 183      *          <code>Position.Bias.Backward</code>
 184      * @param a the allocated region to render into
 185      * @param direction either <code>SwingConstants.NORTH</code>
 186      *          or <code>SwingConstants.SOUTH</code>
 187      * @param biasRet an array containing the bias that were checked
 188      *  in this method
 189      * @return the location in the model that represents the
 190      *  next location visual position
 191      */
 192     protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
 193                                                       Shape a, int direction,
 194                                                       Position.Bias[] biasRet)
 195                                                 throws BadLocationException {
 196         int vIndex;
 197         if(pos == -1) {
 198             vIndex = (direction == NORTH) ?
 199                      getViewCount() - 1 : 0;
 200         }
 201         else {
 202             if(b == Position.Bias.Backward && pos > 0) {
 203                 vIndex = getViewIndexAtPosition(pos - 1);
 204             }
 205             else {
 206                 vIndex = getViewIndexAtPosition(pos);


 225             Shape posBounds;
 226             try {
 227                 posBounds = text.getUI().modelToView(text, pos, b);
 228             } catch (BadLocationException exc) {
 229                 posBounds = null;
 230             }
 231             if(posBounds == null) {
 232                 x = 0;
 233             }
 234             else {
 235                 x = posBounds.getBounds().x;
 236             }
 237         }
 238         else {
 239             x = magicPoint.x;
 240         }
 241         return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
 242     }
 243 
 244     /**
 245      * Returns the closest model position to <code>x</code>.
 246      * <code>rowIndex</code> gives the index of the view that corresponds
 247      * that should be looked in.
 248      * @param pos  position into the model
 249      * @param b the bias
 250      * @param a the allocated region to render into
 251      * @param direction one of the following values:
 252      * <ul>
 253      * <li><code>SwingConstants.NORTH</code>
 254      * <li><code>SwingConstants.SOUTH</code>
 255      * </ul>
 256      * @param biasRet an array containing the bias that were checked
 257      *  in this method
 258      * @param rowIndex the index of the view
 259      * @param x the x coordinate of interest
 260      * @throws BadLocationException if a bad location is encountered
 261      * @return the closest model position to <code>x</code>
 262      */
 263     // NOTE: This will not properly work if ParagraphView contains
 264     // other ParagraphViews. It won't raise, but this does not message
 265     // the children views with getNextVisualPositionFrom.
 266     protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
 267                                        int direction, Position.Bias[] biasRet,
 268                                        int rowIndex, int x)
 269               throws BadLocationException {
 270         JTextComponent text = (JTextComponent)getContainer();
 271         Document doc = getDocument();
 272         View row = getView(rowIndex);
 273         int lastPos = -1;
 274         // This could be made better to check backward positions too.
 275         biasRet[0] = Position.Bias.Forward;
 276         for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
 277             View v = row.getView(vc);
 278             int start = v.getStartOffset();
 279             boolean ltr = AbstractDocument.isLeftToRight(doc, start, start + 1);
 280             if(ltr) {
 281                 lastPos = start;


 295                     lastPos--) {
 296                     float xx = text.modelToView(lastPos).getBounds().x;
 297                     if(xx >= x) {
 298                         while (--lastPos >= start &&
 299                                text.modelToView(lastPos).getBounds().x == xx) {
 300                         }
 301                         return ++lastPos;
 302                     }
 303                 }
 304                 lastPos++;
 305             }
 306         }
 307         if(lastPos == -1) {
 308             return getStartOffset();
 309         }
 310         return lastPos;
 311     }
 312 
 313     /**
 314      * Determines in which direction the next view lays.
 315      * Consider the <code>View</code> at index n.
 316      * Typically the <code>View</code>s are layed out
 317      * from left to right, so that the <code>View</code>
 318      * to the EAST will be at index n + 1, and the
 319      * <code>View</code> to the WEST will be at index n - 1.
 320      * In certain situations, such as with bidirectional text,
 321      * it is possible that the <code>View</code> to EAST is not
 322      * at index n + 1, but rather at index n - 1,
 323      * or that the <code>View</code> to the WEST is not at
 324      * index n - 1, but index n + 1.  In this case this method
 325      * would return true, indicating the <code>View</code>s are
 326      * layed out in descending order.
 327      * <p>
 328      * This will return true if the text is layed out right
 329      * to left at position, otherwise false.
 330      *
 331      * @param position position into the model
 332      * @param bias either <code>Position.Bias.Forward</code> or
 333      *          <code>Position.Bias.Backward</code>
 334      * @return true if the text is layed out right to left at
 335      *         position, otherwise false.
 336      */
 337     protected boolean flipEastAndWestAtEnds(int position,
 338                                             Position.Bias bias) {
 339         Document doc = getDocument();
 340         position = getStartOffset();
 341         return !AbstractDocument.isLeftToRight(doc, position, position + 1);
 342     }
 343 
 344     // --- FlowView methods ---------------------------------------------
 345 
 346     /**
 347      * Fetches the constraining span to flow against for
 348      * the given child index.
 349      * @param index the index of the view being queried
 350      * @return the constraining span for the given view at
 351      *  <code>index</code>
 352      * @since 1.3
 353      */
 354     public int getFlowSpan(int index) {
 355         View child = getView(index);
 356         int adjust = 0;
 357         if (child instanceof Row) {
 358             Row row = (Row) child;
 359             adjust = row.getLeftInset() + row.getRightInset();
 360         }
 361         return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan
 362                                                  : (layoutSpan - adjust);
 363     }
 364 
 365     /**
 366      * Fetches the location along the flow axis that the
 367      * flow span will start at.
 368      * @param index the index of the view being queried
 369      * @return the location for the given view at
 370      *  <code>index</code>
 371      * @since 1.3
 372      */
 373     public int getFlowStart(int index) {
 374         View child = getView(index);
 375         int adjust = 0;
 376         if (child instanceof Row) {
 377             Row row = (Row) child;
 378             adjust = row.getLeftInset();
 379         }
 380         return tabBase + adjust;
 381     }
 382 
 383     /**
 384      * Create a <code>View</code> that should be used to hold a
 385      * a row's worth of children in a flow.
 386      * @return the new <code>View</code>
 387      * @since 1.3
 388      */
 389     protected View createRow() {
 390         return new Row(getElement());
 391     }
 392 
 393     // --- TabExpander methods ------------------------------------------
 394 
 395     /**
 396      * Returns the next tab stop position given a reference position.
 397      * This view implements the tab coordinate system, and calls
 398      * <code>getTabbedSpan</code> on the logical children in the process
 399      * of layout to determine the desired span of the children.  The
 400      * logical children can delegate their tab expansion upward to
 401      * the paragraph which knows how to expand tabs.
 402      * <code>LabelView</code> is an example of a view that delegates
 403      * its tab expansion needs upward to the paragraph.
 404      * <p>
 405      * This is implemented to try and locate a <code>TabSet</code>
 406      * in the paragraph element's attribute set.  If one can be
 407      * found, its settings will be used, otherwise a default expansion
 408      * will be provided.  The base location for tab expansion
 409      * is the left inset from the paragraphs most recent allocation
 410      * (which is what the layout of the children is based upon).
 411      *
 412      * @param x the X reference position
 413      * @param tabOffset the position within the text stream
 414      *   that the tab occurred at &gt;= 0
 415      * @return the trailing end of the tab expansion &gt;= 0
 416      * @see TabSet
 417      * @see TabStop
 418      * @see LabelView
 419      */
 420     public float nextTabStop(float x, int tabOffset) {
 421         // If the text isn't left justified, offset by 10 pixels!
 422         if(justification != StyleConstants.ALIGN_LEFT)
 423             return x + 10.0f;
 424         x -= tabBase;
 425         TabSet tabs = getTabSet();


 456         if (offset == -1) {
 457             offset = getEndOffset();
 458         }
 459         float charsSize = getPartialSize(tabOffset + 1, offset);
 460         switch(alignment) {
 461         case TabStop.ALIGN_RIGHT:
 462         case TabStop.ALIGN_DECIMAL:
 463             // right and decimal are treated the same way, the new
 464             // position will be the location of the tab less the
 465             // partialSize.
 466             return tabBase + Math.max(x, tab.getPosition() - charsSize);
 467         case TabStop.ALIGN_CENTER:
 468             // Similar to right, but half the partialSize.
 469             return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
 470         }
 471         // will never get here!
 472         return x;
 473     }
 474 
 475     /**
 476      * Gets the <code>Tabset</code> to be used in calculating tabs.
 477      *
 478      * @return the <code>TabSet</code>
 479      */
 480     protected TabSet getTabSet() {
 481         return StyleConstants.getTabSet(getElement().getAttributes());
 482     }
 483 
 484     /**
 485      * Returns the size used by the views between
 486      * <code>startOffset</code> and <code>endOffset</code>.
 487      * This uses <code>getPartialView</code> to calculate the
 488      * size if the child view implements the
 489      * <code>TabableView</code> interface. If a
 490      * size is needed and a <code>View</code> does not implement
 491      * the <code>TabableView</code> interface,
 492      * the <code>preferredSpan</code> will be used.
 493      *
 494      * @param startOffset the starting document offset &gt;= 0
 495      * @param endOffset the ending document offset &gt;= startOffset
 496      * @return the size &gt;= 0
 497      */
 498     protected float getPartialSize(int startOffset, int endOffset) {
 499         float size = 0.0f;
 500         int viewIndex;
 501         int numViews = getViewCount();
 502         View view;
 503         int viewEnd;
 504         int tempEnd;
 505 
 506         // Have to search layoutPool!
 507         // PENDING: when ParagraphView supports breaking location
 508         // into layoutPool will have to change!
 509         viewIndex = getElement().getElementIndex(startOffset);
 510         numViews = layoutPool.getViewCount();
 511         while(startOffset < endOffset && viewIndex < numViews) {
 512             view = layoutPool.getView(viewIndex++);
 513             viewEnd = view.getEndOffset();
 514             tempEnd = Math.min(endOffset, viewEnd);
 515             if(view instanceof TabableView)
 516                 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
 517             else if(startOffset == view.getStartOffset() &&
 518                     tempEnd == view.getEndOffset())
 519                 size += view.getPreferredSpan(View.X_AXIS);
 520             else
 521                 // PENDING: should we handle this better?
 522                 return 0.0f;
 523             startOffset = viewEnd;
 524         }
 525         return size;
 526     }
 527 
 528     /**
 529      * Finds the next character in the document with a character in
 530      * <code>string</code>, starting at offset <code>start</code>. If
 531      * there are no characters found, -1 will be returned.
 532      *
 533      * @param string the string of characters
 534      * @param start where to start in the model &gt;= 0
 535      * @return the document offset, or -1 if no characters found
 536      */
 537     protected int findOffsetToCharactersInString(char[] string,
 538                                                  int start) {
 539         int stringLength = string.length;
 540         int end = getEndOffset();
 541         Segment seg = new Segment();
 542         try {
 543             getDocument().getText(start, end - start, seg);
 544         } catch (BadLocationException ble) {
 545             return -1;
 546         }
 547         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
 548             counter < maxCounter; counter++) {
 549             char currentChar = seg.array[counter];
 550             for(int subCounter = 0; subCounter < stringLength;


 591 
 592                 Rectangle clip = g.getClipBounds();
 593                 tempRect.x = x + getOffset(X_AXIS, 0);
 594                 tempRect.y = y + getOffset(Y_AXIS, 0);
 595                 tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
 596                 tempRect.height = getSpan(Y_AXIS, 0);
 597                 if (tempRect.intersects(clip)) {
 598                     tempRect.x = tempRect.x - firstLineIndent;
 599                     paintChild(g, tempRect, 0);
 600                 }
 601             }
 602         }
 603     }
 604 
 605     /**
 606      * Determines the desired alignment for this view along an
 607      * axis.  This is implemented to give the alignment to the
 608      * center of the first row along the y axis, and the default
 609      * along the x axis.
 610      *
 611      * @param axis may be either <code>View.X_AXIS</code> or
 612      *   <code>View.Y_AXIS</code>
 613      * @return the desired alignment.  This should be a value
 614      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
 615      *   origin and 1.0 indicates alignment to the full span
 616      *   away from the origin.  An alignment of 0.5 would be the
 617      *   center of the view.
 618      */
 619     public float getAlignment(int axis) {
 620         switch (axis) {
 621         case Y_AXIS:
 622             float a = 0.5f;
 623             if (getViewCount() != 0) {
 624                 int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
 625                 View v = getView(0);
 626                 int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
 627                 a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
 628             }
 629             return a;
 630         case X_AXIS:
 631             return 0.5f;
 632         default:
 633             throw new IllegalArgumentException("Invalid axis: " + axis);
 634         }
 635     }
 636 
 637     /**
 638      * Breaks this view on the given axis at the given length.
 639      * <p>
 640      * <code>ParagraphView</code> instances are breakable
 641      * along the <code>Y_AXIS</code> only, and only if
 642      * <code>len</code> is after the first line.
 643      *
 644      * @param axis may be either <code>View.X_AXIS</code>
 645      *  or <code>View.Y_AXIS</code>
 646      * @param len specifies where a potential break is desired
 647      *  along the given axis &gt;= 0
 648      * @param a the current allocation of the view
 649      * @return the fragment of the view that represents the
 650      *  given span, if the view can be broken; if the view
 651      *  doesn't support breaking behavior, the view itself is
 652      *  returned
 653      * @see View#breakView
 654      */
 655     public View breakView(int axis, float len, Shape a) {
 656         if(axis == View.Y_AXIS) {
 657             if(a != null) {
 658                 Rectangle alloc = a.getBounds();
 659                 setSize(alloc.width, alloc.height);
 660             }
 661             // Determine what row to break on.
 662 
 663             // PENDING(prinz) add break support
 664             return this;
 665         }
 666         return this;
 667     }
 668 
 669     /**
 670      * Gets the break weight for a given location.
 671      * <p>
 672      * <code>ParagraphView</code> instances are breakable
 673      * along the <code>Y_AXIS</code> only, and only if
 674      * <code>len</code> is after the first row.  If the length
 675      * is less than one row, a value of <code>BadBreakWeight</code>
 676      * is returned.
 677      *
 678      * @param axis may be either <code>View.X_AXIS</code>
 679      *  or <code>View.Y_AXIS</code>
 680      * @param len specifies where a potential break is desired &gt;= 0
 681      * @return a value indicating the attractiveness of breaking here;
 682      *  either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
 683      * @see View#getBreakWeight
 684      */
 685     public int getBreakWeight(int axis, float len) {
 686         if(axis == View.Y_AXIS) {
 687             // PENDING(prinz) make this return a reasonable value
 688             // when paragraph breaking support is re-implemented.
 689             // If less than one row, bad weight value should be
 690             // returned.
 691             //return GoodBreakWeight;
 692             return BadBreakWeight;
 693         }
 694         return BadBreakWeight;
 695     }
 696 
 697     /**
 698      * Calculate the needs for the paragraph along the minor axis.
 699      *
 700      * <p>This uses size requirements of the superclass, modified to take into
 701      * account the non-breakable areas at the adjacent views edges.  The minimal
 702      * size requirements for such views should be no less than the sum of all




  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.util.Arrays;
  28 import java.awt.*;
  29 import java.awt.font.TextAttribute;
  30 import javax.swing.event.*;
  31 import javax.swing.SizeRequirements;
  32 
  33 /**
  34  * View of a simple line-wrapping paragraph that supports
  35  * multiple fonts, colors, components, icons, etc.  It is
  36  * basically a vertical box with a margin around it.  The
  37  * contents of the box are a bunch of rows which are special
  38  * horizontal boxes.  This view creates a collection of
  39  * views that represent the child elements of the paragraph
  40  * element.  Each of these views are placed into a row
  41  * directly if they will fit, otherwise the {@code breakView}
  42  * method is called to try and carve the view into pieces
  43  * that fit.
  44  *
  45  * @author  Timothy Prinzing
  46  * @author  Scott Violet
  47  * @author  Igor Kushnirskiy
  48  * @see     View
  49  */
  50 public class ParagraphView extends FlowView implements TabExpander {
  51 
  52     /**
  53      * Constructs a {@code ParagraphView} for the given element.
  54      *
  55      * @param elem the element that this view is responsible for
  56      */
  57     public ParagraphView(Element elem) {
  58         super(elem, View.Y_AXIS);
  59         setPropertiesFromAttributes();
  60         Document doc = elem.getDocument();
  61         Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
  62         if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
  63             try {
  64                 if (i18nStrategy == null) {
  65                     // the classname should probably come from a property file.
  66                     String classname = "javax.swing.text.TextLayoutStrategy";
  67                     ClassLoader loader = getClass().getClassLoader();
  68                     if (loader != null) {
  69                         i18nStrategy = loader.loadClass(classname);
  70                     } else {
  71                         i18nStrategy = Class.forName(classname);
  72                     }
  73                 }
  74                 Object o = i18nStrategy.newInstance();
  75                 if (o instanceof FlowStrategy) {
  76                     strategy = (FlowStrategy) o;
  77                 }
  78             } catch (Throwable e) {
  79                 throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
  80                                               + e.getMessage());
  81             }
  82         }
  83     }
  84 
  85     /**
  86      * Sets the type of justification.
  87      *
  88      * @param j one of the following values:
  89      * <ul>
  90      * <li>{@code StyleConstants.ALIGN_LEFT}
  91      * <li>{@code StyleConstants.ALIGN_CENTER}
  92      * <li>{@code StyleConstants.ALIGN_RIGHT}
  93      * </ul>
  94      */
  95     protected void setJustification(int j) {
  96         justification = j;
  97     }
  98 
  99     /**
 100      * Sets the line spacing.
 101      *
 102      * @param ls the value is a factor of the line hight
 103      */
 104     protected void setLineSpacing(float ls) {
 105         lineSpacing = ls;
 106     }
 107 
 108     /**
 109      * Sets the indent on the first line.
 110      *
 111      * @param fi the value in points
 112      */


 127                 Document doc = getElement().getDocument();
 128                 Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
 129                 if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
 130                     alignment = StyleConstants.ALIGN_RIGHT;
 131                 } else {
 132                     alignment = StyleConstants.ALIGN_LEFT;
 133                 }
 134             } else {
 135                 alignment = a.intValue();
 136             }
 137             setJustification(alignment);
 138             setLineSpacing(StyleConstants.getLineSpacing(attr));
 139             setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
 140         }
 141     }
 142 
 143     /**
 144      * Returns the number of views that this view is
 145      * responsible for.
 146      * The child views of the paragraph are rows which
 147      * have been used to arrange pieces of the {@code View}s
 148      * that represent the child elements.  This is the number
 149      * of views that have been tiled in two dimensions,
 150      * and should be equivalent to the number of child elements
 151      * to the element this view is responsible for.
 152      *
 153      * @return the number of views that this {@code ParagraphView}
 154      *          is responsible for
 155      */
 156     protected int getLayoutViewCount() {
 157         return layoutPool.getViewCount();
 158     }
 159 
 160     /**
 161      * Returns the view at a given {@code index}.
 162      * The child views of the paragraph are rows which
 163      * have been used to arrange pieces of the {@code Views}
 164      * that represent the child elements.  This methods returns
 165      * the view responsible for the child element index
 166      * (prior to breaking).  These are the Views that were
 167      * produced from a factory (to represent the child
 168      * elements) and used for layout.
 169      *
 170      * @param index the {@code index} of the desired view
 171      * @return the view at {@code index}
 172      */
 173     protected View getLayoutView(int index) {
 174         return layoutPool.getView(index);
 175     }
 176 
 177     /**
 178      * Returns the next visual position for the cursor, in
 179      * either the east or west direction.
 180      * Overridden from {@code CompositeView}.
 181      * @param pos position into the model
 182      * @param b either {@code Position.Bias.Forward} or
 183      *          {@code Position.Bias.Backward}
 184      * @param a the allocated region to render into
 185      * @param direction either {@code SwingConstants.NORTH}
 186      *          or {@code SwingConstants.SOUTH}
 187      * @param biasRet an array containing the bias that were checked
 188      *  in this method
 189      * @return the location in the model that represents the
 190      *  next location visual position
 191      */
 192     protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
 193                                                       Shape a, int direction,
 194                                                       Position.Bias[] biasRet)
 195                                                 throws BadLocationException {
 196         int vIndex;
 197         if(pos == -1) {
 198             vIndex = (direction == NORTH) ?
 199                      getViewCount() - 1 : 0;
 200         }
 201         else {
 202             if(b == Position.Bias.Backward && pos > 0) {
 203                 vIndex = getViewIndexAtPosition(pos - 1);
 204             }
 205             else {
 206                 vIndex = getViewIndexAtPosition(pos);


 225             Shape posBounds;
 226             try {
 227                 posBounds = text.getUI().modelToView(text, pos, b);
 228             } catch (BadLocationException exc) {
 229                 posBounds = null;
 230             }
 231             if(posBounds == null) {
 232                 x = 0;
 233             }
 234             else {
 235                 x = posBounds.getBounds().x;
 236             }
 237         }
 238         else {
 239             x = magicPoint.x;
 240         }
 241         return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
 242     }
 243 
 244     /**
 245      * Returns the closest model position to {@code x}.
 246      * {@code rowIndex} gives the index of the view that corresponds
 247      * that should be looked in.
 248      * @param pos  position into the model
 249      * @param b the bias
 250      * @param a the allocated region to render into
 251      * @param direction one of the following values:
 252      * <ul>
 253      * <li>{@code SwingConstants.NORTH}
 254      * <li>{@code SwingConstants.SOUTH}
 255      * </ul>
 256      * @param biasRet an array containing the bias that were checked
 257      *  in this method
 258      * @param rowIndex the index of the view
 259      * @param x the x coordinate of interest
 260      * @throws BadLocationException if a bad location is encountered
 261      * @return the closest model position to {@code x}
 262      */
 263     // NOTE: This will not properly work if ParagraphView contains
 264     // other ParagraphViews. It won't raise, but this does not message
 265     // the children views with getNextVisualPositionFrom.
 266     protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
 267                                        int direction, Position.Bias[] biasRet,
 268                                        int rowIndex, int x)
 269               throws BadLocationException {
 270         JTextComponent text = (JTextComponent)getContainer();
 271         Document doc = getDocument();
 272         View row = getView(rowIndex);
 273         int lastPos = -1;
 274         // This could be made better to check backward positions too.
 275         biasRet[0] = Position.Bias.Forward;
 276         for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
 277             View v = row.getView(vc);
 278             int start = v.getStartOffset();
 279             boolean ltr = AbstractDocument.isLeftToRight(doc, start, start + 1);
 280             if(ltr) {
 281                 lastPos = start;


 295                     lastPos--) {
 296                     float xx = text.modelToView(lastPos).getBounds().x;
 297                     if(xx >= x) {
 298                         while (--lastPos >= start &&
 299                                text.modelToView(lastPos).getBounds().x == xx) {
 300                         }
 301                         return ++lastPos;
 302                     }
 303                 }
 304                 lastPos++;
 305             }
 306         }
 307         if(lastPos == -1) {
 308             return getStartOffset();
 309         }
 310         return lastPos;
 311     }
 312 
 313     /**
 314      * Determines in which direction the next view lays.
 315      * Consider the {@code View} at index n.
 316      * Typically the {@code View}s are layed out
 317      * from left to right, so that the {@code View}
 318      * to the EAST will be at index n + 1, and the
 319      * {@code View} to the WEST will be at index n - 1.
 320      * In certain situations, such as with bidirectional text,
 321      * it is possible that the {@code View} to EAST is not
 322      * at index n + 1, but rather at index n - 1,
 323      * or that the {@code View} to the WEST is not at
 324      * index n - 1, but index n + 1.  In this case this method
 325      * would return true, indicating the {@code View}s are
 326      * layed out in descending order.
 327      * <p>
 328      * This will return true if the text is layed out right
 329      * to left at position, otherwise false.
 330      *
 331      * @param position position into the model
 332      * @param bias either {@code Position.Bias.Forward} or
 333      *          {@code Position.Bias.Backward}
 334      * @return true if the text is layed out right to left at
 335      *         position, otherwise false.
 336      */
 337     protected boolean flipEastAndWestAtEnds(int position,
 338                                             Position.Bias bias) {
 339         Document doc = getDocument();
 340         position = getStartOffset();
 341         return !AbstractDocument.isLeftToRight(doc, position, position + 1);
 342     }
 343 
 344     // --- FlowView methods ---------------------------------------------
 345 
 346     /**
 347      * Fetches the constraining span to flow against for
 348      * the given child index.
 349      * @param index the index of the view being queried
 350      * @return the constraining span for the given view at
 351      *  {@code index}
 352      * @since 1.3
 353      */
 354     public int getFlowSpan(int index) {
 355         View child = getView(index);
 356         int adjust = 0;
 357         if (child instanceof Row) {
 358             Row row = (Row) child;
 359             adjust = row.getLeftInset() + row.getRightInset();
 360         }
 361         return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan
 362                                                  : (layoutSpan - adjust);
 363     }
 364 
 365     /**
 366      * Fetches the location along the flow axis that the
 367      * flow span will start at.
 368      * @param index the index of the view being queried
 369      * @return the location for the given view at
 370      *  {@code index}
 371      * @since 1.3
 372      */
 373     public int getFlowStart(int index) {
 374         View child = getView(index);
 375         int adjust = 0;
 376         if (child instanceof Row) {
 377             Row row = (Row) child;
 378             adjust = row.getLeftInset();
 379         }
 380         return tabBase + adjust;
 381     }
 382 
 383     /**
 384      * Create a {@code View} that should be used to hold a
 385      * a row's worth of children in a flow.
 386      * @return the new {@code View}
 387      * @since 1.3
 388      */
 389     protected View createRow() {
 390         return new Row(getElement());
 391     }
 392 
 393     // --- TabExpander methods ------------------------------------------
 394 
 395     /**
 396      * Returns the next tab stop position given a reference position.
 397      * This view implements the tab coordinate system, and calls
 398      * {@code getTabbedSpan} on the logical children in the process
 399      * of layout to determine the desired span of the children.  The
 400      * logical children can delegate their tab expansion upward to
 401      * the paragraph which knows how to expand tabs.
 402      * {@code LabelView} is an example of a view that delegates
 403      * its tab expansion needs upward to the paragraph.
 404      * <p>
 405      * This is implemented to try and locate a {@code TabSet}
 406      * in the paragraph element's attribute set.  If one can be
 407      * found, its settings will be used, otherwise a default expansion
 408      * will be provided.  The base location for tab expansion
 409      * is the left inset from the paragraphs most recent allocation
 410      * (which is what the layout of the children is based upon).
 411      *
 412      * @param x the X reference position
 413      * @param tabOffset the position within the text stream
 414      *   that the tab occurred at &gt;= 0
 415      * @return the trailing end of the tab expansion &gt;= 0
 416      * @see TabSet
 417      * @see TabStop
 418      * @see LabelView
 419      */
 420     public float nextTabStop(float x, int tabOffset) {
 421         // If the text isn't left justified, offset by 10 pixels!
 422         if(justification != StyleConstants.ALIGN_LEFT)
 423             return x + 10.0f;
 424         x -= tabBase;
 425         TabSet tabs = getTabSet();


 456         if (offset == -1) {
 457             offset = getEndOffset();
 458         }
 459         float charsSize = getPartialSize(tabOffset + 1, offset);
 460         switch(alignment) {
 461         case TabStop.ALIGN_RIGHT:
 462         case TabStop.ALIGN_DECIMAL:
 463             // right and decimal are treated the same way, the new
 464             // position will be the location of the tab less the
 465             // partialSize.
 466             return tabBase + Math.max(x, tab.getPosition() - charsSize);
 467         case TabStop.ALIGN_CENTER:
 468             // Similar to right, but half the partialSize.
 469             return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
 470         }
 471         // will never get here!
 472         return x;
 473     }
 474 
 475     /**
 476      * Gets the {@code Tabset} to be used in calculating tabs.
 477      *
 478      * @return the {@code TabSet}
 479      */
 480     protected TabSet getTabSet() {
 481         return StyleConstants.getTabSet(getElement().getAttributes());
 482     }
 483 
 484     /**
 485      * Returns the size used by the views between
 486      * {@code startOffset} and {@code endOffset}.
 487      * This uses {@code getPartialView} to calculate the
 488      * size if the child view implements the
 489      * {@code TabableView} interface. If a
 490      * size is needed and a {@code View} does not implement
 491      * the {@code TabableView} interface,
 492      * the {@code preferredSpan} will be used.
 493      *
 494      * @param startOffset the starting document offset &gt;= 0
 495      * @param endOffset the ending document offset &gt;= startOffset
 496      * @return the size &gt;= 0
 497      */
 498     protected float getPartialSize(int startOffset, int endOffset) {
 499         float size = 0.0f;
 500         int viewIndex;
 501         int numViews = getViewCount();
 502         View view;
 503         int viewEnd;
 504         int tempEnd;
 505 
 506         // Have to search layoutPool!
 507         // PENDING: when ParagraphView supports breaking location
 508         // into layoutPool will have to change!
 509         viewIndex = getElement().getElementIndex(startOffset);
 510         numViews = layoutPool.getViewCount();
 511         while(startOffset < endOffset && viewIndex < numViews) {
 512             view = layoutPool.getView(viewIndex++);
 513             viewEnd = view.getEndOffset();
 514             tempEnd = Math.min(endOffset, viewEnd);
 515             if(view instanceof TabableView)
 516                 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
 517             else if(startOffset == view.getStartOffset() &&
 518                     tempEnd == view.getEndOffset())
 519                 size += view.getPreferredSpan(View.X_AXIS);
 520             else
 521                 // PENDING: should we handle this better?
 522                 return 0.0f;
 523             startOffset = viewEnd;
 524         }
 525         return size;
 526     }
 527 
 528     /**
 529      * Finds the next character in the document with a character in
 530      * {@code string}, starting at offset {@code start}. If
 531      * there are no characters found, -1 will be returned.
 532      *
 533      * @param string the string of characters
 534      * @param start where to start in the model &gt;= 0
 535      * @return the document offset, or -1 if no characters found
 536      */
 537     protected int findOffsetToCharactersInString(char[] string,
 538                                                  int start) {
 539         int stringLength = string.length;
 540         int end = getEndOffset();
 541         Segment seg = new Segment();
 542         try {
 543             getDocument().getText(start, end - start, seg);
 544         } catch (BadLocationException ble) {
 545             return -1;
 546         }
 547         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
 548             counter < maxCounter; counter++) {
 549             char currentChar = seg.array[counter];
 550             for(int subCounter = 0; subCounter < stringLength;


 591 
 592                 Rectangle clip = g.getClipBounds();
 593                 tempRect.x = x + getOffset(X_AXIS, 0);
 594                 tempRect.y = y + getOffset(Y_AXIS, 0);
 595                 tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
 596                 tempRect.height = getSpan(Y_AXIS, 0);
 597                 if (tempRect.intersects(clip)) {
 598                     tempRect.x = tempRect.x - firstLineIndent;
 599                     paintChild(g, tempRect, 0);
 600                 }
 601             }
 602         }
 603     }
 604 
 605     /**
 606      * Determines the desired alignment for this view along an
 607      * axis.  This is implemented to give the alignment to the
 608      * center of the first row along the y axis, and the default
 609      * along the x axis.
 610      *
 611      * @param axis may be either {@code View.X_AXIS} or
 612      *   {@code View.Y_AXIS}
 613      * @return the desired alignment.  This should be a value
 614      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
 615      *   origin and 1.0 indicates alignment to the full span
 616      *   away from the origin.  An alignment of 0.5 would be the
 617      *   center of the view.
 618      */
 619     public float getAlignment(int axis) {
 620         switch (axis) {
 621         case Y_AXIS:
 622             float a = 0.5f;
 623             if (getViewCount() != 0) {
 624                 int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
 625                 View v = getView(0);
 626                 int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
 627                 a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
 628             }
 629             return a;
 630         case X_AXIS:
 631             return 0.5f;
 632         default:
 633             throw new IllegalArgumentException("Invalid axis: " + axis);
 634         }
 635     }
 636 
 637     /**
 638      * Breaks this view on the given axis at the given length.
 639      * <p>
 640      * {@code ParagraphView} instances are breakable
 641      * along the {@code Y_AXIS} only, and only if
 642      * {@code len} is after the first line.
 643      *
 644      * @param axis may be either {@code View.X_AXIS}
 645      *  or {@code View.Y_AXIS}
 646      * @param len specifies where a potential break is desired
 647      *  along the given axis &gt;= 0
 648      * @param a the current allocation of the view
 649      * @return the fragment of the view that represents the
 650      *  given span, if the view can be broken; if the view
 651      *  doesn't support breaking behavior, the view itself is
 652      *  returned
 653      * @see View#breakView
 654      */
 655     public View breakView(int axis, float len, Shape a) {
 656         if(axis == View.Y_AXIS) {
 657             if(a != null) {
 658                 Rectangle alloc = a.getBounds();
 659                 setSize(alloc.width, alloc.height);
 660             }
 661             // Determine what row to break on.
 662 
 663             // PENDING(prinz) add break support
 664             return this;
 665         }
 666         return this;
 667     }
 668 
 669     /**
 670      * Gets the break weight for a given location.
 671      * <p>
 672      * {@code ParagraphView} instances are breakable
 673      * along the {@code Y_AXIS} only, and only if
 674      * {@code len} is after the first row.  If the length
 675      * is less than one row, a value of {@code BadBreakWeight}
 676      * is returned.
 677      *
 678      * @param axis may be either {@code View.X_AXIS}
 679      *  or {@code View.Y_AXIS}
 680      * @param len specifies where a potential break is desired &gt;= 0
 681      * @return a value indicating the attractiveness of breaking here;
 682      *  either {@code GoodBreakWeight} or {@code BadBreakWeight}
 683      * @see View#getBreakWeight
 684      */
 685     public int getBreakWeight(int axis, float len) {
 686         if(axis == View.Y_AXIS) {
 687             // PENDING(prinz) make this return a reasonable value
 688             // when paragraph breaking support is re-implemented.
 689             // If less than one row, bad weight value should be
 690             // returned.
 691             //return GoodBreakWeight;
 692             return BadBreakWeight;
 693         }
 694         return BadBreakWeight;
 695     }
 696 
 697     /**
 698      * Calculate the needs for the paragraph along the minor axis.
 699      *
 700      * <p>This uses size requirements of the superclass, modified to take into
 701      * account the non-breakable areas at the adjacent views edges.  The minimal
 702      * size requirements for such views should be no less than the sum of all


< prev index next >