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

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