Source for javax.swing.text.PlainView

   1: /* PlainView.java -- 
   2:    Copyright (C) 2004, 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.Component;
  43: import java.awt.Font;
  44: import java.awt.FontMetrics;
  45: import java.awt.Graphics;
  46: import java.awt.Rectangle;
  47: import java.awt.Shape;
  48: 
  49: import javax.swing.SwingConstants;
  50: import javax.swing.event.DocumentEvent;
  51: import javax.swing.event.DocumentEvent.ElementChange;
  52: 
  53: public class PlainView extends View implements TabExpander
  54: {
  55:   Color selectedColor;
  56:   Color unselectedColor;
  57: 
  58:   /**
  59:    * The color that is used to draw disabled text fields.
  60:    */
  61:   Color disabledColor;
  62: 
  63:   Font font;
  64:   
  65:   /** The length of the longest line in the Document **/
  66:   float maxLineLength = -1;
  67:   
  68:   /** The longest line in the Document **/
  69:   Element longestLine = null;
  70:   
  71:   protected FontMetrics metrics;
  72: 
  73:   /**
  74:    * The instance returned by {@link #getLineBuffer()}.
  75:    */
  76:   private transient Segment lineBuffer;
  77: 
  78:   public PlainView(Element elem)
  79:   {
  80:     super(elem);
  81:   }
  82: 
  83:   /**
  84:    * @since 1.4
  85:    */
  86:   protected void updateMetrics()
  87:   {
  88:     Component component = getContainer();
  89:     Font font = component.getFont();
  90: 
  91:     if (this.font != font)
  92:       {
  93:     this.font = font;
  94:     metrics = component.getFontMetrics(font);
  95:       }
  96:   }
  97:   
  98:   /**
  99:    * @since 1.4
 100:    */
 101:   protected Rectangle lineToRect(Shape a, int line)
 102:   {
 103:     // Ensure metrics are up-to-date.
 104:     updateMetrics();
 105:     
 106:     Rectangle rect = a.getBounds();
 107:     int fontHeight = metrics.getHeight();
 108:     return new Rectangle(rect.x, rect.y + (line * fontHeight),
 109:              rect.width, fontHeight);
 110:   }
 111: 
 112:   public Shape modelToView(int position, Shape a, Position.Bias b)
 113:     throws BadLocationException
 114:   {
 115:     // Ensure metrics are up-to-date.
 116:     updateMetrics();
 117:     
 118:     Document document = getDocument();
 119: 
 120:     // Get rectangle of the line containing position.
 121:     int lineIndex = getElement().getElementIndex(position);
 122:     Rectangle rect = lineToRect(a, lineIndex);
 123: 
 124:     // Get the rectangle for position.
 125:     Element line = getElement().getElement(lineIndex);
 126:     int lineStart = line.getStartOffset();
 127:     Segment segment = getLineBuffer();
 128:     document.getText(lineStart, position - lineStart, segment);
 129:     int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x,
 130:                            this, lineStart);
 131: 
 132:     // Calc the real rectangle.
 133:     rect.x += xoffset;
 134:     rect.width = 1;
 135:     rect.height = metrics.getHeight();
 136: 
 137:     return rect;
 138:   }
 139:   
 140:   protected void drawLine(int lineIndex, Graphics g, int x, int y)
 141:   {
 142:     try
 143:       {
 144:     metrics = g.getFontMetrics();
 145:     // FIXME: Selected text are not drawn yet.
 146:     Element line = getElement().getElement(lineIndex);
 147:     drawUnselectedText(g, x, y, line.getStartOffset(), line.getEndOffset());
 148:     //drawSelectedText(g, , , , );
 149:       }
 150:     catch (BadLocationException e)
 151:       {
 152:     AssertionError ae = new AssertionError("Unexpected bad location");
 153:     ae.initCause(e);
 154:     throw ae;
 155:       }
 156:   }
 157: 
 158:   protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
 159:     throws BadLocationException
 160:   {
 161:     g.setColor(selectedColor);
 162:     Segment segment = getLineBuffer();
 163:     getDocument().getText(p0, p1 - p0, segment);
 164:     return Utilities.drawTabbedText(segment, x, y, g, this, 0);
 165:   }
 166: 
 167:   protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
 168:     throws BadLocationException
 169:   {
 170:     JTextComponent textComponent = (JTextComponent) getContainer();
 171:     if (textComponent.isEnabled())
 172:       g.setColor(unselectedColor);
 173:     else
 174:       g.setColor(disabledColor);
 175: 
 176:     Segment segment = getLineBuffer();
 177:     getDocument().getText(p0, p1 - p0, segment);
 178:     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
 179:   }
 180: 
 181:   public void paint(Graphics g, Shape s)
 182:   {
 183:     // Ensure metrics are up-to-date.
 184:     updateMetrics();
 185:     
 186:     JTextComponent textComponent = (JTextComponent) getContainer();
 187: 
 188:     g.setFont(textComponent.getFont());
 189:     selectedColor = textComponent.getSelectedTextColor();
 190:     unselectedColor = textComponent.getForeground();
 191:     disabledColor = textComponent.getDisabledTextColor();
 192: 
 193:     Rectangle rect = s.getBounds();
 194: 
 195:     // FIXME: Text may be scrolled.
 196:     Document document = textComponent.getDocument();
 197:     Element root = document.getDefaultRootElement();
 198:     int y = rect.y;
 199:     
 200:     for (int i = 0; i < root.getElementCount(); i++)
 201:       {
 202:     drawLine(i, g, rect.x, y);
 203:     y += metrics.getHeight();
 204:       }
 205:   }
 206: 
 207:   /**
 208:    * Returns the tab size of a tab.  Checks the Document's
 209:    * properties for PlainDocument.tabSizeAttribute and returns it if it is
 210:    * defined, otherwise returns 8.
 211:    * 
 212:    * @return the tab size.
 213:    */
 214:   protected int getTabSize()
 215:   {
 216:     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
 217:     if (tabSize == null)
 218:       return 8;
 219:     return ((Integer)tabSize).intValue();
 220:   }
 221: 
 222:   /**
 223:    * Returns the next tab stop position after a given reference position.
 224:    *
 225:    * This implementation ignores the <code>tabStop</code> argument.
 226:    * 
 227:    * @param x the current x position in pixels
 228:    * @param tabStop the position within the text stream that the tab occured at
 229:    */
 230:   public float nextTabStop(float x, int tabStop)
 231:   {
 232:     float tabSizePixels = getTabSize() * metrics.charWidth('m');
 233:     return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
 234:   }
 235: 
 236:   /**
 237:    * Returns the length of the longest line, used for getting the span
 238:    * @return the length of the longest line
 239:    */
 240:   float determineMaxLineLength()
 241:   {
 242:     // if the longest line is cached, return the cached value
 243:     if (maxLineLength != -1)
 244:       return maxLineLength;
 245:     
 246:     // otherwise we have to go through all the lines and find it
 247:     Element el = getElement();
 248:     Segment seg = getLineBuffer();
 249:     float span = 0;
 250:     for (int i = 0; i < el.getElementCount(); i++)
 251:       {
 252:         Element child = el.getElement(i);
 253:         int start = child.getStartOffset();
 254:         int end = child.getEndOffset();
 255:         try
 256:           {
 257:             el.getDocument().getText(start, end - start, seg);
 258:           }
 259:         catch (BadLocationException ex)
 260:           {
 261:             AssertionError ae = new AssertionError("Unexpected bad location");
 262:         ae.initCause(ex);
 263:         throw ae;
 264:           }
 265:         
 266:         if (seg == null || seg.array == null || seg.count == 0)
 267:           continue;
 268:         
 269:         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
 270:         if (width > span)
 271:           {
 272:             longestLine = child;
 273:             span = width;
 274:           }
 275:       }
 276:     maxLineLength = span;
 277:     return maxLineLength;
 278:   }
 279:   
 280:   public float getPreferredSpan(int axis)
 281:   {
 282:     if (axis != X_AXIS && axis != Y_AXIS)
 283:       throw new IllegalArgumentException();
 284: 
 285:     // make sure we have the metrics
 286:     updateMetrics();
 287: 
 288:     float span = 0;
 289:     Element el = getElement();
 290: 
 291:     switch (axis)
 292:       {
 293:       case X_AXIS:
 294:         span = determineMaxLineLength();
 295:       case Y_AXIS:
 296:       default:
 297:         span = metrics.getHeight() * el.getElementCount();
 298:         break;
 299:       }
 300:     return span;
 301:   }
 302: 
 303:   /**
 304:    * Maps coordinates from the <code>View</code>'s space into a position
 305:    * in the document model.
 306:    *
 307:    * @param x the x coordinate in the view space
 308:    * @param y the y coordinate in the view space
 309:    * @param a the allocation of this <code>View</code>
 310:    * @param b the bias to use
 311:    *
 312:    * @return the position in the document that corresponds to the screen
 313:    *         coordinates <code>x, y</code>
 314:    */
 315:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 316:   {
 317:     Rectangle rec = a.getBounds();
 318:     Document doc = getDocument();
 319:     Element root = doc.getDefaultRootElement();
 320:     
 321:     // PlainView doesn't support line-wrapping so we can find out which
 322:     // Element was clicked on just by the y-position    
 323:     int lineClicked = (int) (y - rec.y) / metrics.getHeight();
 324:     if (lineClicked >= root.getElementCount())
 325:       return getEndOffset() - 1;
 326:     
 327:     Element line = root.getElement(lineClicked);
 328:     Segment s = getLineBuffer();
 329:     int start = line.getStartOffset();
 330:     // We don't want the \n at the end of the line.
 331:     int end = line.getEndOffset() - 1;
 332:     try
 333:       {
 334:         doc.getText(start, end - start, s);
 335:       }
 336:     catch (BadLocationException ble)
 337:       {
 338:         AssertionError ae = new AssertionError("Unexpected bad location");
 339:         ae.initCause(ble);
 340:         throw ae;
 341:       }
 342:     
 343:     int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start);
 344:     return Math.max (0, pos);
 345:   }     
 346:   
 347:   /**
 348:    * Since insertUpdate and removeUpdate each deal with children
 349:    * Elements being both added and removed, they both have to perform
 350:    * the same checks.  So they both simply call this method.
 351:    * @param changes the DocumentEvent for the changes to the Document.
 352:    * @param a the allocation of the View.
 353:    * @param f the ViewFactory to use for rebuilding.
 354:    */
 355:   protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
 356:   {
 357:     Element el = getElement();
 358:     ElementChange ec = changes.getChange(el);
 359:     
 360:     // If ec is null then no lines were added or removed, just 
 361:     // repaint the changed line
 362:     if (ec == null)
 363:       {
 364:         int line = getElement().getElementIndex(changes.getOffset());
 365:         damageLineRange(line, line, a, getContainer());
 366:         return;
 367:       }
 368:     
 369:     Element[] removed = ec.getChildrenRemoved();
 370:     Element[] newElements = ec.getChildrenAdded();
 371:     
 372:     // If no Elements were added or removed, we just want to repaint
 373:     // the area containing the line that was modified
 374:     if (removed == null && newElements == null)
 375:       {
 376:         int line = getElement().getElementIndex(changes.getOffset());
 377:         damageLineRange(line, line, a, getContainer());
 378:         return;
 379:       }
 380: 
 381:     // Check to see if we removed the longest line, if so we have to
 382:     // search through all lines and find the longest one again
 383:     if (removed != null)
 384:       {
 385:         for (int i = 0; i < removed.length; i++)
 386:           if (removed[i].equals(longestLine))
 387:             {
 388:               // reset maxLineLength and search through all lines for longest one
 389:               maxLineLength = -1;
 390:               determineMaxLineLength();
 391:               ((JTextComponent)getContainer()).repaint();
 392:               return;
 393:             }
 394:       }
 395:     
 396:     // If we've reached here, that means we haven't removed the longest line
 397:     if (newElements == null)
 398:       {
 399:         // No lines were added, just repaint the container and exit
 400:         ((JTextComponent)getContainer()).repaint();
 401:         return;
 402:       }
 403: 
 404:     //  Make sure we have the metrics
 405:     updateMetrics();
 406:        
 407:     // If we've reached here, that means we haven't removed the longest line
 408:     // and we have added at least one line, so we have to check if added lines
 409:     // are longer than the previous longest line        
 410:     Segment seg = getLineBuffer();
 411:     float longestNewLength = 0;
 412:     Element longestNewLine = null;    
 413: 
 414:     // Loop through the added lines to check their length
 415:     for (int i = 0; i < newElements.length; i++)
 416:       {
 417:         Element child = newElements[i];
 418:         int start = child.getStartOffset();
 419:         int end = child.getEndOffset();
 420:         try
 421:           {
 422:             el.getDocument().getText(start, end - start, seg);
 423:           }
 424:         catch (BadLocationException ex)
 425:           {
 426:             AssertionError ae = new AssertionError("Unexpected bad location");
 427:         ae.initCause(ex);
 428:         throw ae;
 429:           }
 430:                 
 431:         if (seg == null || seg.array == null || seg.count == 0)
 432:           continue;
 433:         
 434:         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
 435:         if (width > longestNewLength)
 436:           {
 437:             longestNewLine = child;
 438:             longestNewLength = width;
 439:           }
 440:       }
 441:     
 442:     // Check if the longest of the new lines is longer than our previous
 443:     // longest line, and if so update our values
 444:     if (longestNewLength > maxLineLength)
 445:       {
 446:         maxLineLength = longestNewLength;
 447:         longestLine = longestNewLine;
 448:       }
 449:     // Repaint the container
 450:     ((JTextComponent)getContainer()).repaint();
 451:   }
 452: 
 453:   /**
 454:    * This method is called when something is inserted into the Document
 455:    * that this View is displaying.
 456:    * 
 457:    * @param changes the DocumentEvent for the changes.
 458:    * @param a the allocation of the View
 459:    * @param f the ViewFactory used to rebuild
 460:    */
 461:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 462:   {
 463:     updateDamage(changes, a, f);
 464:   }
 465: 
 466:   /**
 467:    * This method is called when something is removed from the Document
 468:    * that this View is displaying.
 469:    * 
 470:    * @param changes the DocumentEvent for the changes.
 471:    * @param a the allocation of the View
 472:    * @param f the ViewFactory used to rebuild
 473:    */
 474:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 475:   {
 476:     updateDamage(changes, a, f);
 477:   }
 478:   
 479:   /**
 480:    * This method is called when attributes were changed in the 
 481:    * Document in a location that this view is responsible for.
 482:    */
 483:   public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
 484:   {
 485:     updateDamage(changes, a, f);
 486:   }
 487:   
 488:   /**
 489:    * Repaint the given line range.  This is called from insertUpdate,
 490:    * changedUpdate, and removeUpdate when no new lines were added 
 491:    * and no lines were removed, to repaint the line that was 
 492:    * modified.
 493:    * 
 494:    * @param line0 the start of the range
 495:    * @param line1 the end of the range
 496:    * @param a the rendering region of the host
 497:    * @param host the Component that uses this View (used to call repaint
 498:    * on that Component)
 499:    * 
 500:    * @since 1.4
 501:    */
 502:   protected void damageLineRange (int line0, int line1, Shape a, Component host)
 503:   {
 504:     if (a == null)
 505:       return;
 506: 
 507:     Rectangle rec0 = lineToRect(a, line0);
 508:     Rectangle rec1 = lineToRect(a, line1);
 509: 
 510:     if (rec0 == null || rec1 == null)
 511:       // something went wrong, repaint the entire host to be safe
 512:       host.repaint();
 513:     else
 514:       {
 515:         Rectangle repaintRec = rec0.union(rec1);
 516:         host.repaint();
 517:       }    
 518:   }
 519: 
 520:   /**
 521:    * Provides a {@link Segment} object, that can be used to fetch text from
 522:    * the document.
 523:    *
 524:    * @returna {@link Segment} object, that can be used to fetch text from
 525:    *          the document
 526:    */
 527:   protected Segment getLineBuffer()
 528:   {
 529:     if (lineBuffer == null)
 530:       lineBuffer = new Segment();
 531:     return lineBuffer;
 532:   }
 533: 
 534:   /**
 535:    * Returns the document position that is (visually) nearest to the given
 536:    * document position <code>pos</code> in the given direction <code>d</code>.
 537:    *
 538:    * @param c the text component
 539:    * @param pos the document position
 540:    * @param b the bias for <code>pos</code>
 541:    * @param d the direction, must be either {@link SwingConstants#NORTH},
 542:    *        {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
 543:    *        {@link SwingConstants#EAST}
 544:    * @param biasRet an array of {@link Position.Bias} that can hold at least
 545:    *        one element, which is filled with the bias of the return position
 546:    *        on method exit
 547:    *
 548:    * @return the document position that is (visually) nearest to the given
 549:    *         document position <code>pos</code> in the given direction
 550:    *         <code>d</code>
 551:    *
 552:    * @throws BadLocationException if <code>pos</code> is not a valid offset in
 553:    *         the document model
 554:    */
 555:   public int getNextVisualPositionFrom(JTextComponent c, int pos,
 556:                                        Position.Bias b, int d,
 557:                                        Position.Bias[] biasRet)
 558:     throws BadLocationException
 559:   {
 560:     // TODO: Implement this properly.
 561:     throw new AssertionError("Not implemented yet.");
 562:   }
 563: }