001: /*
002: * TextAreaPainter.java - Paints the text area
003: * Copyright (C) 1999 Slava Pestov
004: *
005: * 08/05/2002 Cursor (caret) rendering fixed for JDK 1.4 (Anonymous)
006: *
007: * You may use and modify this package for any purpose. Redistribution is
008: * permitted, in both source and binary form, provided that this notice
009: * remains intact in all source distributions of this package.
010: */
011:
012: package org.syntax.jedit;
013:
014: import java.awt.Color;
015: import java.awt.Cursor;
016: import java.awt.Dimension;
017: import java.awt.Font;
018: import java.awt.FontMetrics;
019: import java.awt.Graphics;
020: import java.awt.Rectangle;
021: import java.awt.Toolkit;
022: import java.awt.event.MouseEvent;
023:
024: import javax.swing.JComponent;
025: import javax.swing.ToolTipManager;
026: import javax.swing.text.PlainDocument;
027: import javax.swing.text.Segment;
028: import javax.swing.text.TabExpander;
029: import javax.swing.text.Utilities;
030:
031: import org.syntax.jedit.tokenmarker.Token;
032: import org.syntax.jedit.tokenmarker.TokenMarker;
033:
034: import com.eviware.soapui.SoapUI;
035:
036: /**
037: * The text area repaint manager. It performs double buffering and paints
038: * lines of text.
039: * @author Slava Pestov
040: * @version $Id$
041: */
042: public class TextAreaPainter extends JComponent implements TabExpander {
043: private int areaWidth;
044: private int areaHeight;
045:
046: /**
047: * Creates a new repaint manager. This should be not be called
048: * directly.
049: */
050: public TextAreaPainter(JEditTextArea textArea,
051: TextAreaDefaults defaults) {
052: this .textArea = textArea;
053:
054: setAutoscrolls(true);
055: setDoubleBuffered(true);
056: setOpaque(true);
057: setBorder(null);
058:
059: ToolTipManager.sharedInstance().registerComponent(this );
060:
061: currentLine = new Segment();
062: currentLineIndex = -1;
063:
064: setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
065:
066: setFont(new Font("Monospaced", Font.PLAIN, 14));
067: setForeground(Color.black);
068: setBackground(Color.white);
069:
070: blockCaret = defaults.blockCaret;
071: styles = defaults.styles;
072: //cols = defaults.cols;
073: //rows = defaults.rows;
074: caretColor = defaults.caretColor;
075: selectionColor = defaults.selectionColor;
076: lineHighlightColor = defaults.lineHighlightColor;
077: lineHighlight = defaults.lineHighlight;
078: bracketHighlightColor = defaults.bracketHighlightColor;
079: bracketHighlight = defaults.bracketHighlight;
080: paintInvalid = defaults.paintInvalid;
081: eolMarkerColor = defaults.eolMarkerColor;
082: eolMarkers = defaults.eolMarkers;
083: }
084:
085: /**
086: * Returns if this component can be traversed by pressing the
087: * Tab key. This returns false.
088: */
089: public final boolean isManagingFocus() {
090: return false;
091: }
092:
093: /**
094: * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
095: * will be used to paint tokens with id = <i>n</i>.
096: * @see org.syntax.jedit.Token
097: */
098: public final SyntaxStyle[] getStyles() {
099: 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.syntax.jedit.Token
107: */
108: public final void setStyles(SyntaxStyle[] styles) {
109: this .styles = styles;
110: repaint();
111: }
112:
113: /**
114: * Returns the caret color.
115: */
116: public final Color getCaretColor() {
117: return caretColor;
118: }
119:
120: /**
121: * Sets the caret color.
122: * @param caretColor The caret color
123: */
124: public final void setCaretColor(Color caretColor) {
125: this .caretColor = caretColor;
126: invalidateSelectedLines();
127: }
128:
129: /**
130: * Returns the selection color.
131: */
132: public final Color getSelectionColor() {
133: return selectionColor;
134: }
135:
136: /**
137: * Sets the selection color.
138: * @param selectionColor The selection color
139: */
140: public final void setSelectionColor(Color selectionColor) {
141: this .selectionColor = selectionColor;
142: invalidateSelectedLines();
143: }
144:
145: /**
146: * Returns the line highlight color.
147: */
148: public final Color getLineHighlightColor() {
149: return lineHighlightColor;
150: }
151:
152: /**
153: * Sets the line highlight color.
154: * @param lineHighlightColor The line highlight color
155: */
156: public final void setLineHighlightColor(Color lineHighlightColor) {
157: this .lineHighlightColor = lineHighlightColor;
158: invalidateSelectedLines();
159: }
160:
161: /**
162: * Returns true if line highlight is enabled, false otherwise.
163: */
164: public final boolean isLineHighlightEnabled() {
165: return lineHighlight;
166: }
167:
168: /**
169: * Enables or disables current line highlighting.
170: * @param lineHighlight True if current line highlight should be enabled,
171: * false otherwise
172: */
173: public final void setLineHighlightEnabled(boolean lineHighlight) {
174: this .lineHighlight = lineHighlight;
175: invalidateSelectedLines();
176: }
177:
178: /**
179: * Returns the bracket highlight color.
180: */
181: public final Color getBracketHighlightColor() {
182: return bracketHighlightColor;
183: }
184:
185: /**
186: * Sets the bracket highlight color.
187: * @param bracketHighlightColor The bracket highlight color
188: */
189: public final void setBracketHighlightColor(
190: Color bracketHighlightColor) {
191: this .bracketHighlightColor = bracketHighlightColor;
192: invalidateLine(textArea.getBracketLine());
193: }
194:
195: /**
196: * Returns true if bracket highlighting is enabled, false otherwise.
197: * When bracket highlighting is enabled, the bracket matching the
198: * one before the caret (if any) is highlighted.
199: */
200: public final boolean isBracketHighlightEnabled() {
201: return bracketHighlight;
202: }
203:
204: /**
205: * Enables or disables bracket highlighting.
206: * When bracket highlighting is enabled, the bracket matching the
207: * one before the caret (if any) is highlighted.
208: * @param bracketHighlight True if bracket highlighting should be
209: * enabled, false otherwise
210: */
211: public final void setBracketHighlightEnabled(
212: boolean bracketHighlight) {
213: this .bracketHighlight = bracketHighlight;
214: invalidateLine(textArea.getBracketLine());
215: }
216:
217: /**
218: * Returns true if the caret should be drawn as a block, false otherwise.
219: */
220: public final boolean isBlockCaretEnabled() {
221: return blockCaret;
222: }
223:
224: /**
225: * Sets if the caret should be drawn as a block, false otherwise.
226: * @param blockCaret True if the caret should be drawn as a block,
227: * false otherwise.
228: */
229: public final void setBlockCaretEnabled(boolean blockCaret) {
230: this .blockCaret = blockCaret;
231: invalidateSelectedLines();
232: }
233:
234: /**
235: * Returns the EOL marker color.
236: */
237: public final Color getEOLMarkerColor() {
238: return eolMarkerColor;
239: }
240:
241: /**
242: * Sets the EOL marker color.
243: * @param eolMarkerColor The EOL marker color
244: */
245: public final void setEOLMarkerColor(Color eolMarkerColor) {
246: this .eolMarkerColor = eolMarkerColor;
247: repaint();
248: }
249:
250: /**
251: * Returns true if EOL markers are drawn, false otherwise.
252: */
253: public final boolean getEOLMarkersPainted() {
254: return eolMarkers;
255: }
256:
257: /**
258: * Sets if EOL markers are to be drawn.
259: * @param eolMarkers True if EOL markers should be drawn, false otherwise
260: */
261: public final void setEOLMarkersPainted(boolean eolMarkers) {
262: this .eolMarkers = eolMarkers;
263: repaint();
264: }
265:
266: /**
267: * Returns true if invalid lines are painted as red tildes (~),
268: * false otherwise.
269: */
270: public boolean getInvalidLinesPainted() {
271: return paintInvalid;
272: }
273:
274: /**
275: * Sets if invalid lines are to be painted as red tildes.
276: * @param paintInvalid True if invalid lines should be drawn, false otherwise
277: */
278: public void setInvalidLinesPainted(boolean paintInvalid) {
279: this .paintInvalid = paintInvalid;
280: }
281:
282: /**
283: * Adds a custom highlight painter.
284: * @param highlight The highlight
285: */
286: public void addCustomHighlight(Highlight highlight) {
287: highlight.init(textArea, highlights);
288: highlights = highlight;
289: }
290:
291: /**
292: * Highlight interface.
293: */
294: public interface Highlight {
295: /**
296: * Called after the highlight painter has been added.
297: * @param textArea The text area
298: * @param next The painter this one should delegate to
299: */
300: void init(JEditTextArea textArea, Highlight next);
301:
302: /**
303: * This should paint the highlight and delgate to the
304: * next highlight painter.
305: * @param gfx The graphics context
306: * @param line The line number
307: * @param y The y co-ordinate of the line
308: */
309: void paintHighlight(Graphics gfx, int line, int y);
310:
311: /**
312: * Returns the tool tip to display at the specified
313: * location. If this highlighter doesn't know what to
314: * display, it should delegate to the next highlight
315: * painter.
316: * @param evt The mouse event
317: */
318: String getToolTipText(MouseEvent evt);
319: }
320:
321: /**
322: * Returns the tool tip to display at the specified location.
323: * @param evt The mouse event
324: */
325: public String getToolTipText(MouseEvent evt) {
326: if (highlights != null)
327: return highlights.getToolTipText(evt);
328: else
329: return null;
330: }
331:
332: /**
333: * Returns the font metrics used by this component.
334: */
335: public FontMetrics getFontMetrics() {
336: return fm;
337: }
338:
339: /**
340: * Sets the font for this component. This is overridden to update the
341: * cached font metrics and to recalculate which lines are visible.
342: * @param font The font
343: */
344: public void setFont(Font font) {
345: super .setFont(font);
346: fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
347: textArea.recalculateVisibleLines();
348: }
349:
350: /**
351: * Repaints the text.
352: * @param g The graphics context
353: */
354: public void paint(Graphics gfx) {
355: Rectangle clipRect = gfx.getClipBounds();
356:
357: gfx.setColor(getBackground());
358: gfx.fillRect(clipRect.x, clipRect.y, clipRect.width,
359: clipRect.height);
360:
361: // We don't use yToLine() here because that method doesn't
362: // return lines past the end of the document
363: int height = fm.getHeight();
364: int firstLine = textArea.getFirstLine();
365: int firstInvalid = firstLine + clipRect.y / height;
366: // Because the clipRect's height is usually an even multiple
367: // of the font height, we subtract 1 from it, otherwise one
368: // too many lines will always be painted.
369: int lastInvalid = firstLine
370: + (clipRect.y + clipRect.height - 1) / height;
371:
372: try {
373: TokenMarker tokenMarker = textArea.getDocument()
374: .getTokenMarker();
375: int x = 0; //-textArea.getHorizontalOffset();
376:
377: for (int line = firstInvalid; line <= lastInvalid; line++) {
378: paintLine(gfx, tokenMarker, line, x);
379: }
380:
381: if (tokenMarker != null
382: && tokenMarker.isNextLineRequested()) {
383: int h = clipRect.y + clipRect.height;
384: repaint(0, h, getWidth(), getHeight() - h);
385: }
386: } catch (Exception e) {
387: System.err.println("Error repainting line" + " range {"
388: + firstInvalid + "," + lastInvalid + "}:");
389: SoapUI.logError(e);
390: }
391: }
392:
393: /**
394: * Marks a line as needing a repaint.
395: * @param line The line to invalidate
396: */
397: public final void invalidateLine(int line) {
398: repaint(0, textArea.lineToY(line) + fm.getMaxDescent()
399: + fm.getLeading(), getWidth(), fm.getHeight());
400: }
401:
402: /**
403: * Marks a range of lines as needing a repaint.
404: * @param firstLine The first line to invalidate
405: * @param lastLine The last line to invalidate
406: */
407: public final void invalidateLineRange(int firstLine, int lastLine) {
408: repaint(0, textArea.lineToY(firstLine) + fm.getMaxDescent()
409: + fm.getLeading(), getWidth(),
410: (lastLine - firstLine + 1) * fm.getHeight());
411: }
412:
413: /**
414: * Repaints the lines containing the selection.
415: */
416: public final void invalidateSelectedLines() {
417: invalidateLineRange(textArea.getSelectionStartLine(), textArea
418: .getSelectionEndLine());
419: }
420:
421: /**
422: * Implementation of TabExpander interface. Returns next tab stop after
423: * a specified point.
424: * @param x The x co-ordinate
425: * @param tabOffset Ignored
426: * @return The next tab stop after <i>x</i>
427: */
428: public float nextTabStop(float x, int tabOffset) {
429: if (tabSize == 0)
430: initTabSize();
431:
432: int offset = 0; // -textArea.getHorizontalOffset();
433: int ntabs = ((int) x - offset) / tabSize;
434: return (ntabs + 1) * tabSize + offset;
435: }
436:
437: private void initTabSize() {
438: tabSize = fm.charWidth(' ')
439: * ((Integer) textArea.getDocument().getProperty(
440: PlainDocument.tabSizeAttribute)).intValue();
441: if (tabSize == 0)
442: tabSize = fm.charWidth(' ') * 4;
443: }
444:
445: /**
446: * Returns the painter's preferred size.
447: */
448: public Dimension getPreferredSize() {
449: Dimension dim = new Dimension();
450: dim.width = fm.charWidth('w') * textArea.getMaxLineLength() + 5;
451: dim.height = fm.getHeight() * textArea.getLineCount() + 5;
452: return dim;
453: }
454:
455: /**
456: * Returns the painter's minimum size.
457: */
458: public Dimension getMinimumSize() {
459: return getPreferredSize();
460: }
461:
462: public Dimension getMaximumSize() {
463: return getPreferredSize();
464: }
465:
466: // package-private members
467: int currentLineIndex;
468: Token currentLineTokens;
469: Segment currentLine;
470:
471: // protected members
472: protected JEditTextArea textArea;
473:
474: protected SyntaxStyle[] styles;
475: protected Color caretColor;
476: protected Color selectionColor;
477: protected Color lineHighlightColor;
478: protected Color bracketHighlightColor;
479: protected Color eolMarkerColor;
480:
481: protected boolean blockCaret;
482: protected boolean lineHighlight;
483: protected boolean bracketHighlight;
484: protected boolean paintInvalid;
485: protected boolean eolMarkers;
486: //protected int cols;
487: //protected int rows;
488:
489: protected int tabSize;
490: protected FontMetrics fm;
491:
492: protected Highlight highlights;
493:
494: protected void paintLine(Graphics gfx, TokenMarker tokenMarker,
495: int line, int x) {
496: Font defaultFont = getFont();
497: Color defaultColor = getForeground();
498:
499: currentLineIndex = line;
500: int y = textArea.lineToY(line);
501:
502: int lineWidth = 0;
503:
504: if (line < 0 || line >= textArea.getLineCount()) {
505: if (paintInvalid) {
506: paintHighlight(gfx, line, y);
507: styles[Token.INVALID]
508: .setGraphicsFlags(gfx, defaultFont);
509: gfx.drawString("~", 0, y + fm.getHeight());
510: }
511: } else if (tokenMarker == null) {
512: lineWidth = paintPlainLine(gfx, line, defaultFont,
513: defaultColor, x, y);
514: } else {
515: lineWidth = paintSyntaxLine(gfx, tokenMarker, line,
516: defaultFont, defaultColor, x, y);
517: }
518:
519: if (lineWidth > areaWidth)
520: areaWidth = lineWidth;
521: }
522:
523: protected int paintPlainLine(Graphics gfx, int line,
524: Font defaultFont, Color defaultColor, int x, int y) {
525: paintHighlight(gfx, line, y);
526: textArea.getLineText(line, currentLine);
527:
528: gfx.setFont(defaultFont);
529: gfx.setColor(defaultColor);
530:
531: y += fm.getHeight();
532: x = Utilities.drawTabbedText(currentLine, x, y, gfx, this , 0);
533:
534: if (eolMarkers) {
535: gfx.setColor(eolMarkerColor);
536: gfx.drawString(".", x, y);
537: }
538:
539: return x;
540: }
541:
542: protected int paintSyntaxLine(Graphics gfx,
543: TokenMarker tokenMarker, int line, Font defaultFont,
544: Color defaultColor, int x, int y) {
545: textArea.getLineText(currentLineIndex, currentLine);
546: currentLineTokens = tokenMarker.markTokens(currentLine,
547: currentLineIndex);
548:
549: paintHighlight(gfx, line, y);
550:
551: gfx.setFont(defaultFont);
552: gfx.setColor(defaultColor);
553: y += fm.getHeight();
554: x = SyntaxUtilities.paintSyntaxLine(currentLine,
555: currentLineTokens, styles, this , gfx, x, y);
556:
557: if (eolMarkers) {
558: gfx.setColor(eolMarkerColor);
559: gfx.drawString(".", x, y);
560: }
561:
562: return x;
563: }
564:
565: protected void paintHighlight(Graphics gfx, int line, int y) {
566: if (line >= textArea.getSelectionStartLine()
567: && line <= textArea.getSelectionEndLine())
568: paintLineHighlight(gfx, line, y);
569:
570: if (highlights != null)
571: highlights.paintHighlight(gfx, line, y);
572:
573: if (bracketHighlight && line == textArea.getBracketLine())
574: paintBracketHighlight(gfx, line, y);
575:
576: if (line == textArea.getCaretLine())
577: paintCaret(gfx, line, y);
578: }
579:
580: protected void paintLineHighlight(Graphics gfx, int line, int y) {
581: int height = fm.getHeight();
582: y += fm.getLeading() + fm.getMaxDescent();
583:
584: int selectionStart = textArea.getSelectionStart();
585: int selectionEnd = textArea.getSelectionEnd();
586:
587: if (selectionStart == selectionEnd) {
588: if (lineHighlight) {
589: gfx.setColor(lineHighlightColor);
590: gfx.fillRect(0, y, getWidth(), height);
591: }
592: } else {
593: gfx.setColor(selectionColor);
594:
595: int selectionStartLine = textArea.getSelectionStartLine();
596: int selectionEndLine = textArea.getSelectionEndLine();
597: int lineStart = textArea.getLineStartOffset(line);
598:
599: int x1, x2;
600: if (textArea.isSelectionRectangular()) {
601: int lineLen = textArea.getLineLength(line);
602: x1 = textArea
603: ._offsetToX(
604: line,
605: Math
606: .min(
607: lineLen,
608: selectionStart
609: - textArea
610: .getLineStartOffset(selectionStartLine)));
611: x2 = textArea
612: ._offsetToX(
613: line,
614: Math
615: .min(
616: lineLen,
617: selectionEnd
618: - textArea
619: .getLineStartOffset(selectionEndLine)));
620: if (x1 == x2)
621: x2++;
622: } else if (selectionStartLine == selectionEndLine) {
623: x1 = textArea._offsetToX(line, selectionStart
624: - lineStart);
625: x2 = textArea
626: ._offsetToX(line, selectionEnd - lineStart);
627: } else if (line == selectionStartLine) {
628: x1 = textArea._offsetToX(line, selectionStart
629: - lineStart);
630: x2 = getWidth();
631: } else if (line == selectionEndLine) {
632: x1 = 0;
633: x2 = textArea
634: ._offsetToX(line, selectionEnd - lineStart);
635: } else {
636: x1 = 0;
637: x2 = getWidth();
638: }
639:
640: // "inlined" min/max()
641: gfx.fillRect(x1 > x2 ? x2 : x1, y, x1 > x2 ? (x1 - x2)
642: : (x2 - x1), height);
643: }
644:
645: }
646:
647: protected void paintBracketHighlight(Graphics gfx, int line, int y) {
648: int position = textArea.getBracketPosition();
649: if (position == -1)
650: return;
651: y += fm.getLeading() + fm.getMaxDescent();
652: int x = textArea._offsetToX(line, position);
653: gfx.setColor(bracketHighlightColor);
654: // Hack!!! Since there is no fast way to get the character
655: // from the bracket matching routine, we use ( since all
656: // brackets probably have the same width anyway
657: gfx.drawRect(x, y, fm.charWidth('(') - 1, fm.getHeight() - 1);
658: }
659:
660: protected void paintCaret(Graphics gfx, int line, int y) {
661: if (textArea.isCaretVisible()) {
662: int offset = textArea.getCaretPosition()
663: - textArea.getLineStartOffset(line);
664: int caretX = textArea._offsetToX(line, offset);
665: int caretWidth = ((blockCaret || textArea
666: .isOverwriteEnabled()) ? fm.charWidth('w') : 1);
667: y += fm.getLeading() + fm.getMaxDescent();
668: int height = fm.getHeight();
669:
670: gfx.setColor(caretColor);
671:
672: if (textArea.isOverwriteEnabled()) {
673: gfx.fillRect(caretX, y + height - 1, caretWidth, 1);
674: } else {
675: gfx.drawRect(caretX, y, caretWidth, height - 1);
676: }
677: }
678: }
679: }
|