KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > drjava > ui > ReverseHighlighter


1 package edu.rice.cs.drjava.ui;
2
3 import javax.swing.text.*;
4 import java.util.Vector JavaDoc;
5 import java.awt.*;
6 import javax.swing.plaf.*;
7 import javax.swing.*;
8
9 /**
10  * Implements the Highlighter interfaces. Implements a simple highlight painter, but stores
11  * the highlights in reverse order. That means that the selection (for copying) is always
12  * the foremost hightlight, and after that, the highlights are drawn from most recent
13  * to oldest.
14  * Based on DefaultHighlighter by Timothy Prinzing, version 1.39 12/19/03
15  * Unfortunately, as the vector of highlights in DefaultHighlighter was private, there was
16  * no efficient way to make use of inheritance.
17  */

18 public class ReverseHighlighter extends DefaultHighlighter {
19   /**
20    * Creates a new ReverseHighlighter object.
21    */

22   public ReverseHighlighter() {
23     drawsLayeredHighlights = true;
24   }
25   
26   // ---- Highlighter methods ----------------------------------------------
27

28   /**
29    * Renders the highlights.
30    *
31    * @param g the graphics context
32    */

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

63   public void install(JTextComponent c) {
64     component = c;
65     removeAllHighlights();
66   }
67   
68   /** Called when the UI is being removed from the interface of a JTextComponent.
69     * @param c the component
70     * @see Highlighter#deinstall
71     */

72   public void deinstall(JTextComponent c) {
73     component = null;
74   }
75   
76   /** Adds a highlight to the view. Returns a tag that can be used to refer to the highlight.
77     * @param p0 the start offset of the range to highlight >= 0
78     * @param p1 the end offset of the range to highlight >= p0
79     * @param p the painter to use to actually render the highlight
80     * @return an object that can be used as a tag to refer to the highlight
81     * @exception BadLocationException if the specified location is invalid
82     */

83   public Object JavaDoc addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException {
84     Document doc = component.getDocument();
85     HighlightInfo i = (getDrawsLayeredHighlights() &&
86                        (p instanceof LayeredHighlighter.LayerPainter)) ?
87       new LayeredHighlightInfo() : new HighlightInfo();
88     i.painter = p;
89
90     i.p0 = doc.createPosition(p0);
91     i.p1 = doc.createPosition(p1);
92
93     int insertPos = 0;
94     if ((!(p instanceof DefaultFrameHighlightPainter)) && (!(p instanceof DefaultUnderlineHighlightPainter))) {
95       // insert solid painters after the frame and underline painters
96
for(HighlightInfo hli: highlights) {
97         if ((! (hli.getPainter() instanceof DefaultFrameHighlightPainter)) &&
98             (! (hli.getPainter() instanceof DefaultUnderlineHighlightPainter))) {
99           break;
100         }
101         ++insertPos;
102       }
103     }
104     highlights.add(insertPos, i);
105     safeDamageRange(p0, p1);
106     return i;
107   }
108   
109   /**
110    * Removes a highlight from the view.
111    *
112    * @param tag the reference to the highlight
113    */

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

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

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

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

262   public void paintLayeredHighlights(Graphics g, int p0, int p1,
263                                      Shape viewBounds,
264                                      JTextComponent editor, View view) {
265     for (int counter = highlights.size() - 1; counter >= 0; counter--) {
266       Object JavaDoc tag = highlights.elementAt(counter);
267       if (tag instanceof LayeredHighlightInfo) {
268         LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
269         int start = lhi.getStartOffset();
270         int end = lhi.getEndOffset();
271         if ((p0 < start && p1 > start) ||
272             (p0 >= start && p0 < end)) {
273           lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
274                                      editor, view);
275         }
276       }
277     }
278   }
279   
280   /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
281   private void safeDamageRange(final Position p0, final Position p1) {
282     safeDamager.damageRange(p0, p1);
283   }
284   
285   /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
286   private void safeDamageRange(int a0, int a1) throws BadLocationException {
287     Document doc = component.getDocument();
288
289     safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
290   }
291   
292   /**
293    * If true, highlights are drawn as the Views draw the text. That is
294    * the Views will call into <code>paintLayeredHighlight</code> which
295    * will result in a rectangle being drawn before the text is drawn
296    * (if the offsets are in a highlighted region that is). For this to
297    * work the painter supplied must be an instance of
298    * LayeredHighlightPainter.
299    */

