KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > DefaultHighlighter


1 /*
2  * @(#)DefaultHighlighter.java 1.39 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.util.Vector JavaDoc;
10 import java.awt.*;
11 import javax.swing.plaf.*;
12 import javax.swing.*;
13
14 /**
15  * Implements the Highlighter interfaces. Implements a simple highlight
16  * painter that renders in a solid color.
17  *
18  * @author Timothy Prinzing
19  * @version 1.39 12/19/03
20  * @see Highlighter
21  */

22 public class DefaultHighlighter extends LayeredHighlighter JavaDoc {
23
24     /**
25      * Creates a new DefaultHighlighther object.
26      */

27     public DefaultHighlighter() {
28     drawsLayeredHighlights = true;
29     }
30
31     // ---- Highlighter methods ----------------------------------------------
32

33     /**
34      * Renders the highlights.
35      *
36      * @param g the graphics context
37      */

38     public void paint(Graphics g) {
39         // PENDING(prinz) - should cull ranges not visible
40
int len = highlights.size();
41         for (int i = 0; i < len; i++) {
42         HighlightInfo info = (HighlightInfo) highlights.elementAt(i);
43         if (!(info instanceof LayeredHighlightInfo)) {
44         // Avoid allocing unless we need it.
45
Rectangle a = component.getBounds();
46         Insets insets = component.getInsets();
47         a.x = insets.left;
48         a.y = insets.top;
49         a.width -= insets.left + insets.right;
50         a.height -= insets.top + insets.bottom;
51         for (; i < len; i++) {
52             info = (HighlightInfo)highlights.elementAt(i);
53             if (!(info instanceof LayeredHighlightInfo)) {
54             Highlighter.HighlightPainter JavaDoc p = info.getPainter();
55             p.paint(g, info.getStartOffset(), info.getEndOffset(),
56                 a, component);
57             }
58         }
59         }
60     }
61     }
62
63     /**
64      * Called when the UI is being installed into the
65      * interface of a JTextComponent. Installs the editor, and
66      * removes any existing highlights.
67      *
68      * @param c the editor component
69      * @see Highlighter#install
70      */

71     public void install(JTextComponent JavaDoc c) {
72     component = c;
73     removeAllHighlights();
74     }
75
76     /**
77      * Called when the UI is being removed from the interface of
78      * a JTextComponent.
79      *
80      * @param c the component
81      * @see Highlighter#deinstall
82      */

83     public void deinstall(JTextComponent JavaDoc c) {
84     component = null;
85     }
86
87     /**
88      * Adds a highlight to the view. Returns a tag that can be used
89      * to refer to the highlight.
90      *
91      * @param p0 the start offset of the range to highlight >= 0
92      * @param p1 the end offset of the range to highlight >= p0
93      * @param p the painter to use to actually render the highlight
94      * @return an object that can be used as a tag
95      * to refer to the highlight
96      * @exception BadLocationException if the specified location is invalid
97      */

98     public Object JavaDoc addHighlight(int p0, int p1, Highlighter.HighlightPainter JavaDoc p) throws BadLocationException JavaDoc {
99     Document JavaDoc doc = component.getDocument();
100     HighlightInfo i = (getDrawsLayeredHighlights() &&
101                (p instanceof LayeredHighlighter.LayerPainter JavaDoc)) ?
102                       new LayeredHighlightInfo() : new HighlightInfo();
103     i.painter = p;
104     i.p0 = doc.createPosition(p0);
105     i.p1 = doc.createPosition(p1);
106     highlights.addElement(i);
107         safeDamageRange(p0, p1);
108         return i;
109     }
110
111     /**
112      * Removes a highlight from the view.
113      *
114      * @param tag the reference to the highlight
115      */

116     public void removeHighlight(Object JavaDoc tag) {
117     if (tag instanceof LayeredHighlightInfo) {
118         LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
119         if (lhi.width > 0 && lhi.height > 0) {
120         component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
121         }
122     }
123     else {
124         HighlightInfo info = (HighlightInfo) tag;
125             safeDamageRange(info.p0, info.p1);
126     }
127     highlights.removeElement(tag);
128     }
129
130     /**
131      * Removes all highlights.
132      */

133     public void removeAllHighlights() {
134     TextUI mapper = component.getUI();
135     if (getDrawsLayeredHighlights()) {
136         int len = highlights.size();
137         if (len != 0) {
138         int minX = 0;
139         int minY = 0;
140         int maxX = 0;
141         int maxY = 0;
142         int p0 = -1;
143         int p1 = -1;
144         for (int i = 0; i < len; i++) {
145                     HighlightInfo hi = (HighlightInfo)highlights.elementAt(i);
146                     if (hi instanceof LayeredHighlightInfo) {
147                         LayeredHighlightInfo info = (LayeredHighlightInfo)hi;
148                         minX = Math.min(minX, info.x);
149                         minY = Math.min(minY, info.y);
150                         maxX = Math.max(maxX, info.x + info.width);
151                         maxY = Math.max(maxY, info.y + info.height);
152                     }
153                     else {
154                         if (p0 == -1) {
155                             p0 = hi.p0.getOffset();
156                             p1 = hi.p1.getOffset();
157                         }
158                         else {
159                             p0 = Math.min(p0, hi.p0.getOffset());
160                             p1 = Math.max(p1, hi.p1.getOffset());
161                         }
162                     }
163                 }
164         if (minX != maxX && minY != maxY) {
165             component.repaint(minX, minY, maxX - minX, maxY - minY);
166         }
167                 if (p0 != -1) {
168                     try {
169                         safeDamageRange(p0, p1);
170                     } catch (BadLocationException JavaDoc e) {}
171                 }
172         highlights.removeAllElements();
173         }
174     }
175     else if (mapper != null) {
176         int len = highlights.size();
177         if (len != 0) {
178         int p0 = Integer.MAX_VALUE;
179         int p1 = 0;
180         for (int i = 0; i < len; i++) {
181             HighlightInfo info = (HighlightInfo) highlights.elementAt(i);
182             p0 = Math.min(p0, info.p0.getOffset());
183             p1 = Math.max(p1, info.p1.getOffset());
184         }
185                 try {
186                     safeDamageRange(p0, p1);
187                 } catch (BadLocationException JavaDoc e) {}
188
189         highlights.removeAllElements();
190         }
191     }
192     }
193
194     /**
195      * Changes a highlight.
196      *
197      * @param tag the highlight tag
198      * @param p0 the beginning of the range >= 0
199      * @param p1 the end of the range >= p0
200      * @exception BadLocationException if the specified location is invalid
201      */

202     public void changeHighlight(Object JavaDoc tag, int p0, int p1) throws BadLocationException JavaDoc {
203     Document JavaDoc doc = component.getDocument();
204     if (tag instanceof LayeredHighlightInfo) {
205         LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
206         if (lhi.width > 0 && lhi.height > 0) {
207         component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
208         }
209         // Mark the highlights region as invalid, it will reset itself
210
// next time asked to paint.
211
lhi.width = lhi.height = 0;
212         lhi.p0 = doc.createPosition(p0);
213         lhi.p1 = doc.createPosition(p1);
214             safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
215     }
216     else {
217         HighlightInfo info = (HighlightInfo) tag;
218         int oldP0 = info.p0.getOffset();
219         int oldP1 = info.p1.getOffset();
220         if (p0 == oldP0) {
221                 safeDamageRange(Math.min(oldP1, p1),
222                    Math.max(oldP1, p1));
223         } else if (p1 == oldP1) {
224                 safeDamageRange(Math.min(p0, oldP0),
225                    Math.max(p0, oldP0));
226         } else {
227                 safeDamageRange(oldP0, oldP1);
228                 safeDamageRange(p0, p1);
229         }
230         info.p0 = doc.createPosition(p0);
231         info.p1 = doc.createPosition(p1);
232     }
233     }
234
235     /**
236      * Makes a copy of the highlights. Does not actually clone each highlight,
237      * but only makes references to them.
238      *
239      * @return the copy
240      * @see Highlighter#getHighlights
241      */

242     public Highlighter.Highlight JavaDoc[] getHighlights() {
243         int size = highlights.size();
244         if (size == 0) {
245             return noHighlights;
246         }
247     Highlighter.Highlight JavaDoc[] h = new Highlighter.Highlight JavaDoc[size];
248     highlights.copyInto(h);
249     return h;
250     }
251
252     /**
253      * When leaf Views (such as LabelView) are rendering they should
254      * call into this method. If a highlight is in the given region it will
255      * be drawn immediately.
256      *
257      * @param g Graphics used to draw
258      * @param p0 starting offset of view
259      * @param p1 ending offset of view
260      * @param viewBounds Bounds of View
261      * @param editor JTextComponent
262      * @param view View instance being rendered
263      */

264     public void paintLayeredHighlights(Graphics g, int p0, int p1,
265                        Shape viewBounds,
266                        JTextComponent JavaDoc editor, View JavaDoc view) {
267     for (int counter = highlights.size() - 1; counter >= 0; counter--) {
268         Object JavaDoc tag = highlights.elementAt(counter);
269         if (tag instanceof LayeredHighlightInfo) {
270         LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
271         int start = lhi.getStartOffset();
272         int end = lhi.getEndOffset();
273         if ((p0 < start && p1 > start) ||
274             (p0 >= start && p0 < end)) {
275             lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
276                            editor, view);
277         }
278         }
279     }
280     }
281
282     /**
283      * Queues damageRange() call into event dispatch thread
284      * to be sure that views are in consistent state.
285      */

