View Javadoc

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