300   public void setDrawsLayeredHighlights(boolean newValue) {
301     drawsLayeredHighlights = newValue;
302   }
303   
304   public boolean getDrawsLayeredHighlights() {
305     return drawsLayeredHighlights;
306   }
307   
308   // ---- member variables --------------------------------------------
309

310   private final static Highlighter.Highlight[] noHighlights =
311     new Highlighter.Highlight[0];
312   private Vector JavaDoc<HighlightInfo> highlights = new Vector JavaDoc<HighlightInfo>(); // Vector<HighlightInfo>
313
private JTextComponent component;
314   private boolean drawsLayeredHighlights;
315   private SafeDamager safeDamager = new SafeDamager();
316   
317   /** Simple highlight painter that draws a rectangular box around text. */
318   public static class DefaultFrameHighlightPainter extends LayeredHighlighter.LayerPainter {
319     
320     /**
321      * Constructs a new highlight painter. If <code>c</code> is null,
322      * the JTextComponent will be queried for its selection color.
323      *
324      * @param c the color for the highlight
325      * @param t the thickness in pixels
326      */

327     public DefaultFrameHighlightPainter(Color c, int t) {
328       color = c;
329       thickness = t;
330     }
331     
332     /**
333      * Returns the color of the highlight.
334      *
335      * @return the color
336      */

337     public Color getColor() {
338       return color;
339     }
340     
341     /** @return thickness in pixels */
342     public int getThickness() { return thickness; }
343     
344     // --- HighlightPainter methods ---------------------------------------
345

346     private void drawRectThick(Graphics g, int x, int y, int width, int height, int thick) {
347       if (thick<2) { g.drawRect(x, y, width, height); }
348       else {
349         g.fillRect(x, y, width, thick);
350         g.fillRect(x, y+height-thick, width, thick);
351         g.fillRect(x, y, thick, height);
352         g.fillRect(x+width-thick, y, thick, height);
353       }
354     }
355     
356     /**
357      * Paints a highlight.
358      *
359      * @param g the graphics context
360      * @param offs0 the starting model offset >= 0
361      * @param offs1 the ending model offset >= offs1
362      * @param bounds the bounding box for the highlight
363      * @param c the editor
364      */

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

414     public Shape paintLayer(Graphics g, int offs0, int offs1,
415                             Shape bounds, JTextComponent c, View view) {
416       Color color = getColor();
417       
418       if (color == null) {
419         g.setColor(c.getSelectionColor());
420       }
421       else {
422         g.setColor(color);
423       }
424       if (offs0 == view.getStartOffset() &&
425           offs1 == view.getEndOffset()) {
426         // Contained in view, can just use bounds.
427
Rectangle alloc;
428         if (bounds instanceof Rectangle) {
429           alloc = (Rectangle)bounds;
430         }
431         else {
432           alloc = bounds.getBounds();
433         }
434         drawRectThick(g, alloc.x, alloc.y, alloc.width, alloc.height, thickness);
435         return alloc;
436       }
437       else {
438         // Should only render part of View.
439
try {
440           // --- determine locations ---
441
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
442                                          offs1,Position.Bias.Backward,
443                                          bounds);
444           Rectangle r = (shape instanceof Rectangle) ?
445             (Rectangle)shape : shape.getBounds();
446           drawRectThick(g, r.x, r.y, r.width, r.height, thickness);
447           return r;
448         } catch (BadLocationException e) {
449           // can't render
450
}
451       }
452       // Only if exception
453
return null;
454     }
455     
456     private Color color;
457     private int thickness;
458   }
459   
460   
461   /**
462    * Simple highlight painter that underlines text.
463    */