286     private void safeDamageRange(final Position JavaDoc p0, final Position JavaDoc p1) {
287         safeDamager.damageRange(p0, p1);
288     }
289
290     /**
291      * Queues damageRange() call into event dispatch thread
292      * to be sure that views are in consistent state.
293      */

294     private void safeDamageRange(int a0, int a1) throws BadLocationException JavaDoc {
295         Document JavaDoc doc = component.getDocument();
296         safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
297     }
298
299     /**
300      * If true, highlights are drawn as the Views draw the text. That is
301      * the Views will call into <code>paintLayeredHighlight</code> which
302      * will result in a rectangle being drawn before the text is drawn
303      * (if the offsets are in a highlighted region that is). For this to
304      * work the painter supplied must be an instance of
305      * LayeredHighlightPainter.
306      */

307     public void setDrawsLayeredHighlights(boolean newValue) {
308     drawsLayeredHighlights = newValue;
309     }
310
311     public boolean getDrawsLayeredHighlights() {
312     return drawsLayeredHighlights;
313     }
314
315     // ---- member variables --------------------------------------------
316

317     private final static Highlighter.Highlight JavaDoc[] noHighlights =
318             new Highlighter.Highlight JavaDoc[0];
319     private Vector JavaDoc highlights = new Vector JavaDoc(); // Vector<HighlightInfo>
320
private JTextComponent JavaDoc component;
321     private boolean drawsLayeredHighlights;
322     private SafeDamager safeDamager = new SafeDamager();
323
324
325     /**
326      * Default implementation of LayeredHighlighter.LayerPainter that can
327      * be used for painting highlights.
328      * <p>
329      * As of 1.4 this field is final.
330      */

