001/* DefaultHighlighter.java -- The default highlight for Swing 002 Copyright (C) 2004, 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text; 040 041import java.awt.Color; 042import java.awt.Graphics; 043import java.awt.Insets; 044import java.awt.Rectangle; 045import java.awt.Shape; 046import java.util.ArrayList; 047import java.util.Iterator; 048 049import javax.swing.SwingUtilities; 050import javax.swing.plaf.TextUI; 051 052/** 053 * The default highlight for Swing text components. It highlights text 054 * by filling the background with a rectangle. 055 */ 056public class DefaultHighlighter extends LayeredHighlighter 057{ 058 public static class DefaultHighlightPainter 059 extends LayerPainter 060 { 061 private Color color; 062 063 public DefaultHighlightPainter(Color c) 064 { 065 super(); 066 color = c; 067 } 068 069 public Color getColor() 070 { 071 return color; 072 } 073 074 public void paint(Graphics g, int p0, int p1, Shape bounds, 075 JTextComponent t) 076 { 077 if (p0 == p1) 078 return; 079 080 Rectangle rect = bounds.getBounds(); 081 082 Color col = getColor(); 083 if (col == null) 084 col = t.getSelectionColor(); 085 g.setColor(col); 086 087 TextUI ui = t.getUI(); 088 089 try 090 { 091 092 Rectangle l0 = ui.modelToView(t, p0, null); 093 Rectangle l1 = ui.modelToView(t, p1, null); 094 095 // Note: The computed locations may lie outside of the allocation 096 // area if the text is scrolled. 097 098 if (l0.y == l1.y) 099 { 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}