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

    1   /*
    2    * Copyright (c) 1999, 2010, 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.text.BreakIterator;
   29   import javax.swing.event;
   30   import java.util.BitSet;
   31   import java.util.Locale;
   32   
   33   import javax.swing.UIManager;
   34   import sun.swing.SwingUtilities2;
   35   import static sun.swing.SwingUtilities2.IMPLIED_CR;
   36   
   37   /**
   38    * A GlyphView is a styled chunk of text that represents a view
   39    * mapped over an element in the text model. This view is generally
   40    * responsible for displaying text glyphs using character level
   41    * attributes in some way.
   42    * An implementation of the GlyphPainter class is used to do the
   43    * actual rendering and model/view translations.  This separates
   44    * rendering from layout and management of the association with
   45    * the model.
   46    * <p>
   47    * The view supports breaking for the purpose of formatting.
   48    * The fragments produced by breaking share the view that has
   49    * primary responsibility for the element (i.e. they are nested
   50    * classes and carry only a small amount of state of their own)
   51    * so they can share its resources.
   52    * <p>
   53    * Since this view
   54    * represents text that may have tabs embedded in it, it implements the
   55    * <code>TabableView</code> interface.  Tabs will only be
   56    * expanded if this view is embedded in a container that does
   57    * tab expansion.  ParagraphView is an example of a container
   58    * that does tab expansion.
   59    * <p>
   60    *
   61    * @since 1.3
   62    *
   63    * @author  Timothy Prinzing
   64    */
   65   public class GlyphView extends View implements TabableView, Cloneable {
   66   
   67       /**
   68        * Constructs a new view wrapped on an element.
   69        *
   70        * @param elem the element
   71        */
   72       public GlyphView(Element elem) {
   73           super(elem);
   74           offset = 0;
   75           length = 0;
   76           Element parent = elem.getParentElement();
   77           AttributeSet attr = elem.getAttributes();
   78   
   79           //         if there was an implied CR
   80           impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
   81           //         if this is non-empty paragraph
   82                      parent != null && parent.getElementCount() > 1);
   83           skipWidth = elem.getName().equals("br");
   84       }
   85   
   86       /**
   87        * Creates a shallow copy.  This is used by the
   88        * createFragment and breakView methods.
   89        *
   90        * @return the copy
   91        */
   92       protected final Object clone() {
   93           Object o;
   94           try {
   95               o = super.clone();
   96           } catch (CloneNotSupportedException cnse) {
   97               o = null;
   98           }
   99           return o;
  100       }
  101   
  102       /**
  103        * Fetch the currently installed glyph painter.
  104        * If a painter has not yet been installed, and
  105        * a default was not yet needed, null is returned.
  106        */
  107       public GlyphPainter getGlyphPainter() {
  108           return painter;
  109       }
  110   
  111       /**
  112        * Sets the painter to use for rendering glyphs.
  113        */
  114       public void setGlyphPainter(GlyphPainter p) {
  115           painter = p;
  116       }
  117   
  118       /**
  119        * Fetch a reference to the text that occupies
  120        * the given range.  This is normally used by
  121        * the GlyphPainter to determine what characters
  122        * it should render glyphs for.
  123        *
  124        * @param p0  the starting document offset >= 0
  125        * @param p1  the ending document offset >= p0
  126        * @return    the <code>Segment</code> containing the text
  127        */
  128        public Segment getText(int p0, int p1) {
  129            // When done with the returned Segment it should be released by
  130            // invoking:
  131            //    SegmentCache.releaseSharedSegment(segment);
  132            Segment text = SegmentCache.getSharedSegment();
  133            try {
  134                Document doc = getDocument();
  135                doc.getText(p0, p1 - p0, text);
  136            } catch (BadLocationException bl) {
  137                throw new StateInvariantError("GlyphView: Stale view: " + bl);
  138            }
  139            return text;
  140        }
  141   
  142       /**
  143        * Fetch the background color to use to render the
  144        * glyphs.  If there is no background color, null should
  145        * be returned.  This is implemented to call
  146        * <code>StyledDocument.getBackground</code> if the associated
  147        * document is a styled document, otherwise it returns null.
  148        */
  149       public Color getBackground() {
  150           Document doc = getDocument();
  151           if (doc instanceof StyledDocument) {
  152               AttributeSet attr = getAttributes();
  153               if (attr.isDefined(StyleConstants.Background)) {
  154                   return ((StyledDocument)doc).getBackground(attr);
  155               }
  156           }
  157           return null;
  158       }
  159   
  160       /**
  161        * Fetch the foreground color to use to render the
  162        * glyphs.  If there is no foreground color, null should
  163        * be returned.  This is implemented to call
  164        * <code>StyledDocument.getBackground</code> if the associated
  165        * document is a StyledDocument.  If the associated document
  166        * is not a StyledDocument, the associated components foreground
  167        * color is used.  If there is no associated component, null
  168        * is returned.
  169        */
  170       public Color getForeground() {
  171           Document doc = getDocument();
  172           if (doc instanceof StyledDocument) {
  173               AttributeSet attr = getAttributes();
  174               return ((StyledDocument)doc).getForeground(attr);
  175           }
  176           Component c = getContainer();
  177           if (c != null) {
  178               return c.getForeground();
  179           }
  180           return null;
  181       }
  182   
  183       /**
  184        * Fetch the font that the glyphs should be based
  185        * upon.  This is implemented to call
  186        * <code>StyledDocument.getFont</code> if the associated
  187        * document is a StyledDocument.  If the associated document
  188        * is not a StyledDocument, the associated components font
  189        * is used.  If there is no associated component, null
  190        * is returned.
  191        */
  192       public Font getFont() {
  193           Document doc = getDocument();
  194           if (doc instanceof StyledDocument) {
  195               AttributeSet attr = getAttributes();
  196               return ((StyledDocument)doc).getFont(attr);
  197           }
  198           Component c = getContainer();
  199           if (c != null) {
  200               return c.getFont();
  201           }
  202           return null;
  203       }
  204   
  205       /**
  206        * Determine if the glyphs should be underlined.  If true,
  207        * an underline should be drawn through the baseline.
  208        */
  209       public boolean isUnderline() {
  210           AttributeSet attr = getAttributes();
  211           return StyleConstants.isUnderline(attr);
  212       }
  213   
  214       /**
  215        * Determine if the glyphs should have a strikethrough
  216        * line.  If true, a line should be drawn through the center
  217        * of the glyphs.
  218        */
  219       public boolean isStrikeThrough() {
  220           AttributeSet attr = getAttributes();
  221           return StyleConstants.isStrikeThrough(attr);
  222       }
  223   
  224       /**
  225        * Determine if the glyphs should be rendered as superscript.
  226        */
  227       public boolean isSubscript() {
  228           AttributeSet attr = getAttributes();
  229           return StyleConstants.isSubscript(attr);
  230       }
  231   
  232       /**
  233        * Determine if the glyphs should be rendered as subscript.
  234        */
  235       public boolean isSuperscript() {
  236           AttributeSet attr = getAttributes();
  237           return StyleConstants.isSuperscript(attr);
  238       }
  239   
  240       /**
  241        * Fetch the TabExpander to use if tabs are present in this view.
  242        */
  243       public TabExpander getTabExpander() {
  244           return expander;
  245       }
  246   
  247       /**
  248        * Check to see that a glyph painter exists.  If a painter
  249        * doesn't exist, a default glyph painter will be installed.
  250        */
  251       protected void checkPainter() {
  252           if (painter == null) {
  253               if (defaultPainter == null) {
  254                   // the classname should probably come from a property file.
  255                   String classname = "javax.swing.text.GlyphPainter1";
  256                   try {
  257                       Class c;
  258                       ClassLoader loader = getClass().getClassLoader();
  259                       if (loader != null) {
  260                           c = loader.loadClass(classname);
  261                       } else {
  262                           c = Class.forName(classname);
  263                       }
  264                       Object o = c.newInstance();
  265                       if (o instanceof GlyphPainter) {
  266                           defaultPainter = (GlyphPainter) o;
  267                       }
  268                   } catch (Throwable e) {
  269                       throw new StateInvariantError("GlyphView: Can't load glyph painter: "
  270                                                     + classname);
  271                   }
  272               }
  273               setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
  274                                                         getEndOffset()));
  275           }
  276       }
  277   
  278       // --- TabableView methods --------------------------------------
  279   
  280       /**
  281        * Determines the desired span when using the given
  282        * tab expansion implementation.
  283        *
  284        * @param x the position the view would be located
  285        *  at for the purpose of tab expansion >= 0.
  286        * @param e how to expand the tabs when encountered.
  287        * @return the desired span >= 0
  288        * @see TabableView#getTabbedSpan
  289        */
  290       public float getTabbedSpan(float x, TabExpander e) {
  291           checkPainter();
  292   
  293           TabExpander old = expander;
  294           expander = e;
  295   
  296           if (expander != old) {
  297               // setting expander can change horizontal span of the view,
  298               // so we have to call preferenceChanged()
  299               preferenceChanged(null, true, false);
  300           }
  301   
  302           this.x = (int) x;
  303           int p0 = getStartOffset();
  304           int p1 = getEndOffset();
  305           float width = painter.getSpan(this, p0, p1, expander, x);
  306           return width;
  307       }
  308   
  309       /**
  310        * Determines the span along the same axis as tab
  311        * expansion for a portion of the view.  This is
  312        * intended for use by the TabExpander for cases
  313        * where the tab expansion involves aligning the
  314        * portion of text that doesn't have whitespace
  315        * relative to the tab stop.  There is therefore
  316        * an assumption that the range given does not
  317        * contain tabs.
  318        * <p>
  319        * This method can be called while servicing the
  320        * getTabbedSpan or getPreferredSize.  It has to
  321        * arrange for its own text buffer to make the
  322        * measurements.
  323        *
  324        * @param p0 the starting document offset >= 0
  325        * @param p1 the ending document offset >= p0
  326        * @return the span >= 0
  327        */
  328       public float getPartialSpan(int p0, int p1) {
  329           checkPainter();
  330           float width = painter.getSpan(this, p0, p1, expander, x);
  331           return width;
  332       }
  333   
  334       // --- View methods ---------------------------------------------
  335   
  336       /**
  337        * Fetches the portion of the model that this view is responsible for.
  338        *
  339        * @return the starting offset into the model
  340        * @see View#getStartOffset
  341        */
  342       public int getStartOffset() {
  343           Element e = getElement();
  344           return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
  345       }
  346   
  347       /**
  348        * Fetches the portion of the model that this view is responsible for.
  349        *
  350        * @return the ending offset into the model
  351        * @see View#getEndOffset
  352        */
  353       public int getEndOffset() {
  354           Element e = getElement();
  355           return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
  356       }
  357   
  358       /**
  359        * Lazily initializes the selections field
  360        */
  361       private void initSelections(int p0, int p1) {
  362           int viewPosCount = p1 - p0 + 1;
  363           if (selections == null || viewPosCount > selections.length) {
  364               selections = new byte[viewPosCount];
  365               return;
  366           }
  367           for (int i = 0; i < viewPosCount; selections[i++] = 0);
  368       }
  369   
  370       /**
  371        * Renders a portion of a text style run.
  372        *
  373        * @param g the rendering surface to use
  374        * @param a the allocated region to render into
  375        */
  376       public void paint(Graphics g, Shape a) {
  377           checkPainter();
  378   
  379           boolean paintedText = false;
  380           Component c = getContainer();
  381           int p0 = getStartOffset();
  382           int p1 = getEndOffset();
  383           Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  384           Color bg = getBackground();
  385           Color fg = getForeground();
  386   
  387           if (c != null && ! c.isEnabled()) {
  388               fg = (c instanceof JTextComponent ?
  389                   ((JTextComponent)c).getDisabledTextColor() :
  390                   UIManager.getColor("textInactiveText"));
  391           }
  392           if (bg != null) {
  393               g.setColor(bg);
  394               g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
  395           }
  396           if (c instanceof JTextComponent) {
  397               JTextComponent tc = (JTextComponent) c;
  398               Highlighter h = tc.getHighlighter();
  399               if (h instanceof LayeredHighlighter) {
  400                   ((LayeredHighlighter)h).paintLayeredHighlights
  401                       (g, p0, p1, a, tc, this);
  402               }
  403           }
  404   
  405           if (Utilities.isComposedTextElement(getElement())) {
  406               Utilities.paintComposedText(g, a.getBounds(), this);
  407               paintedText = true;
  408           } else if(c instanceof JTextComponent) {
  409               JTextComponent tc = (JTextComponent) c;
  410               Color selFG = tc.getSelectedTextColor();
  411   
  412               if (// there's a highlighter (bug 4532590), and
  413                   (tc.getHighlighter() != null) &&
  414                   // selected text color is different from regular foreground
  415                   (selFG != null) && !selFG.equals(fg)) {
  416   
  417                   Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
  418                   if(h.length != 0) {
  419                       boolean initialized = false;
  420                       int viewSelectionCount = 0;
  421                       for (int i = 0; i < h.length; i++) {
  422                           Highlighter.Highlight highlight = h[i];
  423                           int hStart = highlight.getStartOffset();
  424                           int hEnd = highlight.getEndOffset();
  425                           if (hStart > p1 || hEnd < p0) {
  426                               // the selection is out of this view
  427                               continue;
  428                           }
  429                           if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
  430                               continue;
  431                           }
  432                           if (hStart <= p0 && hEnd >= p1){
  433                               // the whole view is selected
  434                               paintTextUsingColor(g, a, selFG, p0, p1);
  435                               paintedText = true;
  436                               break;
  437                           }
  438                           // the array is lazily created only when the view
  439                           // is partially selected
  440                           if (!initialized) {
  441                               initSelections(p0, p1);
  442                               initialized = true;
  443                           }
  444                           hStart = Math.max(p0, hStart);
  445                           hEnd = Math.min(p1, hEnd);
  446                           paintTextUsingColor(g, a, selFG, hStart, hEnd);
  447                           // the array represents view positions [0, p1-p0+1]
  448                           // later will iterate this array and sum its
  449                           // elements. Positions with sum == 0 are not selected.
  450                           selections[hStart-p0]++;
  451                           selections[hEnd-p0]--;
  452   
  453                           viewSelectionCount++;
  454                       }
  455   
  456                       if (!paintedText && viewSelectionCount > 0) {
  457                           // the view is partially selected
  458                           int curPos = -1;
  459                           int startPos = 0;
  460                           int viewLen = p1 - p0;
  461                           while (curPos++ < viewLen) {
  462                               // searching for the next selection start
  463                               while(curPos < viewLen &&
  464                                       selections[curPos] == 0) curPos++;
  465                               if (startPos != curPos) {
  466                                   // paint unselected text
  467                                   paintTextUsingColor(g, a, fg,
  468                                           p0 + startPos, p0 + curPos);
  469                               }
  470                               int checkSum = 0;
  471                               // searching for next start position of unselected text
  472                               while (curPos < viewLen &&
  473                                       (checkSum += selections[curPos]) != 0) curPos++;
  474                               startPos = curPos;
  475                           }
  476                           paintedText = true;
  477                       }
  478                   }
  479               }
  480           }
  481           if(!paintedText)
  482               paintTextUsingColor(g, a, fg, p0, p1);
  483       }
  484   
  485       /**
  486        * Paints the specified region of text in the specified color.
  487        */
  488       final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
  489           // render the glyphs
  490           g.setColor(c);
  491           painter.paint(this, g, a, p0, p1);
  492   
  493           // render underline or strikethrough if set.
  494           boolean underline = isUnderline();
  495           boolean strike = isStrikeThrough();
  496           if (underline || strike) {
  497               // calculate x coordinates
  498               Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  499               View parent = getParent();
  500               if ((parent != null) && (parent.getEndOffset() == p1)) {
  501                   // strip whitespace on end
  502                   Segment s = getText(p0, p1);
  503                   while (Character.isWhitespace(s.last())) {
  504                       p1 -= 1;
  505                       s.count -= 1;
  506                   }
  507                   SegmentCache.releaseSharedSegment(s);
  508               }
  509               int x0 = alloc.x;
  510               int p = getStartOffset();
  511               if (p != p0) {
  512                   x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
  513               }
  514               int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
  515   
  516               // calculate y coordinate
  517               int y = alloc.y + alloc.height - (int) painter.getDescent(this);
  518               if (underline) {
  519                   int yTmp = y + 1;
  520                   g.drawLine(x0, yTmp, x1, yTmp);
  521               }
  522               if (strike) {
  523                   // move y coordinate above baseline
  524                   int yTmp = y - (int) (painter.getAscent(this) * 0.3f);
  525                   g.drawLine(x0, yTmp, x1, yTmp);
  526               }
  527   
  528           }
  529       }
  530   
  531       /**
  532        * Determines the minimum span for this view along an axis.
  533        *
  534        * <p>This implementation returns the longest non-breakable area within
  535        * the view as a minimum span for {@code View.X_AXIS}.</p>
  536        *
  537        * @param axis  may be either {@code View.X_AXIS} or {@code View.Y_AXIS}
  538        * @return      the minimum span the view can be rendered into
  539        * @throws IllegalArgumentException if the {@code axis} parameter is invalid
  540        * @see         javax.swing.text.View#getMinimumSpan
  541        */
  542       @Override
  543       public float getMinimumSpan(int axis) {
  544           switch (axis) {
  545               case View.X_AXIS:
  546                   if (minimumSpan < 0) {
  547                       minimumSpan = 0;
  548                       int p0 = getStartOffset();
  549                       int p1 = getEndOffset();
  550                       while (p1 > p0) {
  551                           int breakSpot = getBreakSpot(p0, p1);
  552                           if (breakSpot == BreakIterator.DONE) {
  553                               // the rest of the view is non-breakable
  554                               breakSpot = p0;
  555                           }
  556                           minimumSpan = Math.max(minimumSpan,
  557                                   getPartialSpan(breakSpot, p1));
  558                           // Note: getBreakSpot returns the *last* breakspot
  559                           p1 = breakSpot - 1;
  560                       }
  561                   }
  562                   return minimumSpan;
  563               case View.Y_AXIS:
  564                   return super.getMinimumSpan(axis);
  565               default:
  566                   throw new IllegalArgumentException("Invalid axis: " + axis);
  567           }
  568       }
  569   
  570       /**
  571        * Determines the preferred span for this view along an
  572        * axis.
  573        *
  574        * @param axis may be either View.X_AXIS or View.Y_AXIS
  575        * @return   the span the view would like to be rendered into >= 0.
  576        *           Typically the view is told to render into the span
  577        *           that is returned, although there is no guarantee.
  578        *           The parent may choose to resize or break the view.
  579        */
  580       public float getPreferredSpan(int axis) {
  581           if (impliedCR) {
  582               return 0;
  583           }
  584           checkPainter();
  585           int p0 = getStartOffset();
  586           int p1 = getEndOffset();
  587           switch (axis) {
  588           case View.X_AXIS:
  589               if (skipWidth) {
  590                   return 0;
  591               }
  592               return painter.getSpan(this, p0, p1, expander, this.x);
  593           case View.Y_AXIS:
  594               float h = painter.getHeight(this);
  595               if (isSuperscript()) {
  596                   h += h/3;
  597               }
  598               return h;
  599           default:
  600               throw new IllegalArgumentException("Invalid axis: " + axis);
  601           }
  602       }
  603   
  604       /**
  605        * Determines the desired alignment for this view along an
  606        * axis.  For the label, the alignment is along the font
  607        * baseline for the y axis, and the superclasses alignment
  608        * along the x axis.
  609        *
  610        * @param axis may be either View.X_AXIS or View.Y_AXIS
  611        * @return the desired alignment.  This should be a value
  612        *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  613        *   origin and 1.0 indicates alignment to the full span
  614        *   away from the origin.  An alignment of 0.5 would be the
  615        *   center of the view.
  616        */
  617       public float getAlignment(int axis) {
  618           checkPainter();
  619           if (axis == View.Y_AXIS) {
  620               boolean sup = isSuperscript();
  621               boolean sub = isSubscript();
  622               float h = painter.getHeight(this);
  623               float d = painter.getDescent(this);
  624               float a = painter.getAscent(this);
  625               float align;
  626               if (sup) {
  627                   align = 1.0f;
  628               } else if (sub) {
  629                   align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
  630               } else {
  631                   align = (h > 0) ? (h - d) / h : 0;
  632               }
  633               return align;
  634           }
  635           return super.getAlignment(axis);
  636       }
  637   
  638       /**
  639        * Provides a mapping from the document model coordinate space
  640        * to the coordinate space of the view mapped to it.
  641        *
  642        * @param pos the position to convert >= 0
  643        * @param a   the allocated region to render into
  644        * @param b   either <code>Position.Bias.Forward</code>
  645        *                or <code>Position.Bias.Backward</code>
  646        * @return the bounding box of the given position
  647        * @exception BadLocationException  if the given position does not represent a
  648        *   valid location in the associated document
  649        * @see View#modelToView
  650        */
  651       public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  652           checkPainter();
  653           return painter.modelToView(this, pos, b, a);
  654       }
  655   
  656       /**
  657        * Provides a mapping from the view coordinate space to the logical
  658        * coordinate space of the model.
  659        *
  660        * @param x the X coordinate >= 0
  661        * @param y the Y coordinate >= 0
  662        * @param a the allocated region to render into
  663        * @param biasReturn either <code>Position.Bias.Forward</code>
  664        *  or <code>Position.Bias.Backward</code> is returned as the
  665        *  zero-th element of this array
  666        * @return the location within the model that best represents the
  667        *  given point of view >= 0
  668        * @see View#viewToModel
  669        */
  670       public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
  671           checkPainter();
  672           return painter.viewToModel(this, x, y, a, biasReturn);
  673       }
  674   
  675       /**
  676        * Determines how attractive a break opportunity in
  677        * this view is.  This can be used for determining which
  678        * view is the most attractive to call <code>breakView</code>
  679        * on in the process of formatting.  The
  680        * higher the weight, the more attractive the break.  A
  681        * value equal to or lower than <code>View.BadBreakWeight</code>
  682        * should not be considered for a break.  A value greater
  683        * than or equal to <code>View.ForcedBreakWeight</code> should
  684        * be broken.
  685        * <p>
  686        * This is implemented to forward to the superclass for
  687        * the Y_AXIS.  Along the X_AXIS the following values
  688        * may be returned.
  689        * <dl>
  690        * <dt><b>View.ExcellentBreakWeight</b>
  691        * <dd>if there is whitespace proceeding the desired break
  692        *   location.
  693        * <dt><b>View.BadBreakWeight</b>
  694        * <dd>if the desired break location results in a break
  695        *   location of the starting offset.
  696        * <dt><b>View.GoodBreakWeight</b>
  697        * <dd>if the other conditions don't occur.
  698        * </dl>
  699        * This will normally result in the behavior of breaking
  700        * on a whitespace location if one can be found, otherwise
  701        * breaking between characters.
  702        *
  703        * @param axis may be either View.X_AXIS or View.Y_AXIS
  704        * @param pos the potential location of the start of the
  705        *   broken view >= 0.  This may be useful for calculating tab
  706        *   positions.
  707        * @param len specifies the relative length from <em>pos</em>
  708        *   where a potential break is desired >= 0.
  709        * @return the weight, which should be a value between
  710        *   View.ForcedBreakWeight and View.BadBreakWeight.
  711        * @see LabelView
  712        * @see ParagraphView
  713        * @see View#BadBreakWeight
  714        * @see View#GoodBreakWeight
  715        * @see View#ExcellentBreakWeight
  716        * @see View#ForcedBreakWeight
  717        */
  718       public int getBreakWeight(int axis, float pos, float len) {
  719           if (axis == View.X_AXIS) {
  720               checkPainter();
  721               int p0 = getStartOffset();
  722               int p1 = painter.getBoundedPosition(this, p0, pos, len);
  723               return p1 == p0 ? View.BadBreakWeight :
  724                      getBreakSpot(p0, p1) != BreakIterator.DONE ?
  725                               View.ExcellentBreakWeight : View.GoodBreakWeight;
  726           }
  727           return super.getBreakWeight(axis, pos, len);
  728       }
  729   
  730       /**
  731        * Breaks this view on the given axis at the given length.
  732        * This is implemented to attempt to break on a whitespace
  733        * location, and returns a fragment with the whitespace at
  734        * the end.  If a whitespace location can't be found, the
  735        * nearest character is used.
  736        *
  737        * @param axis may be either View.X_AXIS or View.Y_AXIS
  738        * @param p0 the location in the model where the
  739        *  fragment should start it's representation >= 0.
  740        * @param pos the position along the axis that the
  741        *  broken view would occupy >= 0.  This may be useful for
  742        *  things like tab calculations.
  743        * @param len specifies the distance along the axis
  744        *  where a potential break is desired >= 0.
  745        * @return the fragment of the view that represents the
  746        *  given span, if the view can be broken.  If the view
  747        *  doesn't support breaking behavior, the view itself is
  748        *  returned.
  749        * @see View#breakView
  750        */
  751       public View breakView(int axis, int p0, float pos, float len) {
  752           if (axis == View.X_AXIS) {
  753               checkPainter();
  754               int p1 = painter.getBoundedPosition(this, p0, pos, len);
  755               int breakSpot = getBreakSpot(p0, p1);
  756   
  757               if (breakSpot != -1) {
  758                   p1 = breakSpot;
  759               }
  760               // else, no break in the region, return a fragment of the
  761               // bounded region.
  762               if (p0 == getStartOffset() && p1 == getEndOffset()) {
  763                   return this;
  764               }
  765               GlyphView v = (GlyphView) createFragment(p0, p1);
  766               v.x = (int) pos;
  767               return v;
  768           }
  769           return this;
  770       }
  771   
  772       /**
  773        * Returns a location to break at in the passed in region, or
  774        * BreakIterator.DONE if there isn't a good location to break at
  775        * in the specified region.
  776        */
  777       private int getBreakSpot(int p0, int p1) {
  778           if (breakSpots == null) {
  779               // Re-calculate breakpoints for the whole view
  780               int start = getStartOffset();
  781               int end = getEndOffset();
  782               int[] bs = new int[end + 1 - start];
  783               int ix = 0;
  784   
  785               // Breaker should work on the parent element because there may be
  786               // a valid breakpoint at the end edge of the view (space, etc.)
  787               Element parent = getElement().getParentElement();
  788               int pstart = (parent == null ? start : parent.getStartOffset());
  789               int pend = (parent == null ? end : parent.getEndOffset());
  790   
  791               Segment s = getText(pstart, pend);
  792               s.first();
  793               BreakIterator breaker = getBreaker();
  794               breaker.setText(s);
  795   
  796               // Backward search should start from end+1 unless there's NO end+1
  797               int startFrom = end + (pend > end ? 1 : 0);
  798               for (;;) {
  799                   startFrom = breaker.preceding(s.offset + (startFrom - pstart))
  800                             + (pstart - s.offset);
  801                   if (startFrom > start) {
  802                       // The break spot is within the view
  803                       bs[ix++] = startFrom;
  804                   } else {
  805                       break;
  806                   }
  807               }
  808   
  809               SegmentCache.releaseSharedSegment(s);
  810               breakSpots = new int[ix];
  811               System.arraycopy(bs, 0, breakSpots, 0, ix);
  812           }
  813   
  814           int breakSpot = BreakIterator.DONE;
  815           for (int i = 0; i < breakSpots.length; i++) {
  816               int bsp = breakSpots[i];
  817               if (bsp <= p1) {
  818                   if (bsp > p0) {
  819                       breakSpot = bsp;
  820                   }
  821                   break;
  822               }
  823           }
  824           return breakSpot;
  825       }
  826   
  827       /**
  828        * Return break iterator appropriate for the current document.
  829        *
  830        * For non-i18n documents a fast whitespace-based break iterator is used.
  831        */
  832       private BreakIterator getBreaker() {
  833           Document doc = getDocument();
  834           if ((doc != null) && Boolean.TRUE.equals(
  835                       doc.getProperty(AbstractDocument.MultiByteProperty))) {
  836               Container c = getContainer();
  837               Locale locale = (c == null ? Locale.getDefault() : c.getLocale());
  838               return BreakIterator.getLineInstance(locale);
  839           } else {
  840               return new WhitespaceBasedBreakIterator();
  841           }
  842       }
  843   
  844       /**
  845        * Creates a view that represents a portion of the element.
  846        * This is potentially useful during formatting operations
  847        * for taking measurements of fragments of the view.  If
  848        * the view doesn't support fragmenting (the default), it
  849        * should return itself.
  850        * <p>
  851        * This view does support fragmenting.  It is implemented
  852        * to return a nested class that shares state in this view
  853        * representing only a portion of the view.
  854        *
  855        * @param p0 the starting offset >= 0.  This should be a value
  856        *   greater or equal to the element starting offset and
  857        *   less than the element ending offset.
  858        * @param p1 the ending offset > p0.  This should be a value
  859        *   less than or equal to the elements end offset and
  860        *   greater than the elements starting offset.
  861        * @return the view fragment, or itself if the view doesn't
  862        *   support breaking into fragments
  863        * @see LabelView
  864        */
  865       public View createFragment(int p0, int p1) {
  866           checkPainter();
  867           Element elem = getElement();
  868           GlyphView v = (GlyphView) clone();
  869           v.offset = p0 - elem.getStartOffset();
  870           v.length = p1 - p0;
  871           v.painter = painter.getPainter(v, p0, p1);
  872           v.justificationInfo = null;
  873           return v;
  874       }
  875   
  876       /**
  877        * Provides a way to determine the next visually represented model
  878        * location that one might place a caret.  Some views may not be
  879        * visible, they might not be in the same order found in the model, or
  880        * they just might not allow access to some of the locations in the
  881        * model.
  882        *
  883        * @param pos the position to convert >= 0
  884        * @param a the allocated region to render into
  885        * @param direction the direction from the current position that can
  886        *  be thought of as the arrow keys typically found on a keyboard.
  887        *  This may be SwingConstants.WEST, SwingConstants.EAST,
  888        *  SwingConstants.NORTH, or SwingConstants.SOUTH.
  889        * @return the location within the model that best represents the next
  890        *  location visual position.
  891        * @exception BadLocationException
  892        * @exception IllegalArgumentException for an invalid direction
  893        */
  894       public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  895                                            int direction,
  896                                            Position.Bias[] biasRet)
  897           throws BadLocationException {
  898   
  899           return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
  900       }
  901   
  902       /**
  903        * Gives notification that something was inserted into
  904        * the document in a location that this view is responsible for.
  905        * This is implemented to call preferenceChanged along the
  906        * axis the glyphs are rendered.
  907        *
  908        * @param e the change information from the associated document
  909        * @param a the current allocation of the view
  910        * @param f the factory to use to rebuild if the view has children
  911        * @see View#insertUpdate
  912        */
  913       public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  914           justificationInfo = null;
  915           breakSpots = null;
  916           minimumSpan = -1;
  917           syncCR();
  918           preferenceChanged(null, true, false);
  919       }
  920   
  921       /**
  922        * Gives notification that something was removed from the document
  923        * in a location that this view is responsible for.
  924        * This is implemented to call preferenceChanged along the
  925        * axis the glyphs are rendered.
  926        *
  927        * @param e the change information from the associated document
  928        * @param a the current allocation of the view
  929        * @param f the factory to use to rebuild if the view has children
  930        * @see View#removeUpdate
  931        */
  932       public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  933           justificationInfo = null;
  934           breakSpots = null;
  935           minimumSpan = -1;
  936           syncCR();
  937           preferenceChanged(null, true, false);
  938       }
  939   
  940       /**
  941        * Gives notification from the document that attributes were changed
  942        * in a location that this view is responsible for.
  943        * This is implemented to call preferenceChanged along both the
  944        * horizontal and vertical axis.
  945        *
  946        * @param e the change information from the associated document
  947        * @param a the current allocation of the view
  948        * @param f the factory to use to rebuild if the view has children
  949        * @see View#changedUpdate
  950        */
  951       public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  952           minimumSpan = -1;
  953           syncCR();
  954           preferenceChanged(null, true, true);
  955       }
  956   
  957       // checks if the paragraph is empty and updates impliedCR flag
  958       // accordingly
  959       private void syncCR() {
  960           if (impliedCR) {
  961               Element parent = getElement().getParentElement();
  962               impliedCR = (parent != null && parent.getElementCount() > 1);
  963           }
  964       }
  965   
  966       /**
  967        * Class to hold data needed to justify this GlyphView in a PargraphView.Row
  968        */
  969       static class JustificationInfo {
  970           //justifiable content start
  971           final int start;
  972           //justifiable content end
  973           final int end;
  974           final int leadingSpaces;
  975           final int contentSpaces;
  976           final int trailingSpaces;
  977           final boolean hasTab;
  978           final BitSet spaceMap;
  979           JustificationInfo(int start, int end,
  980                             int leadingSpaces,
  981                             int contentSpaces,
  982                             int trailingSpaces,
  983                             boolean hasTab,
  984                             BitSet spaceMap) {
  985               this.start = start;
  986               this.end = end;
  987               this.leadingSpaces = leadingSpaces;
  988               this.contentSpaces = contentSpaces;
  989               this.trailingSpaces = trailingSpaces;
  990               this.hasTab = hasTab;
  991               this.spaceMap = spaceMap;
  992           }
  993       }
  994   
  995   
  996   
  997       JustificationInfo getJustificationInfo(int rowStartOffset) {
  998           if (justificationInfo != null) {
  999               return justificationInfo;
 1000           }
 1001           //states for the parsing
 1002           final int TRAILING = 0;
 1003           final int CONTENT  = 1;
 1004           final int SPACES   = 2;
 1005           int startOffset = getStartOffset();
 1006           int endOffset = getEndOffset();
 1007           Segment segment = getText(startOffset, endOffset);
 1008           int txtOffset = segment.offset;
 1009           int txtEnd = segment.offset + segment.count - 1;
 1010           int startContentPosition = txtEnd + 1;
 1011           int endContentPosition = txtOffset - 1;
 1012           int lastTabPosition = txtOffset - 1;
 1013           int trailingSpaces = 0;
 1014           int contentSpaces = 0;
 1015           int leadingSpaces = 0;
 1016           boolean hasTab = false;
 1017           BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
 1018   
 1019           //we parse conent to the right of the rightmost TAB only.
 1020           //we are looking for the trailing and leading spaces.
 1021           //position after the leading spaces (startContentPosition)
 1022           //position before the trailing spaces (endContentPosition)
 1023           for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
 1024               if (' ' == segment.array[i]) {
 1025                   spaceMap.set(i - txtOffset);
 1026                   if (state == TRAILING) {
 1027                       trailingSpaces++;
 1028                   } else if (state == CONTENT) {
 1029                       state = SPACES;
 1030                       leadingSpaces = 1;
 1031                   } else if (state == SPACES) {
 1032                       leadingSpaces++;
 1033                   }
 1034               } else if ('\t' == segment.array[i]) {
 1035                   hasTab = true;
 1036                   break;
 1037               } else {
 1038                   if (state == TRAILING) {
 1039                       if ('\n' != segment.array[i]
 1040                             && '\r' != segment.array[i]) {
 1041                           state = CONTENT;
 1042                           endContentPosition = i;
 1043                       }
 1044                   } else if (state == CONTENT) {
 1045                       //do nothing
 1046                   } else if (state == SPACES) {
 1047                       contentSpaces += leadingSpaces;
 1048                       leadingSpaces = 0;
 1049                   }
 1050                   startContentPosition = i;
 1051               }
 1052           }
 1053   
 1054           SegmentCache.releaseSharedSegment(segment);
 1055   
 1056           int startJustifiableContent = -1;
 1057           if (startContentPosition < txtEnd) {
 1058               startJustifiableContent =
 1059                   startContentPosition - txtOffset;
 1060           }
 1061           int endJustifiableContent = -1;
 1062           if (endContentPosition > txtOffset) {
 1063               endJustifiableContent =
 1064                   endContentPosition - txtOffset;
 1065           }
 1066           justificationInfo =
 1067               new JustificationInfo(startJustifiableContent,
 1068                                     endJustifiableContent,
 1069                                     leadingSpaces,
 1070                                     contentSpaces,
 1071                                     trailingSpaces,
 1072                                     hasTab,
 1073                                     spaceMap);
 1074           return justificationInfo;
 1075       }
 1076   
 1077       // --- variables ------------------------------------------------
 1078   
 1079       /**
 1080       * Used by paint() to store highlighted view positions
 1081       */
 1082       private byte[] selections = null;
 1083   
 1084       int offset;
 1085       int length;
 1086       // if it is an implied newline character
 1087       boolean impliedCR;
 1088       boolean skipWidth;
 1089   
 1090       /**
 1091        * how to expand tabs
 1092        */
 1093       TabExpander expander;
 1094   
 1095       /** Cached minimum x-span value  */
 1096       private float minimumSpan = -1;
 1097   
 1098       /** Cached breakpoints within the view  */
 1099       private int[] breakSpots = null;
 1100   
 1101       /**
 1102        * location for determining tab expansion against.
 1103        */
 1104       int x;
 1105   
 1106       /**
 1107        * Glyph rendering functionality.
 1108        */
 1109       GlyphPainter painter;
 1110   
 1111       /**
 1112        * The prototype painter used by default.
 1113        */
 1114       static GlyphPainter defaultPainter;
 1115   
 1116       private JustificationInfo justificationInfo = null;
 1117   
 1118       /**
 1119        * A class to perform rendering of the glyphs.
 1120        * This can be implemented to be stateless, or
 1121        * to hold some information as a cache to
 1122        * facilitate faster rendering and model/view
 1123        * translation.  At a minimum, the GlyphPainter
 1124        * allows a View implementation to perform its
 1125        * duties independant of a particular version
 1126        * of JVM and selection of capabilities (i.e.
 1127        * shaping for i18n, etc).
 1128        *
 1129        * @since 1.3
 1130        */
 1131       public static abstract class GlyphPainter {
 1132   
 1133           /**
 1134            * Determine the span the glyphs given a start location
 1135            * (for tab expansion).
 1136            */
 1137           public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
 1138   
 1139           public abstract float getHeight(GlyphView v);
 1140   
 1141           public abstract float getAscent(GlyphView v);
 1142   
 1143           public abstract float getDescent(GlyphView v);
 1144   
 1145           /**
 1146            * Paint the glyphs representing the given range.
 1147            */
 1148           public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
 1149   
 1150           /**
 1151            * Provides a mapping from the document model coordinate space
 1152            * to the coordinate space of the view mapped to it.
 1153            * This is shared by the broken views.
 1154            *
 1155            * @param v     the <code>GlyphView</code> containing the
 1156            *              destination coordinate space
 1157            * @param pos   the position to convert
 1158            * @param bias  either <code>Position.Bias.Forward</code>
 1159            *                  or <code>Position.Bias.Backward</code>
 1160            * @param a     Bounds of the View
 1161            * @return      the bounding box of the given position
 1162            * @exception BadLocationException  if the given position does not represent a
 1163            *   valid location in the associated document
 1164            * @see View#modelToView
 1165            */
 1166           public abstract Shape modelToView(GlyphView v,
 1167                                             int pos, Position.Bias bias,
 1168                                             Shape a) throws BadLocationException;
 1169   
 1170           /**
 1171            * Provides a mapping from the view coordinate space to the logical
 1172            * coordinate space of the model.
 1173            *
 1174            * @param v          the <code>GlyphView</code> to provide a mapping for
 1175            * @param x          the X coordinate
 1176            * @param y          the Y coordinate
 1177            * @param a          the allocated region to render into
 1178            * @param biasReturn either <code>Position.Bias.Forward</code>
 1179            *                   or <code>Position.Bias.Backward</code>
 1180            *                   is returned as the zero-th element of this array
 1181            * @return the location within the model that best represents the
 1182            *         given point of view
 1183            * @see View#viewToModel
 1184            */
 1185           public abstract int viewToModel(GlyphView v,
 1186                                           float x, float y, Shape a,
 1187                                           Position.Bias[] biasReturn);
 1188   
 1189           /**
 1190            * Determines the model location that represents the
 1191            * maximum advance that fits within the given span.
 1192            * This could be used to break the given view.  The result
 1193            * should be a location just shy of the given advance.  This
 1194            * differs from viewToModel which returns the closest
 1195            * position which might be proud of the maximum advance.
 1196            *
 1197            * @param v the view to find the model location to break at.
 1198            * @param p0 the location in the model where the
 1199            *  fragment should start it's representation >= 0.
 1200            * @param x  the graphic location along the axis that the
 1201            *  broken view would occupy >= 0.  This may be useful for
 1202            *  things like tab calculations.
 1203            * @param len specifies the distance into the view
 1204            *  where a potential break is desired >= 0.
 1205            * @return the maximum model location possible for a break.
 1206            * @see View#breakView
 1207            */
 1208           public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
 1209   
 1210           /**
 1211            * Create a painter to use for the given GlyphView.  If
 1212            * the painter carries state it can create another painter
 1213            * to represent a new GlyphView that is being created.  If
 1214            * the painter doesn't hold any significant state, it can
 1215            * return itself.  The default behavior is to return itself.
 1216            * @param v  the <code>GlyphView</code> to provide a painter for
 1217            * @param p0 the starting document offset >= 0
 1218            * @param p1 the ending document offset >= p0
 1219            */
 1220           public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
 1221               return this;
 1222           }
 1223   
 1224           /**
 1225            * Provides a way to determine the next visually represented model
 1226            * location that one might place a caret.  Some views may not be
 1227            * visible, they might not be in the same order found in the model, or
 1228            * they just might not allow access to some of the locations in the
 1229            * model.
 1230            *
 1231            * @param v the view to use
 1232            * @param pos the position to convert >= 0
 1233            * @param b   either <code>Position.Bias.Forward</code>
 1234            *                or <code>Position.Bias.Backward</code>
 1235            * @param a the allocated region to render into
 1236            * @param direction the direction from the current position that can
 1237            *  be thought of as the arrow keys typically found on a keyboard.
 1238            *  This may be SwingConstants.WEST, SwingConstants.EAST,
 1239            *  SwingConstants.NORTH, or SwingConstants.SOUTH.
 1240            * @param biasRet  either <code>Position.Bias.Forward</code>
 1241            *                 or <code>Position.Bias.Backward</code>
 1242            *                 is returned as the zero-th element of this array
 1243            * @return the location within the model that best represents the next
 1244            *  location visual position.
 1245            * @exception BadLocationException
 1246            * @exception IllegalArgumentException for an invalid direction
 1247            */
 1248           public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
 1249                                                int direction,
 1250                                                Position.Bias[] biasRet)
 1251               throws BadLocationException {
 1252   
 1253               int startOffset = v.getStartOffset();
 1254               int endOffset = v.getEndOffset();
 1255               Segment text;
 1256   
 1257               switch (direction) {
 1258               case View.NORTH:
 1259               case View.SOUTH:
 1260                   if (pos != -1) {
 1261                       // Presumably pos is between startOffset and endOffset,
 1262                       // since GlyphView is only one line, we won't contain
 1263                       // the position to the nort/south, therefore return -1.
 1264                       return -1;
 1265                   }
 1266                   Container container = v.getContainer();
 1267   
 1268                   if (container instanceof JTextComponent) {
 1269                       Caret c = ((JTextComponent)container).getCaret();
 1270                       Point magicPoint;
 1271                       magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
 1272   
 1273                       if (magicPoint == null) {
 1274                           biasRet[0] = Position.Bias.Forward;
 1275                           return startOffset;
 1276                       }
 1277                       int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
 1278                       return value;
 1279                   }
 1280                   break;
 1281               case View.EAST:
 1282                   if(startOffset == v.getDocument().getLength()) {
 1283                       if(pos == -1) {
 1284                           biasRet[0] = Position.Bias.Forward;
 1285                           return startOffset;
 1286                       }
 1287                       // End case for bidi text where newline is at beginning
 1288                       // of line.
 1289                       return -1;
 1290                   }
 1291                   if(pos == -1) {
 1292                       biasRet[0] = Position.Bias.Forward;
 1293                       return startOffset;
 1294                   }
 1295                   if(pos == endOffset) {
 1296                       return -1;
 1297                   }
 1298                   if(++pos == endOffset) {
 1299                       // Assumed not used in bidi text, GlyphPainter2 will
 1300                       // override as necessary, therefore return -1.
 1301                       return -1;
 1302                   }
 1303                   else {
 1304                       biasRet[0] = Position.Bias.Forward;
 1305                   }
 1306                   return pos;
 1307               case View.WEST:
 1308                   if(startOffset == v.getDocument().getLength()) {
 1309                       if(pos == -1) {
 1310                           biasRet[0] = Position.Bias.Forward;
 1311                           return startOffset;
 1312                       }
 1313                       // End case for bidi text where newline is at beginning
 1314                       // of line.
 1315                       return -1;
 1316                   }
 1317                   if(pos == -1) {
 1318                       // Assumed not used in bidi text, GlyphPainter2 will
 1319                       // override as necessary, therefore return -1.
 1320                       biasRet[0] = Position.Bias.Forward;
 1321                       return endOffset - 1;
 1322                   }
 1323                   if(pos == startOffset) {
 1324                       return -1;
 1325                   }
 1326                   biasRet[0] = Position.Bias.Forward;
 1327                   return (pos - 1);
 1328               default:
 1329                   throw new IllegalArgumentException("Bad direction: " + direction);
 1330               }
 1331               return pos;
 1332   
 1333           }
 1334       }
 1335   }

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