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 private int areaWidth;
43 private int areaHeight;
44
45 /***
46 * Creates a new repaint manager. This should be not be called
47 * directly.
48 */
49 public TextAreaPainter(JEditTextArea textArea, TextAreaDefaults defaults)
50 {
51 this.textArea = textArea;
52
53 setAutoscrolls(true);
54 setDoubleBuffered(true);
55 setOpaque(true);
56 setBorder( null );
57
58 ToolTipManager.sharedInstance().registerComponent(this);
59
60 currentLine = new Segment();
61 currentLineIndex = -1;
62
63 setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
64
65 setFont(new Font("Monospaced",Font.PLAIN,14));
66 setForeground(Color.black);
67 setBackground(Color.white);
68
69 blockCaret = defaults.blockCaret;
70 styles = defaults.styles;
71
72
73 caretColor = defaults.caretColor;
74 selectionColor = defaults.selectionColor;
75 lineHighlightColor = defaults.lineHighlightColor;
76 lineHighlight = defaults.lineHighlight;
77 bracketHighlightColor = defaults.bracketHighlightColor;
78 bracketHighlight = defaults.bracketHighlight;
79 paintInvalid = defaults.paintInvalid;
80 eolMarkerColor = defaults.eolMarkerColor;
81 eolMarkers = defaults.eolMarkers;
82 }
83
84 /***
85 * Returns if this component can be traversed by pressing the
86 * Tab key. This returns false.
87 */
88 public final boolean isManagingFocus()
89 {
90 return false;
91 }
92
93 /***
94 * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
95 * will be used to paint tokens with id = <i>n</i>.
96 * @see org.syntax.jedit.Token
97 */
98 public final SyntaxStyle[] getStyles()
99 {
100 return styles;
101 }
102
103 /***
104 * Sets the syntax styles used to paint colorized text. Entry <i>n</i>
105 * will be used to paint tokens with id = <i>n</i>.
106 * @param styles The syntax styles
107 * @see org.syntax.jedit.Token
108 */
109 public final void setStyles(SyntaxStyle[] styles)
110 {
111 this.styles = styles;
112 repaint();
113 }
114
115 /***
116 * Returns the caret color.
117 */
118 public final Color getCaretColor()
119 {
120 return caretColor;
121 }
122
123 /***
124 * Sets the caret color.
125 * @param caretColor The caret color
126 */
127 public final void setCaretColor(Color caretColor)
128 {
129 this.caretColor = caretColor;
130 invalidateSelectedLines();
131 }
132
133 /***
134 * Returns the selection color.
135 */
136 public final Color getSelectionColor()
137 {
138 return selectionColor;
139 }
140
141 /***
142 * Sets the selection color.
143 * @param selectionColor The selection color
144 */
145 public final void setSelectionColor(Color selectionColor)
146 {
147 this.selectionColor = selectionColor;
148 invalidateSelectedLines();
149 }
150
151 /***
152 * Returns the line highlight color.
153 */
154 public final Color getLineHighlightColor()
155 {
156 return lineHighlightColor;
157 }
158
159 /***
160 * Sets the line highlight color.
161 * @param lineHighlightColor The line highlight color
162 */
163 public final void setLineHighlightColor(Color lineHighlightColor)
164 {
165 this.lineHighlightColor = lineHighlightColor;
166 invalidateSelectedLines();
167 }
168
169 /***
170 * Returns true if line highlight is enabled, false otherwise.
171 */
172 public final boolean isLineHighlightEnabled()
173 {
174 return lineHighlight;
175 }
176
177 /***
178 * Enables or disables current line highlighting.
179 * @param lineHighlight True if current line highlight should be enabled,
180 * false otherwise
181 */
182 public final void setLineHighlightEnabled(boolean lineHighlight)
183 {
184 this.lineHighlight = lineHighlight;
185 invalidateSelectedLines();
186 }
187
188 /***
189 * Returns the bracket highlight color.
190 */
191 public final Color getBracketHighlightColor()
192 {
193 return bracketHighlightColor;
194 }
195
196 /***
197 * Sets the bracket highlight color.
198 * @param bracketHighlightColor The bracket highlight color
199 */
200 public final void setBracketHighlightColor(Color bracketHighlightColor)
201 {
202 this.bracketHighlightColor = bracketHighlightColor;
203 invalidateLine(textArea.getBracketLine());
204 }
205
206 /***
207 * Returns true if bracket highlighting is enabled, false otherwise.
208 * When bracket highlighting is enabled, the bracket matching the
209 * one before the caret (if any) is highlighted.
210 */
211 public final boolean isBracketHighlightEnabled()
212 {
213 return bracketHighlight;
214 }
215
216 /***
217 * Enables or disables bracket highlighting.
218 * When bracket highlighting is enabled, the bracket matching the
219 * one before the caret (if any) is highlighted.
220 * @param bracketHighlight True if bracket highlighting should be
221 * enabled, false otherwise
222 */
223 public final void setBracketHighlightEnabled(boolean bracketHighlight)
224 {
225 this.bracketHighlight = bracketHighlight;
226 invalidateLine(textArea.getBracketLine());
227 }
228
229 /***
230 * Returns true if the caret should be drawn as a block, false otherwise.
231 */
232 public final boolean isBlockCaretEnabled()
233 {
234 return blockCaret;
235 }
236
237 /***
238 * Sets if the caret should be drawn as a block, false otherwise.
239 * @param blockCaret True if the caret should be drawn as a block,
240 * false otherwise.
241 */
242 public final void setBlockCaretEnabled(boolean blockCaret)
243 {
244 this.blockCaret = blockCaret;
245 invalidateSelectedLines();
246 }
247
248 /***
249 * Returns the EOL marker color.
250 */
251 public final Color getEOLMarkerColor()
252 {
253 return eolMarkerColor;
254 }
255
256 /***
257 * Sets the EOL marker color.
258 * @param eolMarkerColor The EOL marker color
259 */
260 public final void setEOLMarkerColor(Color eolMarkerColor)
261 {
262 this.eolMarkerColor = eolMarkerColor;
263 repaint();
264 }
265
266 /***
267 * Returns true if EOL markers are drawn, false otherwise.
268 */
269 public final boolean getEOLMarkersPainted()
270 {
271 return eolMarkers;
272 }
273
274 /***
275 * Sets if EOL markers are to be drawn.
276 * @param eolMarkers True if EOL markers should be drawn, false otherwise
277 */
278 public final void setEOLMarkersPainted(boolean eolMarkers)
279 {
280 this.eolMarkers = eolMarkers;
281 repaint();
282 }
283
284 /***
285 * Returns true if invalid lines are painted as red tildes (~),
286 * false otherwise.
287 */
288 public boolean getInvalidLinesPainted()
289 {
290 return paintInvalid;
291 }
292
293 /***
294 * Sets if invalid lines are to be painted as red tildes.
295 * @param paintInvalid True if invalid lines should be drawn, false otherwise
296 */
297 public void setInvalidLinesPainted(boolean paintInvalid)
298 {
299 this.paintInvalid = paintInvalid;
300 }
301
302 /***
303 * Adds a custom highlight painter.
304 * @param highlight The highlight
305 */
306 public void addCustomHighlight(Highlight highlight)
307 {
308 highlight.init(textArea,highlights);
309 highlights = highlight;
310 }
311
312 /***
313 * Highlight interface.
314 */
315 public interface Highlight
316 {
317 /***
318 * Called after the highlight painter has been added.
319 * @param textArea The text area
320 * @param next The painter this one should delegate to
321 */
322 void init(JEditTextArea textArea, Highlight next);
323
324 /***
325 * This should paint the highlight and delgate to the
326 * next highlight painter.
327 * @param gfx The graphics context
328 * @param line The line number
329 * @param y The y co-ordinate of the line
330 */
331 void paintHighlight(Graphics gfx, int line, int y);
332
333 /***
334 * Returns the tool tip to display at the specified
335 * location. If this highlighter doesn't know what to
336 * display, it should delegate to the next highlight
337 * painter.
338 * @param evt The mouse event
339 */
340 String getToolTipText(MouseEvent evt);
341 }
342
343 /***
344 * Returns the tool tip to display at the specified location.
345 * @param evt The mouse event
346 */
347 public String getToolTipText(MouseEvent evt)
348 {
349 if(highlights != null)
350 return highlights.getToolTipText(evt);
351 else
352 return null;
353 }
354
355 /***
356 * Returns the font metrics used by this component.
357 */
358 public FontMetrics getFontMetrics()
359 {
360 return fm;
361 }
362
363 /***
364 * Sets the font for this component. This is overridden to update the
365 * cached font metrics and to recalculate which lines are visible.
366 * @param font The font
367 */
368 public void setFont(Font font)
369 {
370 super.setFont(font);
371 fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
372 textArea.recalculateVisibleLines();
373 }
374
375 /***
376 * Repaints the text.
377 * @param g The graphics context
378 */
379 public void paint(Graphics gfx)
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 = 0;
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 if( tabSize == 0 )
462 initTabSize();
463
464 int offset = 0;
465 int ntabs = ((int)x - offset) / tabSize;
466 return (ntabs + 1) * tabSize + offset;
467 }
468
469 private void initTabSize()
470 {
471 tabSize = fm.charWidth(' ') * ((Integer)textArea
472 .getDocument().getProperty(
473 PlainDocument.tabSizeAttribute)).intValue();
474 if( tabSize == 0 )
475 tabSize = fm.charWidth(' ') * 4;
476 }
477
478 /***
479 * Returns the painter's preferred size.
480 */
481 public Dimension getPreferredSize()
482 {
483 Dimension dim = new Dimension();
484 dim.width = fm.charWidth('w') * textArea.getMaxLineLength() + 5;
485 dim.height = fm.getHeight() * textArea.getLineCount() + 5;
486 return dim;
487 }
488
489
490 /***
491 * Returns the painter's minimum size.
492 */
493 public Dimension getMinimumSize()
494 {
495 return getPreferredSize();
496 }
497
498 public Dimension getMaximumSize()
499 {
500 return getPreferredSize();
501 }
502
503
504 int currentLineIndex;
505 Token currentLineTokens;
506 Segment currentLine;
507
508
509 protected JEditTextArea textArea;
510
511 protected SyntaxStyle[] styles;
512 protected Color caretColor;
513 protected Color selectionColor;
514 protected Color lineHighlightColor;
515 protected Color bracketHighlightColor;
516 protected Color eolMarkerColor;
517
518 protected boolean blockCaret;
519 protected boolean lineHighlight;
520 protected boolean bracketHighlight;
521 protected boolean paintInvalid;
522 protected boolean eolMarkers;
523
524
525
526 protected int tabSize;
527 protected FontMetrics fm;
528
529 protected Highlight highlights;
530
531 protected void paintLine(Graphics gfx, TokenMarker tokenMarker,
532 int line, int x)
533 {
534 Font defaultFont = getFont();
535 Color defaultColor = getForeground();
536
537 currentLineIndex = line;
538 int y = textArea.lineToY(line);
539
540 int lineWidth = 0;
541
542 if(line < 0 || line >= textArea.getLineCount())
543 {
544 if(paintInvalid)
545 {
546 paintHighlight(gfx,line,y);
547 styles[Token.INVALID].setGraphicsFlags(gfx,defaultFont);
548 gfx.drawString("~",0,y + fm.getHeight());
549 }
550 }
551 else if(tokenMarker == null)
552 {
553 lineWidth = paintPlainLine(gfx,line,defaultFont,defaultColor,x,y);
554 }
555 else
556 {
557 lineWidth = paintSyntaxLine(gfx,tokenMarker,line,defaultFont,
558 defaultColor,x,y);
559 }
560
561 if( lineWidth > areaWidth )
562 areaWidth = lineWidth;
563 }
564
565 protected int paintPlainLine(Graphics gfx, int line, Font defaultFont,
566 Color defaultColor, int x, int y)
567 {
568 paintHighlight(gfx,line,y);
569 textArea.getLineText(line,currentLine);
570
571 gfx.setFont(defaultFont);
572 gfx.setColor(defaultColor);
573
574 y += fm.getHeight();
575 x = Utilities.drawTabbedText(currentLine,x,y,gfx,this,0);
576
577 if(eolMarkers)
578 {
579 gfx.setColor(eolMarkerColor);
580 gfx.drawString(".",x,y);
581 }
582
583 return x;
584 }
585
586 protected int paintSyntaxLine(Graphics gfx, TokenMarker tokenMarker,
587 int line, Font defaultFont, Color defaultColor, int x, int y)
588 {
589 textArea.getLineText(currentLineIndex,currentLine);
590 currentLineTokens = tokenMarker.markTokens(currentLine,
591 currentLineIndex);
592
593 paintHighlight(gfx,line,y);
594
595 gfx.setFont(defaultFont);
596 gfx.setColor(defaultColor);
597 y += fm.getHeight();
598 x = SyntaxUtilities.paintSyntaxLine(currentLine,
599 currentLineTokens,styles,this,gfx,x,y);
600
601 if(eolMarkers)
602 {
603 gfx.setColor(eolMarkerColor);
604 gfx.drawString(".",x,y);
605 }
606
607 return x;
608 }
609
610 protected void paintHighlight(Graphics gfx, int line, int y)
611 {
612 if(line >= textArea.getSelectionStartLine()
613 && line <= textArea.getSelectionEndLine())
614 paintLineHighlight(gfx,line,y);
615
616 if(highlights != null)
617 highlights.paintHighlight(gfx,line,y);
618
619 if(bracketHighlight && line == textArea.getBracketLine())
620 paintBracketHighlight(gfx,line,y);
621
622 if(line == textArea.getCaretLine())
623 paintCaret(gfx,line,y);
624 }
625
626 protected void paintLineHighlight(Graphics gfx, int line, int y)
627 {
628 int height = fm.getHeight();
629 y += fm.getLeading() + fm.getMaxDescent();
630
631 int selectionStart = textArea.getSelectionStart();
632 int selectionEnd = textArea.getSelectionEnd();
633
634 if(selectionStart == selectionEnd)
635 {
636 if(lineHighlight)
637 {
638 gfx.setColor(lineHighlightColor);
639 gfx.fillRect(0,y,getWidth(),height);
640 }
641 }
642 else
643 {
644 gfx.setColor(selectionColor);
645
646 int selectionStartLine = textArea.getSelectionStartLine();
647 int selectionEndLine = textArea.getSelectionEndLine();
648 int lineStart = textArea.getLineStartOffset(line);
649
650 int x1, x2;
651 if(textArea.isSelectionRectangular())
652 {
653 int lineLen = textArea.getLineLength(line);
654 x1 = textArea._offsetToX(line,Math.min(lineLen,
655 selectionStart - textArea.getLineStartOffset(
656 selectionStartLine)));
657 x2 = textArea._offsetToX(line,Math.min(lineLen,
658 selectionEnd - textArea.getLineStartOffset(
659 selectionEndLine)));
660 if(x1 == x2)
661 x2++;
662 }
663 else if(selectionStartLine == selectionEndLine)
664 {
665 x1 = textArea._offsetToX(line,
666 selectionStart - lineStart);
667 x2 = textArea._offsetToX(line,
668 selectionEnd - lineStart);
669 }
670 else if(line == selectionStartLine)
671 {
672 x1 = textArea._offsetToX(line,
673 selectionStart - lineStart);
674 x2 = getWidth();
675 }
676 else if(line == selectionEndLine)
677 {
678 x1 = 0;
679 x2 = textArea._offsetToX(line,
680 selectionEnd - lineStart);
681 }
682 else
683 {
684 x1 = 0;
685 x2 = getWidth();
686 }
687
688
689 gfx.fillRect(x1 > x2 ? x2 : x1,y,x1 > x2 ?
690 (x1 - x2) : (x2 - x1),height);
691 }
692
693 }
694
695 protected void paintBracketHighlight(Graphics gfx, int line, int y)
696 {
697 int position = textArea.getBracketPosition();
698 if(position == -1)
699 return;
700 y += fm.getLeading() + fm.getMaxDescent();
701 int x = textArea._offsetToX(line,position);
702 gfx.setColor(bracketHighlightColor);
703
704
705
706 gfx.drawRect(x,y,fm.charWidth('(') - 1,
707 fm.getHeight() - 1);
708 }
709
710 protected void paintCaret(Graphics gfx, int line, int y)
711 {
712 if(textArea.isCaretVisible())
713 {
714 int offset = textArea.getCaretPosition()
715 - textArea.getLineStartOffset(line);
716 int caretX = textArea._offsetToX(line,offset);
717 int caretWidth = ((blockCaret ||
718 textArea.isOverwriteEnabled()) ?
719 fm.charWidth('w') : 1);
720 y += fm.getLeading() + fm.getMaxDescent();
721 int height = fm.getHeight();
722
723 gfx.setColor(caretColor);
724
725 if(textArea.isOverwriteEnabled())
726 {
727 gfx.fillRect(caretX,y + height - 1,
728 caretWidth,1);
729 }
730 else
731 {
732 gfx.drawRect(caretX,y,caretWidth, height - 1);
733 }
734 }
735 }
736 }