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 (float)tabBase, 364 (float)(tabBase + currentWidth), 365 this, p0); 366 } else { 367 p = p0 + Utilities.getTabbedTextOffset(segment, metrics, 368 (float)tabBase, 369 (float)(tabBase + currentWidth), 370 this, p0, false); 371 } 372 SegmentCache.releaseSharedSegment(segment); 373 return p; 374 } 375 376 /** 377 * Loads all of the children to initialize the view. 378 * This is called by the <code>setParent</code> method. 379 * Subclasses can reimplement this to initialize their 380 * child views in a different manner. The default 381 * implementation creates a child view for each 382 * child element. 383 * 384 * @param f the view factory 385 */ 386 protected void loadChildren(ViewFactory f) { 387 Element e = getElement(); 388 int n = e.getElementCount(); 389 if (n > 0) { 390 View[] added = new View[n]; 391 for (int i = 0; i < n; i++) { 392 added[i] = new WrappedLine(e.getElement(i)); 393 } 394 replace(0, 0, added); 395 } 396 } 397 398 /** 399 * Update the child views in response to a 400 * document event. 401 */ 402 void updateChildren(DocumentEvent e, Shape a) { 403 Element elem = getElement(); 404 DocumentEvent.ElementChange ec = e.getChange(elem); 405 if (ec != null) { 406 // the structure of this element changed. 407 Element[] removedElems = ec.getChildrenRemoved(); 408 Element[] addedElems = ec.getChildrenAdded(); 409 View[] added = new View[addedElems.length]; 410 for (int i = 0; i < addedElems.length; i++) { 411 added[i] = new WrappedLine(addedElems[i]); 412 } 413 replace(ec.getIndex(), removedElems.length, added); 414 415 // should damge a little more intelligently. 416 if (a != null) { 417 preferenceChanged(null, true, true); 418 getContainer().repaint(); 419 } 420 } 421 422 // update font metrics which may be used by the child views 423 updateMetrics(); 424 } 425 426 /** 427 * Load the text buffer with the given range 428 * of text. This is used by the fragments 429 * broken off of this view as well as this 430 * view itself. 431 */ 432 final void loadText(Segment segment, int p0, int p1) { 433 try { 434 Document doc = getDocument(); 435 doc.getText(p0, p1 - p0, segment); 436 } catch (BadLocationException bl) { 437 throw new StateInvariantError("Can't get line text"); 438 } 439 } 440 441 final void updateMetrics() { 442 Component host = getContainer(); 443 Font f = host.getFont(); 444 metrics = host.getFontMetrics(f); 445 if (useFloatingPointAPI) { 446 FontRenderContext frc = metrics.getFontRenderContext(); 447 float tabWidth = (float) f.getStringBounds("m", frc).getWidth(); 448 tabSize = getTabSize() * tabWidth; 449 } else { 450 tabSize = getTabSize() * metrics.charWidth('m'); 451 } 452 } 453 454 // --- TabExpander methods ------------------------------------------ 455 456 /** 457 * Returns the next tab stop position after a given reference position. 458 * This implementation does not support things like centering so it 459 * ignores the tabOffset argument. 460 * 461 * @param x the current position >= 0 462 * @param tabOffset the position within the text stream 463 * that the tab occurred at >= 0. 464 * @return the tab stop, measured in points >= 0 465 */ 466 public float nextTabStop(float x, int tabOffset) { 467 if (tabSize == 0) 468 return x; 469 float ntabs = (x - tabBase) / tabSize; 470 return tabBase + ((ntabs + 1) * tabSize); 471 } 472 473 474 // --- View methods ------------------------------------- 475 476 /** 477 * Renders using the given rendering surface and area 478 * on that surface. This is implemented to stash the 479 * selection positions, selection colors, and font 480 * metrics for the nested lines to use. 481 * 482 * @param g the rendering surface to use 483 * @param a the allocated region to render into 484 * 485 * @see View#paint 486 */ 487 public void paint(Graphics g, Shape a) { 488 Rectangle alloc = (Rectangle) a; 489 tabBase = alloc.x; 490 JTextComponent host = (JTextComponent) getContainer(); 491 sel0 = host.getSelectionStart(); 492 sel1 = host.getSelectionEnd(); 493 unselected = (host.isEnabled()) ? 494 host.getForeground() : host.getDisabledTextColor(); 495 Caret c = host.getCaret(); 496 selected = c.isSelectionVisible() && host.getHighlighter() != null ? 497 host.getSelectedTextColor() : unselected; 498 g.setFont(host.getFont()); 499 500 // superclass paints the children 501 super.paint(g, a); 502 } 503 504 /** 505 * Sets the size of the view. This should cause 506 * layout of the view along the given axis, if it 507 * has any layout duties. 508 * 509 * @param width the width >= 0 510 * @param height the height >= 0 511 */ 512 public void setSize(float width, float height) { 513 updateMetrics(); 514 if ((int) width != getWidth()) { 515 // invalidate the view itself since the desired widths 516 // of the children will be based upon this views width. 517 preferenceChanged(null, true, true); 518 widthChanging = true; 519 } 520 super.setSize(width, height); 521 widthChanging = false; 522 } 523 524 /** 525 * Determines the preferred span for this view along an 526 * axis. This is implemented to provide the superclass 527 * behavior after first making sure that the current font 528 * metrics are cached (for the nested lines which use 529 * the metrics to determine the height of the potentially 530 * wrapped lines). 531 * 532 * @param axis may be either View.X_AXIS or View.Y_AXIS 533 * @return the span the view would like to be rendered into. 534 * Typically the view is told to render into the span 535 * that is returned, although there is no guarantee. 536 * The parent may choose to resize or break the view. 537 * @see View#getPreferredSpan 538 */ 539 public float getPreferredSpan(int axis) { 540 updateMetrics(); 541 return super.getPreferredSpan(axis); 542 } 543 544 /** 545 * Determines the minimum span for this view along an 546 * axis. This is implemented to provide the superclass 547 * behavior after first making sure that the current font 548 * metrics are cached (for the nested lines which use 549 * the metrics to determine the height of the potentially 550 * wrapped lines). 551 * 552 * @param axis may be either View.X_AXIS or View.Y_AXIS 553 * @return the span the view would like to be rendered into. 554 * Typically the view is told to render into the span 555 * that is returned, although there is no guarantee. 556 * The parent may choose to resize or break the view. 557 * @see View#getMinimumSpan 558 */ 559 public float getMinimumSpan(int axis) { 560 updateMetrics(); 561 return super.getMinimumSpan(axis); 562 } 563 564 /** 565 * Determines the maximum span for this view along an 566 * axis. This is implemented to provide the superclass 567 * behavior after first making sure that the current font 568 * metrics are cached (for the nested lines which use 569 * the metrics to determine the height of the potentially 570 * wrapped lines). 571 * 572 * @param axis may be either View.X_AXIS or View.Y_AXIS 573 * @return the span the view would like to be rendered into. 574 * Typically the view is told to render into the span 575 * that is returned, although there is no guarantee. 576 * The parent may choose to resize or break the view. 577 * @see View#getMaximumSpan 578 */ 579 public float getMaximumSpan(int axis) { 580 updateMetrics(); 581 return super.getMaximumSpan(axis); 582 } 583 584 /** 585 * Gives notification that something was inserted into the 586 * document in a location that this view is responsible for. 587 * This is implemented to simply update the children. 588 * 589 * @param e the change information from the associated document 590 * @param a the current allocation of the view 591 * @param f the factory to use to rebuild if the view has children 592 * @see View#insertUpdate 593 */ 594 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 595 updateChildren(e, a); 596 597 Rectangle alloc = ((a != null) && isAllocationValid()) ? 598 getInsideAllocation(a) : null; 599 int pos = e.getOffset(); 600 View v = getViewAtPosition(pos, alloc); 601 if (v != null) { 602 v.insertUpdate(e, alloc, f); 603 } 604 } 605 606 /** 607 * Gives notification that something was removed from the 608 * document in a location that this view is responsible for. 609 * This is implemented to simply update the children. 610 * 611 * @param e the change information from the associated document 612 * @param a the current allocation of the view 613 * @param f the factory to use to rebuild if the view has children 614 * @see View#removeUpdate 615 */ 616 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 617 updateChildren(e, a); 618 619 Rectangle alloc = ((a != null) && isAllocationValid()) ? 620 getInsideAllocation(a) : null; 621 int pos = e.getOffset(); 622 View v = getViewAtPosition(pos, alloc); 623 if (v != null) { 624 v.removeUpdate(e, alloc, f); 625 } 626 } 627 628 /** 629 * Gives notification from the document that attributes were changed 630 * in a location that this view is responsible for. 631 * 632 * @param e the change information from the associated document 633 * @param a the current allocation of the view 634 * @param f the factory to use to rebuild if the view has children 635 * @see View#changedUpdate 636 */ 637 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { 638 updateChildren(e, a); 639 } 640 641 // --- variables ------------------------------------------- 642 643 FontMetrics metrics; 644 Segment lineBuffer; 645 boolean widthChanging; 646 int tabBase; 647 float tabSize; 648 boolean wordWrap; 649 650 int sel0; 651 int sel1; 652 Color unselected; 653 Color selected; 654 655 656 /** 657 * Simple view of a line that wraps if it doesn't 658 * fit withing the horizontal space allocated. 659 * This class tries to be lightweight by carrying little 660 * state of it's own and sharing the state of the outer class 661 * with it's sibblings. 662 */ 663 class WrappedLine extends View { 664 665 WrappedLine(Element elem) { 666 super(elem); 667 lineCount = -1; 668 } 669 670 /** 671 * Determines the preferred span for this view along an 672 * axis. 673 * 674 * @param axis may be either X_AXIS or Y_AXIS 675 * @return the span the view would like to be rendered into. 676 * Typically the view is told to render into the span 677 * that is returned, although there is no guarantee. 678 * The parent may choose to resize or break the view. 679 * @see View#getPreferredSpan 680 */ 681 public float getPreferredSpan(int axis) { 682 switch (axis) { 683 case View.X_AXIS: 684 float width = getWidth(); 685 if (width == Integer.MAX_VALUE) { 686 // We have been initially set to MAX_VALUE, but we don't 687 // want this as our preferred. 688 return 100f; 689 } 690 return width; 691 case View.Y_AXIS: 692 if (lineCount < 0 || widthChanging) { 693 breakLines(getStartOffset()); 694 } 695 return lineCount * metrics.getHeight(); 696 default: 697 throw new IllegalArgumentException("Invalid axis: " + axis); 698 } 699 } 700 701 /** 702 * Renders using the given rendering surface and area on that 703 * surface. The view may need to do layout and create child 704 * views to enable itself to render into the given allocation. 705 * 706 * @param g the rendering surface to use 707 * @param a the allocated region to render into 708 * @see View#paint 709 */ 710 public void paint(Graphics g, Shape a) { 711 Rectangle alloc = (Rectangle) a; 712 int y = alloc.y + metrics.getAscent(); 713 int x = alloc.x; 714 715 JTextComponent host = (JTextComponent)getContainer(); 716 Highlighter h = host.getHighlighter(); 717 LayeredHighlighter dh = (h instanceof LayeredHighlighter) ? 718 (LayeredHighlighter)h : null; 719 720 int start = getStartOffset(); 721 int end = getEndOffset(); 722 int p0 = start; 723 int[] lineEnds = getLineEnds(); 724 boolean useDrawLineFP = drawLineOverridden && g instanceof Graphics2D; 725 for (int i = 0; i < lineCount; i++) { 726 int p1 = (lineEnds == null) ? end : 727 start + lineEnds[i]; 728 if (dh != null) { 729 int hOffset = (p1 == end) 730 ? (p1 - 1) 731 : p1; 732 dh.paintLayeredHighlights(g, p0, hOffset, a, host, this); 733 } 734 if (useDrawLineFP) { 735 drawLine(p0, p1, (Graphics2D) g, (float) x, (float) y); 736 } else { 737 drawLine(p0, p1, g, x, y); 738 } 739 p0 = p1; 740 y += metrics.getHeight(); 741 } 742 } 743 744 /** 745 * Provides a mapping from the document model coordinate space 746 * to the coordinate space of the view mapped to it. 747 * 748 * @param pos the position to convert 749 * @param a the allocated region to render into 750 * @return the bounding box of the given position is returned 751 * @exception BadLocationException if the given position does not represent a 752 * valid location in the associated document 753 * @see View#modelToView 754 */ 755 public Shape modelToView(int pos, Shape a, Position.Bias b) 756 throws BadLocationException { 757 Rectangle alloc = a.getBounds(); 758 alloc.height = metrics.getHeight(); 759 alloc.width = 1; 760 761 int p0 = getStartOffset(); 762 if (pos < p0 || pos > getEndOffset()) { 763 throw new BadLocationException("Position out of range", pos); 764 } 765 766 int testP = (b == Position.Bias.Forward) ? pos : 767 Math.max(p0, pos - 1); 768 int line = 0; 769 int[] lineEnds = getLineEnds(); 770 if (lineEnds != null) { 771 line = findLine(testP - p0); 772 if (line > 0) { 773 p0 += lineEnds[line - 1]; 774 } 775 alloc.y += alloc.height * line; 776 } 777 778 if (pos > p0) { 779 Segment segment = SegmentCache.getSharedSegment(); 780 loadText(segment, p0, pos); 781 float x = alloc.x; 782 x += Utilities.getTabbedTextWidth(segment, metrics, x, 783 WrappedPlainView.this, p0); 784 SegmentCache.releaseSharedSegment(segment); 785 return new Rectangle2D.Float(x, alloc.y, alloc.width, alloc.height); 786 } 787 return alloc; 788 } 789 790 /** 791 * Provides a mapping from the view coordinate space to the logical 792 * coordinate space of the model. 793 * 794 * @param fx the X coordinate 795 * @param fy the Y coordinate 796 * @param a the allocated region to render into 797 * @return the location within the model that best represents the 798 * given point in the view 799 * @see View#viewToModel 800 */ 801 @SuppressWarnings("deprecation") 802 public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { 803 // PENDING(prinz) implement bias properly 804 bias[0] = Position.Bias.Forward; 805 806 Rectangle alloc = (Rectangle) a; 807 int x = (int) fx; 808 int y = (int) fy; 809 if (y < alloc.y) { 810 // above the area covered by this icon, so the position 811 // is assumed to be the start of the coverage for this view. 812 return getStartOffset(); 813 } else if (y > alloc.y + alloc.height) { 814 // below the area covered by this icon, so the position 815 // is assumed to be the end of the coverage for this view. 816 return getEndOffset() - 1; 817 } else { 818 // positioned within the coverage of this view vertically, 819 // so we figure out which line the point corresponds to. 820 // if the line is greater than the number of lines contained, then 821 // simply use the last line as it represents the last possible place 822 // we can position to. 823 alloc.height = metrics.getHeight(); 824 int line = (alloc.height > 0 ? 825 (y - alloc.y) / alloc.height : lineCount - 1); 826 if (line >= lineCount) { 827 return getEndOffset() - 1; 828 } else { 829 int p0 = getStartOffset(); 830 int p1; 831 if (lineCount == 1) { 832 p1 = getEndOffset(); 833 } else { 834 int[] lineEnds = getLineEnds(); 835 p1 = p0 + lineEnds[line]; 836 if (line > 0) { 837 p0 += lineEnds[line - 1]; 838 } 839 } 840 841 if (x < alloc.x) { 842 // point is to the left of the line 843 return p0; 844 } else if (x > alloc.x + alloc.width) { 845 // point is to the right of the line 846 return p1 - 1; 847 } else { 848 // Determine the offset into the text 849 Segment segment = SegmentCache.getSharedSegment(); 850 loadText(segment, p0, p1); 851 int n = Utilities.getTabbedTextOffset(segment, metrics, 852 (float)alloc.x, (float)x, 853 WrappedPlainView.this, p0, false); 854 SegmentCache.releaseSharedSegment(segment); 855 return Math.min(p0 + n, p1 - 1); 856 } 857 } 858 } 859 } 860 861 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 862 update(e, a); 863 } 864 865 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 866 update(e, a); 867 } 868 869 private void update(DocumentEvent ev, Shape a) { 870 int oldCount = lineCount; 871 breakLines(ev.getOffset()); 872 if (oldCount != lineCount) { 873 WrappedPlainView.this.preferenceChanged(this, false, true); 874 // have to repaint any views after the receiver. 875 getContainer().repaint(); 876 } else if (a != null) { 877 Component c = getContainer(); 878 Rectangle alloc = (Rectangle) a; 879 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height); 880 } 881 } 882 883 /** 884 * Returns line cache. If the cache was GC'ed, recreates it. 885 * If there's no cache, returns null 886 */ 887 final int[] getLineEnds() { 888 if (lineCache == null) { 889 return null; 890 } else { 891 int[] lineEnds = lineCache.get(); 892 if (lineEnds == null) { 893 // Cache was GC'ed, so rebuild it 894 return breakLines(getStartOffset()); 895 } else { 896 return lineEnds; 897 } 898 } 899 } 900 901 /** 902 * Creates line cache if text breaks into more than one physical line. 903 * @param startPos position to start breaking from 904 * @return the cache created, ot null if text breaks into one line 905 */ 906 final int[] breakLines(int startPos) { 907 int[] lineEnds = (lineCache == null) ? null : lineCache.get(); 908 int[] oldLineEnds = lineEnds; 909 int start = getStartOffset(); 910 int lineIndex = 0; 911 if (lineEnds != null) { 912 lineIndex = findLine(startPos - start); 913 if (lineIndex > 0) { 914 lineIndex--; 915 } 916 } 917 918 int p0 = (lineIndex == 0) ? start : start + lineEnds[lineIndex - 1]; 919 int p1 = getEndOffset(); 920 while (p0 < p1) { 921 int p = calculateBreakPosition(p0, p1); 922 p0 = (p == p0) ? ++p : p; // 4410243 923 924 if (lineIndex == 0 && p0 >= p1) { 925 // do not use cache if there's only one line 926 lineCache = null; 927 lineEnds = null; 928 lineIndex = 1; 929 break; 930 } else if (lineEnds == null || lineIndex >= lineEnds.length) { 931 // we have 2+ lines, and the cache is not big enough 932 // we try to estimate total number of lines 933 double growFactor = ((double)(p1 - start) / (p0 - start)); 934 int newSize = (int)Math.ceil((lineIndex + 1) * growFactor); 935 newSize = Math.max(newSize, lineIndex + 2); 936 int[] tmp = new int[newSize]; 937 if (lineEnds != null) { 938 System.arraycopy(lineEnds, 0, tmp, 0, lineIndex); 939 } 940 lineEnds = tmp; 941 } 942 lineEnds[lineIndex++] = p0 - start; 943 } 944 945 lineCount = lineIndex; 946 if (lineCount > 1) { 947 // check if the cache is too big 948 int maxCapacity = lineCount + lineCount / 3; 949 if (lineEnds.length > maxCapacity) { 950 int[] tmp = new int[maxCapacity]; 951 System.arraycopy(lineEnds, 0, tmp, 0, lineCount); 952 lineEnds = tmp; 953 } 954 } 955 956 if (lineEnds != null && lineEnds != oldLineEnds) { 957 lineCache = new SoftReference<int[]>(lineEnds); 958 } 959 return lineEnds; 960 } 961 962 /** 963 * Binary search in the cache for line containing specified offset 964 * (which is relative to the beginning of the view). This method 965 * assumes that cache exists. 966 */ 967 private int findLine(int offset) { 968 int[] lineEnds = lineCache.get(); 969 if (offset < lineEnds[0]) { 970 return 0; 971 } else if (offset > lineEnds[lineCount - 1]) { 972 return lineCount; 973 } else { 974 return findLine(lineEnds, offset, 0, lineCount - 1); 975 } 976 } 977 978 private int findLine(int[] array, int offset, int min, int max) { 979 if (max - min <= 1) { 980 return max; 981 } else { 982 int mid = (max + min) / 2; 983 return (offset < array[mid]) ? 984 findLine(array, offset, min, mid) : 985 findLine(array, offset, mid, max); 986 } 987 } 988 989 int lineCount; 990 SoftReference<int[]> lineCache = null; 991 } 992 993 private final boolean drawLineOverridden = 994 getFPMethodOverridden(getClass(), "drawLine", IIGNN); 995 private final boolean drawSelectedTextOverridden = 996 getFPMethodOverridden(getClass(), "drawSelectedText", GNNII); 997 private final boolean drawUnselectedTextOverridden = 998 getFPMethodOverridden(getClass(), "drawUnselectedText", GNNII); 999 private final boolean useFloatingPointAPI = 1000 drawUnselectedTextOverridden || drawSelectedTextOverridden; 1001 }