331     public static final LayeredHighlighter.LayerPainter JavaDoc DefaultPainter = new DefaultHighlightPainter(null);
332
333
334     /**
335      * Simple highlight painter that fills a highlighted area with
336      * a solid color.
337      */

338     public static class DefaultHighlightPainter extends LayeredHighlighter.LayerPainter JavaDoc {
339
340         /**
341          * Constructs a new highlight painter. If <code>c</code> is null,
342      * the JTextComponent will be queried for its selection color.
343          *
344          * @param c the color for the highlight
345          */

346         public DefaultHighlightPainter(Color c) {
347         color = c;
348     }
349     
350         /**
351          * Returns the color of the highlight.
352          *
353          * @return the color
354          */

355     public Color getColor() {
356         return color;
357     }
358
359     // --- HighlightPainter methods ---------------------------------------
360

361         /**
362          * Paints a highlight.
363          *
364          * @param g the graphics context
365          * @param offs0 the starting model offset >= 0
366          * @param offs1 the ending model offset >= offs1
367          * @param bounds the bounding box for the highlight
368          * @param c the editor
369          */

370         public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent JavaDoc c) {
371         Rectangle alloc = bounds.getBounds();
372         try {
373         // --- determine locations ---
374
TextUI mapper = c.getUI();
375         Rectangle p0 = mapper.modelToView(c, offs0);
376         Rectangle p1 = mapper.modelToView(c, offs1);
377
378         // --- render ---
379
Color color = getColor();
380
381         if (color == null) {
382             g.setColor(c.getSelectionColor());
383         }
384         else {
385             g.setColor(color);
386         }
387         if (p0.y == p1.y) {
388             // same line, render a rectangle
389
Rectangle r = p0.union(p1);
390             g.fillRect(r.x, r.y, r.width, r.height);
391         } else {
392             // different lines
393
int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
394             g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
395             if ((p0.y + p0.height) != p1.y) {
396             g.fillRect(alloc.x, p0.y + p0.height, alloc.width,
397                    p1.y - (p0.y + p0.height));
398             }
399             g.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height);
400         }
401         } catch (BadLocationException JavaDoc e) {
402         // can't render
403
}
404     }
405
406     // --- LayerPainter methods ----------------------------
407
/**
408          * Paints a portion of a highlight.
409          *
410          * @param g the graphics context
411          * @param offs0 the starting model offset >= 0
412          * @param offs1 the ending model offset >= offs1
413          * @param bounds the bounding box of the view, which is not
414      * necessarily the region to paint.
415          * @param c the editor
416      * @param view View painting for
417      * @return region drawing occured in
418          */

