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