Source for javax.swing.text.WrappedPlainView

   1: /* WrappedPlainView.java -- 
   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.Container;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: import java.awt.Shape;
  47: 
  48: import javax.swing.event.DocumentEvent;
  49: import javax.swing.text.Position.Bias;
  50: 
  51: /**
  52:  * @author abalkiss
  53:  *
  54:  */
  55: public class WrappedPlainView extends BoxView implements TabExpander
  56: {
  57:   /** The color for selected text **/
  58:   Color selectedColor;
  59:   
  60:   /** The color for unselected text **/
  61:   Color unselectedColor;
  62:   
  63:   /** The color for disabled components **/
  64:   Color disabledColor;
  65:   
  66:   /** Stores the font metrics **/
  67:   protected FontMetrics metrics;
  68:   
  69:   /** Whether or not to wrap on word boundaries **/
  70:   boolean wordWrap;
  71:   
  72:   /** A ViewFactory that creates WrappedLines **/
  73:   ViewFactory viewFactory = new WrappedLineCreator();
  74:   
  75:   /**
  76:    * The instance returned by {@link #getLineBuffer()}.
  77:    */
  78:   private transient Segment lineBuffer;
  79:   
  80:   public WrappedPlainView (Element elem)
  81:   {
  82:     this (elem, false);
  83:   }
  84:   
  85:   public WrappedPlainView (Element elem, boolean wordWrap)
  86:   {
  87:     super (elem, Y_AXIS);
  88:     this.wordWrap = wordWrap;    
  89:   }  
  90:   
  91:   /**
  92:    * Provides access to the Segment used for retrievals from the Document.
  93:    * @return the Segment.
  94:    */
  95:   protected final Segment getLineBuffer()
  96:   {
  97:     if (lineBuffer == null)
  98:       lineBuffer = new Segment();
  99:     return lineBuffer;
 100:   }
 101:   
 102:   /**
 103:    * Returns the next tab stop position after a given reference position.
 104:    *
 105:    * This implementation ignores the <code>tabStop</code> argument.
 106:    * 
 107:    * @param x the current x position in pixels
 108:    * @param tabStop the position within the text stream that the tab occured at
 109:    */
 110:   public float nextTabStop(float x, int tabStop)
 111:   {
 112:     JTextComponent host = (JTextComponent)getContainer();
 113:     float tabSizePixels = getTabSize()
 114:                           * host.getFontMetrics(host.getFont()).charWidth('m');
 115:     return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
 116:   }
 117:   
 118:   /**
 119:    * Returns the tab size for the Document based on 
 120:    * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
 121:    * not defined
 122:    * 
 123:    * @return the tab size.
 124:    */
 125:   protected int getTabSize()
 126:   {
 127:     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
 128:     if (tabSize == null)
 129:       return 8;
 130:     return ((Integer)tabSize).intValue();
 131:   }
 132:   
 133:   /**
 134:    * Draws a line of text, suppressing white space at the end and expanding
 135:    * tabs.  Calls drawSelectedText and drawUnselectedText.
 136:    * @param p0 starting document position to use
 137:    * @param p1 ending document position to use
 138:    * @param g graphics context
 139:    * @param x starting x position
 140:    * @param y starting y position
 141:    */
 142:   protected void drawLine(int p0, int p1, Graphics g, int x, int y)
 143:   {
 144:     try
 145:     {
 146:       drawUnselectedText(g, x, y, p0, p1);
 147:     }
 148:     catch (BadLocationException ble)
 149:     {
 150:       // shouldn't happen
 151:     }
 152:   }
 153: 
 154:   /**
 155:    * Renders the range of text as selected text.  Just paints the text 
 156:    * in the color specified by the host component.  Assumes the highlighter
 157:    * will render the selected background.
 158:    * @param g the graphics context
 159:    * @param x the starting X coordinate
 160:    * @param y the starting Y coordinate
 161:    * @param p0 the starting model location
 162:    * @param p1 the ending model location 
 163:    * @return the X coordinate of the end of the text
 164:    * @throws BadLocationException if the given range is invalid
 165:    */
 166:   protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
 167:       throws BadLocationException
 168:   {
 169:     g.setColor(selectedColor);
 170:     Segment segment = getLineBuffer();
 171:     getDocument().getText(p0, p1 - p0, segment);
 172:     return Utilities.drawTabbedText(segment, x, y, g, this, 0);
 173:   }
 174: 
 175:   /**
 176:    * Renders the range of text as normal unhighlighted text.
 177:    * @param g the graphics context
 178:    * @param x the starting X coordinate
 179:    * @param y the starting Y coordinate
 180:    * @param p0 the starting model location
 181:    * @param p1 the end model location
 182:    * @return the X location of the end off the range
 183:    * @throws BadLocationException if the range given is invalid
 184:    */
 185:   protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
 186:       throws BadLocationException
 187:   {
 188:     JTextComponent textComponent = (JTextComponent) getContainer();
 189:     if (textComponent.isEnabled())
 190:       g.setColor(unselectedColor);
 191:     else
 192:       g.setColor(disabledColor);
 193: 
 194:     Segment segment = getLineBuffer();
 195:     getDocument().getText(p0, p1 - p0, segment);
 196:     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
 197:   }  
 198:   
 199:   /**
 200:    * Loads the children to initiate the view.  Called by setParent.
 201:    * Creates a WrappedLine for each child Element.
 202:    */
 203:   protected void loadChildren (ViewFactory f)
 204:   {
 205:     Element root = getElement();
 206:     int numChildren = root.getElementCount();
 207:     if (numChildren == 0)
 208:       return;
 209:     
 210:     View[] children = new View[numChildren];
 211:     for (int i = 0; i < numChildren; i++)
 212:       children[i] = new WrappedLine(root.getElement(i));
 213:     replace(0, 0, children);
 214:   }
 215:   
 216:   /**
 217:    * Calculates the break position for the text between model positions
 218:    * p0 and p1.  Will break on word boundaries or character boundaries
 219:    * depending on the break argument given in construction of this 
 220:    * WrappedPlainView.  Used by the nested WrappedLine class to determine
 221:    * when to start the next logical line.
 222:    * @param p0 the start model position
 223:    * @param p1 the end model position
 224:    * @return the model position at which to break the text
 225:    */
 226:   protected int calculateBreakPosition(int p0, int p1)
 227:   {
 228:     Container c = getContainer();
 229:     Rectangle alloc = c.isValid() ? c.getBounds()
 230:                                  : new Rectangle(c.getPreferredSize());
 231:     updateMetrics();
 232:     try
 233:       {
 234:         getDocument().getText(p0, p1 - p0, getLineBuffer());
 235:       }
 236:     catch (BadLocationException ble)
 237:       {
 238:         // this shouldn't happen
 239:       }
 240:     // FIXME: Should we account for the insets of the container?
 241:     if (wordWrap)
 242:       return p0
 243:              + Utilities.getBreakLocation(lineBuffer, metrics, alloc.x,
 244:                                           alloc.x + alloc.width, this, 0);
 245:     else
 246:       {
 247:       return p0
 248:              + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x,
 249:                                              alloc.x + alloc.width, this, 0);
 250:       }
 251:   }
 252:   
 253:   void updateMetrics()
 254:   {
 255:     Container component = getContainer();
 256:     metrics = component.getFontMetrics(component.getFont());
 257:   }
 258:   
 259:   /**
 260:    * Determines the preferred span along the given axis.  Implemented to 
 261:    * cache the font metrics and then call the super classes method.
 262:    */
 263:   public float getPreferredSpan (int axis)
 264:   {
 265:     updateMetrics();
 266:     return super.getPreferredSpan(axis);
 267:   }
 268:   
 269:   /**
 270:    * Called when something was inserted.  Overridden so that
 271:    * the view factory creates WrappedLine views.
 272:    */
 273:   public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
 274:   {
 275:     super.insertUpdate(e, a, viewFactory);
 276:     // FIXME: could improve performance by repainting only the necessary area
 277:     getContainer().repaint();
 278:   }
 279:   
 280:   /**
 281:    * Called when something is removed.  Overridden so that
 282:    * the view factory creates WrappedLine views.
 283:    */
 284:   public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
 285:   {
 286:     super.removeUpdate(e, a, viewFactory);
 287:     // FIXME: could improve performance by repainting only the necessary area
 288:     getContainer().repaint();
 289:   }
 290:   
 291:   /**
 292:    * Called when the portion of the Document that this View is responsible
 293:    * for changes.  Overridden so that the view factory creates
 294:    * WrappedLine views.
 295:    */
 296:   public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
 297:   {
 298:     super.changedUpdate(e, a, viewFactory);
 299:     // FIXME: could improve performance by repainting only the necessary area
 300:     getContainer().repaint();
 301:   }
 302:     
 303:   class WrappedLineCreator implements ViewFactory
 304:   {
 305:     // Creates a new WrappedLine
 306:     public View create(Element elem)
 307:     {
 308:       return new WrappedLine(elem);
 309:     }    
 310:   }
 311:   
 312:   /**
 313:    * Renders the <code>Element</code> that is associated with this
 314:    * <code>View</code>.  Caches the metrics and then calls
 315:    * super.paint to paint all the child views.
 316:    *
 317:    * @param g the <code>Graphics</code> context to render to
 318:    * @param a the allocated region for the <code>Element</code>
 319:    */
 320:   public void paint(Graphics g, Shape a)
 321:   {
 322:     updateMetrics();
 323:     super.paint(g, a);
 324:   }
 325:   
 326:   /**
 327:    * Sets the size of the View.  Implemented to update the metrics
 328:    * and then call super method.
 329:    */
 330:   public void setSize (float width, float height)
 331:   {
 332:     updateMetrics();
 333:     super.setSize(width, height);
 334:   }
 335:   
 336:   class WrappedLine extends View
 337:   { 
 338:     /** Used to cache the number of lines for this View **/
 339:     int numLines;
 340:     
 341:     public WrappedLine(Element elem)
 342:     {
 343:       super(elem);
 344:       determineNumLines();
 345:     }
 346: 
 347:     /**
 348:      * Renders this (possibly wrapped) line using the given Graphics object
 349:      * and on the given rendering surface.
 350:      */
 351:     public void paint(Graphics g, Shape s)
 352:     {
 353:       // Ensure metrics are up-to-date.
 354:       updateMetrics();
 355:       JTextComponent textComponent = (JTextComponent) getContainer();
 356: 
 357:       g.setFont(textComponent.getFont());
 358:       selectedColor = textComponent.getSelectedTextColor();
 359:       unselectedColor = textComponent.getForeground();
 360:       disabledColor = textComponent.getDisabledTextColor();
 361: 
 362:       Rectangle rect = s.getBounds();
 363:       int lineHeight = metrics.getHeight();
 364: 
 365:       int end = getEndOffset();
 366:       int currStart = getStartOffset();
 367:       int currEnd;      
 368:       while (currStart < end)
 369:         {
 370:           currEnd = calculateBreakPosition(currStart, end);
 371:           drawLine(currStart, currEnd, g, rect.x, rect.y);
 372:           rect.y += lineHeight;          
 373:           if (currEnd == currStart)
 374:             currStart ++;
 375:           else
 376:             currStart = currEnd;          
 377:         }
 378:     }
 379:     
 380:     /**
 381:      * Determines the number of logical lines that the Element
 382:      * needs to be displayed
 383:      * @return the number of lines needed to display the Element
 384:      */
 385:     int determineNumLines()
 386:     {      
 387:       int end = getEndOffset();
 388:       if (end == 0)
 389:         return 0;
 390:       
 391:       numLines = 0;
 392:       int breakPoint;
 393:       for (int i = getStartOffset(); i < end;)
 394:         {
 395:           numLines ++;
 396:           // careful: check that there's no off-by-one problem here
 397:           // depending on which position calculateBreakPosition returns
 398:           breakPoint = calculateBreakPosition(i, end);
 399:           if (breakPoint == i)
 400:             i ++;
 401:           else
 402:             i = breakPoint;
 403:         }
 404:       return numLines;
 405:     }
 406:     
 407:     /**
 408:      * Determines the preferred span for this view along the given axis.
 409:      * 
 410:      * @param axis the axis (either X_AXIS or Y_AXIS)
 411:      * 
 412:      * @return the preferred span along the given axis.
 413:      * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
 414:      */
 415:     public float getPreferredSpan(int axis)
 416:     {
 417:       if (axis == X_AXIS)
 418:         return getWidth();
 419:       else if (axis == Y_AXIS)
 420:         return numLines * metrics.getHeight(); 
 421:       
 422:       throw new IllegalArgumentException("Invalid axis for getPreferredSpan: "
 423:                                          + axis);
 424:     }
 425:     
 426:     /**
 427:      * Provides a mapping from model space to view space.
 428:      * 
 429:      * @param pos the position in the model
 430:      * @param a the region into which the view is rendered
 431:      * @param b the position bias (forward or backward)
 432:      * 
 433:      * @return a box in view space that represents the given position 
 434:      * in model space
 435:      * @throws BadLocationException if the given model position is invalid
 436:      */
 437:     public Shape modelToView(int pos, Shape a, Bias b) throws BadLocationException
 438:     {
 439:       Segment s = getLineBuffer();
 440:       int lineHeight = metrics.getHeight();
 441:       Rectangle rect = a.getBounds();
 442:       
 443:       // Return a rectangle with width 1 and height equal to the height 
 444:       // of the text
 445:       rect.height = lineHeight;
 446:       rect.width = 1;
 447: 
 448:       int currLineStart = getStartOffset();
 449:       int end = getEndOffset();
 450:       
 451:       if (pos < currLineStart || pos >= end)
 452:         throw new BadLocationException("invalid offset", pos);
 453:            
 454:       while (true)
 455:         {
 456:           int currLineEnd = calculateBreakPosition(currLineStart, end);
 457:           // If pos is between currLineStart and currLineEnd then just find
 458:           // the width of the text from currLineStart to pos and add that
 459:           // to rect.x
 460:           if (pos >= currLineStart && pos < currLineEnd || pos == end - 1)
 461:             {             
 462:               try
 463:                 {
 464:                   getDocument().getText(currLineStart, pos - currLineStart, s);
 465:                 }
 466:               catch (BadLocationException ble)
 467:                 {
 468:                   // Shouldn't happen
 469:                 }
 470:               rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x, WrappedPlainView.this, currLineStart);
 471:               return rect;
 472:             }
 473:           // Increment rect.y so we're checking the next logical line
 474:           rect.y += lineHeight;
 475:           
 476:           // Increment currLineStart to the model position of the start
 477:           // of the next logical line
 478:           if (currLineEnd == currLineStart)
 479:             currLineStart = end;
 480:           else
 481:             currLineStart = currLineEnd;
 482:         }
 483: 
 484:     }
 485: 
 486:     /**
 487:      * Provides a mapping from view space to model space.
 488:      * 
 489:      * @param x the x coordinate in view space
 490:      * @param y the y coordinate in view space
 491:      * @param a the region into which the view is rendered
 492:      * @param b the position bias (forward or backward)
 493:      * 
 494:      * @return the location in the model that best represents the
 495:      * given point in view space
 496:      */
 497:     public int viewToModel(float x, float y, Shape a, Bias[] b)
 498:     {
 499:       Segment s = getLineBuffer();
 500:       Rectangle rect = a.getBounds();
 501:       int currLineStart = getStartOffset();
 502:       int end = getEndOffset();
 503:       int lineHeight = metrics.getHeight();
 504:       if (y < rect.y)
 505:         return currLineStart;
 506:       if (y > rect.y + rect.height)
 507:         return end - 1;
 508: 
 509:       while (true)
 510:         {
 511:           int currLineEnd = calculateBreakPosition(currLineStart, end);
 512:           // If we're at the right y-position that means we're on the right
 513:           // logical line and we should look for the character
 514:           if (y >= rect.y && y < rect.y + lineHeight)
 515:             {
 516:               // Check if the x position is to the left or right of the text
 517:               if (x < rect.x)
 518:                 return currLineStart;
 519:               if (x > rect.x + rect.width)
 520:                 return currLineEnd - 1;
 521:               
 522:               try
 523:                 {
 524:                   getDocument().getText(currLineStart, end - currLineStart, s);
 525:                 }
 526:               catch (BadLocationException ble)
 527:                 {
 528:                   // Shouldn't happen
 529:                 }
 530:               int mark = Utilities.getTabbedTextOffset(s, metrics, rect.x,
 531:                                                        (int) x,
 532:                                                        WrappedPlainView.this,
 533:                                                        currLineStart);
 534:               return currLineStart + mark;
 535:             }
 536:           // Increment rect.y so we're checking the next logical line
 537:           rect.y += lineHeight;
 538:           
 539:           // Increment currLineStart to the model position of the start
 540:           // of the next logical line
 541:           if (currLineEnd == currLineStart)
 542:             currLineStart = end;
 543:           else
 544:             currLineStart = currLineEnd;
 545:         }
 546:     }    
 547:   }
 548: }