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

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

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