464   public static class DefaultUnderlineHighlightPainter extends LayeredHighlighter.LayerPainter {
465     
466     /**
467      * Constructs a new highlight painter. If <code>c</code> is null,
468      * the JTextComponent will be queried for its selection color.
469      *
470      * @param c the color for the highlight
471      * @param t the thickness in pixels
472      */

473     public DefaultUnderlineHighlightPainter(Color c, int t) {
474       color = c;
475       thickness = t;
476     }
477     
478     /**
479      * Returns the color of the highlight.
480      *
481      * @return the color
482      */

483     public Color getColor() {
484       return color;
485     }
486     
487     /** @return thickness in pixels */
488     public int getThickness() { return thickness; }
489     
490     // --- HighlightPainter methods ---------------------------------------
491

492     private void drawUnderline(Graphics g, int x, int y, int width, int height, int thick) {
493       g.fillRect(x, y+height-thick, width, thick);
494     }
495     
496     /**
497      * Paints a highlight.
498      *
499      * @param g the graphics context
500      * @param offs0 the starting model offset >= 0
501      * @param offs1 the ending model offset >= offs1
502      * @param bounds the bounding box for the highlight
503      * @param c the editor
504      */

505     public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
506       Rectangle alloc = bounds.getBounds();
507       try {
508         // --- determine locations ---
509
TextUI mapper = c.getUI();
510         Rectangle p0 = mapper.modelToView(c, offs0);
511         Rectangle p1 = mapper.modelToView(c, offs1);
512         
513         // --- render ---
514
Color color = getColor();
515         
516         if (color == null) {
517           g.setColor(c.getSelectionColor());
518         }
519         else {
520           g.setColor(color);
521         }
522         if (p0.y == p1.y) {
523           // same line, render a rectangle
524
Rectangle r = p0.union(p1);
525           drawUnderline(g, r.x, r.y, r.width, r.height, thickness);
526         } else {
527           // different lines
528
int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
529           drawUnderline(g, p0.x, p0.y, p0ToMarginWidth, p0.height, thickness);
530           if ((p0.y + p0.height) != p1.y) {
531             drawUnderline(g, alloc.x, p0.y + p0.height, alloc.width,
532                        p1.y - (p0.y + p0.height), thickness);
533           }
534           drawUnderline(g, alloc.x, p1.y, (p1.x - alloc.x), p1.height, thickness);
535         }
536       } catch (BadLocationException e) {
537         // can't render
538
}
539     }
540     
541     // --- LayerPainter methods ----------------------------
542
/**
543      * Paints a portion of a highlight.
544      *
545      * @param g the graphics context
546      * @param offs0 the starting model offset >= 0
547      * @param offs1 the ending model offset >= offs1
548      * @param bounds the bounding box of the view, which is not
549      * necessarily the region to paint.
550      * @param c the editor
551      * @param view View painting for
552      * @return region drawing occured in
553      */

554     public Shape paintLayer(Graphics g, int offs0, int offs1,
555                             Shape bounds, JTextComponent c, View view) {
556       Color color = getColor();
557       
558       if (color == null) {
559         g.setColor(c.getSelectionColor());
560       }
561       else {
562         g.setColor(color);
563       }
564       if (offs0 == view.getStartOffset() &&
565           offs1 == view.getEndOffset()) {
566         // Contained in view, can just use bounds.
567
Rectangle alloc;
568         if (bounds instanceof Rectangle) {
569           alloc = (Rectangle)bounds;
570         }
571         else {
572           alloc = bounds.getBounds();
573         }
574         drawUnderline(g, alloc.x, alloc.y, alloc.width, alloc.height, thickness);
575         return alloc;
576       }
577       else {
578         // Should only render part of View.
579
try {
580           // --- determine locations ---
581
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
582                                          offs1,Position.Bias.Backward,
583                                          bounds);
584           Rectangle r = (shape instanceof Rectangle) ?
585             (Rectangle)shape : shape.getBounds();
586           drawUnderline(g, r.x, r.y, r.width, r.height, thickness);
587           return r;
588         } catch (BadLocationException e) {
589           // can't render
590
}
591       }
592       // Only if exception
593
return null;
594     }
595     
596     private Color color;
597     private int thickness;
598   }
599   
600   class HighlightInfo implements Highlighter.Highlight {
601     
602     public int getStartOffset() {
603       return p0.getOffset();
604     }
605     
606     public int getEndOffset() {
607       return p1.getOffset();
608     }
609     
610     public Highlighter.HighlightPainter getPainter() {
611       return painter;
612     }
613     
614     Position p0;
615     Position p1;
616     Highlighter.HighlightPainter painter;
617   }
618   
619   
620   /**
621    * LayeredHighlightPainter is used when a drawsLayeredHighlights is
622    * true. It maintains a rectangle of the region to paint.
623    */

