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