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:     selectedColor = textComponent.getSelectedTextColor();
 189:     unselectedColor = textComponent.getForeground();
 190:     disabledColor = textComponent.getDisabledTextColor();
 191: 
 192:     Rectangle rect = s.getBounds();
 193: 
 194:     // FIXME: Text may be scrolled.
 195:     Document document = textComponent.getDocument();
 196:     Element root = document.getDefaultRootElement();
 197:     int y = rect.y;
 198:     
 199:     for (int i = 0; i < root.getElementCount(); i++)
 200:       {
 201:     drawLine(i, g, rect.x, y);
 202:     y += metrics.getHeight();
 203:       }
 204:   }
 205: 
 206:   /**
 207:    * Returns the tab size of a tab.  Checks the Document's
 208:    * properties for PlainDocument.tabSizeAttribute and returns it if it is
 209:    * defined, otherwise returns 8.
 210:    * 
 211:    * @return the tab size.
 212:    */
 213:   protected int getTabSize()
 214:   {
 215:     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
 216:     if (tabSize == null)
 217:       return 8;
 218:     return ((Integer)tabSize).intValue();
 219:   }
 220: 
 221:   /**
 222:    * Returns the next tab stop position after a given reference position.
 223:    *
 224:    * This implementation ignores the <code>tabStop</code> argument.
 225:    * 
 226:    * @param x the current x position in pixels
 227:    * @param tabStop the position within the text stream that the tab occured at
 228:    */
 229:   public float nextTabStop(float x, int tabStop)
 230:   {
 231:     float tabSizePixels = getTabSize() * metrics.charWidth('m');
 232:     return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
 233:   }
 234: 
 235:   /**
 236:    * Returns the length of the longest line, used for getting the span
 237:    * @return the length of the longest line
 238:    */
 239:   float determineMaxLineLength()
 240:   {
 241:     // if the longest line is cached, return the cached value
 242:     if (maxLineLength != -1)
 243:       return maxLineLength;
 244:     
 245:     // otherwise we have to go through all the lines and find it
 246:     Element el = getElement();
 247:     Segment seg = getLineBuffer();
 248:     float span = 0;
 249:     for (int i = 0; i < el.getElementCount(); i++)
 250:       {
 251:         Element child = el.getElement(i);
 252:         int start = child.getStartOffset();
 253:         int end = child.getEndOffset();
 254:         try
 255:           {
 256:             el.getDocument().getText(start, end - start, seg);
 257:           }
 258:         catch (BadLocationException ex)
 259:           {
 260:             AssertionError ae = new AssertionError("Unexpected bad location");
 261:         ae.initCause(ex);
 262:         throw ae;
 263:           }
 264:         
 265:         if (seg == null || seg.array == null || seg.count == 0)
 266:           continue;
 267:         
 268:         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
 269:         if (width > span)
 270:           {
 271:             longestLine = child;
 272:             span = width;
 273:           }
 274:       }
 275:     maxLineLength = span;
 276:     return maxLineLength;
 277:   }
 278:   
 279:   public float getPreferredSpan(int axis)
 280:   {
 281:     if (axis != X_AXIS && axis != Y_AXIS)
 282:       throw new IllegalArgumentException();
 283: 
 284:     // make sure we have the metrics
 285:     updateMetrics();
 286: 
 287:     float span = 0;
 288:     Element el = getElement();
 289: 
 290:     switch (axis)
 291:       {
 292:       case X_AXIS:
 293:         span = determineMaxLineLength();
 294:       case Y_AXIS:
 295:       default:
 296:         span = metrics.getHeight() * el.getElementCount();
 297:         break;
 298:       }
 299:     return span;
 300:   }
 301: 
 302:   /**
 303:    * Maps coordinates from the <code>View</code>'s space into a position
 304:    * in the document model.
 305:    *
 306:    * @param x the x coordinate in the view space
 307:    * @param y the y coordinate in the view space
 308:    * @param a the allocation of this <code>View</code>
 309:    * @param b the bias to use
 310:    *
 311:    * @return the position in the document that corresponds to the screen
 312:    *         coordinates <code>x, y</code>
 313:    */
 314:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 315:   {
 316:     Rectangle rec = a.getBounds();
 317:     Document doc = getDocument();
 318:     Element root = doc.getDefaultRootElement();
 319:     
 320:     // PlainView doesn't support line-wrapping so we can find out which
 321:     // Element was clicked on just by the y-position    
 322:     int lineClicked = (int) (y - rec.y) / metrics.getHeight();
 323:     if (lineClicked >= root.getElementCount())
 324:       return getEndOffset() - 1;
 325:     
 326:     Element line = root.getElement(lineClicked);
 327:     Segment s = getLineBuffer();
 328:     int start = line.getStartOffset();
 329:     // We don't want the \n at the end of the line.
 330:     int end = line.getEndOffset() - 1;
 331:     try
 332:       {
 333:         doc.getText(start, end - start, s);
 334:       }
 335:     catch (BadLocationException ble)
 336:       {
 337:         AssertionError ae = new AssertionError("Unexpected bad location");
 338:         ae.initCause(ble);
 339:         throw ae;
 340:       }
 341:     
 342:     int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start);
 343:     return Math.max (0, pos);
 344:   }     
 345:   
 346:   /**
 347:    * Since insertUpdate and removeUpdate each deal with children
 348:    * Elements being both added and removed, they both have to perform
 349:    * the same checks.  So they both simply call this method.
 350:    * @param changes the DocumentEvent for the changes to the Document.
 351:    * @param a the allocation of the View.
 352:    * @param f the ViewFactory to use for rebuilding.
 353:    */
 354:   protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
 355:   {
 356:     Element el = getElement();
 357:     ElementChange ec = changes.getChange(el);
 358:     
 359:     // If ec is null then no lines were added or removed, just 
 360:     // repaint the changed line
 361:     if (ec == null)
 362:       {
 363:         int line = getElement().getElementIndex(changes.getOffset());
 364:         damageLineRange(line, line, a, getContainer());
 365:         return;
 366:       }
 367:     
 368:     Element[] removed = ec.getChildrenRemoved();
 369:     Element[] newElements = ec.getChildrenAdded();
 370:     
 371:     // If no Elements were added or removed, we just want to repaint
 372:     // the area containing the line that was modified
 373:     if (removed == null && newElements == null)
 374:       {
 375:         int line = getElement().getElementIndex(changes.getOffset());
 376:         damageLineRange(line, line, a, getContainer());
 377:         return;
 378:       }
 379: 
 380:     // Check to see if we removed the longest line, if so we have to
 381:     // search through all lines and find the longest one again
 382:     if (removed != null)
 383:       {
 384:         for (int i = 0; i < removed.length; i++)
 385:           if (removed[i].equals(longestLine))
 386:             {
 387:               // reset maxLineLength and search through all lines for longest one
 388:               maxLineLength = -1;
 389:               determineMaxLineLength();
 390:               ((JTextComponent)getContainer()).repaint();
 391:               return;
 392:             }
 393:       }
 394:     
 395:     // If we've reached here, that means we haven't removed the longest line
 396:     if (newElements == null)
 397:       {
 398:         // No lines were added, just repaint the container and exit
 399:         ((JTextComponent)getContainer()).repaint();
 400:         return;
 401:       }
 402: 
 403:     //  Make sure we have the metrics
 404:     updateMetrics();
 405:        
 406:     // If we've reached here, that means we haven't removed the longest line
 407:     // and we have added at least one line, so we have to check if added lines
 408:     // are longer than the previous longest line        
 409:     Segment seg = getLineBuffer();
 410:     float longestNewLength = 0;
 411:     Element longestNewLine = null;    
 412: 
 413:     // Loop through the added lines to check their length
 414:     for (int i = 0; i < newElements.length; i++)
 415:       {
 416:         Element child = newElements[i];
 417:         int start = child.getStartOffset();
 418:         int end = child.getEndOffset();
 419:         try
 420:           {
 421:             el.getDocument().getText(start, end - start, seg);
 422:           }
 423:         catch (BadLocationException ex)
 424:           {
 425:             AssertionError ae = new AssertionError("Unexpected bad location");
 426:         ae.initCause(ex);
 427:         throw ae;
 428:           }
 429:                 
 430:         if (seg == null || seg.array == null || seg.count == 0)
 431:           continue;
 432:         
 433:         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
 434:         if (width > longestNewLength)
 435:           {
 436:             longestNewLine = child;
 437:             longestNewLength = width;
 438:           }
 439:       }
 440:     
 441:     // Check if the longest of the new lines is longer than our previous
 442:     // longest line, and if so update our values
 443:     if (longestNewLength > maxLineLength)
 444:       {
 445:         maxLineLength = longestNewLength;
 446:         longestLine = longestNewLine;
 447:       }
 448:     // Repaint the container
 449:     ((JTextComponent)getContainer()).repaint();
 450:   }
 451: 
 452:   /**
 453:    * This method is called when something is inserted into the Document
 454:    * that this View is displaying.
 455:    * 
 456:    * @param changes the DocumentEvent for the changes.
 457:    * @param a the allocation of the View
 458:    * @param f the ViewFactory used to rebuild
 459:    */
 460:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 461:   {
 462:     updateDamage(changes, a, f);
 463:   }
 464: 
 465:   /**
 466:    * This method is called when something is removed from the Document
 467:    * that this View is displaying.
 468:    * 
 469:    * @param changes the DocumentEvent for the changes.
 470:    * @param a the allocation of the View
 471:    * @param f the ViewFactory used to rebuild
 472:    */
 473:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
 474:   {
 475:     updateDamage(changes, a, f);
 476:   }
 477:   
 478:   /**
 479:    * This method is called when attributes were changed in the 
 480:    * Document in a location that this view is responsible for.
 481:    */
 482:   public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
 483:   {
 484:     updateDamage(changes, a, f);
 485:   }
 486:   
 487:   /**
 488:    * Repaint the given line range.  This is called from insertUpdate,
 489:    * changedUpdate, and removeUpdate when no new lines were added 
 490:    * and no lines were removed, to repaint the line that was 
 491:    * modified.
 492:    * 
 493:    * @param line0 the start of the range
 494:    * @param line1 the end of the range
 495:    * @param a the rendering region of the host
 496:    * @param host the Component that uses this View (used to call repaint
 497:    * on that Component)
 498:    * 
 499:    * @since 1.4
 500:    */
 501:   protected void damageLineRange (int line0, int line1, Shape a, Component host)
 502:   {
 503:     if (a == null)
 504:       return;
 505: 
 506:     Rectangle rec0 = lineToRect(a, line0);
 507:     Rectangle rec1 = lineToRect(a, line1);
 508: 
 509:     if (rec0 == null || rec1 == null)
 510:       // something went wrong, repaint the entire host to be safe
 511:       host.repaint();
 512:     else
 513:       {
 514:         Rectangle repaintRec = rec0.union(rec1);
 515:         host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
 516:                      repaintRec.height);
 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: }