Home » openjdk-7 » javax » swing » text » [javadoc | source]

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

Home » openjdk-7 » javax » swing » text » [javadoc | source]