001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui;
038:
039: import javax.swing.text.*;
040: import java.util.Vector;
041: import java.awt.*;
042: import javax.swing.plaf.*;
043: import javax.swing.*;
044:
045: /**
046: * Implements the Highlighter interfaces. Implements a simple highlight painter, but stores
047: * the highlights in reverse order. That means that the selection (for copying) is always
048: * the foremost hightlight, and after that, the highlights are drawn from most recent
049: * to oldest.
050: * Based on DefaultHighlighter by Timothy Prinzing, version 1.39 12/19/03
051: * Unfortunately, as the vector of highlights in DefaultHighlighter was private, there was
052: * no efficient way to make use of inheritance.
053: */
054: public class ReverseHighlighter extends DefaultHighlighter {
055: /**
056: * Creates a new ReverseHighlighter object.
057: */
058: public ReverseHighlighter() {
059: drawsLayeredHighlights = true;
060: }
061:
062: // ---- Highlighter methods ----------------------------------------------
063:
064: /**
065: * Renders the highlights.
066: *
067: * @param g the graphics context
068: */
069: public void paint(Graphics g) {
070: // PENDING(prinz) - should cull ranges not visible
071: int len = highlights.size();
072: for (int i = 0; i < len; i++) {
073: HighlightInfo info = highlights.elementAt(i);
074: if (!(info instanceof LayeredHighlightInfo)) {
075: // Avoid allocing unless we need it.
076: Rectangle a = component.getBounds();
077: Insets insets = component.getInsets();
078: a.x = insets.left;
079: a.y = insets.top;
080: a.width -= insets.left + insets.right;
081: a.height -= insets.top + insets.bottom;
082: for (; i < len; i++) {
083: info = highlights.elementAt(i);
084: if (!(info instanceof LayeredHighlightInfo)) {
085: Highlighter.HighlightPainter p = info
086: .getPainter();
087: p.paint(g, info.getStartOffset(), info
088: .getEndOffset(), a, component);
089: }
090: }
091: }
092: }
093: }
094:
095: /** Called when the UI is being installed into the interface of a JTextComponent. Installs the editor, and
096: * removes any existing highlights.
097: * @param c the editor component
098: * @see Highlighter#install
099: */
100: public void install(JTextComponent c) {
101: component = c;
102: removeAllHighlights();
103: }
104:
105: /** Called when the UI is being removed from the interface of a JTextComponent.
106: * @param c the component
107: * @see Highlighter#deinstall
108: */
109: public void deinstall(JTextComponent c) {
110: component = null;
111: }
112:
113: //static edu.rice.cs.util.Log _log = new edu.rice.cs.util.Log("highlighter.txt",true);
114:
115: /** Adds a highlight to the view. Returns a tag that can be used to refer to the highlight.
116: * @param p0 the start offset of the range to highlight >= 0
117: * @param p1 the end offset of the range to highlight >= p0
118: * @param p the painter to use to actually render the highlight
119: * @return an object that can be used as a tag to refer to the highlight
120: * @exception BadLocationException if the specified location is invalid
121: */
122: public Object addHighlight(int p0, int p1,
123: Highlighter.HighlightPainter p) throws BadLocationException {
124: Document doc = component.getDocument();
125: HighlightInfo i = (getDrawsLayeredHighlights() && (p instanceof LayeredHighlighter.LayerPainter)) ? new LayeredHighlightInfo()
126: : new HighlightInfo();
127: i.painter = p;
128:
129: i.p0 = doc.createPosition(p0);
130: i.p1 = doc.createPosition(p1);
131:
132: int insertPos = highlights.size();
133: /*if ((!(p instanceof DefaultFrameHighlightPainter)) && (!(p instanceof DefaultUnderlineHighlightPainter))) {
134: // insert solid painters after the frame and underline painters
135: while (insertPos>0) {
136: HighlightInfo hli = highlights.elementAt( insertPos-1 );
137: if ((! (hli.getPainter() instanceof DefaultFrameHighlightPainter)) &&
138: (! (hli.getPainter() instanceof DefaultUnderlineHighlightPainter))) {
139: break;
140: }
141: --insertPos;
142: }
143: }*/
144: if (p instanceof DrJavaHighlightPainter) {
145: while (insertPos > 0) {
146: HighlightInfo hli = highlights.elementAt(insertPos - 1);
147: if (hli.getPainter() instanceof DrJavaHighlightPainter)
148: --insertPos;
149: else
150: break;
151: }
152: } else if (p instanceof DefaultHighlightPainter) {
153: while (insertPos > 0) {
154: HighlightInfo hli = highlights.elementAt(insertPos - 1);
155: if (hli.getPainter() instanceof DefaultHighlightPainter)
156: --insertPos;
157: else
158: break;
159: }
160: } else if (p instanceof DefaultFrameHighlightPainter) {
161: while (insertPos > 0) {
162: HighlightInfo hli = highlights.elementAt(insertPos - 1);
163: if (hli.getPainter() instanceof DefaultHighlightPainter
164: || hli.getPainter() instanceof DefaultFrameHighlightPainter)
165: --insertPos;
166: else
167: break;
168: }
169: } else {
170: insertPos = 0;
171: }
172: highlights.add(insertPos, i);
173: //_log.log(p.toString() + ", pos: " + insertPos);
174: safeDamageRange(p0, p1);
175: return i;
176: }
177:
178: /**
179: * Removes a highlight from the view.
180: *
181: * @param tag the reference to the highlight
182: */
183: public void removeHighlight(Object tag) {
184: if (tag instanceof LayeredHighlightInfo) {
185: LayeredHighlightInfo lhi = (LayeredHighlightInfo) tag;
186: if (lhi.width > 0 && lhi.height > 0) {
187: component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
188: }
189: } else {
190: HighlightInfo info = (HighlightInfo) tag;
191: safeDamageRange(info.p0, info.p1);
192: }
193: highlights.removeElement(tag);
194: }
195:
196: /**
197: * Removes all highlights.
198: */
199: public void removeAllHighlights() {
200: TextUI mapper = component.getUI();
201: if (getDrawsLayeredHighlights()) {
202: int len = highlights.size();
203: if (len != 0) {
204: int minX = 0;
205: int minY = 0;
206: int maxX = 0;
207: int maxY = 0;
208: int p0 = -1;
209: int p1 = -1;
210: for (int i = 0; i < len; i++) {
211: HighlightInfo hi = highlights.elementAt(i);
212: if (hi instanceof LayeredHighlightInfo) {
213: LayeredHighlightInfo info = (LayeredHighlightInfo) hi;
214: minX = Math.min(minX, info.x);
215: minY = Math.min(minY, info.y);
216: maxX = Math.max(maxX, info.x + info.width);
217: maxY = Math.max(maxY, info.y + info.height);
218: } else {
219: if (p0 == -1) {
220: p0 = hi.p0.getOffset();
221: p1 = hi.p1.getOffset();
222: } else {
223: p0 = Math.min(p0, hi.p0.getOffset());
224: p1 = Math.max(p1, hi.p1.getOffset());
225: }
226: }
227: }
228: if (minX != maxX && minY != maxY) {
229: component.repaint(minX, minY, maxX - minX, maxY
230: - minY);
231: }
232: if (p0 != -1) {
233: try {
234: safeDamageRange(p0, p1);
235: } catch (BadLocationException e) {
236: }
237: }
238: highlights.removeAllElements();
239: }
240: } else if (mapper != null) {
241: int len = highlights.size();
242: if (len != 0) {
243: int p0 = Integer.MAX_VALUE;
244: int p1 = 0;
245: for (int i = 0; i < len; i++) {
246: HighlightInfo info = highlights.elementAt(i);
247: p0 = Math.min(p0, info.p0.getOffset());
248: p1 = Math.max(p1, info.p1.getOffset());
249: }
250: try {
251: safeDamageRange(p0, p1);
252: } catch (BadLocationException e) {
253: }
254:
255: highlights.removeAllElements();
256: }
257: }
258: }
259:
260: /**
261: * Changes a highlight.
262: *
263: * @param tag the highlight tag
264: * @param p0 the beginning of the range >= 0
265: * @param p1 the end of the range >= p0
266: * @exception BadLocationException if the specified location is invalid
267: */
268: public void changeHighlight(Object tag, int p0, int p1)
269: throws BadLocationException {
270: Document doc = component.getDocument();
271: if (tag instanceof LayeredHighlightInfo) {
272: LayeredHighlightInfo lhi = (LayeredHighlightInfo) tag;
273: if (lhi.width > 0 && lhi.height > 0) {
274: component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
275: }
276: // Mark the highlights region as invalid, it will reset itself
277: // next time asked to paint.
278: lhi.width = lhi.height = 0;
279:
280: lhi.p0 = doc.createPosition(p0);
281: lhi.p1 = doc.createPosition(p1);
282: safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
283: } else {
284: HighlightInfo info = (HighlightInfo) tag;
285: int oldP0 = info.p0.getOffset();
286: int oldP1 = info.p1.getOffset();
287: if (p0 == oldP0)
288: safeDamageRange(Math.min(oldP1, p1), Math
289: .max(oldP1, p1));
290: else if (p1 == oldP1)
291: safeDamageRange(Math.min(p0, oldP0), Math
292: .max(p0, oldP0));
293: else {
294: safeDamageRange(oldP0, oldP1);
295: safeDamageRange(p0, p1);
296: }
297:
298: info.p0 = doc.createPosition(p0);
299: info.p1 = doc.createPosition(p1);
300:
301: // TODO: figure out what is wrong here. The preceding lines are dead code.
302: }
303: }
304:
305: /**
306: * Makes a copy of the highlights. Does not actually clone each highlight,
307: * but only makes references to them.
308: *
309: * @return the copy
310: * @see Highlighter#getHighlights
311: */
312: public Highlighter.Highlight[] getHighlights() {
313: int size = highlights.size();
314: if (size == 0) {
315: return noHighlights;
316: }
317: Highlighter.Highlight[] h = new Highlighter.Highlight[size];
318: highlights.copyInto(h);
319: return h;
320: }
321:
322: /**
323: * When leaf Views (such as LabelView) are rendering they should
324: * call into this method. If a highlight is in the given region it will
325: * be drawn immediately.
326: *
327: * @param g Graphics used to draw
328: * @param p0 starting offset of view
329: * @param p1 ending offset of view
330: * @param viewBounds Bounds of View
331: * @param editor JTextComponent
332: * @param view View instance being rendered
333: */
334: public void paintLayeredHighlights(Graphics g, int p0, int p1,
335: Shape viewBounds, JTextComponent editor, View view) {
336: for (int counter = highlights.size() - 1; counter >= 0; counter--) {
337: Object tag = highlights.elementAt(counter);
338: if (tag instanceof LayeredHighlightInfo) {
339: LayeredHighlightInfo lhi = (LayeredHighlightInfo) tag;
340: int start = lhi.getStartOffset();
341: int end = lhi.getEndOffset();
342: if ((p0 < start && p1 > start)
343: || (p0 >= start && p0 < end)) {
344: lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
345: editor, view);
346: }
347: }
348: }
349: }
350:
351: /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
352: private void safeDamageRange(final Position p0, final Position p1) {
353: safeDamager.damageRange(p0, p1);
354: }
355:
356: /** Queues damageRange() call into event dispatch thread to be sure that views are in consistent state. */
357: private void safeDamageRange(int a0, int a1)
358: throws BadLocationException {
359: Document doc = component.getDocument();
360:
361: safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
362: }
363:
364: /**
365: * If true, highlights are drawn as the Views draw the text. That is
366: * the Views will call into <code>paintLayeredHighlight</code> which
367: * will result in a rectangle being drawn before the text is drawn
368: * (if the offsets are in a highlighted region that is). For this to
369: * work the painter supplied must be an instance of
370: * LayeredHighlightPainter.
371: */
372: public void setDrawsLayeredHighlights(boolean newValue) {
373: drawsLayeredHighlights = newValue;
374: }
375:
376: public boolean getDrawsLayeredHighlights() {
377: return drawsLayeredHighlights;
378: }
379:
380: // ---- member variables --------------------------------------------
381:
382: private final static Highlighter.Highlight[] noHighlights = new Highlighter.Highlight[0];
383: private Vector<HighlightInfo> highlights = new Vector<HighlightInfo>(); // Vector<HighlightInfo>
384: private JTextComponent component;
385: private boolean drawsLayeredHighlights;
386: private SafeDamager safeDamager = new SafeDamager();
387:
388: /** Simple highlight painter that draws a rectangular box around text. */
389: public static class DefaultFrameHighlightPainter extends
390: LayeredHighlighter.LayerPainter {
391:
392: /**
393: * Constructs a new highlight painter. If <code>c</code> is null,
394: * the JTextComponent will be queried for its selection color.
395: *
396: * @param c the color for the highlight
397: * @param t the thickness in pixels
398: */
399: public DefaultFrameHighlightPainter(Color c, int t) {
400: color = c;
401: thickness = t;
402: }
403:
404: /**
405: * Returns the color of the highlight.
406: *
407: * @return the color
408: */
409: public Color getColor() {
410: return color;
411: }
412:
413: /** @return thickness in pixels */
414: public int getThickness() {
415: return thickness;
416: }
417:
418: // --- HighlightPainter methods ---------------------------------------
419:
420: private void drawRectThick(Graphics g, int x, int y, int width,
421: int height, int thick) {
422: if (thick < 2) {
423: g.drawRect(x, y, width, height);
424: } else {
425: g.fillRect(x, y, width, thick);
426: g.fillRect(x, y + height - thick, width, thick);
427: g.fillRect(x, y, thick, height);
428: g.fillRect(x + width - thick, y, thick, height);
429: }
430: }
431:
432: /**
433: * Paints a highlight.
434: *
435: * @param g the graphics context
436: * @param offs0 the starting model offset >= 0
437: * @param offs1 the ending model offset >= offs1
438: * @param bounds the bounding box for the highlight
439: * @param c the editor
440: */
441: public void paint(Graphics g, int offs0, int offs1,
442: Shape bounds, JTextComponent c) {
443: Rectangle alloc = bounds.getBounds();
444: try {
445: // --- determine locations ---
446: TextUI mapper = c.getUI();
447: Rectangle p0 = mapper.modelToView(c, offs0);
448: Rectangle p1 = mapper.modelToView(c, offs1);
449:
450: // --- render ---
451: Color color = getColor();
452:
453: if (color == null) {
454: g.setColor(c.getSelectionColor());
455: } else {
456: g.setColor(color);
457: }
458: if (p0.y == p1.y) {
459: // same line, render a rectangle
460: Rectangle r = p0.union(p1);
461: drawRectThick(g, r.x, r.y, r.width, r.height,
462: thickness);
463: } else {
464: // different lines
465: int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
466: drawRectThick(g, p0.x, p0.y, p0ToMarginWidth,
467: p0.height, thickness);
468: if ((p0.y + p0.height) != p1.y) {
469: drawRectThick(g, alloc.x, p0.y + p0.height,
470: alloc.width, p1.y - (p0.y + p0.height),
471: thickness);
472: }
473: drawRectThick(g, alloc.x, p1.y, (p1.x - alloc.x),
474: p1.height, thickness);
475: }
476: } catch (BadLocationException e) {
477: // can't render
478: }
479: }
480:
481: // --- LayerPainter methods ----------------------------
482: /**
483: * Paints a portion of a highlight.
484: *
485: * @param g the graphics context
486: * @param offs0 the starting model offset >= 0
487: * @param offs1 the ending model offset >= offs1
488: * @param bounds the bounding box of the view, which is not
489: * necessarily the region to paint.
490: * @param c the editor
491: * @param view View painting for
492: * @return region drawing occured in
493: */
494: public Shape paintLayer(Graphics g, int offs0, int offs1,
495: Shape bounds, JTextComponent c, View view) {
496: Color color = getColor();
497:
498: if (color == null) {
499: g.setColor(c.getSelectionColor());
500: } else {
501: g.setColor(color);
502: }
503: if (offs0 == view.getStartOffset()
504: && offs1 == view.getEndOffset()) {
505: // Contained in view, can just use bounds.
506: Rectangle alloc;
507: if (bounds instanceof Rectangle) {
508: alloc = (Rectangle) bounds;
509: } else {
510: alloc = bounds.getBounds();
511: }
512: drawRectThick(g, alloc.x, alloc.y, alloc.width,
513: alloc.height, thickness);
514: return alloc;
515: } else {
516: // Should only render part of View.
517: try {
518: // --- determine locations ---
519: Shape shape = view.modelToView(offs0,
520: Position.Bias.Forward, offs1,
521: Position.Bias.Backward, bounds);
522: Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape
523: : shape.getBounds();
524: drawRectThick(g, r.x, r.y, r.width, r.height,
525: thickness);
526: return r;
527: } catch (BadLocationException e) {
528: // can't render
529: }
530: }
531: // Only if exception
532: return null;
533: }
534:
535: private Color color;
536: private int thickness;
537: }
538:
539: /**
540: * Simple highlight painter that underlines text.
541: */
542: public static class DefaultUnderlineHighlightPainter extends
543: LayeredHighlighter.LayerPainter {
544:
545: /**
546: * Constructs a new highlight painter. If <code>c</code> is null,
547: * the JTextComponent will be queried for its selection color.
548: *
549: * @param c the color for the highlight
550: * @param t the thickness in pixels
551: */
552: public DefaultUnderlineHighlightPainter(Color c, int t) {
553: color = c;
554: thickness = t;
555: }
556:
557: /**
558: * Returns the color of the highlight.
559: *
560: * @return the color
561: */
562: public Color getColor() {
563: return color;
564: }
565:
566: /** @return thickness in pixels */
567: public int getThickness() {
568: return thickness;
569: }
570:
571: // --- HighlightPainter methods ---------------------------------------
572:
573: private void drawUnderline(Graphics g, int x, int y, int width,
574: int height, int thick) {
575: g.fillRect(x, y + height - thick, width, thick);
576: }
577:
578: /**
579: * Paints a highlight.
580: *
581: * @param g the graphics context
582: * @param offs0 the starting model offset >= 0
583: * @param offs1 the ending model offset >= offs1
584: * @param bounds the bounding box for the highlight
585: * @param c the editor
586: */
587: public void paint(Graphics g, int offs0, int offs1,
588: Shape bounds, JTextComponent c) {
589: Rectangle alloc = bounds.getBounds();
590: try {
591: // --- determine locations ---
592: TextUI mapper = c.getUI();
593: Rectangle p0 = mapper.modelToView(c, offs0);
594: Rectangle p1 = mapper.modelToView(c, offs1);
595:
596: // --- render ---
597: Color color = getColor();
598:
599: if (color == null) {
600: g.setColor(c.getSelectionColor());
601: } else {
602: g.setColor(color);
603: }
604: if (p0.y == p1.y) {
605: // same line, render a rectangle
606: Rectangle r = p0.union(p1);
607: drawUnderline(g, r.x, r.y, r.width, r.height,
608: thickness);
609: } else {
610: // different lines
611: int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
612: drawUnderline(g, p0.x, p0.y, p0ToMarginWidth,
613: p0.height, thickness);
614: if ((p0.y + p0.height) != p1.y) {
615: drawUnderline(g, alloc.x, p0.y + p0.height,
616: alloc.width, p1.y - (p0.y + p0.height),
617: thickness);
618: }
619: drawUnderline(g, alloc.x, p1.y, (p1.x - alloc.x),
620: p1.height, thickness);
621: }
622: } catch (BadLocationException e) {
623: // can't render
624: }
625: }
626:
627: // --- LayerPainter methods ----------------------------
628: /**
629: * Paints a portion of a highlight.
630: *
631: * @param g the graphics context
632: * @param offs0 the starting model offset >= 0
633: * @param offs1 the ending model offset >= offs1
634: * @param bounds the bounding box of the view, which is not
635: * necessarily the region to paint.
636: * @param c the editor
637: * @param view View painting for
638: * @return region drawing occured in
639: */
640: public Shape paintLayer(Graphics g, int offs0, int offs1,
641: Shape bounds, JTextComponent c, View view) {
642: Color color = getColor();
643:
644: if (color == null) {
645: g.setColor(c.getSelectionColor());
646: } else {
647: g.setColor(color);
648: }
649: if (offs0 == view.getStartOffset()
650: && offs1 == view.getEndOffset()) {
651: // Contained in view, can just use bounds.
652: Rectangle alloc;
653: if (bounds instanceof Rectangle) {
654: alloc = (Rectangle) bounds;
655: } else {
656: alloc = bounds.getBounds();
657: }
658: drawUnderline(g, alloc.x, alloc.y, alloc.width,
659: alloc.height, thickness);
660: return alloc;
661: } else {
662: // Should only render part of View.
663: try {
664: // --- determine locations ---
665: Shape shape = view.modelToView(offs0,
666: Position.Bias.Forward, offs1,
667: Position.Bias.Backward, bounds);
668: Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape
669: : shape.getBounds();
670: drawUnderline(g, r.x, r.y, r.width, r.height,
671: thickness);
672: return r;
673: } catch (BadLocationException e) {
674: // can't render
675: }
676: }
677: // Only if exception
678: return null;
679: }
680:
681: private Color color;
682: private int thickness;
683: }
684:
685: class HighlightInfo implements Highlighter.Highlight {
686:
687: public int getStartOffset() {
688: return p0.getOffset();
689: }
690:
691: public int getEndOffset() {
692: return p1.getOffset();
693: }
694:
695: public Highlighter.HighlightPainter getPainter() {
696: return painter;
697: }
698:
699: Position p0;
700: Position p1;
701: Highlighter.HighlightPainter painter;
702: }
703:
704: /**
705: * This class is a wrapper for the DefaultHighlightPainter that allows us to tell whether a highlight was
706: * requested by DrJava or by Swing (as in selected text).
707: */
708: public static class DrJavaHighlightPainter extends
709: DefaultHighlightPainter {
710:
711: public DrJavaHighlightPainter(Color c) {
712: super (c);
713: }
714:
715: }
716:
717: /**
718: * LayeredHighlightPainter is used when a drawsLayeredHighlights is
719: * true. It maintains a rectangle of the region to paint.
720: */
721: class LayeredHighlightInfo extends HighlightInfo {
722:
723: void union(Shape bounds) {
724: if (bounds == null)
725: return;
726:
727: Rectangle alloc;
728: if (bounds instanceof Rectangle) {
729: alloc = (Rectangle) bounds;
730: } else {
731: alloc = bounds.getBounds();
732: }
733: if (width == 0 || height == 0) {
734: x = alloc.x;
735: y = alloc.y;
736: width = alloc.width;
737: height = alloc.height;
738: } else {
739: width = Math.max(x + width, alloc.x + alloc.width);
740: height = Math.max(y + height, alloc.y + alloc.height);
741: x = Math.min(x, alloc.x);
742: width -= x;
743: y = Math.min(y, alloc.y);
744: height -= y;
745: }
746: }
747:
748: /** Restricts the region based on the receivers offsets and messages the painter to paint the region.*/
749: void paintLayeredHighlights(Graphics g, int p0, int p1,
750: Shape viewBounds, JTextComponent editor, View view) {
751: int start = getStartOffset();
752: int end = getEndOffset();
753: // Restrict the region to what we represent
754: p0 = Math.max(start, p0);
755: p1 = Math.min(end, p1);
756: // Paint the appropriate region using the painter and union
757: // the effected region with our bounds.
758: union(((LayeredHighlighter.LayerPainter) painter)
759: .paintLayer(g, p0, p1, viewBounds, editor, view));
760: }
761:
762: int x;
763: int y;
764: int width;
765: int height;
766: }
767:
768: /** This class invokes <code>mapper.damageRange</code> in EventDispatchThread. The only one instance per Highlighter
769: * is cretaed. When a number of ranges should be damaged it collects them into queue and damages them in consecutive
770: * order in <code>run</code> call.
771: */
772: class SafeDamager implements Runnable {
773: private Vector<Position> p0 = new Vector<Position>(10);
774: private Vector<Position> p1 = new Vector<Position>(10);
775: private Document lastDoc = null;
776:
777: /** Executes range(s) damage and cleans range queue. */
778: public synchronized void run() {
779: if (component != null) {
780: TextUI mapper = component.getUI();
781: if (mapper != null
782: && lastDoc == component.getDocument()) {
783: // the Document should be the same to properly display highlights
784: int len = p0.size();
785: for (int i = 0; i < len; i++) {
786: mapper.damageRange(component, p0.get(i)
787: .getOffset(), p1.get(i).getOffset());
788: }
789: }
790: }
791: p0.clear();
792: p1.clear();
793:
794: // release reference
795: lastDoc = null;
796: }
797:
798: /**
799: * Adds the range to be damaged into the range queue. If the
800: * range queue is empty (the first call or run() was already
801: * invoked) then adds this class instance into EventDispatch
802: * queue.
803: *
804: * The method also tracks if the current document changed or
805: * component is null. In this case it removes all ranges added
806: * before from range queue.
807: */
808: public synchronized void damageRange(Position pos0,
809: Position pos1) {
810: if (component == null) {
811: p0.clear();
812: lastDoc = null;
813: return;
814: }
815:
816: boolean addToQueue = p0.isEmpty();
817: Document curDoc = component.getDocument();
818: if (curDoc != lastDoc) {
819: if (!p0.isEmpty()) {
820: p0.clear();
821: p1.clear();
822: }
823: lastDoc = curDoc;
824: }
825: p0.add(pos0);
826: p1.add(pos1);
827:
828: if (addToQueue) {
829: SwingUtilities.invokeLater(this);
830: }
831: }
832: }
833: }
|