1 /* 2 * Copyright (c) 1998, 2015, 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.awt.*; 28 import java.awt.font.FontRenderContext; 29 import java.awt.geom.Rectangle2D; 30 import java.lang.ref.SoftReference; 31 import javax.swing.event.*; 32 import static javax.swing.text.PlainView.FPMethodArgs.*; 33 import static javax.swing.text.PlainView.getFPMethodOverridden; 34 35 /** 36 * View of plain text (text with only one font and color) 37 * that does line-wrapping. This view expects that its 38 * associated element has child elements that represent 39 * the lines it should be wrapping. It is implemented 40 * as a vertical box that contains logical line views. 41 * The logical line views are nested classes that render 42 * the logical line as multiple physical line if the logical 43 * line is too wide to fit within the allocation. The 44 * line views draw upon the outer class for its state 45 * to reduce their memory requirements. 46 * <p> 47 * The line views do all of their rendering through the 48 * <code>drawLine</code> method which in turn does all of 49 * its rendering through the <code>drawSelectedText</code> 50 * and <code>drawUnselectedText</code> methods. This 51 * enables subclasses to easily specialize the rendering 52 * without concern for the layout aspects. 53 * 54 * @author Timothy Prinzing 55 * @see View 56 */ 57 public class WrappedPlainView extends BoxView implements TabExpander { 58 59 /** 60 * Creates a new WrappedPlainView. Lines will be wrapped 61 * on character boundaries. 62 * 63 * @param elem the element underlying the view 64 */ 65 public WrappedPlainView(Element elem) { 66 this(elem, false); 67 } 68 69 /** 70 * Creates a new WrappedPlainView. Lines can be wrapped on 71 * either character or word boundaries depending upon the 72 * setting of the wordWrap parameter. 73 * 74 * @param elem the element underlying the view 75 * @param wordWrap should lines be wrapped on word boundaries? 76 */ 77 public WrappedPlainView(Element elem, boolean wordWrap) { 78 super(elem, Y_AXIS); 79 this.wordWrap = wordWrap; 80 } 81 82 /** 83 * Returns the tab size set for the document, defaulting to 8. 84 * 85 * @return the tab size 86 */ 87 protected int getTabSize() { 88 Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute); 89 int size = (i != null) ? i.intValue() : 8; 90 return size; 91 } 92 93 /** 94 * Renders a line of text, suppressing whitespace at the end 95 * and expanding any tabs. This is implemented to make calls 96 * to the methods <code>drawUnselectedText</code> and 97 * <code>drawSelectedText</code> so that the way selected and 98 * unselected text are rendered can be customized. 99 * 100 * @param p0 the starting document location to use >= 0 101 * @param p1 the ending document location to use >= p1 102 * @param g the graphics context 103 * @param x the starting X position >= 0 104 * @param y the starting Y position >= 0 105 * @see #drawUnselectedText 106 * @see #drawSelectedText 107 * 108 * @deprecated replaced by 109 * {@link #drawLine(int, int, Graphics2D, float, float)} 110 */ 111 @Deprecated(since = "9") 112 protected void drawLine(int p0, int p1, Graphics g, int x, int y) { 113 drawLineImpl(p0, p1, g, x, y, false); 114 } 115 116 private void drawLineImpl(int p0, int p1, Graphics g, float x, float y, 117 boolean useFPAPI) { 118 Element lineMap = getElement(); 119 Element line = lineMap.getElement(lineMap.getElementIndex(p0)); 120 Element elem; 121 122 try { 123 if (line.isLeaf()) { 124 drawText(line, p0, p1, g, x, y); 125 } else { 126 // this line contains the composed text. 127 int idx = line.getElementIndex(p0); 128 int lastIdx = line.getElementIndex(p1); 129 for(; idx <= lastIdx; idx++) { 130 elem = line.getElement(idx); 131 int start = Math.max(elem.getStartOffset(), p0); 132 int end = Math.min(elem.getEndOffset(), p1); 133 x = drawText(elem, start, end, g, x, y); 134 } 135 } 136 } catch (BadLocationException e) { 137 throw new StateInvariantError("Can't render: " + p0 + "," + p1); 138 } 139 } 140 141 /** 142 * Renders a line of text, suppressing whitespace at the end 143 * and expanding any tabs. This is implemented to make calls 144 * to the methods <code>drawUnselectedText</code> and 145 * <code>drawSelectedText</code> so that the way selected and 146 * unselected text are rendered can be customized. 147 * 148 * @param p0 the starting document location to use >= 0 149 * @param p1 the ending document location to use >= p1 150 * @param g the graphics context 151 * @param x the starting X position >= 0 152 * @param y the starting Y position >= 0 153 * @see #drawUnselectedText 154 * @see #drawSelectedText 155 * 156 * @since 9 157 */ 158 protected void drawLine(int p0, int p1, Graphics2D g, float x, float y) { 159 drawLineImpl(p0, p1, g, x, y, true); 160 } 161 162 private float drawText(Element elem, int p0, int p1, Graphics g, 163 float x, float y) 164 throws BadLocationException 165 { 166 p1 = Math.min(getDocument().getLength(), p1); 167 AttributeSet attr = elem.getAttributes(); 168 169 if (Utilities.isComposedTextAttributeDefined(attr)) { 170 g.setColor(unselected); 171 x = Utilities.drawComposedText(this, attr, g, x, y, 172 p0-elem.getStartOffset(), 173 p1-elem.getStartOffset()); 174 } else { 175 if (sel0 == sel1 || selected == unselected) { 176 // no selection, or it is invisible 177 x = callDrawUnselectedText(g, x, y, p0, p1); 178 } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) { 179 x = callDrawSelectedText(g, x, y, p0, p1); 180 } else if (sel0 >= p0 && sel0 <= p1) { 181 if (sel1 >= p0 && sel1 <= p1) { 182 x = callDrawUnselectedText(g, x, y, p0, sel0); 183 x = callDrawSelectedText(g, x, y, sel0, sel1); 184 x = callDrawUnselectedText(g, x, y, sel1, p1); 185 } else { 186 x = callDrawUnselectedText(g, x, y, p0, sel0); 187 x = callDrawSelectedText(g, x, y, sel0, p1); 188 } 189 } else if (sel1 >= p0 && sel1 <= p1) { 190 x = callDrawSelectedText(g, x, y, p0, sel1); 191 x = callDrawUnselectedText(g, x, y, sel1, p1); 192 } else { 193 x = callDrawUnselectedText(g, x, y, p0, p1); 194 } 195 } 196 197 return x; 198 } 199 200 /** 201 * Renders the given range in the model as normal unselected 202 * text. 203 * 204 * @param g the graphics context 205 * @param x the starting X coordinate >= 0 206 * @param y the starting Y coordinate >= 0 207 * @param p0 the beginning position in the model >= 0 208 * @param p1 the ending position in the model >= p0 209 * @return the X location of the end of the range >= 0 210 * @exception BadLocationException if the range is invalid 211 * 212 * @deprecated replaced by 213 * {@link #drawUnselectedText(Graphics2D, float, float, int, int)} 214 */ 215 @Deprecated(since = "9") 216 protected int drawUnselectedText(Graphics g, int x, int y, 217 int p0, int p1) throws BadLocationException 218 { 219 return (int) drawUnselectedTextImpl(g, x, y, p0, p1, false); 220 } 221 222 private float callDrawUnselectedText(Graphics g, float x, float y, 223 int p0, int p1) 224 throws BadLocationException 225 { 226 return drawUnselectedTextOverridden && g instanceof Graphics2D 227 ? drawUnselectedText((Graphics2D) g, x, y, p0, p1) 228 : drawUnselectedText(g, (int) x, (int) y, p0, p1); 229 } 230 231 private float drawUnselectedTextImpl(Graphics g, float x, float y, 232 int p0, int p1, boolean useFPAPI) 233 throws BadLocationException 234 { 235 g.setColor(unselected); 236 Document doc = getDocument(); 237 Segment segment = SegmentCache.getSharedSegment(); 238 doc.getText(p0, p1 - p0, segment); 239 float ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0, 240 null, useFPAPI); 241 SegmentCache.releaseSharedSegment(segment); 242 return ret; 243 } 244 245 /** 246 * Renders the given range in the model as normal unselected 247 * text. 248 * 249 * @param g the graphics context 250 * @param x the starting X coordinate >= 0 251 * @param y the starting Y coordinate >= 0 252 * @param p0 the beginning position in the model >= 0 253 * @param p1 the ending position in the model >= p0 254 * @return the X location of the end of the range >= 0 255 * @exception BadLocationException if the range is invalid 256 * 257 * @since 9 258 */ 259 protected float drawUnselectedText(Graphics2D g, float x, float y, 260 int p0, int p1) throws BadLocationException { 261 return drawUnselectedTextImpl(g, x, y, p0, p1, true); 262 } 263 /** 264 * Renders the given range in the model as selected text. This 265 * is implemented to render the text in the color specified in 266 * the hosting component. It assumes the highlighter will render 267 * the selected background. 268 * 269 * @param g the graphics context 270 * @param x the starting X coordinate >= 0 271 * @param y the starting Y coordinate >= 0 272 * @param p0 the beginning position in the model >= 0 273 * @param p1 the ending position in the model >= p0 274 * @return the location of the end of the range. 275 * @exception BadLocationException if the range is invalid 276 * 277 * @deprecated replaced by 278 * {@link #drawSelectedText(Graphics2D, float, float, int, int)} 279 */ 280 @Deprecated(since = "9") 281 protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) 282 throws BadLocationException 283 { 284 return (int) drawSelectedTextImpl(g, x, y, p0, p1, false); 285 } 286 287 private float callDrawSelectedText(Graphics g, float x, float y, 288 int p0, int p1) 289 throws BadLocationException 290 { 291 return drawSelectedTextOverridden && g instanceof Graphics2D 292 ? drawSelectedText((Graphics2D) g, x, y, p0, p1) 293 : drawSelectedText(g, (int) x, (int) y, p0, p1); 294 } 295 296 private float drawSelectedTextImpl(Graphics g, float x, float y, 297 int p0, int p1, 298 boolean useFPAPI) 299 throws BadLocationException 300 { 301 g.setColor(selected); 302 Document doc = getDocument(); 303 Segment segment = SegmentCache.getSharedSegment(); 304 doc.getText(p0, p1 - p0, segment); 305 float ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0, 306 null, useFPAPI); 307 SegmentCache.releaseSharedSegment(segment); 308 return ret; 309 } 310 311 /** 312 * Renders the given range in the model as selected text. This 313 * is implemented to render the text in the color specified in 314 * the hosting component. It assumes the highlighter will render 315 * the selected background. 316 * 317 * @param g the graphics context 318 * @param x the starting X coordinate >= 0 319 * @param y the starting Y coordinate >= 0 320 * @param p0 the beginning position in the model >= 0 321 * @param p1 the ending position in the model >= p0 322 * @return the location of the end of the range. 323 * @exception BadLocationException if the range is invalid 324 * 325 * @since 9 326 */ 327 protected float drawSelectedText(Graphics2D g, float x, float y, 328 int p0, int p1) throws BadLocationException { 329 return drawSelectedTextImpl(g, x, y, p0, p1, true); 330 } 331 /** 332 * Gives access to a buffer that can be used to fetch 333 * text from the associated document. 334 * 335 * @return the buffer 336 */ 337 protected final Segment getLineBuffer() { 338 if (lineBuffer == null) { 339 lineBuffer = new Segment(); 340 } 341 return lineBuffer; 342 } 343 344 /** 345 * This is called by the nested wrapped line 346 * views to determine the break location. This can 347 * be reimplemented to alter the breaking behavior. 348 * It will either break at word or character boundaries 349 * depending upon the break argument given at 350 * construction. 351 * @param p0 the starting document location 352 * @param p1 the ending document location to use 353 * @return the break position 354 */ 355 @SuppressWarnings("deprecation") 356 protected int calculateBreakPosition(int p0, int p1) { 357 int p; 358 Segment segment = SegmentCache.getSharedSegment(); 359 loadText(segment, p0, p1); 360 int currentWidth = getWidth(); 361 if (wordWrap) { 362 p = p0 + Utilities.getBreakLocation(segment, metrics, 363 tabBase, tabBase + currentWidth, 364 this, p0); 365 } else { 366 p = p0 + Utilities.getTabbedTextOffset(segment, metrics, 367 tabBase, tabBase + currentWidth, 368 this, p0, false); 369 } 370 SegmentCache.releaseSharedSegment(segment); 371 return p; 372 } 373 374 /** 375 * Loads all of the children to initialize the view. 376 * This is called by the <code>setParent</code> method. 377 * Subclasses can reimplement this to initialize their 378 * child views in a different manner. The default 379 * implementation creates a child view for each 380 * child element. 381 * 382 * @param f the view factory 383 */ 384 protected void loadChildren(ViewFactory f) { 385 Element e = getElement(); 386 int n = e.getElementCount(); 387 if (n > 0) { 388 View[] added = new View[n]; 389 for (int i = 0; i < n; i++) { 390 added[i] = new WrappedLine(e.getElement(i)); 391 } 392 replace(0, 0, added); 393 } 394 } 395 396 /** 397 * Update the child views in response to a 398 * document event. 399 */ 400 void updateChildren(DocumentEvent e, Shape a) { 401 Element elem = getElement(); 402 DocumentEvent.ElementChange ec = e.getChange(elem); 403 if (ec != null) { 404 // the structure of this element changed. 405 Element[] removedElems = ec.getChildrenRemoved(); 406 Element[] addedElems = ec.getChildrenAdded(); 407 View[] added = new View[addedElems.length]; 408 for (int i = 0; i < addedElems.length; i++) { 409 added[i] = new WrappedLine(addedElems[i]); 410 } 411 replace(ec.getIndex(), removedElems.length, added); 412 413 // should damge a little more intelligently. 414 if (a != null) { 415 preferenceChanged(null, true, true); 416 getContainer().repaint(); 417 } 418 } 419 420 // update font metrics which may be used by the child views 421 updateMetrics(); 422 } 423 424 /** 425 * Load the text buffer with the given range 426 * of text. This is used by the fragments 427 * broken off of this view as well as this 428 * view itself. 429 */ 430 final void loadText(Segment segment, int p0, int p1) { 431 try { 432 Document doc = getDocument(); 433 doc.getText(p0, p1 - p0, segment); 434 } catch (BadLocationException bl) { 435 throw new StateInvariantError("Can't get line text"); 436 } 437 } 438 439 final void updateMetrics() { 440 Component host = getContainer(); 441 Font f = host.getFont(); 442 metrics = host.getFontMetrics(f); 443 if (useFloatingPointAPI) { 444 FontRenderContext frc = metrics.getFontRenderContext(); 445 float tabWidth = (float) f.getStringBounds("m", frc).getWidth(); 446 tabSize = getTabSize() * tabWidth; 447 } else { 448 tabSize = getTabSize() * metrics.charWidth('m'); 449 } 450 } 451 452 // --- TabExpander methods ------------------------------------------ 453 454 /** 455 * Returns the next tab stop position after a given reference position. 456 * This implementation does not support things like centering so it 457 * ignores the tabOffset argument. 458 * 459 * @param x the current position >= 0 460 * @param tabOffset the position within the text stream 461 * that the tab occurred at >= 0. 462 * @return the tab stop, measured in points >= 0 463 */ 464 public float nextTabStop(float x, int tabOffset) { 465 if (tabSize == 0) 466 return x; 467 float ntabs = (x - tabBase) / tabSize; 468 return tabBase + ((ntabs + 1) * tabSize); 469 } 470 471 472 // --- View methods ------------------------------------- 473 474 /** 475 * Renders using the given rendering surface and area 476 * on that surface. This is implemented to stash the 477 * selection positions, selection colors, and font 478 * metrics for the nested lines to use. 479 * 480 * @param g the rendering surface to use 481 * @param a the allocated region to render into 482 * 483 * @see View#paint 484 */ 485 public void paint(Graphics g, Shape a) { 486 Rectangle alloc = (Rectangle) a; 487 tabBase = alloc.x; 488 JTextComponent host = (JTextComponent) getContainer(); 489 sel0 = host.getSelectionStart(); 490 sel1 = host.getSelectionEnd(); 491 unselected = (host.isEnabled()) ? 492 host.getForeground() : host.getDisabledTextColor(); 493 Caret c = host.getCaret(); 494 selected = c.isSelectionVisible() && host.getHighlighter() != null ? 495 host.getSelectedTextColor() : unselected; 496 g.setFont(host.getFont()); 497 498 // superclass paints the children 499 super.paint(g, a); 500 } 501 502 /** 503 * Sets the size of the view. This should cause 504 * layout of the view along the given axis, if it 505 * has any layout duties. 506 * 507 * @param width the width >= 0 508 * @param height the height >= 0 509 */ 510 public void setSize(float width, float height) { 511 updateMetrics(); 512 if ((int) width != getWidth()) { 513 // invalidate the view itself since the desired widths 514 // of the children will be based upon this views width. 515 preferenceChanged(null, true, true); 516 widthChanging = true; 517 } 518 super.setSize(width, height); 519 widthChanging = false; 520 } 521 522 /** 523 * Determines the preferred span for this view along an 524 * axis. This is implemented to provide the superclass 525 * behavior after first making sure that the current font 526 * metrics are cached (for the nested lines which use 527 * the metrics to determine the height of the potentially 528 * wrapped lines). 529 * 530 * @param axis may be either View.X_AXIS or View.Y_AXIS 531 * @return the span the view would like to be rendered into. 532 * Typically the view is told to render into the span 533 * that is returned, although there is no guarantee. 534 * The parent may choose to resize or break the view. 535 * @see View#getPreferredSpan 536 */ 537 public float getPreferredSpan(int axis) { 538 updateMetrics(); 539 return super.getPreferredSpan(axis); 540 } 541 542 /** 543 * Determines the minimum span for this view along an 544 * axis. This is implemented to provide the superclass 545 * behavior after first making sure that the current font 546 * metrics are cached (for the nested lines which use 547 * the metrics to determine the height of the potentially 548 * wrapped lines). 549 * 550 * @param axis may be either View.X_AXIS or View.Y_AXIS 551 * @return the span the view would like to be rendered into. 552 * Typically the view is told to render into the span 553 * that is returned, although there is no guarantee. 554 * The parent may choose to resize or break the view. 555 * @see View#getMinimumSpan 556 */ 557 public float getMinimumSpan(int axis) { 558 updateMetrics(); 559 return super.getMinimumSpan(axis); 560 } 561 562 /** 563 * Determines the maximum span for this view along an 564 * axis. This is implemented to provide the superclass 565 * behavior after first making sure that the current font 566 * metrics are cached (for the nested lines which use 567 * the metrics to determine the height of the potentially 568 * wrapped lines). 569 * 570 * @param axis may be either View.X_AXIS or View.Y_AXIS 571 * @return the span the view would like to be rendered into. 572 * Typically the view is told to render into the span 573 * that is returned, although there is no guarantee. 574 * The parent may choose to resize or break the view. 575 * @see View#getMaximumSpan 576 */ 577 public float getMaximumSpan(int axis) { 578 updateMetrics(); 579 return super.getMaximumSpan(axis); 580 } 581 582 /** 583 * Gives notification that something was inserted into the 584 * document in a location that this view is responsible for. 585 * This is implemented to simply update the children. 586 * 587 * @param e the change information from the associated document 588 * @param a the current allocation of the view 589 * @param f the factory to use to rebuild if the view has children 590 * @see View#insertUpdate 591 */ 592 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 593 updateChildren(e, a); 594 595 Rectangle alloc = ((a != null) && isAllocationValid()) ? 596 getInsideAllocation(a) : null; 597 int pos = e.getOffset(); 598 View v = getViewAtPosition(pos, alloc); 599 if (v != null) { 600 v.insertUpdate(e, alloc, f); 601 } 602 } 603 604 /** 605 * Gives notification that something was removed from the 606 * document in a location that this view is responsible for. 607 * This is implemented to simply update the children. 608 * 609 * @param e the change information from the associated document 610 * @param a the current allocation of the view 611 * @param f the factory to use to rebuild if the view has children 612 * @see View#removeUpdate 613 */ 614 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 615 updateChildren(e, a); 616 617 Rectangle alloc = ((a != null) && isAllocationValid()) ? 618 getInsideAllocation(a) : null; 619 int pos = e.getOffset(); 620 View v = getViewAtPosition(pos, alloc); 621 if (v != null) { 622 v.removeUpdate(e, alloc, f); 623 } 624 } 625 626 /** 627 * Gives notification from the document that attributes were changed 628 * in a location that this view is responsible for. 629 * 630 * @param e the change information from the associated document 631 * @param a the current allocation of the view 632 * @param f the factory to use to rebuild if the view has children 633 * @see View#changedUpdate 634 */ 635 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { 636 updateChildren(e, a); 637 } 638 639 // --- variables ------------------------------------------- 640 641 FontMetrics metrics; 642 Segment lineBuffer; 643 boolean widthChanging; 644 int tabBase; 645 float tabSize; 646 boolean wordWrap; 647 648 int sel0; 649 int sel1; 650 Color unselected; 651 Color selected; 652 653 654 /** 655 * Simple view of a line that wraps if it doesn't 656 * fit withing the horizontal space allocated. 657 * This class tries to be lightweight by carrying little 658 * state of it's own and sharing the state of the outer class 659 * with it's sibblings. 660 */ 661 class WrappedLine extends View { 662 663 WrappedLine(Element elem) { 664 super(elem); 665 lineCount = -1; 666 } 667 668 /** 669 * Determines the preferred span for this view along an 670 * axis. 671 * 672 * @param axis may be either X_AXIS or Y_AXIS 673 * @return the span the view would like to be rendered into. 674 * Typically the view is told to render into the span 675 * that is returned, although there is no guarantee. 676 * The parent may choose to resize or break the view. 677 * @see View#getPreferredSpan 678 */ 679 public float getPreferredSpan(int axis) { 680 switch (axis) { 681 case View.X_AXIS: 682 float width = getWidth(); 683 if (width == Integer.MAX_VALUE) { 684 // We have been initially set to MAX_VALUE, but we don't 685 // want this as our preferred. 686 return 100f; 687 } 688 return width; 689 case View.Y_AXIS: 690 if (lineCount < 0 || widthChanging) { 691 breakLines(getStartOffset()); 692 } 693 return lineCount * metrics.getHeight(); 694 default: 695 throw new IllegalArgumentException("Invalid axis: " + axis); 696 } 697 } 698 699 /** 700 * Renders using the given rendering surface and area on that 701 * surface. The view may need to do layout and create child 702 * views to enable itself to render into the given allocation. 703 * 704 * @param g the rendering surface to use 705 * @param a the allocated region to render into 706 * @see View#paint 707 */ 708 public void paint(Graphics g, Shape a) { 709 Rectangle alloc = (Rectangle) a; 710 int y = alloc.y + metrics.getAscent(); 711 int x = alloc.x; 712 713 JTextComponent host = (JTextComponent)getContainer(); 714 Highlighter h = host.getHighlighter(); 715 LayeredHighlighter dh = (h instanceof LayeredHighlighter) ? 716 (LayeredHighlighter)h : null; 717 718 int start = getStartOffset(); 719 int end = getEndOffset(); 720 int p0 = start; 721 int[] lineEnds = getLineEnds(); 722 boolean useDrawLineFP = drawLineOverridden && g instanceof Graphics2D; 723 for (int i = 0; i < lineCount; i++) { 724 int p1 = (lineEnds == null) ? end : 725 start + lineEnds[i]; 726 if (dh != null) { 727 int hOffset = (p1 == end) 728 ? (p1 - 1) 729 : p1; 730 dh.paintLayeredHighlights(g, p0, hOffset, a, host, this); 731 } 732 if (useDrawLineFP) { 733 drawLine(p0, p1, (Graphics2D) g, (float) x, (float) y); 734 } else { 735 drawLine(p0, p1, g, x, y); 736 } 737 p0 = p1; 738 y += metrics.getHeight(); 739 } 740 } 741 742 /** 743 * Provides a mapping from the document model coordinate space 744 * to the coordinate space of the view mapped to it. 745 * 746 * @param pos the position to convert 747 * @param a the allocated region to render into 748 * @return the bounding box of the given position is returned 749 * @exception BadLocationException if the given position does not represent a 750 * valid location in the associated document 751 * @see View#modelToView 752 */ 753 public Shape modelToView(int pos, Shape a, Position.Bias b) 754 throws BadLocationException { 755 Rectangle alloc = a.getBounds(); 756 alloc.height = metrics.getHeight(); 757 alloc.width = 1; 758 759 int p0 = getStartOffset(); 760 if (pos < p0 || pos > getEndOffset()) { 761 throw new BadLocationException("Position out of range", pos); 762 } 763 764 int testP = (b == Position.Bias.Forward) ? pos : 765 Math.max(p0, pos - 1); 766 int line = 0; 767 int[] lineEnds = getLineEnds(); 768 if (lineEnds != null) { 769 line = findLine(testP - p0); 770 if (line > 0) { 771 p0 += lineEnds[line - 1]; 772 } 773 alloc.y += alloc.height * line; 774 } 775 776 if (pos > p0) { 777 Segment segment = SegmentCache.getSharedSegment(); 778 loadText(segment, p0, pos); 779 float x = alloc.x; 780 x += Utilities.getTabbedTextWidth(segment, metrics, x, 781 WrappedPlainView.this, p0); 782 SegmentCache.releaseSharedSegment(segment); 783 return new Rectangle2D.Float(x, alloc.y, alloc.width, alloc.height); 784 } 785 return alloc; 786 } 787 788 /** 789 * Provides a mapping from the view coordinate space to the logical 790 * coordinate space of the model. 791 * 792 * @param fx the X coordinate 793 * @param fy the Y coordinate 794 * @param a the allocated region to render into 795 * @return the location within the model that best represents the 796 * given point in the view 797 * @see View#viewToModel 798 */ 799 @SuppressWarnings("deprecation") 800 public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { 801 // PENDING(prinz) implement bias properly 802 bias[0] = Position.Bias.Forward; 803 804 Rectangle alloc = (Rectangle) a; 805 int x = (int) fx; 806 int y = (int) fy; 807 if (y < alloc.y) { 808 // above the area covered by this icon, so the position 809 // is assumed to be the start of the coverage for this view. 810 return getStartOffset(); 811 } else if (y > alloc.y + alloc.height) { 812 // below the area covered by this icon, so the position 813 // is assumed to be the end of the coverage for this view. 814 return getEndOffset() - 1; 815 } else { 816 // positioned within the coverage of this view vertically, 817 // so we figure out which line the point corresponds to. 818 // if the line is greater than the number of lines contained, then 819 // simply use the last line as it represents the last possible place 820 // we can position to. 821 alloc.height = metrics.getHeight(); 822 int line = (alloc.height > 0 ? 823 (y - alloc.y) / alloc.height : lineCount - 1); 824 if (line >= lineCount) { 825 return getEndOffset() - 1; 826 } else { 827 int p0 = getStartOffset(); 828 int p1; 829 if (lineCount == 1) { 830 p1 = getEndOffset(); 831 } else { 832 int[] lineEnds = getLineEnds(); 833 p1 = p0 + lineEnds[line]; 834 if (line > 0) { 835 p0 += lineEnds[line - 1]; 836 } 837 } 838 839 if (x < alloc.x) { 840 // point is to the left of the line 841 return p0; 842 } else if (x > alloc.x + alloc.width) { 843 // point is to the right of the line 844 return p1 - 1; 845 } else { 846 // Determine the offset into the text 847 Segment segment = SegmentCache.getSharedSegment(); 848 loadText(segment, p0, p1); 849 int n = Utilities.getTabbedTextOffset(segment, metrics, 850 alloc.x, x, 851 WrappedPlainView.this, p0); 852 SegmentCache.releaseSharedSegment(segment); 853 return Math.min(p0 + n, p1 - 1); 854 } 855 } 856 } 857 } 858 859 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 860 update(e, a); 861 } 862 863 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 864 update(e, a); 865 } 866 867 private void update(DocumentEvent ev, Shape a) { 868 int oldCount = lineCount; 869 breakLines(ev.getOffset()); 870 if (oldCount != lineCount) { 871 WrappedPlainView.this.preferenceChanged(this, false, true); 872 // have to repaint any views after the receiver. 873 getContainer().repaint(); 874 } else if (a != null) { 875 Component c = getContainer(); 876 Rectangle alloc = (Rectangle) a; 877 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height); 878 } 879 } 880 881 /** 882 * Returns line cache. If the cache was GC'ed, recreates it. 883 * If there's no cache, returns null 884 */ 885 final int[] getLineEnds() { 886 if (lineCache == null) { 887 return null; 888 } else { 889 int[] lineEnds = lineCache.get(); 890 if (lineEnds == null) { 891 // Cache was GC'ed, so rebuild it 892 return breakLines(getStartOffset()); 893 } else { 894 return lineEnds; 895 } 896 } 897 } 898 899 /** 900 * Creates line cache if text breaks into more than one physical line. 901 * @param startPos position to start breaking from 902 * @return the cache created, ot null if text breaks into one line 903 */ 904 final int[] breakLines(int startPos) { 905 int[] lineEnds = (lineCache == null) ? null : lineCache.get(); 906 int[] oldLineEnds = lineEnds; 907 int start = getStartOffset(); 908 int lineIndex = 0; 909 if (lineEnds != null) { 910 lineIndex = findLine(startPos - start); 911 if (lineIndex > 0) { 912 lineIndex--; 913 } 914 } 915 916 int p0 = (lineIndex == 0) ? start : start + lineEnds[lineIndex - 1]; 917 int p1 = getEndOffset(); 918 while (p0 < p1) { 919 int p = calculateBreakPosition(p0, p1); 920 p0 = (p == p0) ? ++p : p; // 4410243 921 922 if (lineIndex == 0 && p0 >= p1) { 923 // do not use cache if there's only one line 924 lineCache = null; 925 lineEnds = null; 926 lineIndex = 1; 927 break; 928 } else if (lineEnds == null || lineIndex >= lineEnds.length) { 929 // we have 2+ lines, and the cache is not big enough 930 // we try to estimate total number of lines 931 double growFactor = ((double)(p1 - start) / (p0 - start)); 932 int newSize = (int)Math.ceil((lineIndex + 1) * growFactor); 933 newSize = Math.max(newSize, lineIndex + 2); 934 int[] tmp = new int[newSize]; 935 if (lineEnds != null) { 936 System.arraycopy(lineEnds, 0, tmp, 0, lineIndex); 937 } 938 lineEnds = tmp; 939 } 940 lineEnds[lineIndex++] = p0 - start; 941 } 942 943 lineCount = lineIndex; 944 if (lineCount > 1) { 945 // check if the cache is too big 946 int maxCapacity = lineCount + lineCount / 3; 947 if (lineEnds.length > maxCapacity) { 948 int[] tmp = new int[maxCapacity]; 949 System.arraycopy(lineEnds, 0, tmp, 0, lineCount); 950 lineEnds = tmp; 951 } 952 } 953 954 if (lineEnds != null && lineEnds != oldLineEnds) { 955 lineCache = new SoftReference<int[]>(lineEnds); 956 } 957 return lineEnds; 958 } 959 960 /** 961 * Binary search in the cache for line containing specified offset 962 * (which is relative to the beginning of the view). This method 963 * assumes that cache exists. 964 */ 965 private int findLine(int offset) { 966 int[] lineEnds = lineCache.get(); 967 if (offset < lineEnds[0]) { 968 return 0; 969 } else if (offset > lineEnds[lineCount - 1]) { 970 return lineCount; 971 } else { 972 return findLine(lineEnds, offset, 0, lineCount - 1); 973 } 974 } 975 976 private int findLine(int[] array, int offset, int min, int max) { 977 if (max - min <= 1) { 978 return max; 979 } else { 980 int mid = (max + min) / 2; 981 return (offset < array[mid]) ? 982 findLine(array, offset, min, mid) : 983 findLine(array, offset, mid, max); 984 } 985 } 986 987 int lineCount; 988 SoftReference<int[]> lineCache = null; 989 } 990 991 private final boolean drawLineOverridden = 992 getFPMethodOverridden(getClass(), "drawLine", IIGNN); 993 private final boolean drawSelectedTextOverridden = 994 getFPMethodOverridden(getClass(), "drawSelectedText", GNNII); 995 private final boolean drawUnselectedTextOverridden = 996 getFPMethodOverridden(getClass(), "drawUnselectedText", GNNII); 997 private final boolean useFloatingPointAPI = 998 drawUnselectedTextOverridden || drawSelectedTextOverridden; 999 }