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

    1   /*
    2    * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.lang.reflect.Method;
   28   
   29   import java.awt.Component;
   30   import java.awt.Rectangle;
   31   import java.awt.Graphics;
   32   import java.awt.FontMetrics;
   33   import java.awt.Shape;
   34   import java.awt.Toolkit;
   35   import java.awt.Graphics2D;
   36   import java.awt.font.FontRenderContext;
   37   import java.awt.font.TextLayout;
   38   import java.awt.font.TextAttribute;
   39   
   40   import java.text;
   41   import javax.swing.JComponent;
   42   import javax.swing.SwingConstants;
   43   import javax.swing.text.ParagraphView.Row;
   44   import sun.swing.SwingUtilities2;
   45   
   46   /**
   47    * A collection of methods to deal with various text
   48    * related activities.
   49    *
   50    * @author  Timothy Prinzing
   51    */
   52   public class Utilities {
   53       /**
   54        * If <code>view</code>'s container is a <code>JComponent</code> it
   55        * is returned, after casting.
   56        */
   57       static JComponent getJComponent(View view) {
   58           if (view != null) {
   59               Component component = view.getContainer();
   60               if (component instanceof JComponent) {
   61                   return (JComponent)component;
   62               }
   63           }
   64           return null;
   65       }
   66   
   67       /**
   68        * Draws the given text, expanding any tabs that are contained
   69        * using the given tab expansion technique.  This particular
   70        * implementation renders in a 1.1 style coordinate system
   71        * where ints are used and 72dpi is assumed.
   72        *
   73        * @param s  the source of the text
   74        * @param x  the X origin >= 0
   75        * @param y  the Y origin >= 0
   76        * @param g  the graphics context
   77        * @param e  how to expand the tabs.  If this value is null,
   78        *   tabs will be expanded as a space character.
   79        * @param startOffset starting offset of the text in the document >= 0
   80        * @return  the X location at the end of the rendered text
   81        */
   82       public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
   83                                              TabExpander e, int startOffset) {
   84           return drawTabbedText(null, s, x, y, g, e, startOffset);
   85       }
   86   
   87       /**
   88        * Draws the given text, expanding any tabs that are contained
   89        * using the given tab expansion technique.  This particular
   90        * implementation renders in a 1.1 style coordinate system
   91        * where ints are used and 72dpi is assumed.
   92        *
   93        * @param view View requesting rendering, may be null.
   94        * @param s  the source of the text
   95        * @param x  the X origin >= 0
   96        * @param y  the Y origin >= 0
   97        * @param g  the graphics context
   98        * @param e  how to expand the tabs.  If this value is null,
   99        *   tabs will be expanded as a space character.
  100        * @param startOffset starting offset of the text in the document >= 0
  101        * @return  the X location at the end of the rendered text
  102        */
  103       static final int drawTabbedText(View view,
  104                                   Segment s, int x, int y, Graphics g,
  105                                   TabExpander e, int startOffset) {
  106           return drawTabbedText(view, s, x, y, g, e, startOffset, null);
  107       }
  108   
  109       // In addition to the previous method it can extend spaces for
  110       // justification.
  111       //
  112       // all params are the same as in the preious method except the last
  113       // one:
  114       // @param justificationData justificationData for the row.
  115       // if null not justification is needed
  116       static final int drawTabbedText(View view,
  117                                   Segment s, int x, int y, Graphics g,
  118                                   TabExpander e, int startOffset,
  119                                   int [] justificationData) {
  120           JComponent component = getJComponent(view);
  121           FontMetrics metrics = SwingUtilities2.getFontMetrics(component, g);
  122           int nextX = x;
  123           char[] txt = s.array;
  124           int txtOffset = s.offset;
  125           int flushLen = 0;
  126           int flushIndex = s.offset;
  127           int spaceAddon = 0;
  128           int spaceAddonLeftoverEnd = -1;
  129           int startJustifiableContent = 0;
  130           int endJustifiableContent = 0;
  131           if (justificationData != null) {
  132               int offset = - startOffset + txtOffset;
  133               View parent = null;
  134               if (view != null
  135                     && (parent = view.getParent()) != null) {
  136                   offset += parent.getStartOffset();
  137               }
  138               spaceAddon =
  139                   justificationData[Row.SPACE_ADDON];
  140               spaceAddonLeftoverEnd =
  141                   justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
  142               startJustifiableContent =
  143                   justificationData[Row.START_JUSTIFIABLE] + offset;
  144               endJustifiableContent =
  145                   justificationData[Row.END_JUSTIFIABLE] + offset;
  146           }
  147           int n = s.offset + s.count;
  148           for (int i = txtOffset; i < n; i++) {
  149               if (txt[i] == '\t'
  150                   || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
  151                       && (txt[i] == ' ')
  152                       && startJustifiableContent <= i
  153                       && i <= endJustifiableContent
  154                       )) {
  155                   if (flushLen > 0) {
  156                       nextX = SwingUtilities2.drawChars(component, g, txt,
  157                                                   flushIndex, flushLen, x, y);
  158                       flushLen = 0;
  159                   }
  160                   flushIndex = i + 1;
  161                   if (txt[i] == '\t') {
  162                       if (e != null) {
  163                           nextX = (int) e.nextTabStop((float) nextX, startOffset + i - txtOffset);
  164                       } else {
  165                           nextX += metrics.charWidth(' ');
  166                       }
  167                   } else if (txt[i] == ' ') {
  168                       nextX += metrics.charWidth(' ') + spaceAddon;
  169                       if (i <= spaceAddonLeftoverEnd) {
  170                           nextX++;
  171                       }
  172                   }
  173                   x = nextX;
  174               } else if ((txt[i] == '\n') || (txt[i] == '\r')) {
  175                   if (flushLen > 0) {
  176                       nextX = SwingUtilities2.drawChars(component, g, txt,
  177                                                   flushIndex, flushLen, x, y);
  178                       flushLen = 0;
  179                   }
  180                   flushIndex = i + 1;
  181                   x = nextX;
  182               } else {
  183                   flushLen += 1;
  184               }
  185           }
  186           if (flushLen > 0) {
  187               nextX = SwingUtilities2.drawChars(component, g,txt, flushIndex,
  188                                                 flushLen, x, y);
  189           }
  190           return nextX;
  191       }
  192   
  193       /**
  194        * Determines the width of the given segment of text taking tabs
  195        * into consideration.  This is implemented in a 1.1 style coordinate
  196        * system where ints are used and 72dpi is assumed.
  197        *
  198        * @param s  the source of the text
  199        * @param metrics the font metrics to use for the calculation
  200        * @param x  the X origin >= 0
  201        * @param e  how to expand the tabs.  If this value is null,
  202        *   tabs will be expanded as a space character.
  203        * @param startOffset starting offset of the text in the document >= 0
  204        * @return  the width of the text
  205        */
  206       public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x,
  207                                                  TabExpander e, int startOffset) {
  208           return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null);
  209       }
  210   
  211   
  212       // In addition to the previous method it can extend spaces for
  213       // justification.
  214       //
  215       // all params are the same as in the preious method except the last
  216       // one:
  217       // @param justificationData justificationData for the row.
  218       // if null not justification is needed
  219       static final int getTabbedTextWidth(View view, Segment s, FontMetrics metrics, int x,
  220                                           TabExpander e, int startOffset,
  221                                           int[] justificationData) {
  222           int nextX = x;
  223           char[] txt = s.array;
  224           int txtOffset = s.offset;
  225           int n = s.offset + s.count;
  226           int charCount = 0;
  227           int spaceAddon = 0;
  228           int spaceAddonLeftoverEnd = -1;
  229           int startJustifiableContent = 0;
  230           int endJustifiableContent = 0;
  231           if (justificationData != null) {
  232               int offset = - startOffset + txtOffset;
  233               View parent = null;
  234               if (view != null
  235                     && (parent = view.getParent()) != null) {
  236                   offset += parent.getStartOffset();
  237               }
  238               spaceAddon =
  239                   justificationData[Row.SPACE_ADDON];
  240               spaceAddonLeftoverEnd =
  241                   justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
  242               startJustifiableContent =
  243                   justificationData[Row.START_JUSTIFIABLE] + offset;
  244               endJustifiableContent =
  245                   justificationData[Row.END_JUSTIFIABLE] + offset;
  246           }
  247   
  248           for (int i = txtOffset; i < n; i++) {
  249               if (txt[i] == '\t'
  250                   || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
  251                       && (txt[i] == ' ')
  252                       && startJustifiableContent <= i
  253                       && i <= endJustifiableContent
  254                       )) {
  255                   nextX += metrics.charsWidth(txt, i-charCount, charCount);
  256                   charCount = 0;
  257                   if (txt[i] == '\t') {
  258                       if (e != null) {
  259                           nextX = (int) e.nextTabStop((float) nextX,
  260                                                       startOffset + i - txtOffset);
  261                       } else {
  262                           nextX += metrics.charWidth(' ');
  263                       }
  264                   } else if (txt[i] == ' ') {
  265                       nextX += metrics.charWidth(' ') + spaceAddon;
  266                       if (i <= spaceAddonLeftoverEnd) {
  267                           nextX++;
  268                       }
  269                   }
  270               } else if(txt[i] == '\n') {
  271               // Ignore newlines, they take up space and we shouldn't be
  272               // counting them.
  273                   nextX += metrics.charsWidth(txt, i - charCount, charCount);
  274                   charCount = 0;
  275               } else {
  276                   charCount++;
  277           }
  278           }
  279           nextX += metrics.charsWidth(txt, n - charCount, charCount);
  280           return nextX - x;
  281       }
  282   
  283       /**
  284        * Determines the relative offset into the given text that
  285        * best represents the given span in the view coordinate
  286        * system.  This is implemented in a 1.1 style coordinate
  287        * system where ints are used and 72dpi is assumed.
  288        *
  289        * @param s  the source of the text
  290        * @param metrics the font metrics to use for the calculation
  291        * @param x0 the starting view location representing the start
  292        *   of the given text >= 0.
  293        * @param x  the target view location to translate to an
  294        *   offset into the text >= 0.
  295        * @param e  how to expand the tabs.  If this value is null,
  296        *   tabs will be expanded as a space character.
  297        * @param startOffset starting offset of the text in the document >= 0
  298        * @return  the offset into the text >= 0
  299        */
  300       public static final int getTabbedTextOffset(Segment s, FontMetrics metrics,
  301                                                int x0, int x, TabExpander e,
  302                                                int startOffset) {
  303           return getTabbedTextOffset(s, metrics, x0, x, e, startOffset, true);
  304       }
  305   
  306       static final int getTabbedTextOffset(View view, Segment s, FontMetrics metrics,
  307                                            int x0, int x, TabExpander e,
  308                                            int startOffset,
  309                                            int[] justificationData) {
  310           return getTabbedTextOffset(view, s, metrics, x0, x, e, startOffset, true,
  311                                      justificationData);
  312       }
  313   
  314       public static final int getTabbedTextOffset(Segment s,
  315                                                   FontMetrics metrics,
  316                                                   int x0, int x, TabExpander e,
  317                                                   int startOffset,
  318                                                   boolean round) {
  319           return getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset, round, null);
  320       }
  321   
  322       // In addition to the previous method it can extend spaces for
  323       // justification.
  324       //
  325       // all params are the same as in the preious method except the last
  326       // one:
  327       // @param justificationData justificationData for the row.
  328       // if null not justification is needed
  329       static final int getTabbedTextOffset(View view,
  330                                            Segment s,
  331                                            FontMetrics metrics,
  332                                            int x0, int x, TabExpander e,
  333                                            int startOffset,
  334                                            boolean round,
  335                                            int[] justificationData) {
  336           if (x0 >= x) {
  337               // x before x0, return.
  338               return 0;
  339           }
  340           int nextX = x0;
  341           // s may be a shared segment, so it is copied prior to calling
  342           // the tab expander
  343           char[] txt = s.array;
  344           int txtOffset = s.offset;
  345           int txtCount = s.count;
  346           int spaceAddon = 0 ;
  347           int spaceAddonLeftoverEnd = -1;
  348           int startJustifiableContent = 0 ;
  349           int endJustifiableContent = 0;
  350           if (justificationData != null) {
  351               int offset = - startOffset + txtOffset;
  352               View parent = null;
  353               if (view != null
  354                     && (parent = view.getParent()) != null) {
  355                   offset += parent.getStartOffset();
  356               }
  357               spaceAddon =
  358                   justificationData[Row.SPACE_ADDON];
  359               spaceAddonLeftoverEnd =
  360                   justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
  361               startJustifiableContent =
  362                   justificationData[Row.START_JUSTIFIABLE] + offset;
  363               endJustifiableContent =
  364                   justificationData[Row.END_JUSTIFIABLE] + offset;
  365           }
  366           int n = s.offset + s.count;
  367           for (int i = s.offset; i < n; i++) {
  368               if (txt[i] == '\t'
  369                   || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
  370                       && (txt[i] == ' ')
  371                       && startJustifiableContent <= i
  372                       && i <= endJustifiableContent
  373                       )){
  374                   if (txt[i] == '\t') {
  375                       if (e != null) {
  376                           nextX = (int) e.nextTabStop((float) nextX,
  377                                                       startOffset + i - txtOffset);
  378                       } else {
  379                           nextX += metrics.charWidth(' ');
  380                       }
  381                   } else if (txt[i] == ' ') {
  382                       nextX += metrics.charWidth(' ') + spaceAddon;
  383                       if (i <= spaceAddonLeftoverEnd) {
  384                           nextX++;
  385                       }
  386                   }
  387               } else {
  388                   nextX += metrics.charWidth(txt[i]);
  389               }
  390               if (x < nextX) {
  391                   // found the hit position... return the appropriate side
  392                   int offset;
  393   
  394                   // the length of the string measured as a whole may differ from
  395                   // the sum of individual character lengths, for example if
  396                   // fractional metrics are enabled; and we must guard from this.
  397                   if (round) {
  398                       offset = i + 1 - txtOffset;
  399   
  400                       int width = metrics.charsWidth(txt, txtOffset, offset);
  401                       int span = x - x0;
  402   
  403                       if (span < width) {
  404                           while (offset > 0) {
  405                               int nextWidth = offset > 1 ? metrics.charsWidth(txt, txtOffset, offset - 1) : 0;
  406   
  407                               if (span >= nextWidth) {
  408                                   if (span - nextWidth < width - span) {
  409                                       offset--;
  410                                   }
  411   
  412                                   break;
  413                               }
  414   
  415                               width = nextWidth;
  416                               offset--;
  417                           }
  418                       }
  419                   } else {
  420                       offset = i - txtOffset;
  421   
  422                       while (offset > 0 && metrics.charsWidth(txt, txtOffset, offset) > (x - x0)) {
  423                           offset--;
  424                       }
  425                   }
  426   
  427                   return offset;
  428               }
  429           }
  430   
  431           // didn't find, return end offset
  432           return txtCount;
  433       }
  434   
  435       /**
  436        * Determine where to break the given text to fit
  437        * within the given span. This tries to find a word boundary.
  438        * @param s  the source of the text
  439        * @param metrics the font metrics to use for the calculation
  440        * @param x0 the starting view location representing the start
  441        *   of the given text.
  442        * @param x  the target view location to translate to an
  443        *   offset into the text.
  444        * @param e  how to expand the tabs.  If this value is null,
  445        *   tabs will be expanded as a space character.
  446        * @param startOffset starting offset in the document of the text
  447        * @return  the offset into the given text
  448        */
  449       public static final int getBreakLocation(Segment s, FontMetrics metrics,
  450                                                int x0, int x, TabExpander e,
  451                                                int startOffset) {
  452           char[] txt = s.array;
  453           int txtOffset = s.offset;
  454           int txtCount = s.count;
  455           int index = Utilities.getTabbedTextOffset(s, metrics, x0, x,
  456                                                     e, startOffset, false);
  457   
  458           if (index >= txtCount - 1) {
  459               return txtCount;
  460           }
  461   
  462           for (int i = txtOffset + index; i >= txtOffset; i--) {
  463               char ch = txt[i];
  464               if (ch < 256) {
  465                   // break on whitespace
  466                   if (Character.isWhitespace(ch)) {
  467                       index = i - txtOffset + 1;
  468                       break;
  469                   }
  470               } else {
  471                   // a multibyte char found; use BreakIterator to find line break
  472                   BreakIterator bit = BreakIterator.getLineInstance();
  473                   bit.setText(s);
  474                   int breakPos = bit.preceding(i + 1);
  475                   if (breakPos > txtOffset) {
  476                       index = breakPos - txtOffset;
  477                   }
  478                   break;
  479               }
  480           }
  481           return index;
  482       }
  483   
  484       /**
  485        * Determines the starting row model position of the row that contains
  486        * the specified model position.  The component given must have a
  487        * size to compute the result.  If the component doesn't have a size
  488        * a value of -1 will be returned.
  489        *
  490        * @param c the editor
  491        * @param offs the offset in the document >= 0
  492        * @return the position >= 0 if the request can be computed, otherwise
  493        *  a value of -1 will be returned.
  494        * @exception BadLocationException if the offset is out of range
  495        */
  496       public static final int getRowStart(JTextComponent c, int offs) throws BadLocationException {
  497           Rectangle r = c.modelToView(offs);
  498           if (r == null) {
  499               return -1;
  500           }
  501           int lastOffs = offs;
  502           int y = r.y;
  503           while ((r != null) && (y == r.y)) {
  504               // Skip invisible elements
  505               if(r.height !=0) {
  506                   offs = lastOffs;
  507               }
  508               lastOffs -= 1;
  509               r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
  510           }
  511           return offs;
  512       }
  513   
  514       /**
  515        * Determines the ending row model position of the row that contains
  516        * the specified model position.  The component given must have a
  517        * size to compute the result.  If the component doesn't have a size
  518        * a value of -1 will be returned.
  519        *
  520        * @param c the editor
  521        * @param offs the offset in the document >= 0
  522        * @return the position >= 0 if the request can be computed, otherwise
  523        *  a value of -1 will be returned.
  524        * @exception BadLocationException if the offset is out of range
  525        */
  526       public static final int getRowEnd(JTextComponent c, int offs) throws BadLocationException {
  527           Rectangle r = c.modelToView(offs);
  528           if (r == null) {
  529               return -1;
  530           }
  531           int n = c.getDocument().getLength();
  532           int lastOffs = offs;
  533           int y = r.y;
  534           while ((r != null) && (y == r.y)) {
  535               // Skip invisible elements
  536               if (r.height !=0) {
  537                   offs = lastOffs;
  538               }
  539               lastOffs += 1;
  540               r = (lastOffs <= n) ? c.modelToView(lastOffs) : null;
  541           }
  542           return offs;
  543       }
  544   
  545       /**
  546        * Determines the position in the model that is closest to the given
  547        * view location in the row above.  The component given must have a
  548        * size to compute the result.  If the component doesn't have a size
  549        * a value of -1 will be returned.
  550        *
  551        * @param c the editor
  552        * @param offs the offset in the document >= 0
  553        * @param x the X coordinate >= 0
  554        * @return the position >= 0 if the request can be computed, otherwise
  555        *  a value of -1 will be returned.
  556        * @exception BadLocationException if the offset is out of range
  557        */
  558       public static final int getPositionAbove(JTextComponent c, int offs, int x) throws BadLocationException {
  559           int lastOffs = getRowStart(c, offs) - 1;
  560           if (lastOffs < 0) {
  561               return -1;
  562           }
  563           int bestSpan = Integer.MAX_VALUE;
  564           int y = 0;
  565           Rectangle r = null;
  566           if (lastOffs >= 0) {
  567               r = c.modelToView(lastOffs);
  568               y = r.y;
  569           }
  570           while ((r != null) && (y == r.y)) {
  571               int span = Math.abs(r.x - x);
  572               if (span < bestSpan) {
  573                   offs = lastOffs;
  574                   bestSpan = span;
  575               }
  576               lastOffs -= 1;
  577               r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
  578           }
  579           return offs;
  580       }
  581   
  582       /**
  583        * Determines the position in the model that is closest to the given
  584        * view location in the row below.  The component given must have a
  585        * size to compute the result.  If the component doesn't have a size
  586        * a value of -1 will be returned.
  587        *
  588        * @param c the editor
  589        * @param offs the offset in the document >= 0
  590        * @param x the X coordinate >= 0
  591        * @return the position >= 0 if the request can be computed, otherwise
  592        *  a value of -1 will be returned.
  593        * @exception BadLocationException if the offset is out of range
  594        */
  595       public static final int getPositionBelow(JTextComponent c, int offs, int x) throws BadLocationException {
  596           int lastOffs = getRowEnd(c, offs) + 1;
  597           if (lastOffs <= 0) {
  598               return -1;
  599           }
  600           int bestSpan = Integer.MAX_VALUE;
  601           int n = c.getDocument().getLength();
  602           int y = 0;
  603           Rectangle r = null;
  604           if (lastOffs <= n) {
  605               r = c.modelToView(lastOffs);
  606               y = r.y;
  607           }
  608           while ((r != null) && (y == r.y)) {
  609               int span = Math.abs(x - r.x);
  610               if (span < bestSpan) {
  611                   offs = lastOffs;
  612                   bestSpan = span;
  613               }
  614               lastOffs += 1;
  615               r = (lastOffs <= n) ? c.modelToView(lastOffs) : null;
  616           }
  617           return offs;
  618       }
  619   
  620       /**
  621        * Determines the start of a word for the given model location.
  622        * Uses BreakIterator.getWordInstance() to actually get the words.
  623        *
  624        * @param c the editor
  625        * @param offs the offset in the document >= 0
  626        * @return the location in the model of the word start >= 0
  627        * @exception BadLocationException if the offset is out of range
  628        */
  629       public static final int getWordStart(JTextComponent c, int offs) throws BadLocationException {
  630           Document doc = c.getDocument();
  631           Element line = getParagraphElement(c, offs);
  632           if (line == null) {
  633               throw new BadLocationException("No word at " + offs, offs);
  634           }
  635           int lineStart = line.getStartOffset();
  636           int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  637   
  638           Segment seg = SegmentCache.getSharedSegment();
  639           doc.getText(lineStart, lineEnd - lineStart, seg);
  640           if(seg.count > 0) {
  641               BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  642               words.setText(seg);
  643               int wordPosition = seg.offset + offs - lineStart;
  644               if(wordPosition >= words.last()) {
  645                   wordPosition = words.last() - 1;
  646               }
  647               words.following(wordPosition);
  648               offs = lineStart + words.previous() - seg.offset;
  649           }
  650           SegmentCache.releaseSharedSegment(seg);
  651           return offs;
  652       }
  653   
  654       /**
  655        * Determines the end of a word for the given location.
  656        * Uses BreakIterator.getWordInstance() to actually get the words.
  657        *
  658        * @param c the editor
  659        * @param offs the offset in the document >= 0
  660        * @return the location in the model of the word end >= 0
  661        * @exception BadLocationException if the offset is out of range
  662        */
  663       public static final int getWordEnd(JTextComponent c, int offs) throws BadLocationException {
  664           Document doc = c.getDocument();
  665           Element line = getParagraphElement(c, offs);
  666           if (line == null) {
  667               throw new BadLocationException("No word at " + offs, offs);
  668           }
  669           int lineStart = line.getStartOffset();
  670           int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  671   
  672           Segment seg = SegmentCache.getSharedSegment();
  673           doc.getText(lineStart, lineEnd - lineStart, seg);
  674           if(seg.count > 0) {
  675               BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  676               words.setText(seg);
  677               int wordPosition = offs - lineStart + seg.offset;
  678               if(wordPosition >= words.last()) {
  679                   wordPosition = words.last() - 1;
  680               }
  681               offs = lineStart + words.following(wordPosition) - seg.offset;
  682           }
  683           SegmentCache.releaseSharedSegment(seg);
  684           return offs;
  685       }
  686   
  687       /**
  688        * Determines the start of the next word for the given location.
  689        * Uses BreakIterator.getWordInstance() to actually get the words.
  690        *
  691        * @param c the editor
  692        * @param offs the offset in the document >= 0
  693        * @return the location in the model of the word start >= 0
  694        * @exception BadLocationException if the offset is out of range
  695        */
  696       public static final int getNextWord(JTextComponent c, int offs) throws BadLocationException {
  697           int nextWord;
  698           Element line = getParagraphElement(c, offs);
  699           for (nextWord = getNextWordInParagraph(c, line, offs, false);
  700                nextWord == BreakIterator.DONE;
  701                nextWord = getNextWordInParagraph(c, line, offs, true)) {
  702   
  703               // didn't find in this line, try the next line
  704               offs = line.getEndOffset();
  705               line = getParagraphElement(c, offs);
  706           }
  707           return nextWord;
  708       }
  709   
  710       /**
  711        * Finds the next word in the given elements text.  The first
  712        * parameter allows searching multiple paragraphs where even
  713        * the first offset is desired.
  714        * Returns the offset of the next word, or BreakIterator.DONE
  715        * if there are no more words in the element.
  716        */
  717       static int getNextWordInParagraph(JTextComponent c, Element line, int offs, boolean first) throws BadLocationException {
  718           if (line == null) {
  719               throw new BadLocationException("No more words", offs);
  720           }
  721           Document doc = line.getDocument();
  722           int lineStart = line.getStartOffset();
  723           int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  724           if ((offs >= lineEnd) || (offs < lineStart)) {
  725               throw new BadLocationException("No more words", offs);
  726           }
  727           Segment seg = SegmentCache.getSharedSegment();
  728           doc.getText(lineStart, lineEnd - lineStart, seg);
  729           BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  730           words.setText(seg);
  731           if ((first && (words.first() == (seg.offset + offs - lineStart))) &&
  732               (! Character.isWhitespace(seg.array[words.first()]))) {
  733   
  734               return offs;
  735           }
  736           int wordPosition = words.following(seg.offset + offs - lineStart);
  737           if ((wordPosition == BreakIterator.DONE) ||
  738               (wordPosition >= seg.offset + seg.count)) {
  739                   // there are no more words on this line.
  740                   return BreakIterator.DONE;
  741           }
  742           // if we haven't shot past the end... check to
  743           // see if the current boundary represents whitespace.
  744           // if so, we need to try again
  745           char ch = seg.array[wordPosition];
  746           if (! Character.isWhitespace(ch)) {
  747               return lineStart + wordPosition - seg.offset;
  748           }
  749   
  750           // it was whitespace, try again.  The assumption
  751           // is that it must be a word start if the last
  752           // one had whitespace following it.
  753           wordPosition = words.next();
  754           if (wordPosition != BreakIterator.DONE) {
  755               offs = lineStart + wordPosition - seg.offset;
  756               if (offs != lineEnd) {
  757                   return offs;
  758               }
  759           }
  760           SegmentCache.releaseSharedSegment(seg);
  761           return BreakIterator.DONE;
  762       }
  763   
  764   
  765       /**
  766        * Determine the start of the prev word for the given location.
  767        * Uses BreakIterator.getWordInstance() to actually get the words.
  768        *
  769        * @param c the editor
  770        * @param offs the offset in the document >= 0
  771        * @return the location in the model of the word start >= 0
  772        * @exception BadLocationException if the offset is out of range
  773        */
  774       public static final int getPreviousWord(JTextComponent c, int offs) throws BadLocationException {
  775           int prevWord;
  776           Element line = getParagraphElement(c, offs);
  777           for (prevWord = getPrevWordInParagraph(c, line, offs);
  778                prevWord == BreakIterator.DONE;
  779                prevWord = getPrevWordInParagraph(c, line, offs)) {
  780   
  781               // didn't find in this line, try the prev line
  782               offs = line.getStartOffset() - 1;
  783               line = getParagraphElement(c, offs);
  784           }
  785           return prevWord;
  786       }
  787   
  788       /**
  789        * Finds the previous word in the given elements text.  The first
  790        * parameter allows searching multiple paragraphs where even
  791        * the first offset is desired.
  792        * Returns the offset of the next word, or BreakIterator.DONE
  793        * if there are no more words in the element.
  794        */
  795       static int getPrevWordInParagraph(JTextComponent c, Element line, int offs) throws BadLocationException {
  796           if (line == null) {
  797               throw new BadLocationException("No more words", offs);
  798           }
  799           Document doc = line.getDocument();
  800           int lineStart = line.getStartOffset();
  801           int lineEnd = line.getEndOffset();
  802           if ((offs > lineEnd) || (offs < lineStart)) {
  803               throw new BadLocationException("No more words", offs);
  804           }
  805           Segment seg = SegmentCache.getSharedSegment();
  806           doc.getText(lineStart, lineEnd - lineStart, seg);
  807           BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  808           words.setText(seg);
  809           if (words.following(seg.offset + offs - lineStart) == BreakIterator.DONE) {
  810               words.last();
  811           }
  812           int wordPosition = words.previous();
  813           if (wordPosition == (seg.offset + offs - lineStart)) {
  814               wordPosition = words.previous();
  815           }
  816   
  817           if (wordPosition == BreakIterator.DONE) {
  818               // there are no more words on this line.
  819               return BreakIterator.DONE;
  820           }
  821           // if we haven't shot past the end... check to
  822           // see if the current boundary represents whitespace.
  823           // if so, we need to try again
  824           char ch = seg.array[wordPosition];
  825           if (! Character.isWhitespace(ch)) {
  826               return lineStart + wordPosition - seg.offset;
  827           }
  828   
  829           // it was whitespace, try again.  The assumption
  830           // is that it must be a word start if the last
  831           // one had whitespace following it.
  832           wordPosition = words.previous();
  833           if (wordPosition != BreakIterator.DONE) {
  834               return lineStart + wordPosition - seg.offset;
  835           }
  836           SegmentCache.releaseSharedSegment(seg);
  837           return BreakIterator.DONE;
  838       }
  839   
  840       /**
  841        * Determines the element to use for a paragraph/line.
  842        *
  843        * @param c the editor
  844        * @param offs the starting offset in the document >= 0
  845        * @return the element
  846        */
  847       public static final Element getParagraphElement(JTextComponent c, int offs) {
  848           Document doc = c.getDocument();
  849           if (doc instanceof StyledDocument) {
  850               return ((StyledDocument)doc).getParagraphElement(offs);
  851           }
  852           Element map = doc.getDefaultRootElement();
  853           int index = map.getElementIndex(offs);
  854           Element paragraph = map.getElement(index);
  855           if ((offs >= paragraph.getStartOffset()) && (offs < paragraph.getEndOffset())) {
  856               return paragraph;
  857           }
  858           return null;
  859       }
  860   
  861       static boolean isComposedTextElement(Document doc, int offset) {
  862           Element elem = doc.getDefaultRootElement();
  863           while (!elem.isLeaf()) {
  864               elem = elem.getElement(elem.getElementIndex(offset));
  865           }
  866           return isComposedTextElement(elem);
  867       }
  868   
  869       static boolean isComposedTextElement(Element elem) {
  870           AttributeSet as = elem.getAttributes();
  871           return isComposedTextAttributeDefined(as);
  872       }
  873   
  874       static boolean isComposedTextAttributeDefined(AttributeSet as) {
  875           return ((as != null) &&
  876                   (as.isDefined(StyleConstants.ComposedTextAttribute)));
  877       }
  878   
  879       /**
  880        * Draws the given composed text passed from an input method.
  881        *
  882        * @param view View hosting text
  883        * @param attr the attributes containing the composed text
  884        * @param g  the graphics context
  885        * @param x  the X origin
  886        * @param y  the Y origin
  887        * @param p0 starting offset in the composed text to be rendered
  888        * @param p1 ending offset in the composed text to be rendered
  889        * @return  the new insertion position
  890        */
  891       static int drawComposedText(View view, AttributeSet attr, Graphics g,
  892                                   int x, int y, int p0, int p1)
  893                                        throws BadLocationException {
  894           Graphics2D g2d = (Graphics2D)g;
  895           AttributedString as = (AttributedString)attr.getAttribute(
  896               StyleConstants.ComposedTextAttribute);
  897           as.addAttribute(TextAttribute.FONT, g.getFont());
  898   
  899           if (p0 >= p1)
  900               return x;
  901   
  902           AttributedCharacterIterator aci = as.getIterator(null, p0, p1);
  903           return x + (int)SwingUtilities2.drawString(
  904                                getJComponent(view), g2d,aci,x,y);
  905       }
  906   
  907       /**
  908        * Paints the composed text in a GlyphView
  909        */
  910       static void paintComposedText(Graphics g, Rectangle alloc, GlyphView v) {
  911           if (g instanceof Graphics2D) {
  912               Graphics2D g2d = (Graphics2D) g;
  913               int p0 = v.getStartOffset();
  914               int p1 = v.getEndOffset();
  915               AttributeSet attrSet = v.getElement().getAttributes();
  916               AttributedString as =
  917                   (AttributedString)attrSet.getAttribute(StyleConstants.ComposedTextAttribute);
  918               int start = v.getElement().getStartOffset();
  919               int y = alloc.y + alloc.height - (int)v.getGlyphPainter().getDescent(v);
  920               int x = alloc.x;
  921   
  922               //Add text attributes
  923               as.addAttribute(TextAttribute.FONT, v.getFont());
  924               as.addAttribute(TextAttribute.FOREGROUND, v.getForeground());
  925               if (StyleConstants.isBold(v.getAttributes())) {
  926                   as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
  927               }
  928               if (StyleConstants.isItalic(v.getAttributes())) {
  929                   as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
  930               }
  931               if (v.isUnderline()) {
  932                   as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
  933               }
  934               if (v.isStrikeThrough()) {
  935                   as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
  936               }
  937               if (v.isSuperscript()) {
  938                   as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
  939               }
  940               if (v.isSubscript()) {
  941                   as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
  942               }
  943   
  944               // draw
  945               AttributedCharacterIterator aci = as.getIterator(null, p0 - start, p1 - start);
  946               SwingUtilities2.drawString(getJComponent(v),
  947                                          g2d,aci,x,y);
  948           }
  949       }
  950   
  951       /*
  952        * Convenience function for determining ComponentOrientation.  Helps us
  953        * avoid having Munge directives throughout the code.
  954        */
  955       static boolean isLeftToRight( java.awt.Component c ) {
  956           return c.getComponentOrientation().isLeftToRight();
  957       }
  958   
  959   
  960       /**
  961        * Provides a way to determine the next visually represented model
  962        * location that one might place a caret.  Some views may not be visible,
  963        * they might not be in the same order found in the model, or they just
  964        * might not allow access to some of the locations in the model.
  965        * <p>
  966        * This implementation assumes the views are layed out in a logical
  967        * manner. That is, that the view at index x + 1 is visually after
  968        * the View at index x, and that the View at index x - 1 is visually
  969        * before the View at x. There is support for reversing this behavior
  970        * only if the passed in <code>View</code> is an instance of
  971        * <code>CompositeView</code>. The <code>CompositeView</code>
  972        * must then override the <code>flipEastAndWestAtEnds</code> method.
  973        *
  974        * @param v View to query
  975        * @param pos the position to convert >= 0
  976        * @param a the allocated region to render into
  977        * @param direction the direction from the current position that can
  978        *  be thought of as the arrow keys typically found on a keyboard;
  979        *  this may be one of the following:
  980        *  <ul>
  981        *  <li><code>SwingConstants.WEST</code>
  982        *  <li><code>SwingConstants.EAST</code>
  983        *  <li><code>SwingConstants.NORTH</code>
  984        *  <li><code>SwingConstants.SOUTH</code>
  985        *  </ul>
  986        * @param biasRet an array contain the bias that was checked
  987        * @return the location within the model that best represents the next
  988        *  location visual position
  989        * @exception BadLocationException
  990        * @exception IllegalArgumentException if <code>direction</code> is invalid
  991        */
  992       static int getNextVisualPositionFrom(View v, int pos, Position.Bias b,
  993                                             Shape alloc, int direction,
  994                                             Position.Bias[] biasRet)
  995                                throws BadLocationException {
  996           if (v.getViewCount() == 0) {
  997               // Nothing to do.
  998               return pos;
  999           }
 1000           boolean top = (direction == SwingConstants.NORTH ||
 1001                          direction == SwingConstants.WEST);
 1002           int retValue;
 1003           if (pos == -1) {
 1004               // Start from the first View.
 1005               int childIndex = (top) ? v.getViewCount() - 1 : 0;
 1006               View child = v.getView(childIndex);
 1007               Shape childBounds = v.getChildAllocation(childIndex, alloc);
 1008               retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
 1009                                                          direction, biasRet);
 1010               if (retValue == -1 && !top && v.getViewCount() > 1) {
 1011                   // Special case that should ONLY happen if first view
 1012                   // isn't valid (can happen when end position is put at
 1013                   // beginning of line.
 1014                   child = v.getView(1);
 1015                   childBounds = v.getChildAllocation(1, alloc);
 1016                   retValue = child.getNextVisualPositionFrom(-1, biasRet[0],
 1017                                                              childBounds,
 1018                                                              direction, biasRet);
 1019               }
 1020           }
 1021           else {
 1022               int increment = (top) ? -1 : 1;
 1023               int childIndex;
 1024               if (b == Position.Bias.Backward && pos > 0) {
 1025                   childIndex = v.getViewIndex(pos - 1, Position.Bias.Forward);
 1026               }
 1027               else {
 1028                   childIndex = v.getViewIndex(pos, Position.Bias.Forward);
 1029               }
 1030               View child = v.getView(childIndex);
 1031               Shape childBounds = v.getChildAllocation(childIndex, alloc);
 1032               retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
 1033                                                          direction, biasRet);
 1034               if ((direction == SwingConstants.EAST ||
 1035                    direction == SwingConstants.WEST) &&
 1036                   (v instanceof CompositeView) &&
 1037                   ((CompositeView)v).flipEastAndWestAtEnds(pos, b)) {
 1038                   increment *= -1;
 1039               }
 1040               childIndex += increment;
 1041               if (retValue == -1 && childIndex >= 0 &&
 1042                                     childIndex < v.getViewCount()) {
 1043                   child = v.getView(childIndex);
 1044                   childBounds = v.getChildAllocation(childIndex, alloc);
 1045                   retValue = child.getNextVisualPositionFrom(
 1046                                        -1, b, childBounds, direction, biasRet);
 1047                   // If there is a bias change, it is a fake position
 1048                   // and we should skip it. This is usually the result
 1049                   // of two elements side be side flowing the same way.
 1050                   if (retValue == pos && biasRet[0] != b) {
 1051                       return getNextVisualPositionFrom(v, pos, biasRet[0],
 1052                                                        alloc, direction,
 1053                                                        biasRet);
 1054                   }
 1055               }
 1056               else if (retValue != -1 && biasRet[0] != b &&
 1057                        ((increment == 1 && child.getEndOffset() == retValue) ||
 1058                         (increment == -1 &&
 1059                          child.getStartOffset() == retValue)) &&
 1060                        childIndex >= 0 && childIndex < v.getViewCount()) {
 1061                   // Reached the end of a view, make sure the next view
 1062                   // is a different direction.
 1063                   child = v.getView(childIndex);
 1064                   childBounds = v.getChildAllocation(childIndex, alloc);
 1065                   Position.Bias originalBias = biasRet[0];
 1066                   int nextPos = child.getNextVisualPositionFrom(
 1067                                       -1, b, childBounds, direction, biasRet);
 1068                   if (biasRet[0] == b) {
 1069                       retValue = nextPos;
 1070                   }
 1071                   else {
 1072                       biasRet[0] = originalBias;
 1073                   }
 1074               }
 1075           }
 1076           return retValue;
 1077       }
 1078   }

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