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 >= 0 415 * @return the trailing end of the tab expansion >= 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 >= 0 495 * @param endOffset the ending document offset >= startOffset 496 * @return the size >= 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 >= 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 >= 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 >= 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 >= 0 415 * @return the trailing end of the tab expansion >= 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 >= 0 495 * @param endOffset the ending document offset >= startOffset 496 * @return the size >= 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 >= 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 >= 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 >= 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 |