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  /***
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  		//cols = defaults.cols;
72  		//rows = defaults.rows;
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 		// We don't use yToLine() here because that method doesn't
387 		// return lines past the end of the document
388 		int height = fm.getHeight();
389 		int firstLine = textArea.getFirstLine();
390 		int firstInvalid = firstLine + clipRect.y / height;
391 		// Because the clipRect's height is usually an even multiple
392 		// of the font height, we subtract 1 from it, otherwise one
393 		// too many lines will always be painted.
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; //-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 		if( tabSize == 0 )
462 			initTabSize();
463 		
464 		int offset = 0; // -textArea.getHorizontalOffset();
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 	// package-private members
504 	int currentLineIndex;
505 	Token currentLineTokens;
506 	Segment currentLine;
507 
508 	// protected members
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 	//protected int cols;
524 	//protected int rows;
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 			// "inlined" min/max()
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 		// Hack!!! Since there is no fast way to get the character
704 		// from the bracket matching routine, we use ( since all
705 		// brackets probably have the same width anyway
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 }