View Javadoc

1   /*
2    * TextAreaPainter.java - Paints the text area
3    * Copyright (C) 1999 Slava Pestov
4    *
5    * 08/05/2002	Cursor (caret) rendering fixed for JDK 1.4 (Anonymous)
6    *
7    * You may use and modify this package for any purpose. Redistribution is
8    * permitted, in both source and binary form, provided that this notice
9    * remains intact in all source distributions of this package.
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  		// cols = defaults.cols;
74  		// rows = defaults.rows;
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 		// We don't use yToLine() here because that method doesn't
426 		// return lines past the end of the document
427 		int height = fm.getHeight();
428 		int firstLine = textArea.getFirstLine();
429 		int firstInvalid = firstLine + clipRect.y / height;
430 		// Because the clipRect's height is usually an even multiple
431 		// of the font height, we subtract 1 from it, otherwise one
432 		// too many lines will always be painted.
433 		int lastInvalid = firstLine + ( clipRect.y + clipRect.height - 1 ) / height;
434 
435 		try
436 		{
437 			TokenMarker tokenMarker = textArea.getDocument().getTokenMarker();
438 			int x = 0; // -textArea.getHorizontalOffset();
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; // -textArea.getHorizontalOffset();
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 	// package-private members
545 	int currentLineIndex;
546 	Token currentLineTokens;
547 	Segment currentLine;
548 
549 	// protected members
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 	// protected int cols;
565 	// protected int rows;
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 			// "inlined" min/max()
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 		// Hack!!! Since there is no fast way to get the character
732 		// from the bracket matching routine, we use ( since all
733 		// brackets probably have the same width anyway
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 }