001: /*
002: * Copyright (C) 2005 - 2008 JasperSoft Corporation. All rights reserved.
003: * http://www.jaspersoft.com.
004: *
005: * Unless you have purchased a commercial license agreement from JasperSoft,
006: * the following license terms apply:
007: *
008: * This program is free software; you can redistribute it and/or modify
009: * it under the terms of the GNU General Public License version 2 as published by
010: * the Free Software Foundation.
011: *
012: * This program is distributed WITHOUT ANY WARRANTY; and without the
013: * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
014: * See the GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, see http://www.gnu.org/licenses/gpl.txt
018: * or write to:
019: *
020: * Free Software Foundation, Inc.,
021: * 59 Temple Place - Suite 330,
022: * Boston, MA USA 02111-1307
023: *
024: *
025: *
026: *
027: * TextAreaPainter.java
028: *
029: */
030:
031: package org.syntax.jedit;
032:
033: import org.syntax.jedit.tokenmarker.*;
034: import javax.swing.ToolTipManager;
035: import javax.swing.text.*;
036: import javax.swing.JComponent;
037: import java.awt.event.MouseEvent;
038: import java.awt.*;
039:
040: /**
041: * The text area repaint manager. It performs double buffering and paints
042: * lines of text.
043: * @author Slava Pestov
044: * @version $Id: TextAreaPainter.java 1167 2008-01-15 18:49:05Z gtoffoli $
045: */
046: public class TextAreaPainter extends JComponent implements TabExpander {
047: /**
048: * Creates a new repaint manager. This should be not be called
049: * directly.
050: */
051: public TextAreaPainter(JEditTextArea textArea,
052: TextAreaDefaults defaults) {
053: this .textArea = textArea;
054:
055: setAutoscrolls(true);
056: setDoubleBuffered(true);
057: setOpaque(true);
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: tabSize = fm.charWidth(' ')
356: * ((Integer) textArea.getDocument().getProperty(
357: PlainDocument.tabSizeAttribute)).intValue();
358:
359: Rectangle clipRect = gfx.getClipBounds();
360:
361: gfx.setColor(getBackground());
362: gfx.fillRect(clipRect.x, clipRect.y, clipRect.width,
363: clipRect.height);
364:
365: // We don't use yToLine() here because that method doesn't
366: // return lines past the end of the document
367: int height = fm.getHeight();
368: int firstLine = textArea.getFirstLine();
369: int firstInvalid = firstLine + clipRect.y / height;
370: // Because the clipRect's height is usually an even multiple
371: // of the font height, we subtract 1 from it, otherwise one
372: // too many lines will always be painted.
373: int lastInvalid = firstLine
374: + (clipRect.y + clipRect.height - 1) / height;
375:
376: try {
377: TokenMarker tokenMarker = textArea.getDocument()
378: .getTokenMarker();
379: int x = textArea.getHorizontalOffset();
380:
381: for (int line = firstInvalid; line <= lastInvalid; line++) {
382: paintLine(gfx, tokenMarker, line, x);
383: }
384:
385: if (tokenMarker != null
386: && tokenMarker.isNextLineRequested()) {
387: int h = clipRect.y + clipRect.height;
388: repaint(0, h, getWidth(), getHeight() - h);
389: }
390: } catch (Exception e) {
391: System.err.println("Error repainting line" + " range {"
392: + firstInvalid + "," + lastInvalid + "}:");
393: e.printStackTrace();
394: }
395: }
396:
397: /**
398: * Marks a line as needing a repaint.
399: * @param line The line to invalidate
400: */
401: public final void invalidateLine(int line) {
402: repaint(0, textArea.lineToY(line) + fm.getMaxDescent()
403: + fm.getLeading(), getWidth(), fm.getHeight());
404: }
405:
406: /**
407: * Marks a range of lines as needing a repaint.
408: * @param firstLine The first line to invalidate
409: * @param lastLine The last line to invalidate
410: */
411: public final void invalidateLineRange(int firstLine, int lastLine) {
412: repaint(0, textArea.lineToY(firstLine) + fm.getMaxDescent()
413: + fm.getLeading(), getWidth(),
414: (lastLine - firstLine + 1) * fm.getHeight());
415: }
416:
417: /**
418: * Repaints the lines containing the selection.
419: */
420: public final void invalidateSelectedLines() {
421: invalidateLineRange(textArea.getSelectionStartLine(), textArea
422: .getSelectionEndLine());
423: }
424:
425: /**
426: * Implementation of TabExpander interface. Returns next tab stop after
427: * a specified point.
428: * @param x The x co-ordinate
429: * @param tabOffset Ignored
430: * @return The next tab stop after <i>x</i>
431: */
432: public float nextTabStop(float x, int tabOffset) {
433: int offset = textArea.getHorizontalOffset();
434: int ntabs = 0;
435: if (tabSize > 0)
436: ntabs = ((int) x - offset) / tabSize;
437: return (ntabs + 1) * tabSize + offset;
438: }
439:
440: /**
441: * Returns the painter's preferred size.
442: */
443: public Dimension getPreferredSize() {
444: Dimension dim = new Dimension();
445: dim.width = fm.charWidth('w') * cols;
446: dim.height = fm.getHeight() * rows;
447: return dim;
448: }
449:
450: /**
451: * Returns the painter's minimum size.
452: */
453: public Dimension getMinimumSize() {
454: return getPreferredSize();
455: }
456:
457: // package-private members
458: int currentLineIndex;
459: Token currentLineTokens;
460: Segment currentLine;
461:
462: // protected members
463: protected JEditTextArea textArea;
464:
465: protected SyntaxStyle[] styles;
466: protected Color caretColor;
467: protected Color selectionColor;
468: protected Color lineHighlightColor;
469: protected Color bracketHighlightColor;
470: protected Color eolMarkerColor;
471:
472: protected boolean blockCaret;
473: protected boolean lineHighlight;
474: protected boolean bracketHighlight;
475: protected boolean paintInvalid;
476: protected boolean eolMarkers;
477: protected int cols;
478: protected int rows;
479:
480: protected int tabSize;
481: protected FontMetrics fm;
482:
483: protected Highlight highlights;
484:
485: protected void paintLine(Graphics gfx, TokenMarker tokenMarker,
486: int line, int x) {
487: Font defaultFont = getFont();
488: Color defaultColor = getForeground();
489:
490: currentLineIndex = line;
491: int y = textArea.lineToY(line);
492:
493: if (line < 0 || line >= textArea.getLineCount()) {
494: if (paintInvalid) {
495: paintHighlight(gfx, line, y);
496: styles[Token.INVALID]
497: .setGraphicsFlags(gfx, defaultFont);
498: gfx.drawString("~", 0, y + fm.getHeight());
499: }
500: } else if (tokenMarker == null) {
501: paintPlainLine(gfx, line, defaultFont, defaultColor, x, y);
502: } else {
503: paintSyntaxLine(gfx, tokenMarker, line, defaultFont,
504: defaultColor, x, y);
505: }
506: }
507:
508: protected void paintPlainLine(Graphics gfx, int line,
509: Font defaultFont, Color defaultColor, int x, int y) {
510: paintHighlight(gfx, line, y);
511: textArea.getLineText(line, currentLine);
512:
513: gfx.setFont(defaultFont);
514: gfx.setColor(defaultColor);
515:
516: y += fm.getHeight();
517: x = Utilities.drawTabbedText(currentLine, x, y, gfx, this , 0);
518:
519: if (eolMarkers) {
520: gfx.setColor(eolMarkerColor);
521: gfx.drawString(".", x, y);
522: }
523: }
524:
525: protected void paintSyntaxLine(Graphics gfx,
526: TokenMarker tokenMarker, int line, Font defaultFont,
527: Color defaultColor, int x, int y) {
528: textArea.getLineText(currentLineIndex, currentLine);
529: currentLineTokens = tokenMarker.markTokens(currentLine,
530: currentLineIndex);
531:
532: paintHighlight(gfx, line, y);
533:
534: gfx.setFont(defaultFont);
535: gfx.setColor(defaultColor);
536: y += fm.getHeight();
537: x = SyntaxUtilities.paintSyntaxLine(currentLine,
538: currentLineTokens, styles, this , gfx, x, y);
539:
540: if (eolMarkers) {
541: gfx.setColor(eolMarkerColor);
542: gfx.drawString(".", x, y);
543: }
544: }
545:
546: protected void paintHighlight(Graphics gfx, int line, int y) {
547: if (line >= textArea.getSelectionStartLine()
548: && line <= textArea.getSelectionEndLine())
549: paintLineHighlight(gfx, line, y);
550:
551: if (highlights != null)
552: highlights.paintHighlight(gfx, line, y);
553:
554: if (bracketHighlight && line == textArea.getBracketLine())
555: paintBracketHighlight(gfx, line, y);
556:
557: if (line == textArea.getCaretLine())
558: paintCaret(gfx, line, y);
559: }
560:
561: protected void paintLineHighlight(Graphics gfx, int line, int y) {
562: int height = fm.getHeight();
563: y += fm.getLeading() + fm.getMaxDescent();
564:
565: int selectionStart = textArea.getSelectionStart();
566: int selectionEnd = textArea.getSelectionEnd();
567:
568: if (selectionStart == selectionEnd) {
569: if (lineHighlight) {
570: gfx.setColor(lineHighlightColor);
571: gfx.fillRect(0, y, getWidth(), height);
572: }
573: } else {
574: gfx.setColor(selectionColor);
575:
576: int selectionStartLine = textArea.getSelectionStartLine();
577: int selectionEndLine = textArea.getSelectionEndLine();
578: int lineStart = textArea.getLineStartOffset(line);
579:
580: int x1, x2;
581: if (textArea.isSelectionRectangular()) {
582: int lineLen = textArea.getLineLength(line);
583: x1 = textArea
584: ._offsetToX(
585: line,
586: Math
587: .min(
588: lineLen,
589: selectionStart
590: - textArea
591: .getLineStartOffset(selectionStartLine)));
592: x2 = textArea
593: ._offsetToX(
594: line,
595: Math
596: .min(
597: lineLen,
598: selectionEnd
599: - textArea
600: .getLineStartOffset(selectionEndLine)));
601: if (x1 == x2)
602: x2++;
603: } else if (selectionStartLine == selectionEndLine) {
604: x1 = textArea._offsetToX(line, selectionStart
605: - lineStart);
606: x2 = textArea
607: ._offsetToX(line, selectionEnd - lineStart);
608: } else if (line == selectionStartLine) {
609: x1 = textArea._offsetToX(line, selectionStart
610: - lineStart);
611: x2 = getWidth();
612: } else if (line == selectionEndLine) {
613: x1 = 0;
614: x2 = textArea
615: ._offsetToX(line, selectionEnd - lineStart);
616: } else {
617: x1 = 0;
618: x2 = getWidth();
619: }
620:
621: // "inlined" min/max()
622: gfx.fillRect(x1 > x2 ? x2 : x1, y, x1 > x2 ? (x1 - x2)
623: : (x2 - x1), height);
624: }
625:
626: }
627:
628: protected void paintBracketHighlight(Graphics gfx, int line, int y) {
629: int position = textArea.getBracketPosition();
630: if (position == -1)
631: return;
632: y += fm.getLeading() + fm.getMaxDescent();
633: int x = textArea._offsetToX(line, position);
634: gfx.setColor(bracketHighlightColor);
635: // Hack!!! Since there is no fast way to get the character
636: // from the bracket matching routine, we use ( since all
637: // brackets probably have the same width anyway
638: gfx.drawRect(x, y, fm.charWidth('(') - 1, fm.getHeight() - 1);
639: }
640:
641: protected void paintCaret(Graphics gfx, int line, int y) {
642: if (textArea.isCaretVisible()) {
643: int offset = textArea.getCaretPosition()
644: - textArea.getLineStartOffset(line);
645: int caretX = textArea._offsetToX(line, offset);
646: int caretWidth = ((blockCaret || textArea
647: .isOverwriteEnabled()) ? fm.charWidth('w') : 1);
648: y += fm.getLeading() + fm.getMaxDescent();
649: int height = fm.getHeight();
650:
651: gfx.setColor(caretColor);
652:
653: if (textArea.isOverwriteEnabled()) {
654: gfx.fillRect(caretX, y + height - 1, caretWidth, 1);
655: } else {
656: gfx.drawRect(caretX, y, caretWidth, height - 1);
657: }
658: }
659: }
660: }
|