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