1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 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.security.AccessController; 31 import java.security.PrivilegedAction; 32 import java.util.Objects; 33 import javax.swing.event.*; 34 import java.lang.reflect.Module; 35 import java.lang.ref.SoftReference; 36 import java.util.HashMap; 37 38 /** 39 * Implements View interface for a simple multi-line text view 40 * that has text in one font and color. The view represents each 41 * child element as a line of text. 42 * 43 * @author Timothy Prinzing 44 * @see View 45 */ 46 public class PlainView extends View implements TabExpander { 47 48 /** 49 * Constructs a new PlainView wrapped on an element. 50 * 51 * @param elem the element 52 */ 53 public PlainView(Element elem) { 54 super(elem); 55 } 56 57 /** 58 * Returns the tab size set for the document, defaulting to 8. 59 * 60 * @return the tab size 61 */ 62 protected int getTabSize() { 63 Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute); 64 int size = (i != null) ? i.intValue() : 8; 65 return size; 66 } 67 68 /** 69 * Renders a line of text, suppressing whitespace at the end 70 * and expanding any tabs. This is implemented to make calls 71 * to the methods <code>drawUnselectedText</code> and 72 * <code>drawSelectedText</code> so that the way selected and 73 * unselected text are rendered can be customized. 74 * 75 * @param lineIndex the line to draw >= 0 76 * @param g the <code>Graphics</code> context 77 * @param x the starting X position >= 0 78 * @param y the starting Y position >= 0 79 * @see #drawUnselectedText 80 * @see #drawSelectedText 81 * 82 * @deprecated replaced by 83 * {@link #drawLine(int, Graphics2D, float, float)} 84 */ 85 @Deprecated(since = "9") 86 protected void drawLine(int lineIndex, Graphics g, int x, int y) { 87 drawLineImpl(lineIndex, g, x, y); 88 } 89 90 private void drawLineImpl(int lineIndex, Graphics g, float x, float y) { 91 Element line = getElement().getElement(lineIndex); 92 Element elem; 93 94 try { 95 if (line.isLeaf()) { 96 drawElement(lineIndex, line, g, x, y); 97 } else { 98 // this line contains the composed text. 99 int count = line.getElementCount(); 100 for(int i = 0; i < count; i++) { 101 elem = line.getElement(i); 102 x = drawElement(lineIndex, elem, g, x, y); 103 } 104 } 105 } catch (BadLocationException e) { 106 throw new StateInvariantError("Can't render line: " + lineIndex); 107 } 108 } 109 110 /** 111 * Renders a line of text, suppressing whitespace at the end 112 * and expanding any tabs. This is implemented to make calls 113 * to the methods {@code drawUnselectedText} and 114 * {@code drawSelectedText} so that the way selected and 115 * unselected text are rendered can be customized. 116 * 117 * @param lineIndex the line to draw {@code >= 0} 118 * @param g the {@code Graphics} context 119 * @param x the starting X position {@code >= 0} 120 * @param y the starting Y position {@code >= 0} 121 * @see #drawUnselectedText 122 * @see #drawSelectedText 123 * 124 * @since 9 125 */ 126 protected void drawLine(int lineIndex, Graphics2D g, float x, float y) { 127 drawLineImpl(lineIndex, g, x, y); 128 } 129 130 private float drawElement(int lineIndex, Element elem, Graphics g, 131 float x, float y) 132 throws BadLocationException 133 { 134 int p0 = elem.getStartOffset(); 135 int p1 = elem.getEndOffset(); 136 p1 = Math.min(getDocument().getLength(), p1); 137 138 if (lineIndex == 0) { 139 x += firstLineOffset; 140 } 141 AttributeSet attr = elem.getAttributes(); 142 if (Utilities.isComposedTextAttributeDefined(attr)) { 143 g.setColor(unselected); 144 x = Utilities.drawComposedText(this, attr, g, x, y, 145 p0-elem.getStartOffset(), 146 p1-elem.getStartOffset()); 147 } else { 148 if (sel0 == sel1 || selected == unselected) { 149 // no selection, or it is invisible 150 x = callDrawUnselectedText(g, x, y, p0, p1); 151 } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) { 152 x = callDrawSelectedText(g, x, y, p0, p1); 153 } else if (sel0 >= p0 && sel0 <= p1) { 154 if (sel1 >= p0 && sel1 <= p1) { 155 x = callDrawUnselectedText(g, x, y, p0, sel0); 156 x = callDrawSelectedText(g, x, y, sel0, sel1); 157 x = callDrawUnselectedText(g, x, y, sel1, p1); 158 } else { 159 x = callDrawUnselectedText(g, x, y, p0, sel0); 160 x = callDrawSelectedText(g, x, y, sel0, p1); 161 } 162 } else if (sel1 >= p0 && sel1 <= p1) { 163 x = callDrawSelectedText(g, x, y, p0, sel1); 164 x = callDrawUnselectedText(g, x, y, sel1, p1); 165 } else { 166 x = callDrawUnselectedText(g, x, y, p0, p1); 167 } 168 } 169 170 return x; 171 } 172 173 /** 174 * Renders the given range in the model as normal unselected 175 * text. Uses the foreground or disabled color to render the text. 176 * 177 * @param g the graphics context 178 * @param x the starting X coordinate >= 0 179 * @param y the starting Y coordinate >= 0 180 * @param p0 the beginning position in the model >= 0 181 * @param p1 the ending position in the model >= 0 182 * @return the X location of the end of the range >= 0 183 * @exception BadLocationException if the range is invalid 184 * 185 * @deprecated replaced by 186 * {@link #drawUnselectedText(Graphics2D, float, float, int, int)} 187 */ 188 @Deprecated(since = "9") 189 protected int drawUnselectedText(Graphics g, int x, int y, 190 int p0, int p1) throws BadLocationException { 191 return (int) drawUnselectedTextImpl(g, x, y, p0, p1, false); 192 } 193 194 private float callDrawUnselectedText(Graphics g, float x, float y, 195 int p0, int p1) 196 throws BadLocationException 197 { 198 return drawUnselectedTextOverridden && (g instanceof Graphics2D) 199 ? drawUnselectedText((Graphics2D) g, x, y, p0, p1) 200 : drawUnselectedText(g, (int) x, (int) y, p0, p1); 201 } 202 203 private float drawUnselectedTextImpl(Graphics g, float x, float y, 204 int p0, int p1, 205 boolean useFPAPI) 206 throws BadLocationException 207 { 208 g.setColor(unselected); 209 Document doc = getDocument(); 210 Segment s = SegmentCache.getSharedSegment(); 211 doc.getText(p0, p1 - p0, s); 212 float ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0, null, 213 useFPAPI); 214 SegmentCache.releaseSharedSegment(s); 215 return ret; 216 } 217 218 /** 219 * Renders the given range in the model as normal unselected 220 * text. Uses the foreground or disabled color to render the text. 221 * 222 * @param g the graphics context 223 * @param x the starting X coordinate {@code >= 0} 224 * @param y the starting Y coordinate {@code >= 0} 225 * @param p0 the beginning position in the model {@code >= 0} 226 * @param p1 the ending position in the model {@code >= 0} 227 * @return the X location of the end of the range {@code >= 0} 228 * @exception BadLocationException if the range is invalid 229 * 230 * @since 9 231 */ 232 protected float drawUnselectedText(Graphics2D g, float x, float y, 233 int p0, int p1) throws BadLocationException { 234 return drawUnselectedTextImpl(g, x, y, p0, p1, true); 235 } 236 237 /** 238 * Renders the given range in the model as selected text. This 239 * is implemented to render the text in the color specified in 240 * the hosting component. It assumes the highlighter will render 241 * the selected background. 242 * 243 * @param g the graphics context 244 * @param x the starting X coordinate >= 0 245 * @param y the starting Y coordinate >= 0 246 * @param p0 the beginning position in the model >= 0 247 * @param p1 the ending position in the model >= 0 248 * @return the location of the end of the range 249 * @exception BadLocationException if the range is invalid 250 * 251 * @deprecated replaced by 252 * {@link #drawSelectedText(Graphics2D, float, float, int, int)} 253 */ 254 @Deprecated(since = "9") 255 protected int drawSelectedText(Graphics g, int x, 256 int y, int p0, int p1) 257 throws BadLocationException 258 { 259 return (int) drawSelectedTextImpl(g, x, y, p0, p1, false); 260 } 261 262 float callDrawSelectedText(Graphics g, float x, float y, 263 int p0, int p1) 264 throws BadLocationException 265 { 266 return drawSelectedTextOverridden && g instanceof Graphics2D 267 ? drawSelectedText((Graphics2D) g, x, y, p0, p1) 268 : drawSelectedText(g, (int) x, (int) y, p0, p1); 269 } 270 271 private float drawSelectedTextImpl(Graphics g, float x, float y, 272 int p0, int p1, 273 boolean useFPAPI) 274 throws BadLocationException 275 { 276 g.setColor(selected); 277 Document doc = getDocument(); 278 Segment s = SegmentCache.getSharedSegment(); 279 doc.getText(p0, p1 - p0, s); 280 float ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0, null, 281 useFPAPI); 282 SegmentCache.releaseSharedSegment(s); 283 return ret; 284 } 285 286 /** 287 * Renders the given range in the model as selected text. This 288 * is implemented to render the text in the color specified in 289 * the hosting component. It assumes the highlighter will render 290 * the selected background. 291 * 292 * @param g the graphics context 293 * @param x the starting X coordinate {@code >= 0} 294 * @param y the starting Y coordinate {@code >= 0} 295 * @param p0 the beginning position in the model {@code >= 0} 296 * @param p1 the ending position in the model {@code >= 0} 297 * @return the location of the end of the range 298 * @exception BadLocationException if the range is invalid 299 * 300 * @since 9 301 */ 302 protected float drawSelectedText(Graphics2D g, float x, 303 float y, int p0, int p1) throws BadLocationException { 304 return drawSelectedTextImpl(g, x, y, p0, p1, true); 305 } 306 307 /** 308 * Gives access to a buffer that can be used to fetch 309 * text from the associated document. 310 * 311 * @return the buffer 312 */ 313 protected final Segment getLineBuffer() { 314 if (lineBuffer == null) { 315 lineBuffer = new Segment(); 316 } 317 return lineBuffer; 318 } 319 320 /** 321 * Checks to see if the font metrics and longest line 322 * are up-to-date. 323 * 324 * @since 1.4 325 */ 326 protected void updateMetrics() { 327 Component host = getContainer(); 328 Font f = host.getFont(); 329 FontMetrics fm = (font == null) ? null : host.getFontMetrics(font); 330 if (font != f || !Objects.equals(metrics, fm)) { 331 // The font changed, we need to recalculate the 332 // longest line. 333 calculateLongestLine(); 334 if (useFloatingPointAPI) { 335 FontRenderContext frc = metrics.getFontRenderContext(); 336 float tabWidth = (float) font.getStringBounds("m", frc).getWidth(); 337 tabSize = getTabSize() * tabWidth; 338 } else { 339 tabSize = getTabSize() * metrics.charWidth('m'); 340 } 341 } 342 } 343 344 // ---- View methods ---------------------------------------------------- 345 346 /** 347 * Determines the preferred span for this view along an 348 * axis. 349 * 350 * @param axis may be either View.X_AXIS or View.Y_AXIS 351 * @return the span the view would like to be rendered into >= 0. 352 * Typically the view is told to render into the span 353 * that is returned, although there is no guarantee. 354 * The parent may choose to resize or break the view. 355 * @exception IllegalArgumentException for an invalid axis 356 */ 357 public float getPreferredSpan(int axis) { 358 updateMetrics(); 359 switch (axis) { 360 case View.X_AXIS: 361 return getLineWidth(longLine); 362 case View.Y_AXIS: 363 return getElement().getElementCount() * metrics.getHeight(); 364 default: 365 throw new IllegalArgumentException("Invalid axis: " + axis); 366 } 367 } 368 369 /** 370 * Renders using the given rendering surface and area on that surface. 371 * The view may need to do layout and create child views to enable 372 * itself to render into the given allocation. 373 * 374 * @param g the rendering surface to use 375 * @param a the allocated region to render into 376 * 377 * @see View#paint 378 */ 379 public void paint(Graphics g, Shape a) { 380 Shape originalA = a; 381 a = adjustPaintRegion(a); 382 Rectangle alloc = (Rectangle) a; 383 tabBase = alloc.x; 384 JTextComponent host = (JTextComponent) getContainer(); 385 Highlighter h = host.getHighlighter(); 386 g.setFont(host.getFont()); 387 sel0 = host.getSelectionStart(); 388 sel1 = host.getSelectionEnd(); 389 unselected = (host.isEnabled()) ? 390 host.getForeground() : host.getDisabledTextColor(); 391 Caret c = host.getCaret(); 392 selected = c.isSelectionVisible() && h != null ? 393 host.getSelectedTextColor() : unselected; 394 updateMetrics(); 395 396 // If the lines are clipped then we don't expend the effort to 397 // try and paint them. Since all of the lines are the same height 398 // with this object, determination of what lines need to be repainted 399 // is quick. 400 Rectangle clip = g.getClipBounds(); 401 int fontHeight = metrics.getHeight(); 402 int heightBelow = (alloc.y + alloc.height) - (clip.y + clip.height); 403 int heightAbove = clip.y - alloc.y; 404 int linesBelow, linesAbove, linesTotal; 405 406 if (fontHeight > 0) { 407 linesBelow = Math.max(0, heightBelow / fontHeight); 408 linesAbove = Math.max(0, heightAbove / fontHeight); 409 linesTotal = alloc.height / fontHeight; 410 if (alloc.height % fontHeight != 0) { 411 linesTotal++; 412 } 413 } else { 414 linesBelow = linesAbove = linesTotal = 0; 415 } 416 417 // update the visible lines 418 Rectangle lineArea = lineToRect(a, linesAbove); 419 int y = lineArea.y + metrics.getAscent(); 420 int x = lineArea.x; 421 Element map = getElement(); 422 int lineCount = map.getElementCount(); 423 int endLine = Math.min(lineCount, linesTotal - linesBelow); 424 lineCount--; 425 LayeredHighlighter dh = (h instanceof LayeredHighlighter) ? 426 (LayeredHighlighter)h : null; 427 for (int line = linesAbove; line < endLine; line++) { 428 if (dh != null) { 429 Element lineElement = map.getElement(line); 430 if (line == lineCount) { 431 dh.paintLayeredHighlights(g, lineElement.getStartOffset(), 432 lineElement.getEndOffset(), 433 originalA, host, this); 434 } 435 else { 436 dh.paintLayeredHighlights(g, lineElement.getStartOffset(), 437 lineElement.getEndOffset() - 1, 438 originalA, host, this); 439 } 440 } 441 if (drawLineOverridden && (g instanceof Graphics2D)) { 442 drawLine(line, (Graphics2D) g, (float) x, (float) y); 443 } else { 444 drawLine(line, g, x, y); 445 } 446 y += fontHeight; 447 if (line == 0) { 448 // This should never really happen, in so far as if 449 // firstLineOffset is non 0, there should only be one 450 // line of text. 451 x -= firstLineOffset; 452 } 453 } 454 } 455 456 /** 457 * Should return a shape ideal for painting based on the passed in 458 * Shape <code>a</code>. This is useful if painting in a different 459 * region. The default implementation returns <code>a</code>. 460 */ 461 Shape adjustPaintRegion(Shape a) { 462 return a; 463 } 464 465 /** 466 * Provides a mapping from the document model coordinate space 467 * to the coordinate space of the view mapped to it. 468 * 469 * @param pos the position to convert >= 0 470 * @param a the allocated region to render into 471 * @return the bounding box of the given position 472 * @exception BadLocationException if the given position does not 473 * represent a valid location in the associated document 474 * @see View#modelToView 475 */ 476 @SuppressWarnings("deprecation") 477 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 478 // line coordinates 479 Document doc = getDocument(); 480 Element map = getElement(); 481 int lineIndex = map.getElementIndex(pos); 482 if (lineIndex < 0) { 483 return lineToRect(a, 0); 484 } 485 Rectangle lineArea = lineToRect(a, lineIndex); 486 487 // determine span from the start of the line 488 tabBase = lineArea.x; 489 Element line = map.getElement(lineIndex); 490 int p0 = line.getStartOffset(); 491 Segment s = SegmentCache.getSharedSegment(); 492 doc.getText(p0, pos - p0, s); 493 494 if (useFloatingPointAPI) { 495 float xOffs = Utilities.getTabbedTextWidth(s, metrics, (float) tabBase, this, p0); 496 SegmentCache.releaseSharedSegment(s); 497 return new Rectangle2D.Float(lineArea.x + xOffs, lineArea.y, 1, metrics.getHeight()); 498 } 499 500 int xOffs = Utilities.getTabbedTextWidth(s, metrics, tabBase, this,p0); 501 SegmentCache.releaseSharedSegment(s); 502 503 // fill in the results and return 504 lineArea.x += xOffs; 505 lineArea.width = 1; 506 lineArea.height = metrics.getHeight(); 507 return lineArea; 508 } 509 510 /** 511 * Provides a mapping from the view coordinate space to the logical 512 * coordinate space of the model. 513 * 514 * @param x the X coordinate >= 0 515 * @param y the Y coordinate >= 0 516 * @param a the allocated region to render into 517 * @return the location within the model that best represents the 518 * given point in the view >= 0 519 * @see View#viewToModel 520 */ 521 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 522 // PENDING(prinz) properly calculate bias 523 bias[0] = Position.Bias.Forward; 524 525 Rectangle alloc = a.getBounds(); 526 Document doc = getDocument(); 527 528 if (y < alloc.y) { 529 // above the area covered by this icon, so the position 530 // is assumed to be the start of the coverage for this view. 531 return getStartOffset(); 532 } else if (y > alloc.y + alloc.height) { 533 // below the area covered by this icon, so the position 534 // is assumed to be the end of the coverage for this view. 535 return getEndOffset() - 1; 536 } else { 537 // positioned within the coverage of this view vertically, 538 // so we figure out which line the point corresponds to. 539 // if the line is greater than the number of lines contained, then 540 // simply use the last line as it represents the last possible place 541 // we can position to. 542 Element map = doc.getDefaultRootElement(); 543 int fontHeight = metrics.getHeight(); 544 int lineIndex = (fontHeight > 0 ? 545 (int)Math.abs((y - alloc.y) / fontHeight) : 546 map.getElementCount() - 1); 547 if (lineIndex >= map.getElementCount()) { 548 return getEndOffset() - 1; 549 } 550 Element line = map.getElement(lineIndex); 551 int dx = 0; 552 if (lineIndex == 0) { 553 alloc.x += firstLineOffset; 554 alloc.width -= firstLineOffset; 555 } 556 if (x < alloc.x) { 557 // point is to the left of the line 558 return line.getStartOffset(); 559 } else if (x > alloc.x + alloc.width) { 560 // point is to the right of the line 561 return line.getEndOffset() - 1; 562 } else { 563 // Determine the offset into the text 564 try { 565 int p0 = line.getStartOffset(); 566 int p1 = line.getEndOffset() - 1; 567 Segment s = SegmentCache.getSharedSegment(); 568 doc.getText(p0, p1 - p0, s); 569 tabBase = alloc.x; 570 int offs = p0 + Utilities.getTabbedTextOffset(s, metrics, 571 tabBase, x, this, p0, true); 572 SegmentCache.releaseSharedSegment(s); 573 return offs; 574 } catch (BadLocationException e) { 575 // should not happen 576 return -1; 577 } 578 } 579 } 580 } 581 582 /** 583 * Gives notification that something was inserted into the document 584 * in a location that this view is responsible for. 585 * 586 * @param changes the change information from the associated document 587 * @param a the current allocation of the view 588 * @param f the factory to use to rebuild if the view has children 589 * @see View#insertUpdate 590 */ 591 public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) { 592 updateDamage(changes, a, f); 593 } 594 595 /** 596 * Gives notification that something was removed from the document 597 * in a location that this view is responsible for. 598 * 599 * @param changes the change information from the associated document 600 * @param a the current allocation of the view 601 * @param f the factory to use to rebuild if the view has children 602 * @see View#removeUpdate 603 */ 604 public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) { 605 updateDamage(changes, a, f); 606 } 607 608 /** 609 * Gives notification from the document that attributes were changed 610 * in a location that this view is responsible for. 611 * 612 * @param changes the change information from the associated document 613 * @param a the current allocation of the view 614 * @param f the factory to use to rebuild if the view has children 615 * @see View#changedUpdate 616 */ 617 public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) { 618 updateDamage(changes, a, f); 619 } 620 621 /** 622 * Sets the size of the view. This should cause 623 * layout of the view along the given axis, if it 624 * has any layout duties. 625 * 626 * @param width the width >= 0 627 * @param height the height >= 0 628 */ 629 public void setSize(float width, float height) { 630 super.setSize(width, height); 631 updateMetrics(); 632 } 633 634 // --- TabExpander methods ------------------------------------------ 635 636 /** 637 * Returns the next tab stop position after a given reference position. 638 * This implementation does not support things like centering so it 639 * ignores the tabOffset argument. 640 * 641 * @param x the current position >= 0 642 * @param tabOffset the position within the text stream 643 * that the tab occurred at >= 0. 644 * @return the tab stop, measured in points >= 0 645 */ 646 public float nextTabStop(float x, int tabOffset) { 647 if (tabSize == 0) { 648 return x; 649 } 650 float ntabs = (x - tabBase) / tabSize; 651 return tabBase + ((ntabs + 1) * tabSize); 652 } 653 654 // --- local methods ------------------------------------------------ 655 656 /** 657 * Repaint the region of change covered by the given document 658 * event. Damages the line that begins the range to cover 659 * the case when the insert/remove is only on one line. 660 * If lines are added or removed, damages the whole 661 * view. The longest line is checked to see if it has 662 * changed. 663 * 664 * @param changes the change information from the associated document 665 * @param a the current allocation of the view 666 * @param f the factory to use to rebuild if the view has children 667 * @since 1.4 668 */ 669 protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) { 670 Component host = getContainer(); 671 updateMetrics(); 672 Element elem = getElement(); 673 DocumentEvent.ElementChange ec = changes.getChange(elem); 674 675 Element[] added = (ec != null) ? ec.getChildrenAdded() : null; 676 Element[] removed = (ec != null) ? ec.getChildrenRemoved() : null; 677 if (((added != null) && (added.length > 0)) || 678 ((removed != null) && (removed.length > 0))) { 679 // lines were added or removed... 680 if (added != null) { 681 int currWide = getLineWidth(longLine); 682 for (int i = 0; i < added.length; i++) { 683 int w = getLineWidth(added[i]); 684 if (w > currWide) { 685 currWide = w; 686 longLine = added[i]; 687 } 688 } 689 } 690 if (removed != null) { 691 for (int i = 0; i < removed.length; i++) { 692 if (removed[i] == longLine) { 693 calculateLongestLine(); 694 break; 695 } 696 } 697 } 698 preferenceChanged(null, true, true); 699 host.repaint(); 700 } else { 701 Element map = getElement(); 702 int line = map.getElementIndex(changes.getOffset()); 703 damageLineRange(line, line, a, host); 704 if (changes.getType() == DocumentEvent.EventType.INSERT) { 705 // check to see if the line is longer than current 706 // longest line. 707 int w = getLineWidth(longLine); 708 Element e = map.getElement(line); 709 if (e == longLine) { 710 preferenceChanged(null, true, false); 711 } else if (getLineWidth(e) > w) { 712 longLine = e; 713 preferenceChanged(null, true, false); 714 } 715 } else if (changes.getType() == DocumentEvent.EventType.REMOVE) { 716 if (map.getElement(line) == longLine) { 717 // removed from longest line... recalc 718 calculateLongestLine(); 719 preferenceChanged(null, true, false); 720 } 721 } 722 } 723 } 724 725 /** 726 * Repaint the given line range. 727 * 728 * @param host the component hosting the view (used to call repaint) 729 * @param a the region allocated for the view to render into 730 * @param line0 the starting line number to repaint. This must 731 * be a valid line number in the model. 732 * @param line1 the ending line number to repaint. This must 733 * be a valid line number in the model. 734 * @since 1.4 735 */ 736 protected void damageLineRange(int line0, int line1, Shape a, Component host) { 737 if (a != null) { 738 Rectangle area0 = lineToRect(a, line0); 739 Rectangle area1 = lineToRect(a, line1); 740 if ((area0 != null) && (area1 != null)) { 741 Rectangle damage = area0.union(area1); 742 host.repaint(damage.x, damage.y, damage.width, damage.height); 743 } else { 744 host.repaint(); 745 } 746 } 747 } 748 749 /** 750 * Determine the rectangle that represents the given line. 751 * 752 * @param a the region allocated for the view to render into 753 * @param line the line number to find the region of. This must 754 * be a valid line number in the model. 755 * @return the rectangle that represents the given line 756 * @since 1.4 757 */ 758 protected Rectangle lineToRect(Shape a, int line) { 759 Rectangle r = null; 760 updateMetrics(); 761 if (metrics != null) { 762 Rectangle alloc = a.getBounds(); 763 if (line == 0) { 764 alloc.x += firstLineOffset; 765 alloc.width -= firstLineOffset; 766 } 767 r = new Rectangle(alloc.x, alloc.y + (line * metrics.getHeight()), 768 alloc.width, metrics.getHeight()); 769 } 770 return r; 771 } 772 773 /** 774 * Iterate over the lines represented by the child elements 775 * of the element this view represents, looking for the line 776 * that is the longest. The <em>longLine</em> variable is updated to 777 * represent the longest line contained. The <em>font</em> variable 778 * is updated to indicate the font used to calculate the 779 * longest line. 780 */ 781 private void calculateLongestLine() { 782 Component c = getContainer(); 783 font = c.getFont(); 784 metrics = c.getFontMetrics(font); 785 Document doc = getDocument(); 786 Element lines = getElement(); 787 int n = lines.getElementCount(); 788 int maxWidth = -1; 789 for (int i = 0; i < n; i++) { 790 Element line = lines.getElement(i); 791 int w = getLineWidth(line); 792 if (w > maxWidth) { 793 maxWidth = w; 794 longLine = line; 795 } 796 } 797 } 798 799 /** 800 * Calculate the width of the line represented by 801 * the given element. It is assumed that the font 802 * and font metrics are up-to-date. 803 */ 804 @SuppressWarnings("deprecation") 805 private int getLineWidth(Element line) { 806 if (line == null) { 807 return 0; 808 } 809 int p0 = line.getStartOffset(); 810 int p1 = line.getEndOffset(); 811 int w; 812 Segment s = SegmentCache.getSharedSegment(); 813 try { 814 line.getDocument().getText(p0, p1 - p0, s); 815 w = Utilities.getTabbedTextWidth(s, metrics, tabBase, this, p0); 816 } catch (BadLocationException ble) { 817 w = 0; 818 } 819 SegmentCache.releaseSharedSegment(s); 820 return w; 821 } 822 823 static boolean getFPMethodOverridden(Class<?> cls, String method, 824 FPMethodArgs methodArgs) { 825 HashMap<FPMethodItem, Boolean> map = null; 826 boolean initialized = methodsOverriddenMapRef != null 827 && (map = methodsOverriddenMapRef.get()) != null; 828 829 if (!initialized) { 830 map = new HashMap<>(); 831 methodsOverriddenMapRef = new SoftReference<>(map); 832 } 833 834 FPMethodItem key = new FPMethodItem(cls, method); 835 Boolean isFPMethodOverridden = map.get(key); 836 if (isFPMethodOverridden == null) { 837 isFPMethodOverridden = checkFPMethodOverridden(cls, method, methodArgs); 838 map.put(key, isFPMethodOverridden); 839 } 840 return isFPMethodOverridden; 841 } 842 843 private static boolean checkFPMethodOverridden(final Class<?> className, 844 final String methodName, 845 final FPMethodArgs methodArgs) { 846 847 return AccessController 848 .doPrivileged(new PrivilegedAction<Boolean>() { 849 @Override 850 public Boolean run() { 851 return isFPMethodOverridden(methodName, className, 852 methodArgs.getMethodArguments(false), 853 methodArgs.getMethodArguments(true)); 854 } 855 }); 856 } 857 858 private static boolean isFPMethodOverridden(String method, 859 Class<?> cls, 860 Class<?>[] intTypes, 861 Class<?>[] fpTypes) 862 { 863 Module thisModule = PlainView.class.getModule(); 864 while (!thisModule.equals(cls.getModule())) { 865 try { 866 cls.getDeclaredMethod(method, fpTypes); 867 return true; 868 } catch (Exception e1) { 869 try { 870 cls.getDeclaredMethod(method, intTypes); 871 return false; 872 } catch (Exception e2) { 873 cls = cls.getSuperclass(); 874 } 875 } 876 } 877 return true; 878 } 879 880 enum FPMethodArgs { 881 882 IGNN, 883 IIGNN, 884 GNNII, 885 GNNC; 886 887 public Class<?>[] getMethodArguments(boolean isFPType) { 888 Class<?> N = (isFPType) ? Float.TYPE : Integer.TYPE; 889 Class<?> G = (isFPType) ? Graphics2D.class : Graphics.class; 890 switch (this) { 891 case IGNN: 892 return new Class<?>[]{Integer.TYPE, G, N, N}; 893 case IIGNN: 894 return new Class<?>[]{Integer.TYPE, Integer.TYPE, G, N, N}; 895 case GNNII: 896 return new Class<?>[]{G, N, N, Integer.TYPE, Integer.TYPE}; 897 case GNNC: 898 return new Class<?>[]{G, N, N, Character.TYPE}; 899 default: 900 throw new RuntimeException("Unknown method arguments!"); 901 } 902 } 903 } 904 905 private static class FPMethodItem { 906 907 final Class<?> className; 908 final String methodName; 909 910 public FPMethodItem(Class<?> className, String methodName) { 911 this.className = className; 912 this.methodName = methodName; 913 } 914 915 @Override 916 public boolean equals(Object obj) { 917 if (obj instanceof FPMethodItem) { 918 FPMethodItem that = (FPMethodItem) obj; 919 return this.className.equals(that.className) 920 && this.methodName.equals(that.methodName); 921 } 922 return false; 923 } 924 925 @Override 926 public int hashCode() { 927 return 31 * methodName.hashCode() + className.hashCode(); 928 } 929 } 930 931 // --- member variables ----------------------------------------------- 932 933 /** 934 * Font metrics for the current font. 935 */ 936 protected FontMetrics metrics; 937 938 /** 939 * The current longest line. This is used to calculate 940 * the preferred width of the view. Since the calculation 941 * is potentially expensive we try to avoid it by stashing 942 * which line is currently the longest. 943 */ 944 Element longLine; 945 946 /** 947 * Font used to calculate the longest line... if this 948 * changes we need to recalculate the longest line 949 */ 950 Font font; 951 952 Segment lineBuffer; 953 float tabSize; 954 int tabBase; 955 956 int sel0; 957 int sel1; 958 Color unselected; 959 Color selected; 960 961 /** 962 * Offset of where to draw the first character on the first line. 963 * This is a hack and temporary until we can better address the problem 964 * of text measuring. This field is actually never set directly in 965 * PlainView, but by FieldView. 966 */ 967 int firstLineOffset; 968 969 private static SoftReference<HashMap<FPMethodItem, Boolean>> methodsOverriddenMapRef; 970 final boolean drawLineOverridden = 971 getFPMethodOverridden(getClass(), "drawLine", FPMethodArgs.IGNN); 972 final boolean drawSelectedTextOverridden = 973 getFPMethodOverridden(getClass(), "drawSelectedText", FPMethodArgs.GNNII); 974 final boolean drawUnselectedTextOverridden = 975 getFPMethodOverridden(getClass(), "drawUnselectedText", FPMethodArgs.GNNII); 976 final boolean useFloatingPointAPI = 977 drawUnselectedTextOverridden || drawSelectedTextOverridden; 978 }