Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]
    1   /*
    2    * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.util.Arrays;
   28   import java.awt;
   29   import java.awt.font.TextAttribute;
   30   import javax.swing.event;
   31   import javax.swing.SizeRequirements;
   32   
   33   /**
   34    * View of a simple line-wrapping paragraph that supports
   35    * multiple fonts, colors, components, icons, etc.  It is
   36    * basically a vertical box with a margin around it.  The
   37    * contents of the box are a bunch of rows which are special
   38    * horizontal boxes.  This view creates a collection of
   39    * views that represent the child elements of the paragraph
   40    * element.  Each of these views are placed into a row
   41    * directly if they will fit, otherwise the <code>breakView</code>
   42    * method is called to try and carve the view into pieces
   43    * that fit.
   44    *
   45    * @author  Timothy Prinzing
   46    * @author  Scott Violet
   47    * @author  Igor Kushnirskiy
   48    * @see     View
   49    */
   50   public class ParagraphView extends FlowView implements TabExpander {
   51   
   52       /**
   53        * Constructs a <code>ParagraphView</code> for the given element.
   54        *
   55        * @param elem the element that this view is responsible for
   56        */
   57       public ParagraphView(Element elem) {
   58           super(elem, View.Y_AXIS);
   59           setPropertiesFromAttributes();
   60           Document doc = elem.getDocument();
   61           Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
   62           if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
   63               try {
   64                   if (i18nStrategy == null) {
   65                       // the classname should probably come from a property file.
   66                       String classname = "javax.swing.text.TextLayoutStrategy";
   67                       ClassLoader loader = getClass().getClassLoader();
   68                       if (loader != null) {
   69                           i18nStrategy = loader.loadClass(classname);
   70                       } else {
   71                           i18nStrategy = Class.forName(classname);
   72                       }
   73                   }
   74                   Object o = i18nStrategy.newInstance();
   75                   if (o instanceof FlowStrategy) {
   76                       strategy = (FlowStrategy) o;
   77                   }
   78               } catch (Throwable e) {
   79                   throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
   80                                                 + e.getMessage());
   81               }
   82           }
   83       }
   84   
   85       /**
   86        * Sets the type of justification.
   87        *
   88        * @param j one of the following values:
   89        * <ul>
   90        * <li><code>StyleConstants.ALIGN_LEFT</code>
   91        * <li><code>StyleConstants.ALIGN_CENTER</code>
   92        * <li><code>StyleConstants.ALIGN_RIGHT</code>
   93        * </ul>
   94        */
   95       protected void setJustification(int j) {
   96           justification = j;
   97       }
   98   
   99       /**
  100        * Sets the line spacing.
  101        *
  102        * @param ls the value is a factor of the line hight
  103        */
  104       protected void setLineSpacing(float ls) {
  105           lineSpacing = ls;
  106       }
  107   
  108       /**
  109        * Sets the indent on the first line.
  110        *
  111        * @param fi the value in points
  112        */
  113       protected void setFirstLineIndent(float fi) {
  114           firstLineIndent = (int) fi;
  115       }
  116   
  117       /**
  118        * Set the cached properties from the attributes.
  119        */
  120       protected void setPropertiesFromAttributes() {
  121           AttributeSet attr = getAttributes();
  122           if (attr != null) {
  123               setParagraphInsets(attr);
  124               Integer a = (Integer)attr.getAttribute(StyleConstants.Alignment);
  125               int alignment;
  126               if (a == null) {
  127                   Document doc = getElement().getDocument();
  128                   Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
  129                   if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
  130                       alignment = StyleConstants.ALIGN_RIGHT;
  131                   } else {
  132                       alignment = StyleConstants.ALIGN_LEFT;
  133                   }
  134               } else {
  135                   alignment = a.intValue();
  136               }
  137               setJustification(alignment);
  138               setLineSpacing(StyleConstants.getLineSpacing(attr));
  139               setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
  140           }
  141       }
  142   
  143       /**
  144        * Returns the number of views that this view is
  145        * responsible for.
  146        * The child views of the paragraph are rows which
  147        * have been used to arrange pieces of the <code>View</code>s
  148        * that represent the child elements.  This is the number
  149        * of views that have been tiled in two dimensions,
  150        * and should be equivalent to the number of child elements
  151        * to the element this view is responsible for.
  152        *
  153        * @return the number of views that this <code>ParagraphView</code>
  154        *          is responsible for
  155        */
  156       protected int getLayoutViewCount() {
  157           return layoutPool.getViewCount();
  158       }
  159   
  160       /**
  161        * Returns the view at a given <code>index</code>.
  162        * The child views of the paragraph are rows which
  163        * have been used to arrange pieces of the <code>Views</code>
  164        * that represent the child elements.  This methods returns
  165        * the view responsible for the child element index
  166        * (prior to breaking).  These are the Views that were
  167        * produced from a factory (to represent the child
  168        * elements) and used for layout.
  169        *
  170        * @param index the <code>index</code> of the desired view
  171        * @return the view at <code>index</code>
  172        */
  173       protected View getLayoutView(int index) {
  174           return layoutPool.getView(index);
  175       }
  176   
  177       /**
  178        * Adjusts the given row if possible to fit within the
  179        * layout span.  By default this will try to find the
  180        * highest break weight possible nearest the end of
  181        * the row.  If a forced break is encountered, the
  182        * break will be positioned there.
  183        * <p>
  184        * This is meant for internal usage, and should not be used directly.
  185        *
  186        * @param r the row to adjust to the current layout
  187        *          span
  188        * @param desiredSpan the current layout span >= 0
  189        * @param x the location r starts at
  190        */
  191       protected void adjustRow(Row r, int desiredSpan, int x) {
  192       }
  193   
  194       /**
  195        * Returns the next visual position for the cursor, in
  196        * either the east or west direction.
  197        * Overridden from <code>CompositeView</code>.
  198        * @param pos position into the model
  199        * @param b either <code>Position.Bias.Forward</code> or
  200        *          <code>Position.Bias.Backward</code>
  201        * @param a the allocated region to render into
  202        * @param direction either <code>SwingConstants.NORTH</code>
  203        *          or <code>SwingConstants.SOUTH</code>
  204        * @param biasRet an array containing the bias that were checked
  205        *  in this method
  206        * @return the location in the model that represents the
  207        *  next location visual position
  208        */
  209       protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
  210                                                         Shape a, int direction,
  211                                                         Position.Bias[] biasRet)
  212                                                   throws BadLocationException {
  213           int vIndex;
  214           if(pos == -1) {
  215               vIndex = (direction == NORTH) ?
  216                        getViewCount() - 1 : 0;
  217           }
  218           else {
  219               if(b == Position.Bias.Backward && pos > 0) {
  220                   vIndex = getViewIndexAtPosition(pos - 1);
  221               }
  222               else {
  223                   vIndex = getViewIndexAtPosition(pos);
  224               }
  225               if(direction == NORTH) {
  226                   if(vIndex == 0) {
  227                       return -1;
  228                   }
  229                   vIndex--;
  230               }
  231               else if(++vIndex >= getViewCount()) {
  232                   return -1;
  233               }
  234           }
  235           // vIndex gives index of row to look in.
  236           JTextComponent text = (JTextComponent)getContainer();
  237           Caret c = text.getCaret();
  238           Point magicPoint;
  239           magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
  240           int x;
  241           if(magicPoint == null) {
  242               Shape posBounds;
  243               try {
  244                   posBounds = text.getUI().modelToView(text, pos, b);
  245               } catch (BadLocationException exc) {
  246                   posBounds = null;
  247               }
  248               if(posBounds == null) {
  249                   x = 0;
  250               }
  251               else {
  252                   x = posBounds.getBounds().x;
  253               }
  254           }
  255           else {
  256               x = magicPoint.x;
  257           }
  258           return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
  259       }
  260   
  261       /**
  262        * Returns the closest model position to <code>x</code>.
  263        * <code>rowIndex</code> gives the index of the view that corresponds
  264        * that should be looked in.
  265        * @param pos  position into the model
  266        * @param a the allocated region to render into
  267        * @param direction one of the following values:
  268        * <ul>
  269        * <li><code>SwingConstants.NORTH</code>
  270        * <li><code>SwingConstants.SOUTH</code>
  271        * </ul>
  272        * @param biasRet an array containing the bias that were checked
  273        *  in this method
  274        * @param rowIndex the index of the view
  275        * @param x the x coordinate of interest
  276        * @return the closest model position to <code>x</code>
  277        */
  278       // NOTE: This will not properly work if ParagraphView contains
  279       // other ParagraphViews. It won't raise, but this does not message
  280       // the children views with getNextVisualPositionFrom.
  281       protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
  282                                          int direction, Position.Bias[] biasRet,
  283                                          int rowIndex, int x)
  284                 throws BadLocationException {
  285           JTextComponent text = (JTextComponent)getContainer();
  286           Document doc = getDocument();
  287           AbstractDocument aDoc = (doc instanceof AbstractDocument) ?
  288                                   (AbstractDocument)doc : null;
  289           View row = getView(rowIndex);
  290           int lastPos = -1;
  291           // This could be made better to check backward positions too.
  292           biasRet[0] = Position.Bias.Forward;
  293           for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
  294               View v = row.getView(vc);
  295               int start = v.getStartOffset();
  296               boolean ltr = (aDoc != null) ? aDoc.isLeftToRight
  297                              (start, start + 1) : true;
  298               if(ltr) {
  299                   lastPos = start;
  300                   for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
  301                       float xx = text.modelToView(lastPos).getBounds().x;
  302                       if(xx >= x) {
  303                           while (++lastPos < end &&
  304                                  text.modelToView(lastPos).getBounds().x == xx) {
  305                           }
  306                           return --lastPos;
  307                       }
  308                   }
  309                   lastPos--;
  310               }
  311               else {
  312                   for(lastPos = v.getEndOffset() - 1; lastPos >= start;
  313                       lastPos--) {
  314                       float xx = text.modelToView(lastPos).getBounds().x;
  315                       if(xx >= x) {
  316                           while (--lastPos >= start &&
  317                                  text.modelToView(lastPos).getBounds().x == xx) {
  318                           }
  319                           return ++lastPos;
  320                       }
  321                   }
  322                   lastPos++;
  323               }
  324           }
  325           if(lastPos == -1) {
  326               return getStartOffset();
  327           }
  328           return lastPos;
  329       }
  330   
  331       /**
  332        * Determines in which direction the next view lays.
  333        * Consider the <code>View</code> at index n.
  334        * Typically the <code>View</code>s are layed out
  335        * from left to right, so that the <code>View</code>
  336        * to the EAST will be at index n + 1, and the
  337        * <code>View</code> to the WEST will be at index n - 1.
  338        * In certain situations, such as with bidirectional text,
  339        * it is possible that the <code>View</code> to EAST is not
  340        * at index n + 1, but rather at index n - 1,
  341        * or that the <code>View</code> to the WEST is not at
  342        * index n - 1, but index n + 1.  In this case this method
  343        * would return true, indicating the <code>View</code>s are
  344        * layed out in descending order.
  345        * <p>
  346        * This will return true if the text is layed out right
  347        * to left at position, otherwise false.
  348        *
  349        * @param position position into the model
  350        * @param bias either <code>Position.Bias.Forward</code> or
  351        *          <code>Position.Bias.Backward</code>
  352        * @return true if the text is layed out right to left at
  353        *         position, otherwise false.
  354        */
  355       protected boolean flipEastAndWestAtEnds(int position,
  356                                               Position.Bias bias) {
  357           Document doc = getDocument();
  358           if(doc instanceof AbstractDocument &&
  359              !((AbstractDocument)doc).isLeftToRight(getStartOffset(),
  360                                                     getStartOffset() + 1)) {
  361               return true;
  362           }
  363           return false;
  364       }
  365   
  366       // --- FlowView methods ---------------------------------------------
  367   
  368       /**
  369        * Fetches the constraining span to flow against for
  370        * the given child index.
  371        * @param index the index of the view being queried
  372        * @return the constraining span for the given view at
  373        *  <code>index</code>
  374        * @since 1.3
  375        */
  376       public int getFlowSpan(int index) {
  377           View child = getView(index);
  378           int adjust = 0;
  379           if (child instanceof Row) {
  380               Row row = (Row) child;
  381               adjust = row.getLeftInset() + row.getRightInset();
  382           }
  383           return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan
  384                                                    : (layoutSpan - adjust);
  385       }
  386   
  387       /**
  388        * Fetches the location along the flow axis that the
  389        * flow span will start at.
  390        * @param index the index of the view being queried
  391        * @return the location for the given view at
  392        *  <code>index</code>
  393        * @since 1.3
  394        */
  395       public int getFlowStart(int index) {
  396           View child = getView(index);
  397           int adjust = 0;
  398           if (child instanceof Row) {
  399               Row row = (Row) child;
  400               adjust = row.getLeftInset();
  401           }
  402           return tabBase + adjust;
  403       }
  404   
  405       /**
  406        * Create a <code>View</code> that should be used to hold a
  407        * a row's worth of children in a flow.
  408        * @return the new <code>View</code>
  409        * @since 1.3
  410        */
  411       protected View createRow() {
  412           return new Row(getElement());
  413       }
  414   
  415       // --- TabExpander methods ------------------------------------------
  416   
  417       /**
  418        * Returns the next tab stop position given a reference position.
  419        * This view implements the tab coordinate system, and calls
  420        * <code>getTabbedSpan</code> on the logical children in the process
  421        * of layout to determine the desired span of the children.  The
  422        * logical children can delegate their tab expansion upward to
  423        * the paragraph which knows how to expand tabs.
  424        * <code>LabelView</code> is an example of a view that delegates
  425        * its tab expansion needs upward to the paragraph.
  426        * <p>
  427        * This is implemented to try and locate a <code>TabSet</code>
  428        * in the paragraph element's attribute set.  If one can be
  429        * found, its settings will be used, otherwise a default expansion
  430        * will be provided.  The base location for for tab expansion
  431        * is the left inset from the paragraphs most recent allocation
  432        * (which is what the layout of the children is based upon).
  433        *
  434        * @param x the X reference position
  435        * @param tabOffset the position within the text stream
  436        *   that the tab occurred at >= 0
  437        * @return the trailing end of the tab expansion >= 0
  438        * @see TabSet
  439        * @see TabStop
  440        * @see LabelView
  441        */
  442       public float nextTabStop(float x, int tabOffset) {
  443           // If the text isn't left justified, offset by 10 pixels!
  444           if(justification != StyleConstants.ALIGN_LEFT)
  445               return x + 10.0f;
  446           x -= tabBase;
  447           TabSet tabs = getTabSet();
  448           if(tabs == null) {
  449               // a tab every 72 pixels.
  450               return (float)(tabBase + (((int)x / 72 + 1) * 72));
  451           }
  452           TabStop tab = tabs.getTabAfter(x + .01f);
  453           if(tab == null) {
  454               // no tab, do a default of 5 pixels.
  455               // Should this cause a wrapping of the line?
  456               return tabBase + x + 5.0f;
  457           }
  458           int alignment = tab.getAlignment();
  459           int offset;
  460           switch(alignment) {
  461           default:
  462           case TabStop.ALIGN_LEFT:
  463               // Simple case, left tab.
  464               return tabBase + tab.getPosition();
  465           case TabStop.ALIGN_BAR:
  466               // PENDING: what does this mean?
  467               return tabBase + tab.getPosition();
  468           case TabStop.ALIGN_RIGHT:
  469           case TabStop.ALIGN_CENTER:
  470               offset = findOffsetToCharactersInString(tabChars,
  471                                                       tabOffset + 1);
  472               break;
  473           case TabStop.ALIGN_DECIMAL:
  474               offset = findOffsetToCharactersInString(tabDecimalChars,
  475                                                       tabOffset + 1);
  476               break;
  477           }
  478           if (offset == -1) {
  479               offset = getEndOffset();
  480           }
  481           float charsSize = getPartialSize(tabOffset + 1, offset);
  482           switch(alignment) {
  483           case TabStop.ALIGN_RIGHT:
  484           case TabStop.ALIGN_DECIMAL:
  485               // right and decimal are treated the same way, the new
  486               // position will be the location of the tab less the
  487               // partialSize.
  488               return tabBase + Math.max(x, tab.getPosition() - charsSize);
  489           case TabStop.ALIGN_CENTER:
  490               // Similar to right, but half the partialSize.
  491               return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  492           }
  493           // will never get here!
  494           return x;
  495       }
  496   
  497       /**
  498        * Gets the <code>Tabset</code> to be used in calculating tabs.
  499        *
  500        * @return the <code>TabSet</code>
  501        */
  502       protected TabSet getTabSet() {
  503           return StyleConstants.getTabSet(getElement().getAttributes());
  504       }
  505   
  506       /**
  507        * Returns the size used by the views between
  508        * <code>startOffset</code> and <code>endOffset</code>.
  509        * This uses <code>getPartialView</code> to calculate the
  510        * size if the child view implements the
  511        * <code>TabableView</code> interface. If a
  512        * size is needed and a <code>View</code> does not implement
  513        * the <code>TabableView</code> interface,
  514        * the <code>preferredSpan</code> will be used.
  515        *
  516        * @param startOffset the starting document offset >= 0
  517        * @param endOffset the ending document offset >= startOffset
  518        * @return the size >= 0
  519        */
  520       protected float getPartialSize(int startOffset, int endOffset) {
  521           float size = 0.0f;
  522           int viewIndex;
  523           int numViews = getViewCount();
  524           View view;
  525           int viewEnd;
  526           int tempEnd;
  527   
  528           // Have to search layoutPool!
  529           // PENDING: when ParagraphView supports breaking location
  530           // into layoutPool will have to change!
  531           viewIndex = getElement().getElementIndex(startOffset);
  532           numViews = layoutPool.getViewCount();
  533           while(startOffset < endOffset && viewIndex < numViews) {
  534               view = layoutPool.getView(viewIndex++);
  535               viewEnd = view.getEndOffset();
  536               tempEnd = Math.min(endOffset, viewEnd);
  537               if(view instanceof TabableView)
  538                   size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  539               else if(startOffset == view.getStartOffset() &&
  540                       tempEnd == view.getEndOffset())
  541                   size += view.getPreferredSpan(View.X_AXIS);
  542               else
  543                   // PENDING: should we handle this better?
  544                   return 0.0f;
  545               startOffset = viewEnd;
  546           }
  547           return size;
  548       }
  549   
  550       /**
  551        * Finds the next character in the document with a character in
  552        * <code>string</code>, starting at offset <code>start</code>. If
  553        * there are no characters found, -1 will be returned.
  554        *
  555        * @param string the string of characters
  556        * @param start where to start in the model >= 0
  557        * @return the document offset, or -1 if no characters found
  558        */
  559       protected int findOffsetToCharactersInString(char[] string,
  560                                                    int start) {
  561           int stringLength = string.length;
  562           int end = getEndOffset();
  563           Segment seg = new Segment();
  564           try {
  565               getDocument().getText(start, end - start, seg);
  566           } catch (BadLocationException ble) {
  567               return -1;
  568           }
  569           for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  570               counter < maxCounter; counter++) {
  571               char currentChar = seg.array[counter];
  572               for(int subCounter = 0; subCounter < stringLength;
  573                   subCounter++) {
  574                   if(currentChar == string[subCounter])
  575                       return counter - seg.offset + start;
  576               }
  577           }
  578           // No match.
  579           return -1;
  580       }
  581   
  582       /**
  583        * Returns where the tabs are calculated from.
  584        * @return where tabs are calculated from
  585        */
  586       protected float getTabBase() {
  587           return (float)tabBase;
  588       }
  589   
  590       // ---- View methods ----------------------------------------------------
  591   
  592       /**
  593        * Renders using the given rendering surface and area on that
  594        * surface.  This is implemented to delgate to the superclass
  595        * after stashing the base coordinate for tab calculations.
  596        *
  597        * @param g the rendering surface to use
  598        * @param a the allocated region to render into
  599        * @see View#paint
  600        */
  601       public void paint(Graphics g, Shape a) {
  602           Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  603           tabBase = alloc.x + getLeftInset();
  604           super.paint(g, a);
  605   
  606           // line with the negative firstLineIndent value needs
  607           // special handling
  608           if (firstLineIndent < 0) {
  609               Shape sh = getChildAllocation(0, a);
  610               if ((sh != null) &&  sh.intersects(alloc)) {
  611                   int x = alloc.x + getLeftInset() + firstLineIndent;
  612                   int y = alloc.y + getTopInset();
  613   
  614                   Rectangle clip = g.getClipBounds();
  615                   tempRect.x = x + getOffset(X_AXIS, 0);
  616                   tempRect.y = y + getOffset(Y_AXIS, 0);
  617                   tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
  618                   tempRect.height = getSpan(Y_AXIS, 0);
  619                   if (tempRect.intersects(clip)) {
  620                       tempRect.x = tempRect.x - firstLineIndent;
  621                       paintChild(g, tempRect, 0);
  622                   }
  623               }
  624           }
  625       }
  626   
  627       /**
  628        * Determines the desired alignment for this view along an
  629        * axis.  This is implemented to give the alignment to the
  630        * center of the first row along the y axis, and the default
  631        * along the x axis.
  632        *
  633        * @param axis may be either <code>View.X_AXIS</code> or
  634        *   <code>View.Y_AXIS</code>
  635        * @return the desired alignment.  This should be a value
  636        *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  637        *   origin and 1.0 indicates alignment to the full span
  638        *   away from the origin.  An alignment of 0.5 would be the
  639        *   center of the view.
  640        */
  641       public float getAlignment(int axis) {
  642           switch (axis) {
  643           case Y_AXIS:
  644               float a = 0.5f;
  645               if (getViewCount() != 0) {
  646                   int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  647                   View v = getView(0);
  648                   int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  649                   a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  650               }
  651               return a;
  652           case X_AXIS:
  653               return 0.5f;
  654           default:
  655               throw new IllegalArgumentException("Invalid axis: " + axis);
  656           }
  657       }
  658   
  659       /**
  660        * Breaks this view on the given axis at the given length.
  661        * <p>
  662        * <code>ParagraphView</code> instances are breakable
  663        * along the <code>Y_AXIS</code> only, and only if
  664        * <code>len</code> is after the first line.
  665        *
  666        * @param axis may be either <code>View.X_AXIS</code>
  667        *  or <code>View.Y_AXIS</code>
  668        * @param len specifies where a potential break is desired
  669        *  along the given axis >= 0
  670        * @param a the current allocation of the view
  671        * @return the fragment of the view that represents the
  672        *  given span, if the view can be broken; if the view
  673        *  doesn't support breaking behavior, the view itself is
  674        *  returned
  675        * @see View#breakView
  676        */
  677       public View breakView(int axis, float len, Shape a) {
  678           if(axis == View.Y_AXIS) {
  679               if(a != null) {
  680                   Rectangle alloc = a.getBounds();
  681                   setSize(alloc.width, alloc.height);
  682               }
  683               // Determine what row to break on.
  684   
  685               // PENDING(prinz) add break support
  686               return this;
  687           }
  688           return this;
  689       }
  690   
  691       /**
  692        * Gets the break weight for a given location.
  693        * <p>
  694        * <code>ParagraphView</code> instances are breakable
  695        * along the <code>Y_AXIS</code> only, and only if
  696        * <code>len</code> is after the first row.  If the length
  697        * is less than one row, a value of <code>BadBreakWeight</code>
  698        * is returned.
  699        *
  700        * @param axis may be either <code>View.X_AXIS</code>
  701        *  or <code>View.Y_AXIS</code>
  702        * @param len specifies where a potential break is desired >= 0
  703        * @return a value indicating the attractiveness of breaking here;
  704        *  either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
  705        * @see View#getBreakWeight
  706        */
  707       public int getBreakWeight(int axis, float len) {
  708           if(axis == View.Y_AXIS) {
  709               // PENDING(prinz) make this return a reasonable value
  710               // when paragraph breaking support is re-implemented.
  711               // If less than one row, bad weight value should be
  712               // returned.
  713               //return GoodBreakWeight;
  714               return BadBreakWeight;
  715           }
  716           return BadBreakWeight;
  717       }
  718   
  719       /**
  720        * Calculate the needs for the paragraph along the minor axis.
  721        *
  722        * <p>This uses size requirements of the superclass, modified to take into
  723        * account the non-breakable areas at the adjacent views edges.  The minimal
  724        * size requirements for such views should be no less than the sum of all
  725        * adjacent fragments.</p>
  726        *
  727        * <p>If the {@code axis} parameter is neither {@code View.X_AXIS} nor
  728        * {@code View.Y_AXIS}, {@link IllegalArgumentException} is thrown.  If the
  729        * {@code r} parameter is {@code null,} a new {@code SizeRequirements}
  730        * object is created, otherwise the supplied {@code SizeRequirements}
  731        * object is returned.</p>
  732        *
  733        * @param axis  the minor axis
  734        * @param r     the input {@code SizeRequirements} object
  735        * @return      the new or adjusted {@code SizeRequirements} object
  736        * @throw IllegalArgumentException  if the {@code axis} parameter is invalid
  737        */
  738       @Override
  739       protected SizeRequirements calculateMinorAxisRequirements(int axis,
  740                                                           SizeRequirements r) {
  741           r = super.calculateMinorAxisRequirements(axis, r);
  742   
  743           float min = 0;
  744           float glue = 0;
  745           int n = getLayoutViewCount();
  746           for (int i = 0; i < n; i++) {
  747               View v = getLayoutView(i);
  748               float span = v.getMinimumSpan(axis);
  749               if (v.getBreakWeight(axis, 0, v.getMaximumSpan(axis))
  750                                                           > View.BadBreakWeight) {
  751                   // find the longest non-breakable fragments at the view edges
  752                   int p0 = v.getStartOffset();
  753                   int p1 = v.getEndOffset();
  754                   float start = findEdgeSpan(v, axis, p0, p0, p1);
  755                   float end = findEdgeSpan(v, axis, p1, p0, p1);
  756                   glue += start;
  757                   min = Math.max(min, Math.max(span, glue));
  758                   glue = end;
  759               } else {
  760                   // non-breakable view
  761                   glue += span;
  762                   min = Math.max(min, glue);
  763               }
  764           }
  765           r.minimum = Math.max(r.minimum, (int) min);
  766           r.preferred = Math.max(r.minimum,  r.preferred);
  767           r.maximum = Math.max(r.preferred, r.maximum);
  768   
  769           return r;
  770       }
  771   
  772       /**
  773        * Binary search for the longest non-breakable fragment at the view edge.
  774        */
  775       private float findEdgeSpan(View v, int axis, int fp, int p0, int p1) {
  776           int len = p1 - p0;
  777           if (len <= 1) {
  778               // further fragmentation is not possible
  779               return v.getMinimumSpan(axis);
  780           } else {
  781               int mid = p0 + len / 2;
  782               boolean startEdge = mid > fp;
  783               // initial view is breakable hence must support fragmentation
  784               View f = startEdge ?
  785                   v.createFragment(fp, mid) : v.createFragment(mid, fp);
  786               boolean breakable = f.getBreakWeight(
  787                       axis, 0, f.getMaximumSpan(axis)) > View.BadBreakWeight;
  788               if (breakable == startEdge) {
  789                   p1 = mid;
  790               } else {
  791                   p0 = mid;
  792               }
  793               return findEdgeSpan(f, axis, fp, p0, p1);
  794           }
  795       }
  796   
  797       /**
  798        * Gives notification from the document that attributes were changed
  799        * in a location that this view is responsible for.
  800        *
  801        * @param changes the change information from the
  802        *  associated document
  803        * @param a the current allocation of the view
  804        * @param f the factory to use to rebuild if the view has children
  805        * @see View#changedUpdate
  806        */
  807       public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  808           // update any property settings stored, and layout should be
  809           // recomputed
  810           setPropertiesFromAttributes();
  811           layoutChanged(X_AXIS);
  812           layoutChanged(Y_AXIS);
  813           super.changedUpdate(changes, a, f);
  814       }
  815   
  816   
  817       // --- variables -----------------------------------------------
  818   
  819       private int justification;
  820       private float lineSpacing;
  821       /** Indentation for the first line, from the left inset. */
  822       protected int firstLineIndent = 0;
  823   
  824       /**
  825        * Used by the TabExpander functionality to determine
  826        * where to base the tab calculations.  This is basically
  827        * the location of the left side of the paragraph.
  828        */
  829       private int tabBase;
  830   
  831       /**
  832        * Used to create an i18n-based layout strategy
  833        */
  834       static Class i18nStrategy;
  835   
  836       /** Used for searching for a tab. */
  837       static char[] tabChars;
  838       /** Used for searching for a tab or decimal character. */
  839       static char[] tabDecimalChars;
  840   
  841       static {
  842           tabChars = new char[1];
  843           tabChars[0] = '\t';
  844           tabDecimalChars = new char[2];
  845           tabDecimalChars[0] = '\t';
  846           tabDecimalChars[1] = '.';
  847       }
  848   
  849       /**
  850        * Internally created view that has the purpose of holding
  851        * the views that represent the children of the paragraph
  852        * that have been arranged in rows.
  853        */
  854       class Row extends BoxView {
  855   
  856           Row(Element elem) {
  857               super(elem, View.X_AXIS);
  858           }
  859   
  860           /**
  861            * This is reimplemented to do nothing since the
  862            * paragraph fills in the row with its needed
  863            * children.
  864            */
  865           protected void loadChildren(ViewFactory f) {
  866           }
  867   
  868           /**
  869            * Fetches the attributes to use when rendering.  This view
  870            * isn't directly responsible for an element so it returns
  871            * the outer classes attributes.
  872            */
  873           public AttributeSet getAttributes() {
  874               View p = getParent();
  875               return (p != null) ? p.getAttributes() : null;
  876           }
  877   
  878           public float getAlignment(int axis) {
  879               if (axis == View.X_AXIS) {
  880                   switch (justification) {
  881                   case StyleConstants.ALIGN_LEFT:
  882                       return 0;
  883                   case StyleConstants.ALIGN_RIGHT:
  884                       return 1;
  885                   case StyleConstants.ALIGN_CENTER:
  886                       return 0.5f;
  887                   case StyleConstants.ALIGN_JUSTIFIED:
  888                       float rv = 0.5f;
  889                       //if we can justifiy the content always align to
  890                       //the left.
  891                       if (isJustifiableDocument()) {
  892                           rv = 0f;
  893                       }
  894                       return rv;
  895                   }
  896               }
  897               return super.getAlignment(axis);
  898           }
  899   
  900           /**
  901            * Provides a mapping from the document model coordinate space
  902            * to the coordinate space of the view mapped to it.  This is
  903            * implemented to let the superclass find the position along
  904            * the major axis and the allocation of the row is used
  905            * along the minor axis, so that even though the children
  906            * are different heights they all get the same caret height.
  907            *
  908            * @param pos the position to convert
  909            * @param a the allocated region to render into
  910            * @return the bounding box of the given position
  911            * @exception BadLocationException  if the given position does not represent a
  912            *   valid location in the associated document
  913            * @see View#modelToView
  914            */
  915           public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  916               Rectangle r = a.getBounds();
  917               View v = getViewAtPosition(pos, r);
  918               if ((v != null) && (!v.getElement().isLeaf())) {
  919                   // Don't adjust the height if the view represents a branch.
  920                   return super.modelToView(pos, a, b);
  921               }
  922               r = a.getBounds();
  923               int height = r.height;
  924               int y = r.y;
  925               Shape loc = super.modelToView(pos, a, b);
  926               r = loc.getBounds();
  927               r.height = height;
  928               r.y = y;
  929               return r;
  930           }
  931   
  932           /**
  933            * Range represented by a row in the paragraph is only
  934            * a subset of the total range of the paragraph element.
  935            * @see View#getRange
  936            */
  937           public int getStartOffset() {
  938               int offs = Integer.MAX_VALUE;
  939               int n = getViewCount();
  940               for (int i = 0; i < n; i++) {
  941                   View v = getView(i);
  942                   offs = Math.min(offs, v.getStartOffset());
  943               }
  944               return offs;
  945           }
  946   
  947           public int getEndOffset() {
  948               int offs = 0;
  949               int n = getViewCount();
  950               for (int i = 0; i < n; i++) {
  951                   View v = getView(i);
  952                   offs = Math.max(offs, v.getEndOffset());
  953               }
  954               return offs;
  955           }
  956   
  957           /**
  958            * Perform layout for the minor axis of the box (i.e. the
  959            * axis orthoginal to the axis that it represents).  The results
  960            * of the layout should be placed in the given arrays which represent
  961            * the allocations to the children along the minor axis.
  962            * <p>
  963            * This is implemented to do a baseline layout of the children
  964            * by calling BoxView.baselineLayout.
  965            *
  966            * @param targetSpan the total span given to the view, which
  967            *  whould be used to layout the children.
  968            * @param axis the axis being layed out.
  969            * @param offsets the offsets from the origin of the view for
  970            *  each of the child views.  This is a return value and is
  971            *  filled in by the implementation of this method.
  972            * @param spans the span of each child view.  This is a return
  973            *  value and is filled in by the implementation of this method.
  974            * @return the offset and span for each child view in the
  975            *  offsets and spans parameters
  976            */
  977           protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
  978               baselineLayout(targetSpan, axis, offsets, spans);
  979           }
  980   
  981           protected SizeRequirements calculateMinorAxisRequirements(int axis,
  982                                                                     SizeRequirements r) {
  983               return baselineRequirements(axis, r);
  984           }
  985   
  986   
  987           private boolean isLastRow() {
  988               View parent;
  989               return ((parent = getParent()) == null
  990                       || this == parent.getView(parent.getViewCount() - 1));
  991           }
  992   
  993           private boolean isBrokenRow() {
  994               boolean rv = false;
  995               int viewsCount = getViewCount();
  996               if (viewsCount > 0) {
  997                   View lastView = getView(viewsCount - 1);
  998                   if (lastView.getBreakWeight(X_AXIS, 0, 0) >=
  999                         ForcedBreakWeight) {
 1000                       rv = true;
 1001                   }
 1002               }
 1003               return rv;
 1004           }
 1005   
 1006           private boolean isJustifiableDocument() {
 1007               return (! Boolean.TRUE.equals(getDocument().getProperty(
 1008                             AbstractDocument.I18NProperty)));
 1009           }
 1010   
 1011           /**
 1012            * Whether we need to justify this {@code Row}.
 1013            * At this time (jdk1.6) we support justification on for non
 1014            * 18n text.
 1015            *
 1016            * @return {@code true} if this {@code Row} should be justified.
 1017            */
 1018           private boolean isJustifyEnabled() {
 1019               boolean ret = (justification == StyleConstants.ALIGN_JUSTIFIED);
 1020   
 1021               //no justification for i18n documents
 1022               ret = ret && isJustifiableDocument();
 1023   
 1024               //no justification for the last row
 1025               ret = ret && ! isLastRow();
 1026   
 1027               //no justification for the broken rows
 1028               ret = ret && ! isBrokenRow();
 1029   
 1030               return ret;
 1031           }
 1032   
 1033   
 1034           //Calls super method after setting spaceAddon to 0.
 1035           //Justification should not affect MajorAxisRequirements
 1036           @Override
 1037           protected SizeRequirements calculateMajorAxisRequirements(int axis,
 1038                   SizeRequirements r) {
 1039               int oldJustficationData[] = justificationData;
 1040               justificationData = null;
 1041               SizeRequirements ret = super.calculateMajorAxisRequirements(axis, r);
 1042               if (isJustifyEnabled()) {
 1043                   justificationData = oldJustficationData;
 1044               }
 1045               return ret;
 1046           }
 1047   
 1048           @Override
 1049           protected void layoutMajorAxis(int targetSpan, int axis,
 1050                                          int[] offsets, int[] spans) {
 1051               int oldJustficationData[] = justificationData;
 1052               justificationData = null;
 1053               super.layoutMajorAxis(targetSpan, axis, offsets, spans);
 1054               if (! isJustifyEnabled()) {
 1055                   return;
 1056               }
 1057   
 1058               int currentSpan = 0;
 1059               for (int span : spans) {
 1060                   currentSpan += span;
 1061               }
 1062               if (currentSpan == targetSpan) {
 1063                   //no need to justify
 1064                   return;
 1065               }
 1066   
 1067               // we justify text by enlarging spaces by the {@code spaceAddon}.
 1068               // justification is started to the right of the rightmost TAB.
 1069               // leading and trailing spaces are not extendable.
 1070               //
 1071               // GlyphPainter1 uses
 1072               // justificationData
 1073               // for all painting and measurement.
 1074   
 1075               int extendableSpaces = 0;
 1076               int startJustifiableContent = -1;
 1077               int endJustifiableContent = -1;
 1078               int lastLeadingSpaces = 0;
 1079   
 1080               int rowStartOffset = getStartOffset();
 1081               int rowEndOffset = getEndOffset();
 1082               int spaceMap[] = new int[rowEndOffset - rowStartOffset];
 1083               Arrays.fill(spaceMap, 0);
 1084               for (int i = getViewCount() - 1; i >= 0 ; i--) {
 1085                   View view = getView(i);
 1086                   if (view instanceof GlyphView) {
 1087                       GlyphView.JustificationInfo justificationInfo =
 1088                           ((GlyphView) view).getJustificationInfo(rowStartOffset);
 1089                       final int viewStartOffset = view.getStartOffset();
 1090                       final int offset = viewStartOffset - rowStartOffset;
 1091                       for (int j = 0; j < justificationInfo.spaceMap.length(); j++) {
 1092                           if (justificationInfo.spaceMap.get(j)) {
 1093                               spaceMap[j + offset] = 1;
 1094                           }
 1095                       }
 1096                       if (startJustifiableContent > 0) {
 1097                           if (justificationInfo.end >= 0) {
 1098                               extendableSpaces += justificationInfo.trailingSpaces;
 1099                           } else {
 1100                               lastLeadingSpaces += justificationInfo.trailingSpaces;
 1101                           }
 1102                       }
 1103                       if (justificationInfo.start >= 0) {
 1104                           startJustifiableContent =
 1105                               justificationInfo.start + viewStartOffset;
 1106                           extendableSpaces += lastLeadingSpaces;
 1107                       }
 1108                       if (justificationInfo.end >= 0
 1109                             && endJustifiableContent < 0) {
 1110                           endJustifiableContent =
 1111                               justificationInfo.end + viewStartOffset;
 1112                       }
 1113                       extendableSpaces += justificationInfo.contentSpaces;
 1114                       lastLeadingSpaces = justificationInfo.leadingSpaces;
 1115                       if (justificationInfo.hasTab) {
 1116                           break;
 1117                       }
 1118                   }
 1119               }
 1120               if (extendableSpaces <= 0) {
 1121                   //there is nothing we can do to justify
 1122                   return;
 1123               }
 1124               int adjustment = (targetSpan - currentSpan);
 1125               int spaceAddon = (extendableSpaces > 0)
 1126                   ?  adjustment / extendableSpaces
 1127                   : 0;
 1128               int spaceAddonLeftoverEnd = -1;
 1129               for (int i = startJustifiableContent - rowStartOffset,
 1130                        leftover = adjustment - spaceAddon * extendableSpaces;
 1131                        leftover > 0;
 1132                        leftover -= spaceMap[i],
 1133                        i++) {
 1134                   spaceAddonLeftoverEnd = i;
 1135               }
 1136               if (spaceAddon > 0 || spaceAddonLeftoverEnd >= 0) {
 1137                   justificationData = (oldJustficationData != null)
 1138                       ? oldJustficationData
 1139                       : new int[END_JUSTIFIABLE + 1];
 1140                   justificationData[SPACE_ADDON] = spaceAddon;
 1141                   justificationData[SPACE_ADDON_LEFTOVER_END] =
 1142                       spaceAddonLeftoverEnd;
 1143                   justificationData[START_JUSTIFIABLE] =
 1144                       startJustifiableContent - rowStartOffset;
 1145                   justificationData[END_JUSTIFIABLE] =
 1146                       endJustifiableContent - rowStartOffset;
 1147                   super.layoutMajorAxis(targetSpan, axis, offsets, spans);
 1148               }
 1149           }
 1150   
 1151           //for justified row we assume the maximum horizontal span
 1152           //is MAX_VALUE.
 1153           @Override
 1154           public float getMaximumSpan(int axis) {
 1155               float ret;
 1156               if (View.X_AXIS == axis
 1157                     && isJustifyEnabled()) {
 1158                   ret = Float.MAX_VALUE;
 1159               } else {
 1160                 ret = super.getMaximumSpan(axis);
 1161               }
 1162               return ret;
 1163           }
 1164   
 1165           /**
 1166            * Fetches the child view index representing the given position in
 1167            * the model.
 1168            *
 1169            * @param pos the position >= 0
 1170            * @return  index of the view representing the given position, or
 1171            *   -1 if no view represents that position
 1172            */
 1173           protected int getViewIndexAtPosition(int pos) {
 1174               // This is expensive, but are views are not necessarily layed
 1175               // out in model order.
 1176               if(pos < getStartOffset() || pos >= getEndOffset())
 1177                   return -1;
 1178               for(int counter = getViewCount() - 1; counter >= 0; counter--) {
 1179                   View v = getView(counter);
 1180                   if(pos >= v.getStartOffset() &&
 1181                      pos < v.getEndOffset()) {
 1182                       return counter;
 1183                   }
 1184               }
 1185               return -1;
 1186           }
 1187   
 1188           /**
 1189            * Gets the left inset.
 1190            *
 1191            * @return the inset
 1192            */
 1193           protected short getLeftInset() {
 1194               View parentView;
 1195               int adjustment = 0;
 1196               if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
 1197                   if (this == parentView.getView(0)) {
 1198                       adjustment = firstLineIndent;
 1199                   }
 1200               }
 1201               return (short)(super.getLeftInset() + adjustment);
 1202           }
 1203   
 1204           protected short getBottomInset() {
 1205               return (short)(super.getBottomInset() +
 1206                              ((minorRequest != null) ? minorRequest.preferred : 0) *
 1207                              lineSpacing);
 1208           }
 1209   
 1210           final static int SPACE_ADDON = 0;
 1211           final static int SPACE_ADDON_LEFTOVER_END = 1;
 1212           final static int START_JUSTIFIABLE = 2;
 1213           //this should be the last index in justificationData
 1214           final static int END_JUSTIFIABLE = 3;
 1215   
 1216           int justificationData[] = null;
 1217       }
 1218   
 1219   }

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