419     public Shape paintLayer(Graphics g, int offs0, int offs1,
420                 Shape bounds, JTextComponent JavaDoc c, View JavaDoc view) {
421         Color color = getColor();
422
423         if (color == null) {
424         g.setColor(c.getSelectionColor());
425         }
426         else {
427         g.setColor(color);
428         }
429         if (offs0 == view.getStartOffset() &&
430         offs1 == view.getEndOffset()) {
431         // Contained in view, can just use bounds.
432
Rectangle alloc;
433         if (bounds instanceof Rectangle) {
434             alloc = (Rectangle)bounds;
435         }
436         else {
437             alloc = bounds.getBounds();
438         }
439         g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
440         return alloc;
441         }
442         else {
443         // Should only render part of View.
444
try {
445             // --- determine locations ---
446
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
447                                                    offs1,Position.Bias.Backward,
448                                                    bounds);
449                     Rectangle r = (shape instanceof Rectangle) ?
450                                   (Rectangle)shape : shape.getBounds();
451                     g.fillRect(r.x, r.y, r.width, r.height);
452                     return r;
453         } catch (BadLocationException JavaDoc e) {
454             // can't render
455
}
456         }
457         // Only if exception
458
return null;
459     }
460
461     private Color color;
462
463     }
464
465
466     class HighlightInfo implements Highlighter.Highlight JavaDoc {
467
468     public int getStartOffset() {
469         return p0.getOffset();
470     }
471
472     public int getEndOffset() {
473         return p1.getOffset();
474     }
475
476     public Highlighter.HighlightPainter JavaDoc getPainter() {
477         return painter;
478     }
479
480     Position JavaDoc p0;
481     Position JavaDoc p1;
482         Highlighter.HighlightPainter JavaDoc painter;
483     }
484
485
486     /**
487      * LayeredHighlightPainter is used when a drawsLayeredHighlights is
488      * true. It maintains a rectangle of the region to paint.
489      */

