Source for javax.swing.text.ParagraphView

   1: /* ParagraphView.java -- A composite View
   2:    Copyright (C) 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text;
  40: 
  41: import java.awt.Shape;
  42: 
  43: import javax.swing.SizeRequirements;
  44: import javax.swing.event.DocumentEvent;
  45: 
  46: /**
  47:  * A {@link FlowView} that flows it's children horizontally and boxes the rows
  48:  * vertically.
  49:  *
  50:  * @author Roman Kennke (roman@kennke.org)
  51:  */
  52: public class ParagraphView extends FlowView implements TabExpander
  53: {
  54:   /**
  55:    * A specialized horizontal <code>BoxView</code> that represents exactly
  56:    * one row in a <code>ParagraphView</code>.
  57:    */
  58:   class Row extends BoxView
  59:   {
  60:     /**
  61:      * Creates a new instance of <code>Row</code>.
  62:      */
  63:     Row(Element el)
  64:     {
  65:       super(el, X_AXIS);
  66:     }
  67: 
  68:     /**
  69:      * Overridden to adjust when we are the first line, and firstLineIndent
  70:      * is not 0.
  71:      */
  72:     public short getLeftInset()
  73:     {
  74:       short leftInset = super.getLeftInset();
  75:       View parent = getParent();
  76:       if (parent != null)
  77:         {
  78:           if (parent.getView(0) == this)
  79:             leftInset += firstLineIndent;
  80:         }
  81:       return leftInset;
  82:     }
  83: 
  84:     public float getAlignment(int axis)
  85:     {
  86:       float align;
  87:       if (axis == X_AXIS)
  88:         switch (justification)
  89:           {
  90:           case StyleConstants.ALIGN_RIGHT:
  91:             align = 1.0F;
  92:             break;
  93:           case StyleConstants.ALIGN_CENTER:
  94:           case StyleConstants.ALIGN_JUSTIFIED:
  95:             align = 0.5F;
  96:             break;
  97:           case StyleConstants.ALIGN_LEFT:
  98:           default:
  99:             align = 0.0F;
 100:           }
 101:       else
 102:         align = super.getAlignment(axis);
 103:       return align;
 104:     }
 105: 
 106:     /**
 107:      * Overridden because child views are not necessarily laid out in model
 108:      * order.
 109:      */
 110:     protected int getViewIndexAtPosition(int pos)
 111:     {
 112:       int index = -1;
 113:       if (pos >= getStartOffset() && pos < getEndOffset())
 114:         {
 115:           int nviews = getViewCount();
 116:           for (int i = 0; i < nviews && index == -1; i++)
 117:             {
 118:               View child = getView(i);
 119:               if (pos >= child.getStartOffset() && pos < child.getEndOffset())
 120:                 index = i;
 121:             }
 122:         }
 123:       return index;
 124:     }
 125: 
 126: 
 127:     /**
 128:      * Overridden to perform a baseline layout. The normal BoxView layout
 129:      * isn't completely suitable for rows.
 130:      */
 131:     protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
 132:                                    int[] spans)
 133:     {
 134:       baselineLayout(targetSpan, axis, offsets, spans);
 135:     }
 136: 
 137:     /**
 138:      * Overridden to perform a baseline layout. The normal BoxView layout
 139:      * isn't completely suitable for rows.
 140:      */
 141:     protected SizeRequirements calculateMinorAxisRequirements(int axis,
 142:                                                             SizeRequirements r)
 143:     {
 144:       return baselineRequirements(axis, r);
 145:     }
 146: 
 147:     protected void loadChildren(ViewFactory vf)
 148:     {
 149:       // Do nothing here. The children are added while layouting.
 150:     }
 151: 
 152:     /**
 153:      * Overridden to determine the minimum start offset of the row's children.
 154:      */
 155:     public int getStartOffset()
 156:     {
 157:       // Determine minimum start offset of the children.
 158:       int offset = Integer.MAX_VALUE;
 159:       int n = getViewCount();
 160:       for (int i = 0; i < n; i++)
 161:         {
 162:           View v = getView(i);
 163:           offset = Math.min(offset, v.getStartOffset());
 164:         }
 165:       return offset;
 166:     }
 167: 
 168:     /**
 169:      * Overridden to determine the maximum end offset of the row's children.
 170:      */
 171:     public int getEndOffset()
 172:     {
 173:       // Determine minimum start offset of the children.
 174:       int offset = 0;
 175:       int n = getViewCount();
 176:       for (int i = 0; i < n; i++)
 177:         {
 178:           View v = getView(i);
 179:           offset = Math.max(offset, v.getEndOffset());
 180:         }
 181:       return offset;
 182:     }
 183:   }
 184: 
 185:   /**
 186:    * The indentation of the first line of the paragraph.
 187:    */
 188:   protected int firstLineIndent;
 189: 
 190:   /**
 191:    * The justification of the paragraph.
 192:    */
 193:   private int justification;
 194: 
 195:   /**
 196:    * The line spacing of this paragraph.
 197:    */
 198:   private float lineSpacing;
 199: 
 200:   /**
 201:    * The TabSet of this paragraph.
 202:    */
 203:   private TabSet tabSet;
 204: 
 205:   /**
 206:    * Creates a new <code>ParagraphView</code> for the given
 207:    * <code>Element</code>.
 208:    *
 209:    * @param element the element that is rendered by this ParagraphView
 210:    */
 211:   public ParagraphView(Element element)
 212:   {
 213:     super(element, Y_AXIS);
 214:   }
 215: 
 216:   public float nextTabStop(float x, int tabOffset)
 217:   {
 218:     throw new InternalError("Not implemented yet");
 219:   }
 220: 
 221:   /**
 222:    * Creates a new view that represents a row within a flow.
 223:    *
 224:    * @return a view for a new row
 225:    */
 226:   protected View createRow()
 227:   {
 228:     return new Row(getElement());
 229:   }
 230: 
 231:   /**
 232:    * Returns the alignment for this paragraph view for the specified axis.
 233:    * For the X_AXIS the paragraph view will be aligned at it's left edge
 234:    * (0.0F). For the Y_AXIS the paragraph view will be aligned at the
 235:    * center of it's first row.
 236:    *
 237:    * @param axis the axis which is examined
 238:    *
 239:    * @return the alignment for this paragraph view for the specified axis
 240:    */
 241:   public float getAlignment(int axis)
 242:   {
 243:     float align;
 244:     if (axis == X_AXIS)
 245:       align = 0.5F;
 246:     else if (getViewCount() > 0)
 247:       {
 248:         float prefHeight = getPreferredSpan(Y_AXIS);
 249:         float firstRowHeight = getView(0).getPreferredSpan(Y_AXIS);
 250:         align = (firstRowHeight / 2.F) / prefHeight;
 251:       }
 252:     else
 253:       align = 0.5F;
 254:     return align;
 255:   }
 256: 
 257:   /**
 258:    * Receives notification when some attributes of the displayed element
 259:    * changes. This triggers a refresh of the cached attributes of this
 260:    * paragraph.
 261:    *
 262:    * @param ev the document event
 263:    * @param a the allocation of this view
 264:    * @param vf the view factory to use for creating new child views
 265:    */
 266:   public void changedUpdate(DocumentEvent ev, Shape a, ViewFactory vf)
 267:   {
 268:     setPropertiesFromAttributes();
 269:     layoutChanged(X_AXIS);
 270:     layoutChanged(Y_AXIS);
 271:     super.changedUpdate(ev, a, vf);
 272:   }
 273: 
 274:   /**
 275:    * Fetches the cached properties from the element's attributes.
 276:    */
 277:   protected void setPropertiesFromAttributes()
 278:   {
 279:     Element el = getElement();
 280:     AttributeSet atts = el.getAttributes();
 281:     setFirstLineIndent(StyleConstants.getFirstLineIndent(atts));
 282:     setLineSpacing(StyleConstants.getLineSpacing(atts));
 283:     setJustification(StyleConstants.getAlignment(atts));
 284:     tabSet = StyleConstants.getTabSet(atts);
 285:   }
 286: 
 287:   /**
 288:    * Sets the indentation of the first line of the paragraph.
 289:    *
 290:    * @param i the indentation to set
 291:    */
 292:   protected void setFirstLineIndent(float i)
 293:   {
 294:     firstLineIndent = (int) i;
 295:   }
 296: 
 297:   /**
 298:    * Sets the justification of the paragraph.
 299:    *
 300:    * @param j the justification to set
 301:    */
 302:   protected void setJustification(int j)
 303:   {
 304:     justification = j;
 305:   }
 306: 
 307:   /**
 308:    * Sets the line spacing for this paragraph.
 309:    *
 310:    * @param s the line spacing to set
 311:    */
 312:   protected void setLineSpacing(float s)
 313:   {
 314:     lineSpacing = s;
 315:   }
 316: 
 317:   /**
 318:    * Returns the i-th view from the logical views, before breaking into rows.
 319:    *
 320:    * @param i the index of the logical view to return
 321:    *
 322:    * @return the i-th view from the logical views, before breaking into rows
 323:    */
 324:   protected View getLayoutView(int i)
 325:   {
 326:     return layoutPool.getView(i);
 327:   }
 328: 
 329:   /**
 330:    * Returns the number of logical child views.
 331:    *
 332:    * @return the number of logical child views
 333:    */
 334:   protected int getLayoutViewCount()
 335:   {
 336:     return layoutPool.getViewCount();
 337:   }
 338: 
 339:   /**
 340:    * Returns the TabSet used by this ParagraphView.
 341:    *
 342:    * @return the TabSet used by this ParagraphView
 343:    */
 344:   protected TabSet getTabSet()
 345:   {
 346:     return tabSet;
 347:   }
 348: 
 349:   /**
 350:    * Finds the next offset in the document that has one of the characters
 351:    * specified in <code>string</code>. If there is no such character found,
 352:    * this returns -1.
 353:    *
 354:    * @param string the characters to search for
 355:    * @param start the start offset
 356:    *
 357:    * @return the next offset in the document that has one of the characters
 358:    *         specified in <code>string</code>
 359:    */
 360:   protected int findOffsetToCharactersInString(char[] string, int start)
 361:   {
 362:     int offset = -1;
 363:     Document doc = getDocument();
 364:     Segment text = new Segment();
 365:     try
 366:       {
 367:         doc.getText(start, doc.getLength() - start, text);
 368:         int index = start;
 369: 
 370:         searchLoop:
 371:         while (true)
 372:           {
 373:             char ch = text.next();
 374:             if (ch == Segment.DONE)
 375:               break;
 376: 
 377:             for (int j = 0; j < string.length; ++j)
 378:               {
 379:                 if (string[j] == ch)
 380:                   {
 381:                     offset = index;
 382:                     break searchLoop;
 383:                   }
 384:               }
 385:             index++;
 386:           }
 387:       }
 388:     catch (BadLocationException ex)
 389:       {
 390:         // Ignore this and return -1.
 391:       }
 392:     return offset;
 393:   }
 394: 
 395:   protected int getClosestPositionTo(int pos, Position.Bias bias, Shape a,
 396:                                      int direction, Position.Bias[] biasRet,
 397:                                      int rowIndex, int x)
 398:     throws BadLocationException
 399:   {
 400:     // FIXME: Implement this properly. However, this looks like it might
 401:     // have been replaced by viewToModel.
 402:     return pos;
 403:   }
 404: 
 405:   /**
 406:    * Returns the size that is used by this view (or it's child views) between
 407:    * <code>startOffset</code> and <code>endOffset</code>. If the child views
 408:    * implement the {@link TabableView} interface, then this is used to
 409:    * determine the span, otherwise we use the preferred span of the child
 410:    * views.
 411:    *
 412:    * @param startOffset the start offset
 413:    * @param endOffset the end offset
 414:    *
 415:    * @return the span used by the view between <code>startOffset</code> and
 416:    *         <code>endOffset</cod>
 417:    */
 418:   protected float getPartialSize(int startOffset, int endOffset)
 419:   {
 420:     int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
 421:     int endIndex = getViewIndex(endOffset, Position.Bias.Forward);
 422:     float span;
 423:     if (startIndex == endIndex)
 424:       {
 425:         View child = getView(startIndex);
 426:         if (child instanceof TabableView)
 427:           {
 428:             TabableView tabable = (TabableView) child;
 429:             span = tabable.getPartialSpan(startOffset, endOffset);
 430:           }
 431:         else
 432:           span = child.getPreferredSpan(X_AXIS);
 433:       }
 434:     else if (endIndex - startIndex == 1)
 435:       {
 436:         View child1 = getView(startIndex);
 437:         if (child1 instanceof TabableView)
 438:           {
 439:             TabableView tabable = (TabableView) child1;
 440:             span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
 441:           }
 442:         else
 443:           span = child1.getPreferredSpan(X_AXIS);
 444:         View child2 = getView(endIndex);
 445:         if (child2 instanceof TabableView)
 446:           {
 447:             TabableView tabable = (TabableView) child2;
 448:             span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
 449:           }
 450:         else
 451:           span += child2.getPreferredSpan(X_AXIS);
 452:       }
 453:     else
 454:       {
 455:         // Start with the first view.
 456:         View child1 = getView(startIndex);
 457:         if (child1 instanceof TabableView)
 458:           {
 459:             TabableView tabable = (TabableView) child1;
 460:             span = tabable.getPartialSpan(startOffset, child1.getEndOffset());
 461:           }
 462:         else
 463:           span = child1.getPreferredSpan(X_AXIS);
 464: 
 465:         // Add up the view spans between the start and the end view.
 466:         for (int i = startIndex + 1; i < endIndex; i++)
 467:           {
 468:             View child = getView(i);
 469:             span += child.getPreferredSpan(X_AXIS);
 470:           }
 471: 
 472:         // Add the span of the last view.
 473:         View child2 = getView(endIndex);
 474:         if (child2 instanceof TabableView)
 475:           {
 476:             TabableView tabable = (TabableView) child2;
 477:             span += tabable.getPartialSpan(child2.getStartOffset(), endOffset);
 478:           }
 479:         else
 480:           span += child2.getPreferredSpan(X_AXIS);
 481:       }
 482:     return span;
 483:   }
 484: 
 485:   /**
 486:    * Returns the location where the tabs are calculated from. This returns
 487:    * <code>0.0F</code> by default.
 488:    *
 489:    * @return the location where the tabs are calculated from
 490:    */
 491:   protected float getTabBase()
 492:   {
 493:     return 0.0F;
 494:   }
 495: 
 496:   /**
 497:    * @specnote This method is specified to take a Row parameter, which is a
 498:    *           private inner class of that class, which makes it unusable from
 499:    *           application code. Also, this method seems to be replaced by
 500:    *           {@link FlowStrategy#adjustRow(FlowView, int, int, int)}.
 501:    *
 502:    */
 503:   protected void adjustRow(Row r, int desiredSpan, int x)
 504:   {
 505:   }
 506: 
 507:   /**
 508:    * @specnote This method's signature differs from the one defined in
 509:    *           {@link View} and is therefore never called. It is probably there
 510:    *           for historical reasons.
 511:    */
 512:   public View breakView(int axis, float len, Shape a)
 513:   {
 514:     // This method is not used.
 515:     return null;
 516:   }
 517: 
 518:   /**
 519:    * @specnote This method's signature differs from the one defined in
 520:    *           {@link View} and is therefore never called. It is probably there
 521:    *           for historical reasons.
 522:    */
 523:   public int getBreakWeight(int axis, float len)
 524:   {
 525:     // This method is not used.
 526:     return 0;
 527:   }
 528: }