624   class LayeredHighlightInfo extends HighlightInfo {
625     
626     void union(Shape bounds) {
627       if (bounds == null)
628         return;
629       
630       Rectangle alloc;
631       if (bounds instanceof Rectangle) {
632         alloc = (Rectangle)bounds;
633       }
634       else {
635         alloc = bounds.getBounds();
636       }
637       if (width == 0 || height == 0) {
638         x = alloc.x;
639         y = alloc.y;
640         width = alloc.width;
641         height = alloc.height;
642       }
643       else {
644         width = Math.max(x + width, alloc.x + alloc.width);
645         height = Math.max(y + height, alloc.y + alloc.height);
646         x = Math.min(x, alloc.x);
647         width -= x;
648         y = Math.min(y, alloc.y);
649         height -= y;
650       }
651     }
652     
653     /** Restricts the region based on the receivers offsets and messages the painter to paint the region.*/
654     void paintLayeredHighlights(Graphics g, int p0, int p1,
655                                 Shape viewBounds, JTextComponent editor,
656                                 View view) {
657       int start = getStartOffset();
658       int end = getEndOffset();
659       // Restrict the region to what we represent
660
p0 = Math.max(start, p0);
661       p1 = Math.min(end, p1);
662       // Paint the appropriate region using the painter and union
663
// the effected region with our bounds.
664
union(((LayeredHighlighter.LayerPainter)painter).paintLayer
665               (g, p0, p1, viewBounds, editor, view));
666     }
667     
668     int x;
669     int y;
670     int width;
671     int height;
672   }
673
674   
675   /** This class invokes <code>mapper.damageRange</code> in EventDispatchThread. The only one instance per Highlighter
676    * is cretaed. When a number of ranges should be damaged it collects them into queue and damages them in consecutive
677    * order in <code>run</code> call.
678    */

679   class SafeDamager implements Runnable JavaDoc {
680     private Vector JavaDoc<Position> p0 = new Vector JavaDoc<Position>(10);
681     private Vector JavaDoc<Position> p1 = new Vector JavaDoc<Position>(10);
682     private Document lastDoc = null;
683     
684     /** Executes range(s) damage and cleans range queue. */
685     public synchronized void run() {
686       if (component != null) {
687         TextUI mapper = component.getUI();
688         if (mapper != null && lastDoc == component.getDocument()) {
689           // the Document should be the same to properly display highlights
690
int len = p0.size();
691           for (int i = 0; i < len; i++){
692             mapper.damageRange(component, p0.get(i).getOffset(), p1.get(i).getOffset());
693           }
694         }
695       }
696       p0.clear();
697       p1.clear();
698       
699       // release reference
700
lastDoc = null;
701     }
702     
703     /**
704      * Adds the range to be damaged into the range queue. If the
705      * range queue is empty (the first call or run() was already
706      * invoked) then adds this class instance into EventDispatch
707      * queue.
708      *
709      * The method also tracks if the current document changed or
710      * component is null. In this case it removes all ranges added
711      * before from range queue.
712      */

713     public synchronized void damageRange(Position pos0, Position pos1) {
714       if (component == null) {
715         p0.clear();
716         lastDoc = null;
717         return;
718       }
719       
720       boolean addToQueue = p0.isEmpty();
721       Document curDoc = component.getDocument();
722       if (curDoc != lastDoc) {
723         if (!p0.isEmpty()) {
724           p0.clear();
725           p1.clear();
726         }
727         lastDoc = curDoc;
728       }
729       p0.add(pos0);
730       p1.add(pos1);
731       
732       if (addToQueue) {
733         SwingUtilities.invokeLater(this);
734       }
735     }
736   }
737 }
738
Popular Tags