Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 698   Methods: 34
NCLOC: 422   Classes: 7
 
 Source file Conditionals Statements Methods TOTAL
ReverseHighlighter.java 16.1% 20.2% 41.2% 20.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.ui;
 38   
 39    import javax.swing.text.*;
 40    import java.util.ArrayList;
 41    import java.awt.*;
 42    import javax.swing.plaf.*;
 43   
 44    /** Implements the Highlighter interfaces. Implements a simple highlight painter, but stores
 45    * the highlights in reverse order. That means that the selection (for copying) is always
 46    * the foremost hightlight, and after that, the highlights are drawn from most recent
 47    * to oldest.
 48    * Based on DefaultHighlighter by Timothy Prinzing, version 1.39 12/19/03
 49    * Unfortunately, as the vector of highlights in DefaultHighlighter was private, there was
 50    * no efficient way to make use of inheritance.
 51    */
 52    public class ReverseHighlighter extends DefaultHighlighter {
 53   
 54    /** Creates a new ReverseHighlighter object. */
 55  412 public ReverseHighlighter() { drawsLayeredHighlights = true; }
 56   
 57    // ---- Highlighter methods ----------------------------------------------
 58   
 59    /** Renders the highlights.
 60    *
 61    * @param g the graphics context
 62    */
 63  0 public void paint(Graphics g) {
 64    // PENDING(prinz) - should cull ranges not visible
 65  0 int len = _highlights.size();
 66  0 for (int i = 0; i < len; i++) {
 67  0 HighlightInfo info = _highlights.get(i);
 68  0 if (! (info instanceof LayeredHighlightInfo)) {
 69    // Avoid allocing unless we need it.
 70  0 Rectangle a = component.getBounds();
 71  0 Insets insets = component.getInsets();
 72  0 a.x = insets.left;
 73  0 a.y = insets.top;
 74  0 a.width -= insets.left + insets.right;
 75  0 a.height -= insets.top + insets.bottom;
 76  0 for (; i < len; i++) {
 77  0 info = _highlights.get(i);
 78  0 if (! (info instanceof LayeredHighlightInfo)) {
 79  0 Highlighter.HighlightPainter p = info.getPainter();
 80  0 p.paint(g, info.getStartOffset(), info.getEndOffset(),
 81    a, component);
 82    }
 83    }
 84    }
 85    }
 86    }
 87   
 88    /** Called when the UI is being installed into the interface of a JTextComponent. Installs the editor, and
 89    * removes any existing highlights.
 90    * @param c the editor component
 91    * @see Highlighter#install
 92    */
 93  412 public void install(JTextComponent c) {
 94  412 component = c;
 95  412 removeAllHighlights();
 96    }
 97   
 98    /** Called when the UI is being removed from the interface of a JTextComponent.
 99    * @param c the component
 100    * @see Highlighter#deinstall
 101    */
 102  0 public void deinstall(JTextComponent c) {
 103  0 component = null;
 104    }
 105   
 106    //static edu.rice.cs.util.Log _log = new edu.rice.cs.util.Log("highlighter.txt",true);
 107   
 108    /** Adds a highlight to the view. Returns a tag that can be used to refer to the highlight.
 109    * @param p0 the start offset of the range to highlight >= 0
 110    * @param p1 the end offset of the range to highlight >= p0
 111    * @param p the painter to use to actually render the highlight
 112    * @return an object that can be used as a tag to refer to the highlight
 113    * @exception BadLocationException if the specified location is invalid
 114    */
 115  34 public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException {
 116  34 Document doc = component.getDocument();
 117  34 HighlightInfo i = (getDrawsLayeredHighlights() &&
 118    (p instanceof LayeredHighlighter.LayerPainter)) ?
 119    new LayeredHighlightInfo() : new HighlightInfo();
 120  34 i._painter = p;
 121   
 122  34 i.p0 = doc.createPosition(p0);
 123  34 i.p1 = doc.createPosition(p1);
 124   
 125  34 int insertPos = _highlights.size();
 126   
 127  34 if (p instanceof DrJavaHighlightPainter) {
 128  34 while (insertPos > 0) {
 129  2 HighlightInfo hli = _highlights.get( insertPos-1 );
 130  2 if (hli.getPainter() instanceof DrJavaHighlightPainter)
 131  2 --insertPos;
 132  0 else break;
 133    }
 134  0 } else if (p instanceof DefaultHighlightPainter) {
 135  0 while (insertPos > 0) {
 136  0 HighlightInfo hli = _highlights.get( insertPos-1 );
 137  0 if (hli.getPainter() instanceof DefaultHighlightPainter)
 138  0 --insertPos;
 139  0 else break;
 140    }
 141  0 } else if (p instanceof DefaultFrameHighlightPainter) {
 142  0 while (insertPos > 0) {
 143  0 HighlightInfo hli = _highlights.get( insertPos-1 );
 144  0 if (hli.getPainter() instanceof DefaultHighlightPainter || hli.getPainter() instanceof DefaultFrameHighlightPainter)
 145  0 --insertPos;
 146  0 else break;
 147    }
 148    } else {
 149  0 insertPos = 0;
 150    }
 151  34 _highlights.add(insertPos, i);
 152    //_log.log(p.toString() + ", pos: " + insertPos);
 153  34 safeDamageRange(p0, p1);
 154  34 return i;
 155    }
 156   
 157    /** Removes a highlight from the view.
 158    *
 159    * @param tag the reference to the highlight
 160    */
 161  30 public void removeHighlight(Object tag) {
 162  30 if (tag instanceof LayeredHighlightInfo) {
 163  30 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
 164  30 if (lhi.width > 0 && lhi.height > 0) {
 165  0 component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
 166    }
 167    }
 168    else {
 169  0 HighlightInfo info = (HighlightInfo) tag;
 170  0 safeDamageRange(info.p0, info.p1);
 171    }
 172  30 _highlights.remove(tag);
 173    }
 174   
 175    /** Removes all highlights.
 176    */
 177  412 public void removeAllHighlights() {
 178  412 TextUI mapper = component.getUI();
 179  412 if (getDrawsLayeredHighlights()) {
 180  412 int len = _highlights.size();
 181  412 if (len != 0) {
 182  0 int minX = 0;
 183  0 int minY = 0;
 184  0 int maxX = 0;
 185  0 int maxY = 0;
 186  0 int p0 = -1;
 187  0 int p1 = -1;
 188  0 for (int i = 0; i < len; i++) {
 189  0 HighlightInfo hi = _highlights.get(i);
 190  0 if (hi instanceof LayeredHighlightInfo) {
 191  0 LayeredHighlightInfo info = (LayeredHighlightInfo)hi;
 192  0 minX = Math.min(minX, info.x);
 193  0 minY = Math.min(minY, info.y);
 194  0 maxX = Math.max(maxX, info.x + info.width);
 195  0 maxY = Math.max(maxY, info.y + info.height);
 196    }
 197    else {
 198  0 if (p0 == -1) {
 199  0 p0 = hi.p0.getOffset();
 200  0 p1 = hi.p1.getOffset();
 201    }
 202    else {
 203  0 p0 = Math.min(p0, hi.p0.getOffset());
 204  0 p1 = Math.max(p1, hi.p1.getOffset());
 205    }
 206    }
 207    }
 208  0 if (minX != maxX && minY != maxY) {
 209  0 component.repaint(minX, minY, maxX - minX, maxY - minY);
 210    }
 211  0 if (p0 != -1) {
 212  0 try {
 213  0 safeDamageRange(p0, p1);
 214    } catch (BadLocationException e) { }
 215    }
 216  0 _highlights.clear();
 217    }
 218    }
 219  0 else if (mapper != null) {
 220  0 int len = _highlights.size();
 221  0 if (len != 0) {
 222  0 int p0 = Integer.MAX_VALUE;
 223  0 int p1 = 0;
 224  0 for (int i = 0; i < len; i++) {
 225  0 HighlightInfo info = _highlights.get(i);
 226  0 p0 = Math.min(p0, info.p0.getOffset());
 227  0 p1 = Math.max(p1, info.p1.getOffset());
 228    }
 229  0 try {
 230  0 safeDamageRange(p0, p1);
 231    } catch (BadLocationException e) { }
 232   
 233  0 _highlights.clear();
 234    }
 235    }
 236    }
 237   
 238    /** Changes a highlight.
 239    *
 240    * @param tag the highlight tag
 241    * @param p0 the beginning of the range >= 0
 242    * @param p1 the end of the range >= p0
 243    * @exception BadLocationException if the specified location is invalid
 244    */
 245  0 public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException {
 246  0 Document doc = component.getDocument();
 247  0 if (tag instanceof LayeredHighlightInfo) {
 248  0 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
 249  0 if (lhi.width > 0 && lhi.height > 0) {
 250  0 component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
 251    }
 252    // Mark the highlights region as invalid, it will reset itself
 253    // next time asked to paint.
 254  0 lhi.width = lhi.height = 0;
 255   
 256  0 lhi.p0 = doc.createPosition(p0);
 257  0 lhi.p1 = doc.createPosition(p1);
 258  0 safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
 259    }
 260    else {
 261  0 HighlightInfo info = (HighlightInfo) tag;
 262  0 int oldP0 = info.p0.getOffset();
 263  0 int oldP1 = info.p1.getOffset();
 264  0 if (p0 == oldP0) safeDamageRange(Math.min(oldP1, p1), Math.max(oldP1, p1));
 265  0 else if (p1 == oldP1) safeDamageRange(Math.min(p0, oldP0), Math.max(p0, oldP0));
 266    else {
 267  0 safeDamageRange(oldP0, oldP1);
 268  0 safeDamageRange(p0, p1);
 269    }
 270   
 271  0 info.p0 = doc.createPosition(p0);
 272  0 info.p1 = doc.createPosition(p1);
 273   
 274    // TODO: figure out what is wrong here. The preceding lines are dead code.
 275    }
 276    }
 277   
 278    /** Makes a copy of the highlights. Does not actually clone each highlight,
 279    * but only makes references to them.
 280    *
 281    * @return the copy
 282    * @see Highlighter#getHighlights
 283    */
 284  0 public Highlighter.Highlight[] getHighlights() {
 285  0 int size = _highlights.size();
 286  0 if (size == 0) {
 287  0 return noHighlights;
 288    }
 289  0 Highlighter.Highlight[] h = _highlights.toArray(EMTPY_HIGHLIGHTS);
 290  0 return h;
 291    }
 292   
 293    private static final Highlight[] EMTPY_HIGHLIGHTS = new Highlighter.Highlight[0];
 294   
 295    /** When leaf Views (such as LabelView) are rendering they should
 296    * call into this method. If a highlight is in the given region it will
 297    * be drawn immediately.
 298    *
 299    * @param g Graphics used to draw
 300    * @param p0 starting offset of view
 301    * @param p1 ending offset of view
 302    * @param viewBounds Bounds of View
 303    * @param editor JTextComponent
 304    * @param view View instance being rendered
 305    */
 306  0 public void paintLayeredHighlights(Graphics g, int p0, int p1,
 307    Shape viewBounds,
 308    JTextComponent editor, View view) {
 309  0 for (int counter = _highlights.size() - 1; counter >= 0; counter--) {
 310  0 Object tag = _highlights.get(counter);
 311  0 if (tag instanceof LayeredHighlightInfo) {
 312  0 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
 313  0 int start = lhi.getStartOffset();
 314  0 int end = lhi.getEndOffset();
 315  0 if ((p0 < start && p1 > start) ||
 316    (p0 >= start && p0 < end)) {
 317  0 lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
 318    editor, view);
 319    }
 320    }
 321    }
 322    }
 323   
 324    /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
 325  34 private void safeDamageRange(final Position p0, final Position p1) {
 326  34 safeDamager.damageRange(p0, p1);
 327    }
 328   
 329    /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
 330  34 private void safeDamageRange(int a0, int a1) throws BadLocationException {
 331  34 Document doc = component.getDocument();
 332   
 333  34 safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
 334    }
 335   
 336    /** If true, highlights are drawn as the Views draw the text. That is
 337    * the Views will call into <code>paintLayeredHighlight</code> which
 338    * will result in a rectangle being drawn before the text is drawn
 339    * (if the offsets are in a highlighted region that is). For this to
 340    * work the painter supplied must be an instance of
 341    * LayeredHighlightPainter.
 342    */
 343  0 public void setDrawsLayeredHighlights(boolean newValue) {
 344  0 drawsLayeredHighlights = newValue;
 345    }
 346   
 347  446 public boolean getDrawsLayeredHighlights() {
 348  446 return drawsLayeredHighlights;
 349    }
 350   
 351    // ---- member variables --------------------------------------------
 352   
 353    private final static Highlighter.Highlight[] noHighlights =
 354    new Highlighter.Highlight[0];
 355    private ArrayList<HighlightInfo> _highlights = new ArrayList<HighlightInfo>(); // Vector<HighlightInfo>
 356    private JTextComponent component;
 357    private boolean drawsLayeredHighlights;
 358    private SafeDamager safeDamager = new SafeDamager();
 359   
 360    /** Simple highlight painter that draws a rectangular box around text. */
 361    public static class DefaultFrameHighlightPainter extends LayeredHighlighter.LayerPainter {
 362   
 363    /** Constructs a new highlight painter. If c is null, the JTextComponent will be queried for its selection color.
 364    * @param c the color for the highlight
 365    * @param t the thickness in pixels
 366    */
 367  50 public DefaultFrameHighlightPainter(Color c, int t) {
 368  50 color = c;
 369  50 thickness = t;
 370    }
 371   
 372    /** @return the color of the highlight */
 373  0 public Color getColor() { return color; }
 374   
 375    /** @return thickness in pixels */
 376  0 public int getThickness() { return thickness; }
 377   
 378    // --- HighlightPainter methods ---------------------------------------
 379   
 380  0 private void drawRectThick(Graphics g, int x, int y, int width, int height, int thick) {
 381  0 if (thick < 2) { g.drawRect(x, y, width, height); }
 382    else {
 383  0 g.fillRect(x, y, width, thick);
 384  0 g.fillRect(x, y+height-thick, width, thick);
 385  0 g.fillRect(x, y, thick, height);
 386  0 g.fillRect(x+width-thick, y, thick, height);
 387    }
 388    }
 389   
 390    /** Paints a highlight.
 391    * @param g the graphics context
 392    * @param offs0 the starting model offset >= 0
 393    * @param offs1 the ending model offset >= offs1
 394    * @param bounds the bounding box for the highlight
 395    * @param c the editor
 396    */
 397  0 public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
 398  0 Rectangle alloc = bounds.getBounds();
 399  0 try {
 400    // --- determine locations ---
 401  0 TextUI mapper = c.getUI();
 402  0 Rectangle p0 = mapper.modelToView(c, offs0);
 403  0 Rectangle p1 = mapper.modelToView(c, offs1);
 404   
 405    // --- render ---
 406  0 Color color = getColor();
 407   
 408  0 if (color == null) g.setColor(c.getSelectionColor());
 409  0 else g.setColor(color);
 410   
 411  0 if (p0.y == p1.y) { // same line, render a rectangle
 412  0 Rectangle r = p0.union(p1);
 413  0 drawRectThick(g, r.x, r.y, r.width, r.height, thickness);
 414    }
 415    else { // different lines
 416  0 int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
 417  0 drawRectThick(g, p0.x, p0.y, p0ToMarginWidth, p0.height, thickness);
 418  0 if ((p0.y + p0.height) != p1.y)
 419  0 drawRectThick(g, alloc.x, p0.y + p0.height, alloc.width, p1.y - (p0.y + p0.height), thickness);
 420  0 drawRectThick(g, alloc.x, p1.y, (p1.x - alloc.x), p1.height, thickness);
 421    }
 422    }
 423    catch (BadLocationException e) { /* can't render */ }
 424    }
 425   
 426    // --- LayerPainter methods ----------------------------
 427    /** Paints a portion of a highlight.
 428    * @param g the graphics context
 429    * @param offs0 the starting model offset >= 0
 430    * @param offs1 the ending model offset >= offs1
 431    * @param bounds the bounding box of the view, which is not necessarily the region to paint.
 432    * @param c the editor
 433    * @param view View painting for
 434    * @return region drawing occured in
 435    */
 436  0 public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) {
 437  0 Color color = getColor();
 438   
 439  0 if (color == null) g.setColor(c.getSelectionColor());
 440  0 else g.setColor(color);
 441   
 442  0 if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) { // Contained in view, can just use bounds.
 443  0 Rectangle alloc;
 444  0 if (bounds instanceof Rectangle) alloc = (Rectangle)bounds;
 445  0 else alloc = bounds.getBounds();
 446   
 447  0 drawRectThick(g, alloc.x, alloc.y, alloc.width, alloc.height, thickness);
 448  0 return alloc;
 449    }
 450    else { // Should only render part of View.
 451  0 try {
 452    // --- determine locations ---
 453  0 Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1,Position.Bias.Backward, bounds);
 454  0 Rectangle r = (shape instanceof Rectangle) ? (Rectangle)shape : shape.getBounds();
 455   
 456  0 drawRectThick(g, r.x, r.y, r.width, r.height, thickness);
 457  0 return r;
 458    }
 459    catch (BadLocationException e) { /* can't render */ }
 460    }
 461    // Only if exception
 462  0 return null;
 463    }
 464   
 465    private Color color;
 466    private int thickness;
 467    }
 468   
 469   
 470    /** Simple highlight painter that underlines text. */
 471    public static class DefaultUnderlineHighlightPainter extends LayeredHighlighter.LayerPainter {
 472   
 473    /** Constructs a new highlight painter. If c< is null, the JTextComponent will be queried for its selection color.
 474    * @param c the color for the highlight
 475    * @param t the thickness in pixels
 476    */
 477  12 public DefaultUnderlineHighlightPainter(Color c, int t) {
 478  12 color = c;
 479  12 thickness = t;
 480    }
 481   
 482    /** @return the color of the highlight */
 483  0 public Color getColor() { return color; }
 484   
 485    /** @return thickness in pixels */
 486  0 public int getThickness() { return thickness; }
 487   
 488    // --- HighlightPainter methods ---------------------------------------
 489   
 490  0 private void drawUnderline(Graphics g, int x, int y, int width, int height, int thick) {
 491  0 g.fillRect(x, y+height-thick, width, thick);
 492    }
 493   
 494    /** Paints a highlight.
 495    * @param g the graphics context
 496    * @param offs0 the starting model offset >= 0
 497    * @param offs1 the ending model offset >= offs1
 498    * @param bounds the bounding box for the highlight
 499    * @param c the editor
 500    */
 501  0 public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
 502  0 Rectangle alloc = bounds.getBounds();
 503  0 try {
 504    // --- determine locations ---
 505  0 TextUI mapper = c.getUI();
 506  0 Rectangle p0 = mapper.modelToView(c, offs0);
 507  0 Rectangle p1 = mapper.modelToView(c, offs1);
 508   
 509    // --- render ---
 510  0 Color color = getColor();
 511   
 512  0 if (color == null) g.setColor(c.getSelectionColor());
 513  0 else g.setColor(color);
 514   
 515  0 if (p0.y == p1.y) { // same line, render a rectangle
 516  0 Rectangle r = p0.union(p1);
 517  0 drawUnderline(g, r.x, r.y, r.width, r.height, thickness);
 518    }
 519    else { // different lines
 520  0 int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
 521  0 drawUnderline(g, p0.x, p0.y, p0ToMarginWidth, p0.height, thickness);
 522  0 if ((p0.y + p0.height) != p1.y)
 523  0 drawUnderline(g, alloc.x, p0.y + p0.height, alloc.width, p1.y - (p0.y + p0.height), thickness);
 524   
 525  0 drawUnderline(g, alloc.x, p1.y, (p1.x - alloc.x), p1.height, thickness);
 526    }
 527    }
 528    catch (BadLocationException e) { /* can't render */ }
 529    }
 530   
 531    // --- LayerPainter methods ----------------------------
 532    /** Paints a portion of a highlight.
 533    * @param g the graphics context
 534    * @param offs0 the starting model offset >= 0
 535    * @param offs1 the ending model offset >= offs1
 536    * @param bounds the bounding box of the view, which is not necessarily the region to paint.
 537    * @param c the editor
 538    * @param view View painting for
 539    * @return region drawing occured in
 540    */
 541  0 public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) {
 542  0 Color color = getColor();
 543   
 544  0 if (color == null) g.setColor(c.getSelectionColor());
 545  0 else g.setColor(color);
 546   
 547  0 if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) { // Contained in view, can just use bounds
 548  0 Rectangle alloc;
 549  0 if (bounds instanceof Rectangle) alloc = (Rectangle)bounds;
 550  0 else alloc = bounds.getBounds();
 551   
 552  0 drawUnderline(g, alloc.x, alloc.y, alloc.width, alloc.height, thickness);
 553  0 return alloc;
 554    }
 555    else { // Should only render part of View.
 556  0 try {
 557    // --- determine locations ---
 558  0 Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1,Position.Bias.Backward, bounds);
 559  0 Rectangle r = (shape instanceof Rectangle) ? (Rectangle)shape : shape.getBounds();
 560  0 drawUnderline(g, r.x, r.y, r.width, r.height, thickness);
 561  0 return r;
 562    } catch (BadLocationException e) { /* can't render */ }
 563    }
 564    // Only if exception
 565  0 return null;
 566    }
 567   
 568    private Color color;
 569    private int thickness;
 570    }
 571   
 572    class HighlightInfo implements Highlighter.Highlight {
 573   
 574    Position p0;
 575    Position p1;
 576    Highlighter.HighlightPainter _painter;
 577   
 578  0 public int getStartOffset() { return p0.getOffset(); }
 579   
 580  0 public int getEndOffset() { return p1.getOffset(); }
 581   
 582  2 public Highlighter.HighlightPainter getPainter() { return _painter; }
 583   
 584   
 585    }
 586   
 587   
 588    /** This class is a wrapper for the DefaultHighlightPainter that allows us to tell whether a highlight was
 589    * requested by DrJava or by Swing (as in selected text).
 590    */
 591    public static class DrJavaHighlightPainter extends DefaultHighlightPainter {
 592   
 593  70 public DrJavaHighlightPainter(Color c) { super(c); }
 594    }
 595   
 596   
 597    /** LayeredHighlightPainter is used when a drawsLayeredHighlights is
 598    * true. It maintains a rectangle of the region to paint.
 599    */
 600    class LayeredHighlightInfo extends HighlightInfo {
 601   
 602  0 void union(Shape bounds) {
 603  0 if (bounds == null)
 604  0 return;
 605   
 606  0 Rectangle alloc;
 607  0 if (bounds instanceof Rectangle) alloc = (Rectangle)bounds;
 608  0 else alloc = bounds.getBounds();
 609   
 610  0 if (width == 0 || height == 0) {
 611  0 x = alloc.x;
 612  0 y = alloc.y;
 613  0 width = alloc.width;
 614  0 height = alloc.height;
 615    }
 616    else {
 617  0 width = Math.max(x + width, alloc.x + alloc.width);
 618  0 height = Math.max(y + height, alloc.y + alloc.height);
 619  0 x = Math.min(x, alloc.x);
 620  0 width -= x;
 621  0 y = Math.min(y, alloc.y);
 622  0 height -= y;
 623    }
 624    }
 625   
 626    /** Restricts the region based on the receivers offsets and messages the painter to paint the region.*/
 627  0 void paintLayeredHighlights(Graphics g, int p0, int p1, Shape viewBounds, JTextComponent editor, View view) {
 628  0 int start = getStartOffset();
 629  0 int end = getEndOffset();
 630    // Restrict the region to what we represent
 631  0 p0 = Math.max(start, p0);
 632  0 p1 = Math.min(end, p1);
 633    // Paint the appropriate region using the painter and union the effected region with our bounds.
 634  0 LayeredHighlighter.LayerPainter lp = (LayeredHighlighter.LayerPainter) _painter;
 635  0 union(lp.paintLayer(g, p0, p1, viewBounds, editor, view));
 636    }
 637   
 638    int x;
 639    int y;
 640    int width;
 641    int height;
 642    }
 643   
 644   
 645    /** This class invokes <code>mapper.damageRange</code> in EventDispatchThread. The only one instance per Highlighter
 646    * is cretaed. When a number of ranges should be damaged it collects them into queue and damages them in consecutive
 647    * order in <code>run</code> call.
 648    */
 649    class SafeDamager implements Runnable {
 650    private ArrayList<Position> p0 = new ArrayList<Position>(10);
 651    private ArrayList<Position> p1 = new ArrayList<Position>(10);
 652    private Document lastDoc = null;
 653   
 654    /** Executes range(s) damage and cleans range queue. */
 655  27 public synchronized void run() {
 656  27 if (component != null) {
 657  27 TextUI mapper = component.getUI();
 658  27 if (mapper != null && lastDoc == component.getDocument()) { // Doc must match to properly display highlights
 659  27 int len = p0.size();
 660  27 for (int i = 0; i < len; i++) {
 661  34 mapper.damageRange(component, p0.get(i).getOffset(), p1.get(i).getOffset());
 662    }
 663    }
 664    }
 665  27 p0.clear();
 666  27 p1.clear();
 667   
 668    // release reference
 669  27 lastDoc = null;
 670    }
 671   
 672    /** Adds range to be damaged to the range queue. If the range queue is empty (the first call or run() was already
 673    * invoked) then adds this class instance into EventDispatch queue. The method also tracks if the current document
 674    * changed or component is null. In this case it removes all ranges added before from range queue.
 675    */
 676  34 public synchronized void damageRange(Position pos0, Position pos1) {
 677  34 if (component == null) {
 678  0 p0.clear();
 679  0 lastDoc = null;
 680  0 return;
 681    }
 682   
 683  34 boolean addToQueue = p0.isEmpty();
 684  34 Document curDoc = component.getDocument();
 685  34 if (curDoc != lastDoc) {
 686  27 if (!p0.isEmpty()) {
 687  0 p0.clear();
 688  0 p1.clear();
 689    }
 690  27 lastDoc = curDoc;
 691    }
 692  34 p0.add(pos0);
 693  34 p1.add(pos1);
 694   
 695  27 if (addToQueue) EventQueue.invokeLater(this); // Why invokeLater here? Context switches are costly.
 696    }
 697    }
 698    }