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