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