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