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