1/* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 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 */ 25package javax.swing.text; 26 27import java.util.Arrays; 28import java.awt.*; 29import java.awt.font.TextAttribute; 30import java.awt.geom.Rectangle2D; 31import javax.swing.event.*; 32import javax.swing.SizeRequirements; 33 34/** 35 * View of a simple line-wrapping paragraph that supports 36 * multiple fonts, colors, components, icons, etc. It is 37 * basically a vertical box with a margin around it. The 38 * contents of the box are a bunch of rows which are special 39 * horizontal boxes. This view creates a collection of 40 * views that represent the child elements of the paragraph 41 * element. Each of these views are placed into a row 42 * directly if they will fit, otherwise the <code>breakView</code> 43 * method is called to try and carve the view into pieces 44 * that fit. 45 * 46 * @author Timothy Prinzing 47 * @author Scott Violet 48 * @author Igor Kushnirskiy 49 * @see View 50 */ 51public class ParagraphView extends FlowView implements TabExpander { 52 53 /** 54 * Constructs a <code>ParagraphView</code> for the given element. 55 * 56 * @param elem the element that this view is responsible for 57 */ 58 public ParagraphView(Element elem) { 59 super(elem, View.Y_AXIS); 60 setPropertiesFromAttributes(); 61 Document doc = elem.getDocument(); 62 Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty); 63 if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) { 64 try { 65 // the classname should probably come from a property file. 66 strategy = new TextLayoutStrategy(); 67 } catch (Throwable e) { 68 throw new StateInvariantError("ParagraphView: Can't create i18n strategy: " 69 + e.getMessage()); 70 } 71 } 72 } 73 74 /** 75 * Sets the type of justification. 76 * 77 * @param j one of the following values: 78 * <ul> 79 * <li><code>StyleConstants.ALIGN_LEFT</code> 80 * <li><code>StyleConstants.ALIGN_CENTER</code> 81 * <li><code>StyleConstants.ALIGN_RIGHT</code> 82 * </ul> 83 */ 84 protected void setJustification(int j) { 85 justification = j; 86 } 87 88 /** 89 * Sets the line spacing. 90 * 91 * @param ls the value is a factor of the line hight 92 */ 93 protected void setLineSpacing(float ls) { 94 lineSpacing = ls; 95 } 96 97 /** 98 * Sets the indent on the first line. 99 * 100 * @param fi the value in points 101 */ 102 protected void setFirstLineIndent(float fi) { 103 firstLineIndent = (int) fi; 104 } 105 106 /** 107 * Set the cached properties from the attributes. 108 */ 109 protected void setPropertiesFromAttributes() { 110 AttributeSet attr = getAttributes(); 111 if (attr != null) { 112 setParagraphInsets(attr); 113 Integer a = (Integer)attr.getAttribute(StyleConstants.Alignment); 114 int alignment; 115 if (a == null) { 116 Document doc = getElement().getDocument(); 117 Object o = doc.getProperty(TextAttribute.RUN_DIRECTION); 118 if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) { 119 alignment = StyleConstants.ALIGN_RIGHT; 120 } else { 121 alignment = StyleConstants.ALIGN_LEFT; 122 } 123 } else { 124 alignment = a.intValue(); 125 } 126 setJustification(alignment); 127 setLineSpacing(StyleConstants.getLineSpacing(attr)); 128 setFirstLineIndent(StyleConstants.getFirstLineIndent(attr)); 129 } 130 } 131 132 /** 133 * Returns the number of views that this view is 134 * responsible for. 135 * The child views of the paragraph are rows which 136 * have been used to arrange pieces of the <code>View</code>s 137 * that represent the child elements. This is the number 138 * of views that have been tiled in two dimensions, 139 * and should be equivalent to the number of child elements 140 * to the element this view is responsible for. 141 * 142 * @return the number of views that this <code>ParagraphView</code> 143 * is responsible for 144 */ 145 protected int getLayoutViewCount() { 146 return layoutPool.getViewCount(); 147 } 148 149 /** 150 * Returns the view at a given <code>index</code>. 151 * The child views of the paragraph are rows which 152 * have been used to arrange pieces of the <code>Views</code> 153 * that represent the child elements. This methods returns 154 * the view responsible for the child element index 155 * (prior to breaking). These are the Views that were 156 * produced from a factory (to represent the child 157 * elements) and used for layout. 158 * 159 * @param index the <code>index</code> of the desired view 160 * @return the view at <code>index</code> 161 */ 162 protected View getLayoutView(int index) { 163 return layoutPool.getView(index); 164 } 165 166 /** 167 * Returns the next visual position for the cursor, in 168 * either the east or west direction. 169 * Overridden from <code>CompositeView</code>. 170 * @param pos position into the model 171 * @param b either <code>Position.Bias.Forward</code> or 172 * <code>Position.Bias.Backward</code> 173 * @param a the allocated region to render into 174 * @param direction either <code>SwingConstants.NORTH</code> 175 * or <code>SwingConstants.SOUTH</code> 176 * @param biasRet an array containing the bias that were checked 177 * in this method 178 * @return the location in the model that represents the 179 * next location visual position 180 */ 181 @SuppressWarnings("deprecation") 182 protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 183 Shape a, int direction, 184 Position.Bias[] biasRet) 185 throws BadLocationException { 186 int vIndex; 187 if(pos == -1) { 188 vIndex = (direction == NORTH) ? 189 getViewCount() - 1 : 0; 190 } 191 else { 192 if(b == Position.Bias.Backward && pos > 0) { 193 vIndex = getViewIndexAtPosition(pos - 1); 194 } 195 else { 196 vIndex = getViewIndexAtPosition(pos); 197 } 198 if(direction == NORTH) { 199 if(vIndex == 0) { 200 return -1; 201 } 202 vIndex--; 203 } 204 else if(++vIndex >= getViewCount()) { 205 return -1; 206 } 207 } 208 // vIndex gives index of row to look in. 209 JTextComponent text = (JTextComponent)getContainer(); 210 Caret c = text.getCaret(); 211 Point magicPoint; 212 magicPoint = (c != null) ? c.getMagicCaretPosition() : null; 213 int x; 214 if(magicPoint == null) { 215 Shape posBounds; 216 try { 217 posBounds = text.getUI().modelToView(text, pos, b); 218 } catch (BadLocationException exc) { 219 posBounds = null; 220 } 221 if(posBounds == null) { 222 x = 0; 223 } 224 else { 225 x = posBounds.getBounds().x; 226 } 227 } 228 else { 229 x = magicPoint.x; 230 } 231 return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x); 232 } 233 234 /** 235 * Returns the closest model position to <code>x</code>. 236 * <code>rowIndex</code> gives the index of the view that corresponds 237 * that should be looked in. 238 * @param pos position into the model 239 * @param b the bias 240 * @param a the allocated region to render into 241 * @param direction one of the following values: 242 * <ul> 243 * <li><code>SwingConstants.NORTH</code> 244 * <li><code>SwingConstants.SOUTH</code> 245 * </ul> 246 * @param biasRet an array containing the bias that were checked 247 * in this method 248 * @param rowIndex the index of the view 249 * @param x the x coordinate of interest 250 * @throws BadLocationException if a bad location is encountered 251 * @return the closest model position to <code>x</code> 252 */ 253 // NOTE: This will not properly work if ParagraphView contains 254 // other ParagraphViews. It won't raise, but this does not message 255 // the children views with getNextVisualPositionFrom. 256 @SuppressWarnings("deprecation") 257 protected int getClosestPositionTo(int pos, Position.Bias b, Shape a, 258 int direction, Position.Bias[] biasRet, 259 int rowIndex, int x) 260 throws BadLocationException { 261 JTextComponent text = (JTextComponent)getContainer(); 262 Document doc = getDocument(); 263 View row = getView(rowIndex); 264 int lastPos = -1; 265 // This could be made better to check backward positions too. 266 biasRet[0] = Position.Bias.Forward; 267 for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) { 268 View v = row.getView(vc); 269 int start = v.getStartOffset(); 270 boolean ltr = AbstractDocument.isLeftToRight(doc, start, start + 1); 271 if(ltr) { 272 lastPos = start; 273 for(int end = v.getEndOffset(); lastPos < end; lastPos++) { 274 float xx = text.modelToView(lastPos).getBounds().x; 275 if(xx >= x) { 276 while (++lastPos < end && 277 text.modelToView(lastPos).getBounds().x == xx) { 278 } 279 return --lastPos; 280 } 281 } 282 lastPos--; 283 } 284 else { 285 for(lastPos = v.getEndOffset() - 1; lastPos >= start; 286 lastPos--) { 287 float xx = text.modelToView(lastPos).getBounds().x; 288 if(xx >= x) { 289 while (--lastPos >= start && 290 text.modelToView(lastPos).getBounds().x == xx) { 291 } 292 return ++lastPos; 293 } 294 } 295 lastPos++; 296 } 297 } 298 if(lastPos == -1) { 299 return getStartOffset(); 300 } 301 return lastPos; 302 } 303 304 /** 305 * Determines in which direction the next view lays. 306 * Consider the <code>View</code> at index n. 307 * Typically the <code>View</code>s are layed out 308 * from left to right, so that the <code>View</code> 309 * to the EAST will be at index n + 1, and the 310 * <code>View</code> to the WEST will be at index n - 1. 311 * In certain situations, such as with bidirectional text, 312 * it is possible that the <code>View</code> to EAST is not 313 * at index n + 1, but rather at index n - 1, 314 * or that the <code>View</code> to the WEST is not at 315 * index n - 1, but index n + 1. In this case this method 316 * would return true, indicating the <code>View</code>s are 317 * layed out in descending order. 318 * <p> 319 * This will return true if the text is layed out right 320 * to left at position, otherwise false. 321 * 322 * @param position position into the model 323 * @param bias either <code>Position.Bias.Forward</code> or 324 * <code>Position.Bias.Backward</code> 325 * @return true if the text is layed out right to left at 326 * position, otherwise false. 327 */ 328 protected boolean flipEastAndWestAtEnds(int position, 329 Position.Bias bias) { 330 Document doc = getDocument(); 331 position = getStartOffset(); 332 return !AbstractDocument.isLeftToRight(doc, position, position + 1); 333 } 334 335 // --- FlowView methods --------------------------------------------- 336 337 /** 338 * Fetches the constraining span to flow against for 339 * the given child index. 340 * @param index the index of the view being queried 341 * @return the constraining span for the given view at 342 * <code>index</code> 343 * @since 1.3 344 */ 345 public int getFlowSpan(int index) { 346 View child = getView(index); 347 int adjust = 0; 348 if (child instanceof Row) { 349 Row row = (Row) child; 350 adjust = row.getLeftInset() + row.getRightInset(); 351 } 352 return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan 353 : (layoutSpan - adjust); 354 } 355 356 /** 357 * Fetches the location along the flow axis that the 358 * flow span will start at. 359 * @param index the index of the view being queried 360 * @return the location for the given view at 361 * <code>index</code> 362 * @since 1.3 363 */ 364 public int getFlowStart(int index) { 365 View child = getView(index); 366 int adjust = 0; 367 if (child instanceof Row) { 368 Row row = (Row) child; 369 adjust = row.getLeftInset(); 370 } 371 return tabBase + adjust; 372 } 373 374 /** 375 * Create a <code>View</code> that should be used to hold a 376 * a row's worth of children in a flow. 377 * @return the new <code>View</code> 378 * @since 1.3 379 */ 380 protected View createRow() { 381 return new Row(getElement()); 382 } 383 384 // --- TabExpander methods ------------------------------------------ 385 386 /** 387 * Returns the next tab stop position given a reference position. 388 * This view implements the tab coordinate system, and calls 389 * <code>getTabbedSpan</code> on the logical children in the process 390 * of layout to determine the desired span of the children. The 391 * logical children can delegate their tab expansion upward to 392 * the paragraph which knows how to expand tabs. 393 * <code>LabelView</code> is an example of a view that delegates 394 * its tab expansion needs upward to the paragraph. 395 * <p> 396 * This is implemented to try and locate a <code>TabSet</code> 397 * in the paragraph element's attribute set. If one can be 398 * found, its settings will be used, otherwise a default expansion 399 * will be provided. The base location for tab expansion 400 * is the left inset from the paragraphs most recent allocation 401 * (which is what the layout of the children is based upon). 402 * 403 * @param x the X reference position 404 * @param tabOffset the position within the text stream 405 * that the tab occurred at >= 0 406 * @return the trailing end of the tab expansion >= 0 407 * @see TabSet 408 * @see TabStop 409 * @see LabelView 410 */ 411 public float nextTabStop(float x, int tabOffset) { 412 // If the text isn't left justified, offset by 10 pixels! 413 if(justification != StyleConstants.ALIGN_LEFT) 414 return x + 10.0f; 415 x -= tabBase; 416 TabSet tabs = getTabSet(); 417 if(tabs == null) { 418 // a tab every 72 pixels. 419 return (float)(tabBase + (((int)x / 72 + 1) * 72)); 420 } 421 TabStop tab = tabs.getTabAfter(x + .01f); 422 if(tab == null) { 423 // no tab, do a default of 5 pixels. 424 // Should this cause a wrapping of the line? 425 return tabBase + x + 5.0f; 426 } 427 int alignment = tab.getAlignment(); 428 int offset; 429 switch(alignment) { 430 default: 431 case TabStop.ALIGN_LEFT: 432 // Simple case, left tab. 433 return tabBase + tab.getPosition(); 434 case TabStop.ALIGN_BAR: 435 // PENDING: what does this mean? 436 return tabBase + tab.getPosition(); 437 case TabStop.ALIGN_RIGHT: 438 case TabStop.ALIGN_CENTER: 439 offset = findOffsetToCharactersInString(tabChars, 440 tabOffset + 1); 441 break; 442 case TabStop.ALIGN_DECIMAL: 443 offset = findOffsetToCharactersInString(tabDecimalChars, 444 tabOffset + 1); 445 break; 446 } 447 if (offset == -1) { 448 offset = getEndOffset(); 449 } 450 float charsSize = getPartialSize(tabOffset + 1, offset); 451 switch(alignment) { 452 case TabStop.ALIGN_RIGHT: 453 case TabStop.ALIGN_DECIMAL: 454 // right and decimal are treated the same way, the new 455 // position will be the location of the tab less the 456 // partialSize. 457 return tabBase + Math.max(x, tab.getPosition() - charsSize); 458 case TabStop.ALIGN_CENTER: 459 // Similar to right, but half the partialSize. 460 return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f); 461 } 462 // will never get here! 463 return x; 464 } 465 466 /** 467 * Gets the <code>Tabset</code> to be used in calculating tabs. 468 * 469 * @return the <code>TabSet</code> 470 */ 471 protected TabSet getTabSet() { 472 return StyleConstants.getTabSet(getElement().getAttributes()); 473 } 474 475 /** 476 * Returns the size used by the views between 477 * <code>startOffset</code> and <code>endOffset</code>. 478 * This uses <code>getPartialView</code> to calculate the 479 * size if the child view implements the 480 * <code>TabableView</code> interface. If a 481 * size is needed and a <code>View</code> does not implement 482 * the <code>TabableView</code> interface, 483 * the <code>preferredSpan</code> will be used. 484 * 485 * @param startOffset the starting document offset >= 0 486 * @param endOffset the ending document offset >= startOffset 487 * @return the size >= 0 488 */ 489 protected float getPartialSize(int startOffset, int endOffset) { 490 float size = 0.0f; 491 int viewIndex; 492 int numViews = getViewCount(); 493 View view; 494 int viewEnd; 495 int tempEnd; 496 497 // Have to search layoutPool! 498 // PENDING: when ParagraphView supports breaking location 499 // into layoutPool will have to change! 500 viewIndex = getElement().getElementIndex(startOffset); 501 numViews = layoutPool.getViewCount(); 502 while(startOffset < endOffset && viewIndex < numViews) { 503 view = layoutPool.getView(viewIndex++); 504 viewEnd = view.getEndOffset(); 505 tempEnd = Math.min(endOffset, viewEnd); 506 if(view instanceof TabableView) 507 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd); 508 else if(startOffset == view.getStartOffset() && 509 tempEnd == view.getEndOffset()) 510 size += view.getPreferredSpan(View.X_AXIS); 511 else 512 // PENDING: should we handle this better? 513 return 0.0f; 514 startOffset = viewEnd; 515 } 516 return size; 517 } 518 519 /** 520 * Finds the next character in the document with a character in 521 * <code>string</code>, starting at offset <code>start</code>. If 522 * there are no characters found, -1 will be returned. 523 * 524 * @param string the string of characters 525 * @param start where to start in the model >= 0 526 * @return the document offset, or -1 if no characters found 527 */ 528 protected int findOffsetToCharactersInString(char[] string, 529 int start) { 530 int stringLength = string.length; 531 int end = getEndOffset(); 532 Segment seg = new Segment(); 533 try { 534 getDocument().getText(start, end - start, seg); 535 } catch (BadLocationException ble) { 536 return -1; 537 } 538 for(int counter = seg.offset, maxCounter = seg.offset + seg.count; 539 counter < maxCounter; counter++) { 540 char currentChar = seg.array[counter]; 541 for(int subCounter = 0; subCounter < stringLength; 542 subCounter++) { 543 if(currentChar == string[subCounter]) 544 return counter - seg.offset + start; 545 } 546 } 547 // No match. 548 return -1; 549 } 550 551 /** 552 * Returns where the tabs are calculated from. 553 * @return where tabs are calculated from 554 */ 555 protected float getTabBase() { 556 return (float)tabBase; 557 } 558 559 // ---- View methods ---------------------------------------------------- 560 561 /** 562 * Renders using the given rendering surface and area on that 563 * surface. This is implemented to delegate to the superclass 564 * after stashing the base coordinate for tab calculations. 565 * 566 * @param g the rendering surface to use 567 * @param a the allocated region to render into 568 * @see View#paint 569 */ 570 public void paint(Graphics g, Shape a) { 571 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); 572 tabBase = alloc.x + getLeftInset(); 573 super.paint(g, a); 574 575 // line with the negative firstLineIndent value needs 576 // special handling 577 if (firstLineIndent < 0) { 578 Shape sh = getChildAllocation(0, a); 579 if ((sh != null) && sh.intersects(alloc)) { 580 int x = alloc.x + getLeftInset() + firstLineIndent; 581 int y = alloc.y + getTopInset(); 582 583 Rectangle clip = g.getClipBounds(); 584 tempRect.x = x + getOffset(X_AXIS, 0); 585 tempRect.y = y + getOffset(Y_AXIS, 0); 586 tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent; 587 tempRect.height = getSpan(Y_AXIS, 0); 588 if (tempRect.intersects(clip)) { 589 tempRect.x = tempRect.x - firstLineIndent; 590 paintChild(g, tempRect, 0); 591 } 592 } 593 } 594 } 595 596 /** 597 * Determines the desired alignment for this view along an 598 * axis. This is implemented to give the alignment to the 599 * center of the first row along the y axis, and the default 600 * along the x axis. 601 * 602 * @param axis may be either <code>View.X_AXIS</code> or 603 * <code>View.Y_AXIS</code> 604 * @return the desired alignment. This should be a value 605 * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the 606 * origin and 1.0 indicates alignment to the full span 607 * away from the origin. An alignment of 0.5 would be the 608 * center of the view. 609 */ 610 public float getAlignment(int axis) { 611 switch (axis) { 612 case Y_AXIS: 613 float a = 0.5f; 614 if (getViewCount() != 0) { 615 int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS); 616 View v = getView(0); 617 int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS); 618 a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0; 619 } 620 return a; 621 case X_AXIS: 622 return 0.5f; 623 default: 624 throw new IllegalArgumentException("Invalid axis: " + axis); 625 } 626 } 627 628 /** 629 * Breaks this view on the given axis at the given length. 630 * <p> 631 * <code>ParagraphView</code> instances are breakable 632 * along the <code>Y_AXIS</code> only, and only if 633 * <code>len</code> is after the first line. 634 * 635 * @param axis may be either <code>View.X_AXIS</code> 636 * or <code>View.Y_AXIS</code> 637 * @param len specifies where a potential break is desired 638 * along the given axis >= 0 639 * @param a the current allocation of the view 640 * @return the fragment of the view that represents the 641 * given span, if the view can be broken; if the view 642 * doesn't support breaking behavior, the view itself is 643 * returned 644 * @see View#breakView 645 */ 646 public View breakView(int axis, float len, Shape a) { 647 if(axis == View.Y_AXIS) { 648 if(a != null) { 649 Rectangle alloc = a.getBounds(); 650 setSize(alloc.width, alloc.height); 651 } 652 // Determine what row to break on. 653 654 // PENDING(prinz) add break support 655 return this; 656 } 657 return this; 658 } 659 660 /** 661 * Gets the break weight for a given location. 662 * <p> 663 * <code>ParagraphView</code> instances are breakable 664 * along the <code>Y_AXIS</code> only, and only if 665 * <code>len</code> is after the first row. If the length 666 * is less than one row, a value of <code>BadBreakWeight</code> 667 * is returned. 668 * 669 * @param axis may be either <code>View.X_AXIS</code> 670 * or <code>View.Y_AXIS</code> 671 * @param len specifies where a potential break is desired >= 0 672 * @return a value indicating the attractiveness of breaking here; 673 * either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code> 674 * @see View#getBreakWeight 675 */ 676 public int getBreakWeight(int axis, float len) { 677 if(axis == View.Y_AXIS) { 678 // PENDING(prinz) make this return a reasonable value 679 // when paragraph breaking support is re-implemented. 680 // If less than one row, bad weight value should be 681 // returned. 682 //return GoodBreakWeight; 683 return BadBreakWeight; 684 } 685 return BadBreakWeight; 686 } 687 688 /** 689 * Calculate the needs for the paragraph along the minor axis. 690 * 691 * <p>This uses size requirements of the superclass, modified to take into 692 * account the non-breakable areas at the adjacent views edges. The minimal 693 * size requirements for such views should be no less than the sum of all 694 * adjacent fragments.</p> 695 * 696 * <p>If the {@code axis} parameter is neither {@code View.X_AXIS} nor 697 * {@code View.Y_AXIS}, {@link IllegalArgumentException} is thrown. If the 698 * {@code r} parameter is {@code null,} a new {@code SizeRequirements} 699 * object is created, otherwise the supplied {@code SizeRequirements} 700 * object is returned.</p> 701 * 702 * @param axis the minor axis 703 * @param r the input {@code SizeRequirements} object 704 * @return the new or adjusted {@code SizeRequirements} object 705 * @throws IllegalArgumentException if the {@code axis} parameter is invalid 706 */ 707 @Override 708 protected SizeRequirements calculateMinorAxisRequirements(int axis, 709 SizeRequirements r) { 710 r = super.calculateMinorAxisRequirements(axis, r); 711 712 float min = 0; 713 float glue = 0; 714 int n = getLayoutViewCount(); 715 for (int i = 0; i < n; i++) { 716 View v = getLayoutView(i); 717 float span = v.getMinimumSpan(axis); 718 if (v.getBreakWeight(axis, 0, v.getMaximumSpan(axis)) > View.BadBreakWeight) { 719 // find the longest non-breakable fragments at the view edges 720 int p0 = v.getStartOffset(); 721 int p1 = v.getEndOffset(); 722 float start = findEdgeSpan(v, axis, p0, p0, p1); 723 float end = findEdgeSpan(v, axis, p1, p0, p1); 724 glue += start; 725 min = Math.max(min, Math.max(span, glue)); 726 glue = end; 727 } else { 728 // non-breakable view 729 glue += span; 730 min = Math.max(min, glue); 731 } 732 } 733 r.minimum = Math.max(r.minimum, (int) min); 734 r.preferred = Math.max(r.minimum, r.preferred); 735 r.maximum = Math.max(r.preferred, r.maximum); 736 737 return r; 738 } 739 740 /** 741 * Binary search for the longest non-breakable fragment at the view edge. 742 */ 743 private float findEdgeSpan(View v, int axis, int fp, int p0, int p1) { 744 int len = p1 - p0; 745 if (len <= 1) { 746 // further fragmentation is not possible 747 return v.getMinimumSpan(axis); 748 } else { 749 int mid = p0 + len / 2; 750 boolean startEdge = mid > fp; 751 // initial view is breakable hence must support fragmentation 752 View f = startEdge ? 753 v.createFragment(fp, mid) : v.createFragment(mid, fp); 754 boolean breakable = f.getBreakWeight( 755 axis, 0, f.getMaximumSpan(axis)) > View.BadBreakWeight; 756 if (breakable == startEdge) { 757 p1 = mid; 758 } else { 759 p0 = mid; 760 } 761 return findEdgeSpan(f, axis, fp, p0, p1); 762 } 763 } 764 765 /** 766 * Gives notification from the document that attributes were changed 767 * in a location that this view is responsible for. 768 * 769 * @param changes the change information from the 770 * associated document 771 * @param a the current allocation of the view 772 * @param f the factory to use to rebuild if the view has children 773 * @see View#changedUpdate 774 */ 775 public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) { 776 // update any property settings stored, and layout should be 777 // recomputed 778 setPropertiesFromAttributes(); 779 layoutChanged(X_AXIS); 780 layoutChanged(Y_AXIS); 781 super.changedUpdate(changes, a, f); 782 } 783 784 785 // --- variables ----------------------------------------------- 786 787 private int justification; 788 private float lineSpacing; 789 /** Indentation for the first line, from the left inset. */ 790 protected int firstLineIndent = 0; 791 792 /** 793 * Used by the TabExpander functionality to determine 794 * where to base the tab calculations. This is basically 795 * the location of the left side of the paragraph. 796 */ 797 private int tabBase; 798 799 /** 800 * Used to create an i18n-based layout strategy 801 */ 802 static Class<?> i18nStrategy; 803 804 /** Used for searching for a tab. */ 805 static char[] tabChars; 806 /** Used for searching for a tab or decimal character. */ 807 static char[] tabDecimalChars; 808 809 static { 810 tabChars = new char[1]; 811 tabChars[0] = '\t'; 812 tabDecimalChars = new char[2]; 813 tabDecimalChars[0] = '\t'; 814 tabDecimalChars[1] = '.'; 815 } 816 817 /** 818 * Internally created view that has the purpose of holding 819 * the views that represent the children of the paragraph 820 * that have been arranged in rows. 821 */ 822 class Row extends BoxView { 823 824 Row(Element elem) { 825 super(elem, View.X_AXIS); 826 } 827 828 /** 829 * This is reimplemented to do nothing since the 830 * paragraph fills in the row with its needed 831 * children. 832 */ 833 protected void loadChildren(ViewFactory f) { 834 } 835 836 /** 837 * Fetches the attributes to use when rendering. This view 838 * isn't directly responsible for an element so it returns 839 * the outer classes attributes. 840 */ 841 public AttributeSet getAttributes() { 842 View p = getParent(); 843 return (p != null) ? p.getAttributes() : null; 844 } 845 846 public float getAlignment(int axis) { 847 if (axis == View.X_AXIS) { 848 switch (justification) { 849 case StyleConstants.ALIGN_LEFT: 850 return 0; 851 case StyleConstants.ALIGN_RIGHT: 852 return 1; 853 case StyleConstants.ALIGN_CENTER: 854 return 0.5f; 855 case StyleConstants.ALIGN_JUSTIFIED: 856 float rv = 0.5f; 857 //if we can justifiy the content always align to 858 //the left. 859 if (isJustifiableDocument()) { 860 rv = 0f; 861 } 862 return rv; 863 } 864 } 865 return super.getAlignment(axis); 866 } 867 868 /** 869 * Provides a mapping from the document model coordinate space 870 * to the coordinate space of the view mapped to it. This is 871 * implemented to let the superclass find the position along 872 * the major axis and the allocation of the row is used 873 * along the minor axis, so that even though the children 874 * are different heights they all get the same caret height. 875 * 876 * @param pos the position to convert 877 * @param a the allocated region to render into 878 * @return the bounding box of the given position 879 * @exception BadLocationException if the given position does not represent a 880 * valid location in the associated document 881 * @see View#modelToView 882 */ 883 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 884 Rectangle r = a.getBounds(); 885 View v = getViewAtPosition(pos, r); 886 if ((v != null) && (!v.getElement().isLeaf())) { 887 // Don't adjust the height if the view represents a branch. 888 return super.modelToView(pos, a, b); 889 } 890 r = a.getBounds(); 891 int height = r.height; 892 int y = r.y; 893 Shape loc = super.modelToView(pos, a, b); 894 Rectangle2D bounds = loc.getBounds2D(); 895 bounds.setRect(bounds.getX(), y, bounds.getWidth(), height); 896 return bounds; 897 } 898 899 /** 900 * Range represented by a row in the paragraph is only 901 * a subset of the total range of the paragraph element. 902 */ 903 public int getStartOffset() { 904 int offs = Integer.MAX_VALUE; 905 int n = getViewCount(); 906 for (int i = 0; i < n; i++) { 907 View v = getView(i); 908 offs = Math.min(offs, v.getStartOffset()); 909 } 910 return offs; 911 } 912 913 public int getEndOffset() { 914 int offs = 0; 915 int n = getViewCount(); 916 for (int i = 0; i < n; i++) { 917 View v = getView(i); 918 offs = Math.max(offs, v.getEndOffset()); 919 } 920 return offs; 921 } 922 923 /** 924 * Perform layout for the minor axis of the box (i.e. the 925 * axis orthogonal to the axis that it represents). The results 926 * of the layout should be placed in the given arrays which represent 927 * the allocations to the children along the minor axis. 928 * <p> 929 * This is implemented to do a baseline layout of the children 930 * by calling BoxView.baselineLayout. 931 * 932 * @param targetSpan the total span given to the view, which 933 * would be used to layout the children. 934 * @param axis the axis being layed out. 935 * @param offsets the offsets from the origin of the view for 936 * each of the child views. This is a return value and is 937 * filled in by the implementation of this method. 938 * @param spans the span of each child view. This is a return 939 * value and is filled in by the implementation of this method. 940 * @return the offset and span for each child view in the 941 * offsets and spans parameters 942 */ 943 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { 944 baselineLayout(targetSpan, axis, offsets, spans); 945 } 946 947 protected SizeRequirements calculateMinorAxisRequirements(int axis, 948 SizeRequirements r) { 949 return baselineRequirements(axis, r); 950 } 951 952 953 private boolean isLastRow() { 954 View parent; 955 return ((parent = getParent()) == null 956 || this == parent.getView(parent.getViewCount() - 1)); 957 } 958 959 private boolean isBrokenRow() { 960 boolean rv = false; 961 int viewsCount = getViewCount(); 962 if (viewsCount > 0) { 963 View lastView = getView(viewsCount - 1); 964 if (lastView.getBreakWeight(X_AXIS, 0, 0) >= 965 ForcedBreakWeight) { 966 rv = true; 967 } 968 } 969 return rv; 970 } 971 972 private boolean isJustifiableDocument() { 973 return (! Boolean.TRUE.equals(getDocument().getProperty( 974 AbstractDocument.I18NProperty))); 975 } 976 977 /** 978 * Whether we need to justify this {@code Row}. 979 * At this time (jdk1.6) we support justification on for non 980 * 18n text. 981 * 982 * @return {@code true} if this {@code Row} should be justified. 983 */ 984 private boolean isJustifyEnabled() { 985 boolean ret = (justification == StyleConstants.ALIGN_JUSTIFIED); 986 987 //no justification for i18n documents 988 ret = ret && isJustifiableDocument(); 989 990 //no justification for the last row 991 ret = ret && ! isLastRow(); 992 993 //no justification for the broken rows 994 ret = ret && ! isBrokenRow(); 995 996 return ret; 997 } 998 999 1000 //Calls super method after setting spaceAddon to 0. 1001 //Justification should not affect MajorAxisRequirements 1002 @Override 1003 protected SizeRequirements calculateMajorAxisRequirements(int axis, 1004 SizeRequirements r) { 1005 int oldJustficationData[] = justificationData; 1006 justificationData = null; 1007 SizeRequirements ret = super.calculateMajorAxisRequirements(axis, r); 1008 if (isJustifyEnabled()) { 1009 justificationData = oldJustficationData; 1010 } 1011 return ret; 1012 } 1013 1014 @Override 1015 protected void layoutMajorAxis(int targetSpan, int axis, 1016 int[] offsets, int[] spans) { 1017 int oldJustficationData[] = justificationData; 1018 justificationData = null; 1019 super.layoutMajorAxis(targetSpan, axis, offsets, spans); 1020 if (! isJustifyEnabled()) { 1021 return; 1022 } 1023 1024 int currentSpan = 0; 1025 for (int span : spans) { 1026 currentSpan += span; 1027 } 1028 if (currentSpan == targetSpan) { 1029 //no need to justify 1030 return; 1031 } 1032 1033 // we justify text by enlarging spaces by the {@code spaceAddon}. 1034 // justification is started to the right of the rightmost TAB. 1035 // leading and trailing spaces are not extendable. 1036 // 1037 // GlyphPainter1 uses 1038 // justificationData 1039 // for all painting and measurement. 1040 1041 int extendableSpaces = 0; 1042 int startJustifiableContent = -1; 1043 int endJustifiableContent = -1; 1044 int lastLeadingSpaces = 0; 1045 1046 int rowStartOffset = getStartOffset(); 1047 int rowEndOffset = getEndOffset(); 1048 int spaceMap[] = new int[rowEndOffset - rowStartOffset]; 1049 Arrays.fill(spaceMap, 0); 1050 for (int i = getViewCount() - 1; i >= 0 ; i--) { 1051 View view = getView(i); 1052 if (view instanceof GlyphView) { 1053 GlyphView.JustificationInfo justificationInfo = 1054 ((GlyphView) view).getJustificationInfo(rowStartOffset); 1055 final int viewStartOffset = view.getStartOffset(); 1056 final int offset = viewStartOffset - rowStartOffset; 1057 for (int j = 0; j < justificationInfo.spaceMap.length(); j++) { 1058 if (justificationInfo.spaceMap.get(j)) { 1059 spaceMap[j + offset] = 1; 1060 } 1061 } 1062 if (startJustifiableContent > 0) { 1063 if (justificationInfo.end >= 0) { 1064 extendableSpaces += justificationInfo.trailingSpaces; 1065 } else { 1066 lastLeadingSpaces += justificationInfo.trailingSpaces; 1067 } 1068 } 1069 if (justificationInfo.start >= 0) { 1070 startJustifiableContent = 1071 justificationInfo.start + viewStartOffset; 1072 extendableSpaces += lastLeadingSpaces; 1073 } 1074 if (justificationInfo.end >= 0 1075 && endJustifiableContent < 0) { 1076 endJustifiableContent = 1077 justificationInfo.end + viewStartOffset; 1078 } 1079 extendableSpaces += justificationInfo.contentSpaces; 1080 lastLeadingSpaces = justificationInfo.leadingSpaces; 1081 if (justificationInfo.hasTab) { 1082 break; 1083 } 1084 } 1085 } 1086 if (extendableSpaces <= 0) { 1087 //there is nothing we can do to justify 1088 return; 1089 } 1090 int adjustment = (targetSpan - currentSpan); 1091 int spaceAddon = (extendableSpaces > 0) 1092 ? adjustment / extendableSpaces 1093 : 0; 1094 int spaceAddonLeftoverEnd = -1; 1095 for (int i = startJustifiableContent - rowStartOffset, 1096 leftover = adjustment - spaceAddon * extendableSpaces; 1097 leftover > 0; 1098 leftover -= spaceMap[i], 1099 i++) { 1100 spaceAddonLeftoverEnd = i; 1101 } 1102 if (spaceAddon > 0 || spaceAddonLeftoverEnd >= 0) { 1103 justificationData = (oldJustficationData != null) 1104 ? oldJustficationData 1105 : new int[END_JUSTIFIABLE + 1]; 1106 justificationData[SPACE_ADDON] = spaceAddon; 1107 justificationData[SPACE_ADDON_LEFTOVER_END] = 1108 spaceAddonLeftoverEnd; 1109 justificationData[START_JUSTIFIABLE] = 1110 startJustifiableContent - rowStartOffset; 1111 justificationData[END_JUSTIFIABLE] = 1112 endJustifiableContent - rowStartOffset; 1113 super.layoutMajorAxis(targetSpan, axis, offsets, spans); 1114 } 1115 } 1116 1117 //for justified row we assume the maximum horizontal span 1118 //is MAX_VALUE. 1119 @Override 1120 public float getMaximumSpan(int axis) { 1121 float ret; 1122 if (View.X_AXIS == axis 1123 && isJustifyEnabled()) { 1124 ret = Float.MAX_VALUE; 1125 } else { 1126 ret = super.getMaximumSpan(axis); 1127 } 1128 return ret; 1129 } 1130 1131 /** 1132 * Fetches the child view index representing the given position in 1133 * the model. 1134 * 1135 * @param pos the position >= 0 1136 * @return index of the view representing the given position, or 1137 * -1 if no view represents that position 1138 */ 1139 protected int getViewIndexAtPosition(int pos) { 1140 // This is expensive, but are views are not necessarily layed 1141 // out in model order. 1142 if(pos < getStartOffset() || pos >= getEndOffset()) 1143 return -1; 1144 for(int counter = getViewCount() - 1; counter >= 0; counter--) { 1145 View v = getView(counter); 1146 if(pos >= v.getStartOffset() && 1147 pos < v.getEndOffset()) { 1148 return counter; 1149 } 1150 } 1151 return -1; 1152 } 1153 1154 /** 1155 * Gets the left inset. 1156 * 1157 * @return the inset 1158 */ 1159 protected short getLeftInset() { 1160 View parentView; 1161 int adjustment = 0; 1162 if ((parentView = getParent()) != null) { //use firstLineIdent for the first row 1163 if (this == parentView.getView(0)) { 1164 adjustment = firstLineIndent; 1165 } 1166 } 1167 return (short)(super.getLeftInset() + adjustment); 1168 } 1169 1170 protected short getBottomInset() { 1171 return (short)(super.getBottomInset() + 1172 ((minorRequest != null) ? minorRequest.preferred : 0) * 1173 lineSpacing); 1174 } 1175 1176 static final int SPACE_ADDON = 0; 1177 static final int SPACE_ADDON_LEFTOVER_END = 1; 1178 static final int START_JUSTIFIABLE = 2; 1179 //this should be the last index in justificationData 1180 static final int END_JUSTIFIABLE = 3; 1181 1182 int justificationData[] = null; 1183 } 1184 1185} 1186