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