Source code: jsource/gui/TextAreaPainter.java
1 package jsource.gui;
2
3
4 /**
5 * TextAreaPainter.java 12/17/02
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Library General Public License as published
9 * by the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Library General Public License for more details.
16 */
17 import javax.swing.ToolTipManager;
18 import javax.swing.text.*;
19 import javax.swing.JComponent;
20 import java.awt.event.MouseEvent;
21 import java.awt.*;
22 import jsource.syntax.*;
23 import jsource.syntax.tokenmarker.*;
24
25
26 /**
27 * <code>TextAreaPainter</code> is the repaint manager for a <code>JSEditor</code> object.
28 *
29 * Based on <code>TextAreaPainter</code> in jEdit syntax package.
30 *
31 * @author Panagiotis Plevrakis
32 * <br>Email: pplevrakis@hotmail.com
33 * <br>URL: http://jsource.sourceforge.net
34 */
35 public class TextAreaPainter extends JComponent implements TabExpander {
36
37 int currentLineIndex = 0;
38 Token currentLineTokens = null;
39 Segment currentLine = null;
40
41 protected JSEditor textArea = null;
42
43 protected SyntaxStyle[] styles = null;
44 protected Color caretColor = null;
45 protected Color selectionColor = null;
46 protected Color lineHighlightColor = null;
47 protected Color bracketHighlightColor = null;
48 protected Color eolMarkerColor = null;
49
50 protected boolean blockCaret = false;
51 protected boolean lineHighlight = false;
52 protected boolean bracketHighlight = false;
53 protected boolean paintInvalid = false;
54 protected boolean eolMarkers = false;
55 protected int cols = 0;
56 protected int rows = 0;
57
58 protected int tabSize = 0;
59 protected FontMetrics fm = null;
60
61 protected Highlight highlights = null;
62
63 /**
64 * Creates a new repaint manager. This should not be called directly.
65 */
66 public TextAreaPainter(JSEditor textArea, TextAreaDefaults defaults) {
67 this.textArea = textArea;
68
69 setAutoscrolls(true);
70 setDoubleBuffered(true);
71 setOpaque(true);
72
73 ToolTipManager.sharedInstance().registerComponent(this);
74
75 currentLine = new Segment();
76 currentLineIndex = -1;
77
78 setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
79
80 setFont(new Font("Courier", Font.PLAIN, 14));
81 setForeground(new Color(209, 209, 209));
82 setBackground(new Color(70, 70, 70));
83
84 blockCaret = defaults.blockCaret;
85 styles = defaults.styles;
86 cols = defaults.cols;
87 rows = defaults.rows;
88 caretColor = defaults.caretColor;
89 selectionColor = defaults.selectionColor;
90 lineHighlightColor = defaults.lineHighlightColor;
91 lineHighlight = defaults.lineHighlight;
92 bracketHighlightColor = defaults.bracketHighlightColor;
93 bracketHighlight = defaults.bracketHighlight;
94 paintInvalid = defaults.paintInvalid;
95 eolMarkerColor = defaults.eolMarkerColor;
96 eolMarkers = defaults.eolMarkers;
97 }
98
99 /**
100 * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
101 * will be used to paint tokens with id = <i>n</i>.
102 */
103 public final SyntaxStyle[] getStyles() {
104 return styles;
105 }
106
107 /**
108 * Sets the syntax styles used to paint colorized text. Entry <i>n</i>
109 * will be used to paint tokens with id = <i>n</i>.
110 * @param styles The syntax styles
111 */
112 public final void setStyles(SyntaxStyle[] styles) {
113 this.styles = styles;
114 repaint();
115 }
116
117 /**
118 * Returns the caret color.
119 */
120 public final Color getCaretColor() {
121 return caretColor;
122 }
123
124 /**
125 * Sets the caret color.
126 * @param caretColor The caret color
127 */
128 public final void setCaretColor(Color caretColor) {
129 this.caretColor = caretColor;
130 invalidateSelectedLines();
131 }
132
133 /**
134 * Returns the selection color.
135 */
136 public final Color getSelectionColor() {
137 return selectionColor;
138 }
139
140 /**
141 * Sets the selection color.
142 * @param selectionColor The selection color
143 */
144 public final void setSelectionColor(Color selectionColor) {
145 this.selectionColor = selectionColor;
146 invalidateSelectedLines();
147 }
148
149 /**
150 * Returns the line highlight color.
151 */
152 public final Color getLineHighlightColor() {
153 return lineHighlightColor;
154 }
155
156 /**
157 * Sets the line highlight color.
158 * @param lineHighlightColor The line highlight color
159 */
160 public final void setLineHighlightColor(Color lineHighlightColor) {
161 this.lineHighlightColor = lineHighlightColor;
162 invalidateSelectedLines();
163 }
164
165 /**
166 * Returns true if line highlight is enabled, false otherwise.
167 */
168 public final boolean isLineHighlightEnabled() {
169 return lineHighlight;
170 }
171
172 /**
173 * Enables or disables current line highlighting.
174 * @param lineHighlight true if current line highlight should be enabled,false otherwise
175 */
176 public final void setLineHighlightEnabled(boolean lineHighlight) {
177 this.lineHighlight = lineHighlight;
178 invalidateSelectedLines();
179 }
180
181 /**
182 * Returns the bracket highlight color.
183 */
184 public final Color getBracketHighlightColor() {
185 return bracketHighlightColor;
186 }
187
188 /**
189 * Sets the bracket highlight color.
190 * @param bracketHighlightColor The bracket highlight color
191 */
192 public final void setBracketHighlightColor(Color bracketHighlightColor) {
193 this.bracketHighlightColor = bracketHighlightColor;
194 invalidateLine(textArea.getBracketLine());
195 }
196
197 /**
198 * Returns true if bracket highlighting is enabled, false otherwise.
199 * When bracket highlighting is enabled, the bracket matching the
200 * one before the caret (if any) is highlighted.
201 */
202 public final boolean isBracketHighlightEnabled() {
203 return bracketHighlight;
204 }
205
206 /**
207 * Enables or disables bracket highlighting.
208 * When bracket highlighting is enabled, the bracket matching the
209 * one before the caret (if any) is highlighted.
210 * @param bracketHighlight True if bracket highlighting should be enabled, false otherwise
211 */
212 public final void setBracketHighlightEnabled(boolean bracketHighlight) {
213 this.bracketHighlight = bracketHighlight;
214 invalidateLine(textArea.getBracketLine());
215 }
216
217 /**
218 * Returns true if the caret should be drawn as a block, false otherwise.
219 */
220 public final boolean isBlockCaretEnabled() {
221 return blockCaret;
222 }
223
224 /**
225 * Sets if the caret should be drawn as a block, false otherwise.
226 * @param blockCaret True if the caret should be drawn as a block,false otherwise.
227 */
228 public final void setBlockCaretEnabled(boolean blockCaret) {
229 this.blockCaret = blockCaret;
230 invalidateSelectedLines();
231 }
232
233 /**
234 * Returns the EOL marker color.
235 */
236 public final Color getEOLMarkerColor() {
237 return eolMarkerColor;
238 }
239
240 /**
241 * Sets the EOL marker color.
242 * @param eolMarkerColor The EOL marker color
243 */
244 public final void setEOLMarkerColor(Color eolMarkerColor) {
245 this.eolMarkerColor = eolMarkerColor;
246 repaint();
247 }
248
249 /**
250 * Returns true if EOL markers are drawn, false otherwise.
251 */
252 public final boolean getEOLMarkersPainted() {
253 return eolMarkers;
254 }
255
256 /**
257 * Sets if EOL markers are to be drawn.
258 * @param eolMarkers True if EOL markers should be drawn, false otherwise
259 */
260 public final void setEOLMarkersPainted(boolean eolMarkers) {
261 this.eolMarkers = eolMarkers;
262 repaint();
263 }
264
265 /**
266 * Returns true if invalid lines are painted as red tildes (~),false otherwise.
267 */
268 public boolean getInvalidLinesPainted() {
269 return paintInvalid;
270 }
271
272 /**
273 * Sets if invalid lines are to be painted as red tildes.
274 * @param paintInvalid True if invalid lines should be drawn, false otherwise
275 */
276 public void setInvalidLinesPainted(boolean paintInvalid) {
277 this.paintInvalid = paintInvalid;
278 }
279
280 /**
281 * Adds a custom highlight painter.
282 * @param highlight The highlight
283 */
284 public void addCustomHighlight(Highlight highlight) {
285 highlight.init(textArea, highlights);
286 highlights = highlight;
287 }
288
289 /**
290 * Highlight interface.
291 */
292 public interface Highlight {
293
294 /**
295 * Called after the highlight painter has been added.
296 * @param textArea The text area
297 * @param next The painter this one should delegate to
298 */
299 void init(JSEditor textArea, Highlight next);
300
301 /**
302 * This should paint the highlight and delgate to the
303 * next highlight painter.
304 * @param gfx The graphics context
305 * @param line The line number
306 * @param y The y co-ordinate of the line
307 */
308 void paintHighlight(Graphics gfx, int line, int y);
309
310 /**
311 * Returns the tool tip to display at the specified
312 * location. If this highlighter doesn't know what to
313 * display, it should delegate to the next highlight
314 * painter.
315 * @param evt The mouse event
316 */
317 String getToolTipText(MouseEvent evt);
318 }
319
320 /**
321 * Returns the tool tip to display at the specified location.
322 * @param evt The mouse event
323 */
324 public String getToolTipText(MouseEvent evt) {
325 if (highlights != null)
326 return highlights.getToolTipText(evt);
327 else
328 return null;
329 }
330
331 /**
332 * Returns the font metrics used by this component.
333 */
334 public FontMetrics getFontMetrics() {
335 return fm;
336 }
337
338 /**
339 * Sets the font for this component. This is overridden to update the
340 * cached font metrics and to recalculate which lines are visible.
341 * @param font The font
342 */
343 public void setFont(Font font) {
344 super.setFont(font);
345 fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
346 textArea.recalculateVisibleLines();
347 }
348
349 /**
350 * Repaints the text.
351 * @param g The graphics context
352 */
353 public void paint(Graphics gfx) {
354 tabSize = fm.charWidth(' ') * ((Integer) textArea.getDocument().getProperty(
355 PlainDocument.tabSizeAttribute)).intValue();
356
357 Rectangle clipRect = gfx.getClipBounds();
358
359 gfx.setColor(getBackground());
360 gfx.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
361
362 // We don't use yToLine() here because that method doesn't
363 // return lines past the end of the document
364 int height = fm.getHeight();
365 int firstLine = textArea.getFirstLine();
366 int firstInvalid = firstLine + clipRect.y / height;
367 // Because the clipRect's height is usually an even multiple
368 // of the font height, we subtract 1 from it, otherwise one
369 // too many lines will always be painted.
370 int lastInvalid = firstLine
371 + (clipRect.y + clipRect.height - 1) / height;
372
373 try {
374 TokenMarker tokenMarker = textArea.getDocument().getTokenMarker();
375 int x = textArea.getHorizontalOffset();
376
377 for (int line = firstInvalid; line <= lastInvalid; line++) {
378 paintLine(gfx, tokenMarker, line, x);
379 }
380
381 if (tokenMarker != null && tokenMarker.isNextLineRequested()) {
382 int h = clipRect.y + clipRect.height;
383
384 repaint(0, h, getWidth(), getHeight() - h);
385 }
386 } catch (Exception e) {
387 System.err.println("Error repainting line" + " range {" + firstInvalid + "," + lastInvalid + "}:");
388 e.printStackTrace();
389 }
390 }
391
392 /**
393 * Marks a line as needing a repaint.
394 * @param line The line to invalidate
395 */
396 public final void invalidateLine(int line) {
397 repaint(0, textArea.lineToY(line) + fm.getMaxDescent() + fm.getLeading(),
398 getWidth(), fm.getHeight());
399 }
400
401 /**
402 * Marks a range of lines as needing a repaint.
403 * @param firstLine The first line to invalidate
404 * @param lastLine The last line to invalidate
405 */
406 public final void invalidateLineRange(int firstLine, int lastLine) {
407 repaint(0, textArea.lineToY(firstLine) + fm.getMaxDescent() + fm.getLeading(),
408 getWidth(), (lastLine - firstLine + 1) * fm.getHeight());
409 }
410
411 /**
412 * Repaints the lines containing the selection.
413 */
414 public final void invalidateSelectedLines() {
415 invalidateLineRange(textArea.getSelectionStartLine(),
416 textArea.getSelectionEndLine());
417 }
418
419 /**
420 * Implementation of TabExpander interface. Returns next tab stop after
421 * a specified point.
422 * @param x The x co-ordinate
423 * @param tabOffset Ignored
424 * @return The next tab stop after <i>x</i>
425 */
426 public float nextTabStop(float x, int tabOffset) {
427 int offset = textArea.getHorizontalOffset();
428 int ntabs = ((int) x - offset) / tabSize;
429
430 return (ntabs + 1) * tabSize + offset;
431 }
432
433 /**
434 * Returns the painter's preferred size.
435 */
436 public Dimension getPreferredSize() {
437 Dimension dim = new Dimension();
438
439 dim.width = fm.charWidth('w') * cols;
440 dim.height = fm.getHeight() * rows;
441 return dim;
442 }
443
444 /**
445 * Returns the painter's minimum size.
446 */
447 public Dimension getMinimumSize() {
448 return getPreferredSize();
449 }
450
451 protected void paintLine(Graphics gfx, TokenMarker tokenMarker,
452 int line, int x) {
453 Font defaultFont = getFont();
454 Color defaultColor = getForeground();
455
456 currentLineIndex = line;
457 int y = textArea.lineToY(line);
458
459 if (line < 0 || line >= textArea.getLineCount()) {
460 if (paintInvalid) {
461 paintHighlight(gfx, line, y);
462 styles[Token.INVALID].setGraphicsFlags(gfx, defaultFont);
463 gfx.drawString("~", 0, y + fm.getHeight());
464 }
465 } else if (tokenMarker == null) {
466 paintPlainLine(gfx, line, defaultFont, defaultColor, x, y);
467 } else {
468 paintSyntaxLine(gfx, tokenMarker, line, defaultFont,
469 defaultColor, x, y);
470 }
471 }
472
473 protected void paintPlainLine(Graphics gfx, int line, Font defaultFont,
474 Color defaultColor, int x, int y) {
475 paintHighlight(gfx, line, y);
476 textArea.getLineText(line, currentLine);
477
478 gfx.setFont(defaultFont);
479 gfx.setColor(defaultColor);
480
481 y += fm.getHeight();
482 x = Utilities.drawTabbedText(currentLine, x, y, gfx, this, 0);
483
484 if (eolMarkers) {
485 gfx.setColor(eolMarkerColor);
486 gfx.drawString(".", x, y);
487 }
488 }
489
490 protected void paintSyntaxLine(Graphics gfx, TokenMarker tokenMarker,
491 int line, Font defaultFont, Color defaultColor, int x, int y) {
492 textArea.getLineText(currentLineIndex, currentLine);
493 currentLineTokens = tokenMarker.markTokens(currentLine,
494 currentLineIndex);
495
496 paintHighlight(gfx, line, y);
497
498 gfx.setFont(defaultFont);
499 gfx.setColor(defaultColor);
500 y += fm.getHeight();
501 x = SyntaxUtilities.paintSyntaxLine(currentLine,
502 currentLineTokens, styles, this, gfx, x, y);
503
504 if (eolMarkers) {
505 gfx.setColor(eolMarkerColor);
506 gfx.drawString(".", x, y);
507 }
508 }
509
510 protected void paintHighlight(Graphics gfx, int line, int y) {
511 if (line >= textArea.getSelectionStartLine()
512 && line <= textArea.getSelectionEndLine())
513 paintLineHighlight(gfx, line, y);
514
515 if (highlights != null)
516 highlights.paintHighlight(gfx, line, y);
517
518 if (bracketHighlight && line == textArea.getBracketLine())
519 paintBracketHighlight(gfx, line, y);
520
521 if (line == textArea.getCaretLine())
522 paintCaret(gfx, line, y);
523 }
524
525 protected void paintLineHighlight(Graphics gfx, int line, int y) {
526 int height = fm.getHeight();
527
528 y += fm.getLeading() + fm.getMaxDescent();
529
530 int selectionStart = textArea.getSelectionStart();
531 int selectionEnd = textArea.getSelectionEnd();
532
533 if (selectionStart == selectionEnd) {
534 if (lineHighlight) {
535 gfx.setColor(lineHighlightColor);
536 gfx.fillRect(0, y, getWidth(), height);
537 }
538 } else {
539 gfx.setColor(selectionColor);
540
541 int selectionStartLine = textArea.getSelectionStartLine();
542 int selectionEndLine = textArea.getSelectionEndLine();
543 int lineStart = textArea.getLineStartOffset(line);
544
545 int x1, x2;
546
547 if (textArea.isSelectionRectangular()) {
548 int lineLen = textArea.getLineLength(line);
549
550 x1 = textArea._offsetToX(line, Math.min(lineLen,
551 selectionStart - textArea.getLineStartOffset(
552 selectionStartLine)));
553 x2 = textArea._offsetToX(line, Math.min(lineLen,
554 selectionEnd - textArea.getLineStartOffset(
555 selectionEndLine)));
556 if (x1 == x2)
557 x2++;
558 } else if (selectionStartLine == selectionEndLine) {
559 x1 = textArea._offsetToX(line,
560 selectionStart - lineStart);
561 x2 = textArea._offsetToX(line,
562 selectionEnd - lineStart);
563 } else if (line == selectionStartLine) {
564 x1 = textArea._offsetToX(line,
565 selectionStart - lineStart);
566 x2 = getWidth();
567 } else if (line == selectionEndLine) {
568 x1 = 0;
569 x2 = textArea._offsetToX(line,
570 selectionEnd - lineStart);
571 } else {
572 x1 = 0;
573 x2 = getWidth();
574 }
575
576 // "inlined" min/max()
577 gfx.fillRect(x1 > x2 ? x2 : x1, y, x1 > x2 ? (x1 - x2) : (x2 - x1), height);
578 }
579
580 }
581
582 protected void paintBracketHighlight(Graphics gfx, int line, int y) {
583 int position = textArea.getBracketPosition();
584
585 if (position == -1)
586 return;
587 y += fm.getLeading() + fm.getMaxDescent();
588 int x = textArea._offsetToX(line, position);
589
590 gfx.setColor(bracketHighlightColor);
591
592 // Hack!!! Since there is no fast way to get the character
593 // from the bracket matching routine, we use ( since all
594 // brackets probably have the same width anyway
595 gfx.drawRect(x, y, fm.charWidth('(') - 1, fm.getHeight() - 1);
596 }
597
598 protected void paintCaret(Graphics gfx, int line, int y) {
599 if (textArea.isCaretVisible()) {
600 int offset = textArea.getCaretPosition()
601 - textArea.getLineStartOffset(line);
602 int caretX = textArea._offsetToX(line, offset);
603 int caretWidth = ((blockCaret || textArea.isOverwriteEnabled())
604 ? fm.charWidth('w')
605 : 1);
606
607 y += fm.getLeading() + fm.getMaxDescent();
608 int height = fm.getHeight();
609
610 gfx.setColor(caretColor);
611
612 if (textArea.isOverwriteEnabled()) {
613 gfx.fillRect(caretX, y + height - 1,
614 caretWidth, 1);
615 } else {
616 gfx.drawRect(caretX, y, caretWidth, height - 1);
617 }
618 }
619 }
620 }