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

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