001 /* DefaultHighlighter.java -- The default highlight for Swing
002 Copyright (C) 2004, 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import java.awt.Color;
042 import java.awt.Graphics;
043 import java.awt.Insets;
044 import java.awt.Rectangle;
045 import java.awt.Shape;
046 import java.util.ArrayList;
047 import java.util.Iterator;
048
049 import javax.swing.SwingUtilities;
050 import 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 */
056 public 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 }