490     class LayeredHighlightInfo extends HighlightInfo {
491
492     void union(Shape bounds) {
493         if (bounds == null)
494         return;
495
496         Rectangle alloc;
497         if (bounds instanceof Rectangle) {
498         alloc = (Rectangle)bounds;
499         }
500         else {
501         alloc = bounds.getBounds();
502         }
503         if (width == 0 || height == 0) {
504         x = alloc.x;
505         y = alloc.y;
506         width = alloc.width;
507         height = alloc.height;
508         }
509         else {
510         width = Math.max(x + width, alloc.x + alloc.width);
511         height = Math.max(y + height, alloc.y + alloc.height);
512         x = Math.min(x, alloc.x);
513         width -= x;
514         y = Math.min(y, alloc.y);
515         height -= y;
516         }
517     }
518
519     /**
520      * Restricts the region based on the receivers offsets and messages
521      * the painter to paint the region.
522      */

523     void paintLayeredHighlights(Graphics g, int p0, int p1,
524                     Shape viewBounds, JTextComponent JavaDoc editor,
525                     View JavaDoc view) {
526         int start = getStartOffset();
527         int end = getEndOffset();
528         // Restrict the region to what we represent
529
p0 = Math.max(start, p0);
530         p1 = Math.min(end, p1);
531         // Paint the appropriate region using the painter and union
532
// the effected region with our bounds.
533
union(((LayeredHighlighter.LayerPainter JavaDoc)painter).paintLayer
534           (g, p0, p1, viewBounds, editor, view));
535     }
536
537     int x;
538     int y;
539     int width;
540     int height;
541     }
542
543     /**
544      * This class invokes <code>mapper.damageRange</code> in
545      * EventDispatchThread. The only one instance per Highlighter
546      * is cretaed. When a number of ranges should be damaged
547      * it collects them into queue and damages
548      * them in consecutive order in <code>run</code>
549      * call.
550      */

551     class SafeDamager implements Runnable JavaDoc {
552         private Vector JavaDoc p0 = new Vector JavaDoc(10);
553         private Vector JavaDoc p1 = new Vector JavaDoc(10);
554         private Document JavaDoc lastDoc = null;
555
556         /**
557          * Executes range(s) damage and cleans range queue.
558          */

559         public synchronized void run() {
560             if (component != null) {
561                 TextUI mapper = component.getUI();
562                 if (mapper != null && lastDoc == component.getDocument()) {
563                     // the Document should be the same to properly
564
// display highlights
565
int len = p0.size();
566                     for (int i = 0; i < len; i++){
567                         mapper.damageRange(component,
568                                 ((Position JavaDoc)p0.get(i)).getOffset(),
569                                 ((Position JavaDoc)p1.get(i)).getOffset());
570                     }
571                 }
572             }
573             p0.clear();
574             p1.clear();
575
576             // release reference
577
lastDoc = null;
578         }
579
580         /**
581          * Adds the range to be damaged into the range queue. If the
582          * range queue is empty (the first call or run() was already
583          * invoked) then adds this class instance into EventDispatch
584          * queue.
585          *
586          * The method also tracks if the current document changed or
587          * component is null. In this case it removes all ranges added
588          * before from range queue.
589          */

590         public synchronized void damageRange(Position JavaDoc pos0, Position JavaDoc pos1) {
591             if (component == null) {
592                 p0.clear();
593                 lastDoc = null;
594                 return;
595             }
596
597             boolean addToQueue = p0.isEmpty();
598             Document JavaDoc curDoc = component.getDocument();
599             if (curDoc != lastDoc) {
600                 if (!p0.isEmpty()) {
601                     p0.clear();
602                     p1.clear();
603                 }
604                 lastDoc = curDoc;
605             }
606             p0.add(pos0);
607             p1.add(pos1);
608
609             if (addToQueue) {
610                 SwingUtilities.invokeLater(this);
611             }
612         }
613     }
614 }
615
Popular Tags