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