Source for javax.swing.text.DefaultHighlighter

   1: /* DefaultHighlighter.java -- The default highlight for Swing
   2:    Copyright (C) 2004, 2006  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.Insets;
  44: import java.awt.Rectangle;
  45: import java.awt.Shape;
  46: import java.util.ArrayList;
  47: import java.util.Iterator;
  48: 
  49: import javax.swing.SwingUtilities;
  50: import javax.swing.plaf.TextUI;
  51: 
  52: /**
  53:  * The default highlight for Swing text components. It highlights text
  54:  * by filling the background with a rectangle. 
  55:  */
  56: public class DefaultHighlighter extends LayeredHighlighter
  57: {
  58:   public static class DefaultHighlightPainter
  59:     extends LayerPainter
  60:   {
  61:     private Color color;
  62:     
  63:     public DefaultHighlightPainter(Color c)
  64:     {
  65:       super();
  66:       color = c;
  67:     }
  68: 
  69:     public Color getColor()
  70:     {
  71:       return color;
  72:     }
  73: 
  74:     public void paint(Graphics g, int p0, int p1, Shape bounds,
  75:               JTextComponent t)
  76:     {
  77:       if (p0 == p1)
  78:         return;
  79:       
  80:       Rectangle rect = bounds.getBounds();
  81: 
  82:       Color col = getColor();
  83:       if (col == null)
  84:         col = t.getSelectionColor();
  85:       g.setColor(col);
  86: 
  87:       TextUI ui = t.getUI();
  88:       
  89:       try
  90:         {
  91: 
  92:           Rectangle l0 = ui.modelToView(t, p0, null);
  93:           Rectangle l1 = ui.modelToView(t, p1, null);
  94:         
  95:           // Note: The computed locations may lie outside of the allocation
  96:           // area if the text is scrolled.
  97: 
  98:           if (l0.y == l1.y)
  99:           {
 100:             SwingUtilities.computeUnion(l0.x, l0.y, l0.width, l0.height, l1);
 101: 
 102:             // Paint only inside the allocation area.
 103:             SwingUtilities.computeIntersection(rect.x, rect.y, rect.width,
 104:                            rect.height, l1);
 105:         
 106:             g.fillRect(l1.x, l1.y, l1.width, l1.height);
 107:           }
 108:         else
 109:           {
 110:             // 1. The line of p0 is painted from the position of p0
 111:             // to the right border.
 112:             // 2. All lines between the ones where p0 and p1 lie on
 113:             // are completely highlighted. The allocation area is used to find
 114:             // out the bounds.
 115:             // 3. The final line is painted from the left border to the
 116:             // position of p1.
 117: 
 118:         int firstLineWidth = rect.x + rect.width - l0.x;
 119:         g.fillRect(l0.x, l0.y, firstLineWidth, l0.height);
 120:         if (l0.y + l0.height != l1.y)
 121:           {
 122:         g.fillRect(rect.x, l0.y + l0.height, rect.width,
 123:                l1.y - l0.y - l0.height);
 124:           }
 125:         g.fillRect(rect.x, l1.y, l1.x - rect.x, l1.height);
 126:       }
 127:       }
 128:     catch (BadLocationException ex)
 129:       {
 130:     // Can't render. Comment out for debugging.
 131:     // ex.printStackTrace();
 132:       }
 133:     }
 134: 
 135:     public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds,
 136:                 JTextComponent c, View view)
 137:     {
 138:       Color col = getColor();
 139:       if (col == null)
 140:     col = c.getSelectionColor();
 141:       g.setColor(col);
 142: 
 143:       Rectangle rect = null;
 144:       if (p0 == view.getStartOffset() && p1 == view.getEndOffset())
 145:     {
 146:       // Paint complete bounds region.
 147:           rect = bounds instanceof Rectangle ? (Rectangle) bounds
 148:                                          : bounds.getBounds();
 149:     }
 150:       else
 151:     {
 152:       // Only partly inside the view.
 153:           try
 154:         {
 155:               Shape s = view.modelToView(p0, Position.Bias.Forward,
 156:                      p1, Position.Bias.Backward,
 157:                      bounds);
 158:           rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
 159:         }
 160:       catch (BadLocationException ex)
 161:         {
 162:           // Can't render the highlight.
 163:         }
 164:     }
 165: 
 166:       if (rect != null)
 167:     {
 168:           g.fillRect(rect.x, rect.y, rect.width, rect.height);
 169:     }
 170:       return rect;
 171:     }
 172:   }
 173:   
 174:   private class HighlightEntry implements Highlighter.Highlight
 175:   {
 176:     Position p0;
 177:     Position p1;
 178:     Highlighter.HighlightPainter painter;
 179: 
 180:     public HighlightEntry(Position p0, Position p1,
 181:                           Highlighter.HighlightPainter painter)
 182:     {
 183:       this.p0 = p0;
 184:       this.p1 = p1;
 185:       this.painter = painter;
 186:     }
 187: 
 188:     public int getStartOffset()
 189:     {
 190:       return p0.getOffset();
 191:     }
 192: 
 193:     public int getEndOffset()
 194:     {
 195:       return p1.getOffset();
 196:     }
 197: 
 198:     public Highlighter.HighlightPainter getPainter()
 199:     {
 200:       return painter;
 201:     }
 202:   }
 203: 
 204:   /**
 205:    * A HighlightEntry that is used for LayerPainter painters. In addition
 206:    * to the info maintained by the HighlightEntry, this class maintains
 207:    * a painting rectangle. This is used as repaint region when the
 208:    * highlight changes and the text component needs repainting.
 209:    */
 210:   private class LayerHighlightEntry
 211:     extends HighlightEntry
 212:   {
 213: 
 214:     /**
 215:      * The paint rectangle.
 216:      */
 217:     Rectangle paintRect = new Rectangle();
 218: 
 219:     LayerHighlightEntry(Position p0, Position p1,
 220:                         Highlighter.HighlightPainter p)
 221:     {
 222:       super(p0, p1, p);
 223:     }
 224: 
 225:     /**
 226:      * Paints the highlight by calling the LayerPainter. This
 227:      * restricts the area to be painted by startOffset and endOffset
 228:      * and manages the paint rectangle.
 229:      */
 230:     void paintLayeredHighlight(Graphics g, int p0, int p1, Shape bounds,
 231:                    JTextComponent tc, View view)
 232:     {
 233:       p0 = Math.max(getStartOffset(), p0);
 234:       p1 = Math.min(getEndOffset(), p1);
 235: 
 236:       Highlighter.HighlightPainter painter = getPainter();
 237:       if (painter instanceof LayerPainter)
 238:     {
 239:       LayerPainter layerPainter = (LayerPainter) painter;
 240:       Shape area = layerPainter.paintLayer(g, p0, p1, bounds, tc, view);
 241:       Rectangle rect;
 242:       if (area instanceof Rectangle && paintRect != null)
 243:         rect = (Rectangle) area;
 244:       else
 245:         rect = area.getBounds();
 246: 
 247:       if (paintRect.width == 0 || paintRect.height == 0)
 248:         paintRect = rect.getBounds();
 249:       else
 250:         paintRect = SwingUtilities.computeUnion(rect.x, rect.y, rect.width,
 251:                             rect.height, paintRect);
 252:     }
 253:     }
 254:   }
 255: 
 256:   /**
 257:    * @specnote final as of 1.4
 258:    */
 259:   public static final LayeredHighlighter.LayerPainter DefaultPainter =
 260:     new DefaultHighlightPainter(null);
 261:   
 262:   private JTextComponent textComponent;
 263:   private ArrayList highlights = new ArrayList();
 264:   private boolean drawsLayeredHighlights = true;
 265:   
 266:   public DefaultHighlighter()
 267:   {
 268:     // Nothing to do here.
 269:   }
 270: 
 271:   public boolean getDrawsLayeredHighlights()
 272:   {
 273:     return drawsLayeredHighlights;
 274:   }
 275: 
 276:   public void setDrawsLayeredHighlights(boolean newValue)
 277:   {
 278:     drawsLayeredHighlights = newValue;
 279:   }
 280:   
 281:   private void checkPositions(int p0, int p1)
 282:     throws BadLocationException
 283:   {
 284:     if (p0 < 0)
 285:       throw new BadLocationException("DefaultHighlighter", p0);
 286:     
 287:     if (p1 < p0)
 288:       throw new BadLocationException("DefaultHighlighter", p1);
 289:   }
 290: 
 291:   public void install(JTextComponent c)
 292:   {
 293:     textComponent = c;
 294:     removeAllHighlights();
 295:   }
 296: 
 297:   public void deinstall(JTextComponent c)
 298:   {
 299:     textComponent = null;
 300:   }
 301: 
 302:   public Object addHighlight(int p0, int p1,
 303:                              Highlighter.HighlightPainter painter)
 304:     throws BadLocationException
 305:   {
 306:     checkPositions(p0, p1);
 307:     HighlightEntry entry;
 308:     Document doc = textComponent.getDocument();
 309:     Position pos0 = doc.createPosition(p0);
 310:     Position pos1 = doc.createPosition(p1);
 311:     if (getDrawsLayeredHighlights() && painter instanceof LayerPainter)
 312:       entry = new LayerHighlightEntry(pos0, pos1, painter);
 313:     else
 314:       entry = new HighlightEntry(pos0, pos1, painter);
 315:     highlights.add(entry);
 316:     
 317:     textComponent.getUI().damageRange(textComponent, p0, p1);
 318:     
 319:     return entry;
 320:   }
 321: 
 322:   public void removeHighlight(Object tag)
 323:   {
 324:     HighlightEntry entry = (HighlightEntry) tag;
 325:     if (entry instanceof LayerHighlightEntry)
 326:       {
 327:     LayerHighlightEntry lEntry = (LayerHighlightEntry) entry;
 328:     Rectangle paintRect = lEntry.paintRect;
 329:     textComponent.repaint(paintRect.x, paintRect.y, paintRect.width,
 330:                   paintRect.height);
 331:       }
 332:     else
 333:       {
 334:     textComponent.getUI().damageRange(textComponent,
 335:                       entry.getStartOffset(),
 336:                       entry.getEndOffset());
 337:       }
 338:     highlights.remove(tag);
 339: 
 340:   }
 341: 
 342:   public void removeAllHighlights()
 343:   {
 344:     // Repaint damaged region.
 345:     int minX = 0;
 346:     int maxX = 0;
 347:     int minY = 0;
 348:     int maxY = 0;
 349:     int p0 = -1;
 350:     int p1 = -1;
 351:     for (Iterator i = highlights.iterator(); i.hasNext();)
 352:       {
 353:         HighlightEntry e = (HighlightEntry) i.next();
 354:         if (e instanceof LayerHighlightEntry)
 355:           {
 356:             LayerHighlightEntry le = (LayerHighlightEntry) e;
 357:             Rectangle r = le.paintRect;
 358:             minX = Math.min(r.x, minX);
 359:             maxX = Math.max(r.x + r.width, maxX);
 360:             minY = Math.min(r.y, minY);
 361:             maxY = Math.max(r.y + r.height, maxY);
 362:           }
 363:         else
 364:           {
 365:             if (p0 == -1 || p1 == -1)
 366:               {
 367:                 p0 = e.getStartOffset();
 368:                 p1 = e.getEndOffset();
 369:               }
 370:             else
 371:               {
 372:                 p0 = Math.min(p0, e.getStartOffset());
 373:                 p1 = Math.max(p1, e.getEndOffset());
 374:               }
 375:           }
 376:         if (minX != maxX && minY != maxY)
 377:           textComponent.repaint(minX, minY, maxX - minX, maxY - minY);
 378:         if (p0 != -1 && p1 != -1)
 379:           {
 380:             TextUI ui = textComponent.getUI();
 381:             ui.damageRange(textComponent, p0, p1);
 382:           }
 383:         
 384:       }
 385:     highlights.clear();
 386:   }
 387: 
 388:   public Highlighter.Highlight[] getHighlights()
 389:   {
 390:     return (Highlighter.Highlight[]) 
 391:       highlights.toArray(new Highlighter.Highlight[highlights.size()]);
 392:   }
 393: 
 394:   public void changeHighlight(Object tag, int n0, int n1)
 395:     throws BadLocationException
 396:   {
 397:     Document doc = textComponent.getDocument();
 398:     TextUI ui = textComponent.getUI();
 399:     if (tag instanceof LayerHighlightEntry)
 400:       {
 401:         LayerHighlightEntry le = (LayerHighlightEntry) tag;
 402:         Rectangle r = le.paintRect;
 403:         if (r.width > 0 && r.height > 0)
 404:           textComponent.repaint(r.x, r.y, r.width, r.height);
 405:         r.width = 0;
 406:         r.height = 0;
 407:         le.p0 = doc.createPosition(n0);
 408:         le.p1 = doc.createPosition(n1);
 409:         ui.damageRange(textComponent, Math.min(n0, n1), Math.max(n0, n1));
 410:       }
 411:     else if (tag instanceof HighlightEntry)
 412:       {
 413:         HighlightEntry e = (HighlightEntry) tag;
 414:         int p0 = e.getStartOffset();
 415:         int p1 = e.getEndOffset();
 416:         if (p0 == n0)
 417:           {
 418:             ui.damageRange(textComponent, Math.min(p1, n1),
 419:                            Math.max(p1, n1));
 420:           }
 421:         else if (n1 == p1)
 422:           {
 423:             ui.damageRange(textComponent, Math.min(p0, n0),
 424:                            Math.max(p0, n0));
 425:           }
 426:         else
 427:           {
 428:             ui.damageRange(textComponent, p0, p1);
 429:             ui.damageRange(textComponent, n0, n1);
 430:           }
 431:         e.p0 = doc.createPosition(n0);
 432:         e.p1 = doc.createPosition(n1);
 433:       }
 434:   }
 435: 
 436:   public void paintLayeredHighlights(Graphics g, int p0, int p1,
 437:                                      Shape viewBounds, JTextComponent editor,
 438:                                      View view)
 439:   {
 440:     for (Iterator i = highlights.iterator(); i.hasNext();)
 441:       {
 442:     Object o = i.next();
 443:     if (o instanceof LayerHighlightEntry)
 444:       {
 445:         LayerHighlightEntry entry = (LayerHighlightEntry) o;
 446:         int start = entry.getStartOffset();
 447:         int end = entry.getEndOffset();
 448:         if ((p0 < start && p1 > start) || (p0 >= start && p0 < end))
 449:           entry.paintLayeredHighlight(g, p0, p1, viewBounds, editor, view);
 450:       }
 451:       }
 452:   }
 453: 
 454:   public void paint(Graphics g)
 455:   {
 456:     int size = highlights.size();
 457:     
 458:     // Check if there are any highlights.
 459:     if (size == 0)
 460:       return;
 461: 
 462:     // Prepares the rectangle of the inner drawing area.
 463:     Insets insets = textComponent.getInsets();
 464:     Shape bounds =
 465:       new Rectangle(insets.left,
 466:                     insets.top,
 467:                     textComponent.getWidth() - insets.left - insets.right,
 468:                     textComponent.getHeight() - insets.top - insets.bottom);
 469:     
 470:     for (int index = 0; index < size; ++index)
 471:       {
 472:     HighlightEntry entry = (HighlightEntry) highlights.get(index);
 473:     if (! (entry instanceof LayerHighlightEntry))
 474:           entry.painter.paint(g, entry.getStartOffset(), entry.getEndOffset(),
 475:                               bounds, textComponent);
 476:       }
 477:   }
 478: }