source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaHighlighter.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 12.9 KB
Line 
1/*
2 * 04/23/2009
3 *
4 * RSyntaxTextAreaHighlighter.java - Highlighter for RTextAreas.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rsyntaxtextarea;
10
11import java.awt.Color;
12import java.awt.Graphics;
13import java.awt.Insets;
14import java.awt.Rectangle;
15import java.awt.Shape;
16import java.util.ArrayList;
17import java.util.Iterator;
18import java.util.List;
19import javax.swing.plaf.TextUI;
20import javax.swing.plaf.basic.BasicTextUI.BasicHighlighter;
21import javax.swing.text.BadLocationException;
22import javax.swing.text.Document;
23import javax.swing.text.Element;
24import javax.swing.text.Highlighter;
25import javax.swing.text.JTextComponent;
26import javax.swing.text.LayeredHighlighter;
27import javax.swing.text.Position;
28import javax.swing.text.View;
29
30import org.fife.ui.rsyntaxtextarea.parser.Parser;
31import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
32import org.fife.ui.rtextarea.RTextArea;
33
34
35/**
36 * The highlighter implementation used by {@link RSyntaxTextArea}s. It knows to
37 * always paint "marked occurrences" highlights below selection highlights,
38 * and squiggle underline highlights above all other highlights.<p>
39 *
40 * Most of this code is copied from javax.swing.text.DefaultHighlighter;
41 * unfortunately, we cannot re-use much of it since it is package private.
42 *
43 * @author Robert Futrell
44 * @version 1.0
45 */
46public class RSyntaxTextAreaHighlighter extends BasicHighlighter {
47
48 /**
49 * The text component we are the highlighter for.
50 */
51 private RTextArea textArea;
52
53 /**
54 * Marked occurrences in the document (to be painted separately from
55 * other highlights).
56 */
57 private List markedOccurrences;
58
59 /**
60 * Highlights from document parsers. These should be painted "on top of"
61 * all other highlights to ensure they are always above the selection.
62 */
63 private List parserHighlights;
64
65 /**
66 * The default color used for parser notices when none is specified.
67 */
68 private static final Color DEFAULT_PARSER_NOTICE_COLOR = Color.RED;
69
70
71 /**
72 * Constructor.
73 */
74 public RSyntaxTextAreaHighlighter() {
75 markedOccurrences = new ArrayList();
76 parserHighlights = new ArrayList(0); // Often unused
77 }
78
79
80 /**
81 * Adds a special "marked occurrence" highlight.
82 *
83 * @param start
84 * @param end
85 * @param p
86 * @return A tag to reference the highlight later.
87 * @throws BadLocationException
88 * @see {@link #removeMarkOccurrencesHighlight(Object)}
89 */
90 Object addMarkedOccurrenceHighlight(int start, int end,
91 MarkOccurrencesHighlightPainter p) throws BadLocationException {
92 Document doc = textArea.getDocument();
93 TextUI mapper = textArea.getUI();
94 // Always layered highlights for marked occurrences.
95 HighlightInfo i = new LayeredHighlightInfo();
96 i.painter = p;
97 i.p0 = doc.createPosition(start);
98 // HACK: Use "end-1" to prevent chars the user types at the "end" of
99 // the highlight to be absorbed into the highlight (default Highlight
100 // behavior).
101 i.p1 = doc.createPosition(end-1);
102 markedOccurrences.add(i);
103 mapper.damageRange(textArea, start, end);
104 return i;
105 }
106
107
108 /**
109 * Adds a special "marked occurrence" highlight.
110 *
111 * @param notice The notice from a {@link Parser}.
112 * @return A tag with which to reference the highlight.
113 * @throws BadLocationException
114 * @see {@link #clearParserHighlights()}
115 */
116 Object addParserHighlight(ParserNotice notice, HighlightPainter p)
117 throws BadLocationException {
118
119 Document doc = textArea.getDocument();
120 TextUI mapper = textArea.getUI();
121
122 int start = notice.getOffset();
123 int end = 0;
124 if (start==-1) { // Could just define an invalid line number
125 int line = notice.getLine();
126 Element root = doc.getDefaultRootElement();
127 if (line>=0 && line<root.getElementCount()) {
128 Element elem = root.getElement(line);
129 start = elem.getStartOffset();
130 end = elem.getEndOffset();
131 }
132 }
133 else {
134 end = start + notice.getLength();
135 }
136
137 // Always layered highlights for parser highlights.
138 HighlightInfo i = new LayeredHighlightInfo();
139 i.painter = p;
140 i.p0 = doc.createPosition(start);
141 i.p1 = doc.createPosition(end);
142 i.notice = notice;//i.color = notice.getColor();
143
144 parserHighlights.add(i);
145 mapper.damageRange(textArea, start, end);
146 return i;
147
148 }
149
150
151 /**
152 * Removes all parser highlights.
153 *
154 * @see #addParserHighlight(ParserNotice, javax.swing.text.Highlighter.HighlightPainter)
155 */
156 void clearParserHighlights() {
157
158 for (int i=0; i<parserHighlights.size(); i++) {
159
160 Object tag = parserHighlights.get(i);
161
162 if (tag instanceof LayeredHighlightInfo) {
163 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
164 if (lhi.width > 0 && lhi.height > 0) {
165 textArea.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
166 }
167 }
168 else {
169 HighlightInfo info = (HighlightInfo) tag;
170 TextUI ui = textArea.getUI();
171 ui.damageRange(textArea, info.getStartOffset(),info.getEndOffset());
172 //safeDamageRange(info.p0, info.p1);
173 }
174
175 }
176
177 parserHighlights.clear();
178
179 }
180
181
182 /**
183 * Removes all of the highlights for a specific parser.
184 *
185 * @param parser The parser.
186 */
187 public void clearParserHighlights(Parser parser) {
188
189 for (Iterator i=parserHighlights.iterator(); i.hasNext(); ) {
190
191 HighlightInfo info = (HighlightInfo)i.next();
192
193 if (info.notice.getParser()==parser) {
194 if (info instanceof LayeredHighlightInfo) {
195 LayeredHighlightInfo lhi = (LayeredHighlightInfo)info;
196 if (lhi.width > 0 && lhi.height > 0) {
197 textArea.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
198 }
199 }
200 else {
201 TextUI ui = textArea.getUI();
202 ui.damageRange(textArea, info.getStartOffset(),info.getEndOffset());
203 //safeDamageRange(info.p0, info.p1);
204 }
205 i.remove();
206 }
207
208 }
209
210 }
211
212
213 /**
214 * {@inheritDoc}
215 */
216 public void deinstall(JTextComponent c) {
217 this.textArea = null;
218 markedOccurrences.clear();
219 parserHighlights.clear();
220 }
221
222
223 /**
224 * Returns a list of "marked occurrences" in the text area. If there are
225 * no marked occurrences, this will be an empty list.
226 *
227 * @return The list of marked occurrences.
228 */
229 public List getMarkedOccurrences() {
230 List list = new ArrayList(markedOccurrences.size());
231 for (Iterator i=markedOccurrences.iterator(); i.hasNext(); ) {
232 HighlightInfo info = (HighlightInfo)i.next();
233 int start = info.getStartOffset();
234 int end = info.getEndOffset() + 1; // HACK
235 DocumentRange range = new DocumentRangeImpl(start, end);
236 list.add(range);
237 }
238 return list;
239 }
240
241
242 /**
243 * {@inheritDoc}
244 */
245 public void install(JTextComponent c) {
246 super.install(c);
247 this.textArea = (RTextArea)c;
248 }
249
250
251 /**
252 * Renders the highlights.
253 *
254 * @param g the graphics context
255 */
256 public void paint(Graphics g) {
257 paintList(g, markedOccurrences);
258 super.paint(g);
259 paintList(g, parserHighlights);
260 }
261
262
263 private void paintList(Graphics g, List highlights) {
264
265 int len = highlights.size();
266
267 for (int i = 0; i < len; i++) {
268 HighlightInfo info = (HighlightInfo)highlights.get(i);
269 if (!(info instanceof LayeredHighlightInfo)) {
270 // Avoid allocating unless we need it.
271 Rectangle a = textArea.getBounds();
272 Insets insets = textArea.getInsets();
273 a.x = insets.left;
274 a.y = insets.top;
275 a.width -= insets.left + insets.right;
276 a.height -= insets.top + insets.bottom;
277 for (; i < len; i++) {
278 info = (HighlightInfo)markedOccurrences.get(i);
279 if (!(info instanceof LayeredHighlightInfo)) {
280 Color c = info.getColor();
281 Highlighter.HighlightPainter p = info.getPainter();
282 if (c!=null && p instanceof ChangeableColorHighlightPainter) {
283 ((ChangeableColorHighlightPainter)p).setColor(c);
284 }
285 p.paint(g, info.getStartOffset(), info.getEndOffset(),
286 a, textArea);
287 }
288 }
289 }
290 }
291
292 }
293
294
295 /**
296 * When leaf Views (such as LabelView) are rendering they should
297 * call into this method. If a highlight is in the given region it will
298 * be drawn immediately.
299 *
300 * @param g Graphics used to draw
301 * @param p0 starting offset of view
302 * @param p1 ending offset of view
303 * @param viewBounds Bounds of View
304 * @param editor JTextComponent
305 * @param view View instance being rendered
306 */
307 public void paintLayeredHighlights(Graphics g, int p0, int p1,
308 Shape viewBounds, JTextComponent editor, View view) {
309 paintListLayered(g, p0,p1, viewBounds, editor, view, markedOccurrences);
310 super.paintLayeredHighlights(g, p0, p1, viewBounds, editor, view);
311 paintListLayered(g, p0,p1, viewBounds, editor, view, parserHighlights);
312 }
313
314
315 private void paintListLayered(Graphics g, int p0, int p1, Shape viewBounds,
316 JTextComponent editor, View view, List highlights) {
317 for (int i=highlights.size()-1; i>=0; i--) {
318 Object tag = highlights.get(i);
319 if (tag instanceof LayeredHighlightInfo) {
320 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
321 int start = lhi.getStartOffset();
322 int end = lhi.getEndOffset();
323 if ((p0 < start && p1 > start) ||
324 (p0 >= start && p0 < end)) {
325 lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
326 editor, view);
327 }
328 }
329 }
330 }
331
332
333 private void removeListHighlight(List list, Object tag) {
334 if (tag instanceof LayeredHighlightInfo) {
335 LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
336 if (lhi.width > 0 && lhi.height > 0) {
337 textArea.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
338 }
339 }
340 else {
341 HighlightInfo info = (HighlightInfo) tag;
342 TextUI ui = textArea.getUI();
343 ui.damageRange(textArea, info.getStartOffset(),info.getEndOffset());
344 //safeDamageRange(info.p0, info.p1);
345 }
346 list.remove(tag);
347 }
348
349
350 /**
351 * Removes a "marked occurrences" highlight from the view.
352 *
353 * @param tag The reference to the highlight
354 * @see #addMarkedOccurrenceHighlight(int, int, MarkOccurrencesHighlightPainter)
355 */
356 void removeMarkOccurrencesHighlight(Object tag) {
357 removeListHighlight(markedOccurrences, tag);
358 }
359
360
361 /**
362 * Removes a parser highlight from this view.
363 *
364 * @param tag The reference to the highlight.
365 * @see #addParserHighlight(ParserNotice, javax.swing.text.Highlighter.HighlightPainter)
366 */
367 void removeParserHighlight(Object tag) {
368 removeListHighlight(parserHighlights, tag);
369 }
370
371
372 private static class DocumentRangeImpl implements DocumentRange {
373
374 private int startOffs;
375 private int endOffs;
376
377 public DocumentRangeImpl(int startOffs, int endOffs) {
378 this.startOffs = startOffs;
379 this.endOffs = endOffs;
380 }
381
382 public int getEndOffset() {
383 return endOffs;
384 }
385
386 public int getStartOffset() {
387 return startOffs;
388 }
389
390 }
391
392
393 private static class HighlightInfo implements Highlighter.Highlight {
394
395 private Position p0;
396 private Position p1;
397 protected Highlighter.HighlightPainter painter;
398 private ParserNotice notice;//Color color; // Used only by Parser highlights.
399
400 public Color getColor() {
401 //return color;
402 Color color = null;
403 if (notice!=null) {
404 color = notice.getColor();
405 if (color==null) {
406 color = DEFAULT_PARSER_NOTICE_COLOR;
407 }
408 }
409 return color;
410 }
411
412 public int getStartOffset() {
413 return p0.getOffset();
414 }
415
416 public int getEndOffset() {
417 return p1.getOffset();
418 }
419
420 public Highlighter.HighlightPainter getPainter() {
421 return painter;
422 }
423
424 }
425
426
427 private static class LayeredHighlightInfo extends HighlightInfo {
428
429 private int x;
430 private int y;
431 private int width;
432 private int height;
433
434 void union(Shape bounds) {
435 if (bounds == null) {
436 return;
437 }
438 Rectangle alloc = (bounds instanceof Rectangle) ?
439 (Rectangle)bounds : bounds.getBounds();
440 if (width == 0 || height == 0) {
441 x = alloc.x;
442 y = alloc.y;
443 width = alloc.width;
444 height = alloc.height;
445 }
446 else {
447 width = Math.max(x + width, alloc.x + alloc.width);
448 height = Math.max(y + height, alloc.y + alloc.height);
449 x = Math.min(x, alloc.x);
450 width -= x;
451 y = Math.min(y, alloc.y);
452 height -= y;
453 }
454 }
455
456 /**
457 * Restricts the region based on the receivers offsets and messages
458 * the painter to paint the region.
459 */
460 void paintLayeredHighlights(Graphics g, int p0, int p1,
461 Shape viewBounds, JTextComponent editor,
462 View view) {
463 int start = getStartOffset();
464 int end = getEndOffset();
465 // Restrict the region to what we represent
466 p0 = Math.max(start, p0);
467 p1 = Math.min(end, p1);
468 if (getColor()!=null &&
469 (painter instanceof ChangeableColorHighlightPainter)) {
470 ((ChangeableColorHighlightPainter)painter).setColor(getColor());
471 }
472 // Paint the appropriate region using the painter and union
473 // the effected region with our bounds.
474 union(((LayeredHighlighter.LayerPainter)painter).paintLayer
475 (g, p0, p1, viewBounds, editor, view));
476 }
477
478 }
479
480
481}
Note: See TracBrowser for help on using the repository browser.