/* * TextAreaPainter.java - Paints the text area * :tabSize=4:indentSize=4:noTabs=false:encoding=UTF-8: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1999-2013 Slava Pestov, Shlomy Reinstein * Matthieu Casanova, Kazutoshi Satoda, Alan Ezust, Dale Anson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.textarea; //{{{ Imports import javax.swing.text.*; import javax.swing.JComponent; import java.awt.event.MouseEvent; import java.awt.font.*; import java.awt.*; import java.util.*; import org.gjt.sp.jedit.buffer.IndentFoldHandler; import org.gjt.sp.jedit.buffer.JEditBuffer; import org.gjt.sp.jedit.syntax.Chunk; import org.gjt.sp.jedit.syntax.DefaultTokenHandler; import org.gjt.sp.jedit.syntax.SyntaxStyle; import org.gjt.sp.jedit.syntax.Token; import org.gjt.sp.jedit.Debug; import org.gjt.sp.util.Log; //}}} /** * The text area painter is the component responsible for displaying the * text of the current buffer. The only methods in this class that should * be called by plugins are those for adding and removing * text area extensions. * * @see #addExtension(TextAreaExtension) * @see #addExtension(int,TextAreaExtension) * @see #removeExtension(TextAreaExtension) * @see TextAreaExtension * @see TextArea * * @author Slava Pestov * @version $Id$ */ public class TextAreaPainter extends JComponent implements TabExpander { //{{{ Layers /** * The lowest possible layer. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre4 */ public static final int LOWEST_LAYER = Integer.MIN_VALUE; /** * Below selection layer. The JDiff plugin will use this. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre4 */ public static final int BACKGROUND_LAYER = -60; /** * The line highlight and collapsed fold highlight layer. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre7 */ public static final int LINE_BACKGROUND_LAYER = -50; /** * Below selection layer. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre4 */ public static final int BELOW_SELECTION_LAYER = -40; /** * Selection layer. Most extensions will be above this layer, but some * (eg, JDiff) will want to be below the selection. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre4 */ public static final int SELECTION_LAYER = -30; /** * Wrap guide layer. Most extensions will be above this layer. * @since jEdit 4.0pre4 */ public static final int WRAP_GUIDE_LAYER = -20; /** * Below most extensions layer. * @see #addExtension(int,TextAreaExtension) * @since jEdit 4.0pre4 */ public static final int BELOW_MOST_EXTENSIONS_LAYER = -10; /** * Default extension layer. This is above the wrap guide but below the * structure highlight. * @since jEdit 4.0pre4 */ public static final int DEFAULT_LAYER = 0; /** * Block caret layer. Most extensions will be below this layer. * @since jEdit 4.2pre1 */ public static final int BLOCK_CARET_LAYER = 50; /** * Bracket highlight layer. Most extensions will be below this layer. * @since jEdit 4.0pre4 */ public static final int BRACKET_HIGHLIGHT_LAYER = 100; /** * Text layer. Most extensions will be below this layer. * @since jEdit 4.2pre1 */ public static final int TEXT_LAYER = 200; /** * Caret layer. Most extensions will be below this layer. * @since jEdit 4.2pre1 */ public static final int CARET_LAYER = 300; /** * Highest possible layer. * @since jEdit 4.0pre4 */ public static final int HIGHEST_LAYER = Integer.MAX_VALUE; //}}} //{{{ setBounds() method /** * It is a bad idea to override this, but we need to get the component * event before the first repaint. */ @Override public void setBounds(int x, int y, int width, int height) { // for some reason, the height is always off by one when the bounds have // not changed at all, hence the +1 on the height check. Without the +1, // everything gets reset every time, which leads to other problems. if(x == getX() && y == getY() && width == getWidth() && (height == getHeight() || height == getHeight() + 1)) { return; } super.setBounds(x,y,width,height); textArea.recalculateVisibleLines(); if(!textArea.getBuffer().isLoading()) textArea.recalculateLastPhysicalLine(); textArea.propertiesChanged(); textArea.updateMaxHorizontalScrollWidth(); textArea.scrollBarsInitialized = true; } //}}} //{{{ addNotify() method @Override public void addNotify() { super.addNotify(); hiddenCursor = getToolkit().createCustomCursor( getGraphicsConfiguration() .createCompatibleImage(16,16, Transparency.BITMASK), new Point(0,0),"Hidden"); } //}}} //{{{ setCursor() method /** * Change the mouse cursor. * If the cursor is hiddenCursor or TEXT_CURSOR, it is the default cursor and the cursor will not disappear * anymore while typing until {@link #resetCursor()} is called. * @param cursor the new cursor * @since jEdit 4.4pre1 */ public void setCursor(Cursor cursor) { defaultCursor = cursor == hiddenCursor || cursor.getType() == Cursor.TEXT_CURSOR; super.setCursor(cursor); } //}}} //{{{ setCursor() method /** * Reset the cursor to it's default value. * @since jEdit 4.4pre1 */ public void resetCursor() { defaultCursor = true; } //}}} //{{{ showCursor() method /** * Show the cursor if it is the default cursor */ void showCursor() { if (defaultCursor) { setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } } //}}} //{{{ hideCursor() method /** * Hide the cursor if it is the default cursor */ void hideCursor() { if (defaultCursor) { setCursor(hiddenCursor); } } //}}} //{{{ getFocusTraversalKeysEnabled() method /** * Makes the tab key work in Java 1.4. * @since jEdit 3.2pre4 */ @Override public boolean getFocusTraversalKeysEnabled() { return false; } //}}} //{{{ Getters and setters //{{{ getStyles() method /** * Returns the syntax styles used to paint colorized text. Entry <i>n</i> * will be used to paint tokens with id = <i>n</i>. * @return an array of SyntaxStyles * @see org.gjt.sp.jedit.syntax.Token */ public final SyntaxStyle[] getStyles() { return styles; } //}}} //{{{ setStyles() method /** * Sets the syntax styles used to paint colorized text. Entry <i>n</i> * will be used to paint tokens with id = <i>n</i>. * @param styles The syntax styles * @see org.gjt.sp.jedit.syntax.Token */ public final void setStyles(SyntaxStyle[] styles) { this.styles = styles; styles[Token.NULL] = new SyntaxStyle(getForeground(),null,getFont()); textArea.chunkCache.reset(); repaint(); } //}}} //{{{ getCaretColor() method /** * Returns the caret color. */ public final Color getCaretColor() { return caretColor; } //}}} //{{{ setCaretColor() method /** * Sets the caret color. * @param caretColor The caret color */ public final void setCaretColor(Color caretColor) { this.caretColor = caretColor; if(textArea.getBuffer() != null) textArea.invalidateLine(textArea.getCaretLine()); } //}}} //{{{ getSelectionColor() method /** * Returns the selection color. */ public final Color getSelectionColor() { return selectionColor; } //}}} //{{{ setSelectionColor() method /** * Sets the selection color. * @param selectionColor The selection color */ public final void setSelectionColor(Color selectionColor) { this.selectionColor = selectionColor; textArea.repaint(); } //}}} //{{{ getMultipleSelectionColor() method /** * Returns the multiple selection color. * @since jEdit 4.2pre1 */ public final Color getMultipleSelectionColor() { return multipleSelectionColor; } //}}} //{{{ setMultipleSelectionColor() method /** * Sets the multiple selection color. * @param multipleSelectionColor The multiple selection color * @since jEdit 4.2pre1 */ public final void setMultipleSelectionColor(Color multipleSelectionColor) { this.multipleSelectionColor = multipleSelectionColor; textArea.repaint(); } //}}} //{{{ getLineHighlightColor() method /** * Returns the line highlight color. */ public final Color getLineHighlightColor() { return lineHighlightColor; } //}}} //{{{ setLineHighlightColor() method /** * Sets the line highlight color. * @param lineHighlightColor The line highlight color */ public final void setLineHighlightColor(Color lineHighlightColor) { this.lineHighlightColor = lineHighlightColor; if(textArea.getBuffer() != null) textArea.invalidateLine(textArea.getCaretLine()); } //}}} //{{{ isLineHighlightEnabled() method /** * Returns true if line highlight is enabled, false otherwise. */ public final boolean isLineHighlightEnabled() { return lineHighlight; } //}}} //{{{ setLineHighlightEnabled() method /** * Enables or disables current line highlighting. * @param lineHighlight True if current line highlight should be enabled, * false otherwise */ public final void setLineHighlightEnabled(boolean lineHighlight) { this.lineHighlight = lineHighlight; textArea.repaint(); } //}}} //{{{ getSelectionFgColor() method /** * Returns the selection foreground color, if one is set. * @since jEdit 4.4.1 */ public final Color getSelectionFgColor() { return selectionFgColor; } //}}} //{{{ setSelectionFgColor() method /** * Sets the selection foreground color. * @param selectionFgColor The selection foreground color * @since jEdit 4.4.1 */ public final void setSelectionFgColor(Color selectionFgColor) { this.selectionFgColor = selectionFgColor; if (isSelectionFgColorEnabled()) textArea.repaint(); } //}}} //{{{ isSelectionFgColorEnabled() method /** * Returns true if selection foreground color is enabled - i.e. a specific * color is used for the selection foreground instead of the syntax highlight * color. * @since jEdit 4.4.1 */ public final boolean isSelectionFgColorEnabled() { return selectionFg; } //}}} //{{{ setSelectionFgColorEnabled() method /** * Enables or disables selection foreground color. * @param selectionFg True if selection foreground should be enabled, * false otherwise * @since jEdit 4.4.1 */ public final void setSelectionFgColorEnabled(boolean selectionFg) { this.selectionFg = selectionFg; textArea.repaint(); } //}}} //{{{ getStructureHighlightColor() method /** * Returns the structure highlight color. * @since jEdit 4.2pre3 */ public final Color getStructureHighlightColor() { return structureHighlightColor; } //}}} //{{{ setStructureHighlightColor() method /** * Sets the structure highlight color. * @param structureHighlightColor The bracket highlight color * @since jEdit 4.2pre3 */ public final void setStructureHighlightColor( Color structureHighlightColor) { this.structureHighlightColor = structureHighlightColor; textArea.invalidateStructureMatch(); } //}}} //{{{ isStructureHighlightEnabled() method /** * Returns true if structure highlighting is enabled, false otherwise. * @since jEdit 4.2pre3 */ public final boolean isStructureHighlightEnabled() { return structureHighlight; } //}}} //{{{ setStructureHighlightEnabled() method /** * Enables or disables structure highlighting. * @param structureHighlight True if structure highlighting should be * enabled, false otherwise * @since jEdit 4.2pre3 */ public final void setStructureHighlightEnabled(boolean structureHighlight) { this.structureHighlight = structureHighlight; textArea.invalidateStructureMatch(); } //}}} //{{{ isBlockCaretEnabled() method /** * Returns true if the caret should be drawn as a block, false otherwise. */ public final boolean isBlockCaretEnabled() { return blockCaret; } //}}} //{{{ setBlockCaretEnabled() method /** * Sets if the caret should be drawn as a block, false otherwise. * @param blockCaret True if the caret should be drawn as a block, * false otherwise. */ public final void setBlockCaretEnabled(boolean blockCaret) { this.blockCaret = blockCaret; extensionMgr.removeExtension(caretExtension); if(blockCaret) addExtension(BLOCK_CARET_LAYER,caretExtension); else addExtension(CARET_LAYER,caretExtension); if(textArea.getBuffer() != null) textArea.invalidateLine(textArea.getCaretLine()); } //}}} //{{{ isThickCaretEnabled() method /** * Returns true if the caret should be drawn with a thick line, false otherwise. * @since jEdit 4.3pre15 */ public final boolean isThickCaretEnabled() { return thickCaret; } //}}} //{{{ setThickCaretEnabled() method /** * Sets if the caret should be drawn with a thick line. * @param thickCaret * True if the caret should be drawn as a block, false otherwise. * @since jEdit 4.3pre15 */ public final void setThickCaretEnabled(boolean thickCaret) { this.thickCaret = thickCaret; if(textArea.getBuffer() != null) textArea.invalidateLine(textArea.getCaretLine()); } //}}} public String getEOLMarkerChar() { return eolMarkerChar; } public void setEOLMarkerChar(String emc) { eolMarkerChar = emc; } //{{{ getEOLMarkerColor() method /** * Returns the EOL marker color. */ public final Color getEOLMarkerColor() { return eolMarkerColor; } //}}} //{{{ setEOLMarkerColor() method /** * Sets the EOL marker color. * @param eolMarkerColor The EOL marker color */ public final void setEOLMarkerColor(Color eolMarkerColor) { this.eolMarkerColor = eolMarkerColor; repaint(); } //}}} //{{{ getEOLMarkersPainted() method /** * Returns true if EOL markers are drawn, false otherwise. */ public final boolean getEOLMarkersPainted() { return eolMarkers; } //}}} //{{{ setEOLMarkersPainted() method /** * Sets if EOL markers are to be drawn. * @param eolMarkers True if EOL markers should be drawn, false otherwise */ public final void setEOLMarkersPainted(boolean eolMarkers) { this.eolMarkers = eolMarkers; repaint(); } //}}} //{{{ getWrapGuideColor() method /** * Returns the wrap guide color. */ public final Color getWrapGuideColor() { return wrapGuideColor; } //}}} //{{{ setWrapGuideColor() method /** * Sets the wrap guide color. * @param wrapGuideColor The wrap guide color */ public final void setWrapGuideColor(Color wrapGuideColor) { this.wrapGuideColor = wrapGuideColor; repaint(); } //}}} //{{{ isWrapGuidePainted() method /** * Returns true if the wrap guide is drawn, false otherwise. * @since jEdit 4.0pre4 */ public final boolean isWrapGuidePainted() { return wrapGuide; } //}}} //{{{ setWrapGuidePainted() method /** * Sets if the wrap guide is to be drawn. * @param wrapGuide True if the wrap guide should be drawn, false otherwise */ public final void setWrapGuidePainted(boolean wrapGuide) { this.wrapGuide = wrapGuide; repaint(); } //}}} //{{{ getFoldLineStyle() method /** * Returns the fold line style. The first element is the style for * lines with a fold level greater than 3. The remaining elements * are for fold levels 1 to 3. */ public final SyntaxStyle[] getFoldLineStyle() { return foldLineStyle; } //}}} //{{{ setFoldLineStyle() method /** * Sets the fold line style. The first element is the style for * lines with a fold level greater than 3. The remaining elements * are for fold levels 1 to 3. * @param foldLineStyle The fold line style */ public final void setFoldLineStyle(SyntaxStyle[] foldLineStyle) { this.foldLineStyle = foldLineStyle; textArea.chunkCache.reset(); repaint(); } //}}} //{{{ setAntiAliasEnabled() method /** * As of jEdit 4.3, subpixel antialias mode is supported. * * @since jEdit 4.2pre4 */ public void setAntiAlias(AntiAlias newValue) { antiAlias = newValue; updateRenderingHints(); } //}}} //{{{ getAntiAlias() method /** * @return the AntiAlias value that is currently used for TextAreas. * @since jedit 4.3pre4 */ public AntiAlias getAntiAlias() { return antiAlias; } //}}} //{{{ setFractionalFontMetricsEnabled() method /** * Sets if fractional font metrics should be enabled. Has no effect when * running on Java 1.1. * @since jEdit 3.2pre6 */ public void setFractionalFontMetricsEnabled(boolean fracFontMetrics) { this.fracFontMetrics = fracFontMetrics; updateRenderingHints(); } //}}} //{{{ isFractionalFontMetricsEnabled() method /** * Returns if fractional font metrics are enabled. * @since jEdit 3.2pre6 */ public boolean isFractionalFontMetricsEnabled() { return fracFontMetrics; } //}}} //{{{ getFontRenderContext() method /** * Returns the font render context. * @since jEdit 4.0pre4 */ public FontRenderContext getFontRenderContext() { return fontRenderContext; } //}}} //}}} //{{{ addExtension() method /** * Adds a text area extension, which can perform custom painting and * tool tip handling. * @param extension The extension * @since jEdit 4.0pre4 */ public void addExtension(TextAreaExtension extension) { extensionMgr.addExtension(DEFAULT_LAYER,extension); repaint(); } //}}} //{{{ addExtension() method /** * Adds a text area extension, which can perform custom painting and * tool tip handling. * @param layer The layer to add the extension to. Note that more than * extension can share the same layer. * @param extension The extension * @since jEdit 4.0pre4 */ public void addExtension(int layer, TextAreaExtension extension) { extensionMgr.addExtension(layer,extension); repaint(); } //}}} //{{{ removeExtension() method /** * Removes a text area extension. It will no longer be asked to * perform custom painting and tool tip handling. * @param extension The extension * @since jEdit 4.0pre4 */ public void removeExtension(TextAreaExtension extension) { extensionMgr.removeExtension(extension); repaint(); } //}}} //{{{ getExtensions() method /** * Returns an array of registered text area extensions. Useful for * debugging purposes. * @since jEdit 4.1pre5 */ public TextAreaExtension[] getExtensions() { return extensionMgr.getExtensions(); } //}}} //{{{ getToolTipText() method /** * Returns the tool tip to display at the specified location. * @param evt The mouse event */ @Override public String getToolTipText(MouseEvent evt) { if(textArea.getBuffer().isLoading()) return null; return extensionMgr.getToolTipText(evt.getX(),evt.getY()); } //}}} //{{{ getFontMetrics() method /** * Returns the font metrics used by this component. */ public FontMetrics getFontMetrics() { return fm; } //}}} //{{{ getLineHeight() method /** * Returns the line height as given by the font metrics plus the * added line spacing. */ public int getLineHeight() { return fm.getHeight() + extraLineSpacing; } //}}} //{{{ getFontHeight() method /** * Returns the font height as given by the font metrics. */ public int getFontHeight() { return fm.getHeight(); } //}}} //{{{ getLineExtraSpacing() method /** * Returns the number of pixels from the start of the line to the start * of text (the extra line spacing). */ public int getLineExtraSpacing() { return extraLineSpacing; } //}}} //{{{ setLineExtraSpacing() method /** * Sets extra spacing between lines in pixels. */ public void setLineExtraSpacing(int spacing) { extraLineSpacing = spacing; } //}}} //{{{ setFont() method /** * Sets the font for this component. This is overridden to update the * cached font metrics and to recalculate which lines are visible. * @param font The font */ @Override public void setFont(Font font) { super.setFont(font); fm = getFontMetrics(font); textArea.recalculateVisibleLines(); if(textArea.getBuffer() != null && !textArea.getBuffer().isLoading()) textArea.recalculateLastPhysicalLine(); //textArea.propertiesChanged(); } //}}} //{{{ getStringWidth() method /** * Returns the width of the given string, in pixels, using the text * area's current font. * * @since jEdit 4.2final */ public float getStringWidth(String str) { if(textArea.charWidth != 0) return textArea.charWidth * str.length(); else { return (float)getFont().getStringBounds( str,getFontRenderContext()).getWidth(); } } //}}} //{{{ getRenderingHints() method /** * Returns the rendering hints used by the Graphics2D class; in this * case, for anti-aliasing of text. * * @since jEdit 4.5pre1 */ public RenderingHints getRenderingHints() { return renderingHints; } //}}} //{{{ update() method /** * Repaints the text. * @param _gfx The graphics context */ @Override public void update(Graphics _gfx) { paint(_gfx); } //}}} //{{{ paint() method /** * Repaints the text. * @param _gfx The graphics context */ @Override public void paint(Graphics _gfx) { assert _gfx instanceof Graphics2D; Graphics2D gfx = (Graphics2D)_gfx; gfx.setRenderingHints(renderingHints); fontRenderContext = gfx.getFontRenderContext(); Rectangle clipRect = gfx.getClipBounds(); int lineHeight = getLineHeight(); int charHeight = getFontHeight(); if(lineHeight == 0 || textArea.getBuffer().isLoading()) { gfx.setColor(getBackground()); gfx.fillRect(clipRect.x,clipRect.y,clipRect.width,clipRect.height); } else { long prepareTime = System.nanoTime(); // Because the clipRect's height is usually an even multiple // of the font height, we subtract 1 from it, otherwise one // too many lines will always be painted. int firstLine = clipRect.y / lineHeight; int lastLine = (clipRect.y + clipRect.height - 1) / lineHeight; gfx.setColor(getBackground()); gfx.setFont(getFont()); prepareTime = System.nanoTime() - prepareTime; long linesTime = System.nanoTime(); int numLines = lastLine - firstLine + 1; int y = firstLine * lineHeight; gfx.fillRect(0,y,getWidth(),numLines * lineHeight); extensionMgr.paintScreenLineRange(textArea,gfx, firstLine,lastLine, y, lineHeight); linesTime = System.nanoTime() - linesTime; if(Debug.PAINT_TIMER && numLines >= 1) Log.log(Log.DEBUG,this,"repainting " + numLines + " lines took " + prepareTime + "/" + linesTime + " ns"); } textArea.updateMaxHorizontalScrollWidth(); } //}}} //{{{ nextTabStop() method /** * Implementation of TabExpander interface. Returns next tab stop after * a specified point. * @param x The x co-ordinate * @param tabOffset Ignored * @return The next tab stop after <i>x</i> */ public float nextTabStop(float x, int tabOffset) { int ntabs = (int)(x / textArea.tabSize); return (ntabs + 1) * textArea.tabSize; } //}}} //{{{ getPreferredSize() method /** * Returns the painter's preferred size. */ @Override public Dimension getPreferredSize() { Dimension dim = new Dimension(); char[] foo = new char[80]; for(int i = 0; i < foo.length; i++) foo[i] = ' '; dim.width = (int)getStringWidth(new String(foo)); dim.height = getLineHeight() * 25; return dim; } //}}} //{{{ getMinimumSize() method /** * Returns the painter's minimum size. */ @Override public Dimension getMinimumSize() { return getPreferredSize(); } //}}} //{{{ Package-private members //{{{ Instance variables /* package-private since they are accessed by inner classes and we * want this to be fast */ TextArea textArea; SyntaxStyle[] styles; Color caretColor; Color selectionColor; Color multipleSelectionColor; Color lineHighlightColor; Color structureHighlightColor; Color eolMarkerColor; String eolMarkerChar; Color wrapGuideColor; SyntaxStyle[] foldLineStyle; boolean blockCaret; boolean thickCaret; boolean lineHighlight; boolean structureHighlight; boolean eolMarkers; boolean wrapGuide; AntiAlias antiAlias; boolean fracFontMetrics; RenderingHints renderingHints; boolean selectionFg; Color selectionFgColor; // should try to use this as little as possible. FontMetrics fm; int extraLineSpacing; //}}} //{{{ TextAreaPainter constructor /** * Creates a new painter. Do not create instances of this class * directly. */ TextAreaPainter(TextArea textArea) { enableEvents(AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); this.textArea = textArea; antiAlias = new AntiAlias(0); extensionMgr = new ExtensionManager(); eolMarkerChar = "ยท"; setAutoscrolls(true); setOpaque(true); setRequestFocusEnabled(false); setDoubleBuffered(false); setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); fontRenderContext = new FontRenderContext(null,false,false); addExtension(LINE_BACKGROUND_LAYER,new PaintLineBackground()); addExtension(SELECTION_LAYER,new PaintSelection()); addExtension(WRAP_GUIDE_LAYER,new PaintWrapGuide()); addExtension(BRACKET_HIGHLIGHT_LAYER,new StructureMatcher .Highlight(textArea)); addExtension(TEXT_LAYER,new PaintText()); addExtension(TEXT_LAYER,new PaintSelectionText()); caretExtension = new PaintCaret(); extraLineSpacing = 0; } //}}} //}}} //{{{ Private members //{{{ Instance variables private final ExtensionManager extensionMgr; private final PaintCaret caretExtension; private FontRenderContext fontRenderContext; private Cursor hiddenCursor; private boolean defaultCursor = true; //}}} //{{{ updateRenderingHints() method private void updateRenderingHints() { Map<RenderingHints.Key,Object> hints = new HashMap<RenderingHints.Key,Object>(); hints.put(RenderingHints.KEY_FRACTIONALMETRICS, fracFontMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF); hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, antiAlias.renderHint()); if (antiAlias.val() == 0) { hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); fontRenderContext = new FontRenderContext(null, antiAlias.val() > 0, fracFontMetrics); } /* Subpixel antialiasing mode */ else if (antiAlias.val() > 1) { Object fontRenderHint = fracFontMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF; fontRenderContext = new FontRenderContext(null, antiAlias.renderHint(), fontRenderHint); } else /** Standard Antialias Version */ { hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); fontRenderContext = new FontRenderContext(null, antiAlias.val() > 0, fracFontMetrics); } renderingHints = new RenderingHints(hints); } //}}} //}}} //{{{ Inner classes //{{{ PaintLineBackground class private class PaintLineBackground extends TextAreaExtension { //{{{ shouldPaintLineHighlight() method private boolean shouldPaintLineHighlight(int caret, int start, int end) { if(!isLineHighlightEnabled() || caret < start || caret >= end) { return false; } int count = textArea.getSelectionCount(); if(count == 1) { Selection s = textArea.getSelection(0); return s.getStartLine() == s.getEndLine(); } else return count == 0; } //}}} //{{{ paintValidLine() method @Override public void paintValidLine(Graphics2D gfx, int screenLine, int physicalLine, int start, int end, int y) { // minimise access$ methods TextArea textArea = TextAreaPainter.this.textArea; JEditBuffer buffer = textArea.getBuffer(); //{{{ Paint line highlight and collapsed fold highlight boolean collapsedFold = physicalLine < buffer.getLineCount() - 1 && buffer.isFoldStart(physicalLine) && !textArea.displayManager .isLineVisible(physicalLine + 1); SyntaxStyle foldLineStyle = null; if(collapsedFold) { int level = buffer.getFoldLevel(physicalLine + 1); if(buffer.getFoldHandler() instanceof IndentFoldHandler) level = Math.max(1,level / buffer.getIndentSize()); if(level > 3) level = 0; foldLineStyle = TextAreaPainter.this.foldLineStyle[level]; } int caret = textArea.getCaretPosition(); boolean paintLineHighlight = shouldPaintLineHighlight( caret,start,end); Color bgColor; if(paintLineHighlight) bgColor = lineHighlightColor; else if(collapsedFold) { bgColor = foldLineStyle.getBackgroundColor(); if(bgColor == null) bgColor = getBackground(); } else bgColor = getBackground(); if(paintLineHighlight || collapsedFold) { gfx.setColor(bgColor); gfx.fillRect(0,y,getWidth(),getLineHeight()); } //}}} //{{{ Paint token backgrounds ChunkCache.LineInfo lineInfo = textArea.chunkCache.getLineInfo(screenLine); if(lineInfo.chunks != null) { float baseLine = y + getLineHeight() - (fm.getLeading() + 1) - fm.getDescent(); Chunk.paintChunkBackgrounds( lineInfo.chunks, gfx, textArea.getHorizontalOffset(), baseLine, getLineHeight()); } //}}} } //}}} } //}}} //{{{ PaintSelection class private class PaintSelection extends TextAreaExtension { //{{{ paintValidLine() method @Override public void paintValidLine(Graphics2D gfx, int screenLine, int physicalLine, int start, int end, int y) { if(textArea.getSelectionCount() == 0) return; gfx.setColor(textArea.isMultipleSelectionEnabled() ? getMultipleSelectionColor() : getSelectionColor()); Iterator<Selection> iter = textArea.getSelectionIterator(); while(iter.hasNext()) { Selection s = iter.next(); paintSelection(gfx,screenLine,physicalLine,y,s); } } //}}} //{{{ paintSelection() method private void paintSelection(Graphics2D gfx, int screenLine, int physicalLine, int y, Selection s) { int[] selectionStartAndEnd = textArea.selectionManager .getSelectionStartAndEnd( screenLine,physicalLine,s); if(selectionStartAndEnd == null) return; int x1 = selectionStartAndEnd[0]; int x2 = selectionStartAndEnd[1]; gfx.fillRect(x1, y, x2 - x1, getLineHeight()); } //}}} } //}}} //{{{ PaintSelectionText class private class PaintSelectionText extends TextAreaExtension { // All screen lines of the same physical line use the same indentation private float indent; private boolean indentFound = false; //{{{ paintValidLine() method @Override public void paintValidLine(Graphics2D gfx, int screenLine, int physicalLine, int start, int end, int y) { if(textArea.getSelectionCount() == 0) return; if ((! isSelectionFgColorEnabled()) || (getSelectionFgColor() == null)) return; Iterator<Selection> iter = textArea.getSelectionIterator(); while(iter.hasNext()) { Selection s = iter.next(); paintSelection(gfx,screenLine,physicalLine,y,s); } } //}}} //{{{ paintSelection() method private void paintSelection(Graphics2D gfx, int screenLine, int physicalLine, int y, Selection s) { if ((physicalLine < s.getStartLine()) || (physicalLine > s.getEndLine())) return; float x = indent = textArea.getHorizontalOffset(); float baseLine = y + getLineHeight() - (fm.getLeading()+1) - fm.getDescent(); DefaultTokenHandler tokenHandler = new DefaultTokenHandler(); textArea.getBuffer().markTokens(physicalLine, tokenHandler); Token token = tokenHandler.getTokens(); int lineStart = textArea.getLineStartOffset(physicalLine); int startOffset, endOffset; if (s instanceof Selection.Rect) { Selection.Rect r = (Selection.Rect)s; startOffset = r.getStart(textArea.getBuffer(), physicalLine); endOffset = r.getEnd(textArea.getBuffer(), physicalLine); } else { startOffset = (s.getStart() > lineStart) ? s.getStart() : lineStart; endOffset = s.getEnd(); } // Soft-wrap int screenLineStart = textArea.getScreenLineStartOffset(screenLine); int screenLineEnd = textArea.getScreenLineEndOffset(screenLine); if (screenLineStart > startOffset) startOffset = screenLineStart; if (screenLineEnd < endOffset) endOffset = screenLineEnd; indentFound = false; int tokenStart = lineStart; while(token.id != Token.END) { int next = tokenStart + token.length; String sub = null; SyntaxStyle style = styles[token.id]; if (next > startOffset) // Reached selection start { if (tokenStart >= endOffset) // Got past selection break; if(style != null) { gfx.setFont(style.getFont()); gfx.setColor(getSelectionFgColor()); int strStart; if (startOffset > tokenStart) { strStart = startOffset; x = nextX(x, style, sub, tokenStart, startOffset); } else strStart = tokenStart; int strEnd = (endOffset > next) ? next : endOffset; sub = textArea.getText(strStart, strEnd - strStart); gfx.drawString(sub, x, baseLine); x = nextX(x, style, sub, strStart, strEnd); } } if (sub == null) x = nextX(x, style, null, tokenStart, next); tokenStart = next; token = token.next; if (tokenStart == screenLineStart) x = indent; } } //}}} //{{{ float nextX(float x, SyntaxStyle style, String s, int startOffset, int endOffset) { if (s == null) s = textArea.getText(startOffset, endOffset - startOffset); if (s.equals("\t")) { int horzOffset = textArea.getHorizontalOffset(); x = nextTabStop(x - horzOffset, endOffset) + horzOffset; } else { if ((! indentFound) && (! s.equals(" "))) { indentFound = true; indent = x; } Font font = (style != null) ? style.getFont() : getFont(); x += font.getStringBounds(s, getFontRenderContext()).getWidth(); } return x; } } //}}} //{{{ PaintWrapGuide class private class PaintWrapGuide extends TextAreaExtension { @Override public void paintScreenLineRange(Graphics2D gfx, int firstLine, int lastLine, int[] physicalLines, int[] start, int[] end, int y, int lineHeight) { if(textArea.wrapMargin != 0 && !textArea.wrapToWidth && isWrapGuidePainted()) { gfx.setColor(getWrapGuideColor()); int x = textArea.getHorizontalOffset() + textArea.wrapMargin; gfx.drawLine(x,y,x,y + (lastLine - firstLine + 1) * lineHeight); } } @Override public String getToolTipText(int x, int y) { if(textArea.wrapMargin != 0 && !textArea.wrapToWidth && isWrapGuidePainted()) { int wrapGuidePos = textArea.wrapMargin + textArea.getHorizontalOffset(); if(Math.abs(x - wrapGuidePos) < 5) { return String.valueOf(textArea.getBuffer() .getProperty("maxLineLen")); } } return null; } } //}}} //{{{ PaintText class private class PaintText extends TextAreaExtension { @Override public void paintValidLine(Graphics2D gfx, int screenLine, int physicalLine, int start, int end, int y) { ChunkCache.LineInfo lineInfo = textArea.chunkCache.getLineInfo(screenLine); Font defaultFont = getFont(); Color defaultColor = getForeground(); gfx.setFont(defaultFont); gfx.setColor(defaultColor); int x = textArea.getHorizontalOffset(); int originalX = x; float baseLine = y + getLineHeight() - (fm.getLeading()+1) - fm.getDescent(); if(lineInfo.chunks != null) { x += Chunk.paintChunkList( lineInfo.chunks, gfx, textArea.getHorizontalOffset(), baseLine, !Debug.DISABLE_GLYPH_VECTOR); } JEditBuffer buffer = textArea.getBuffer(); if(!lineInfo.lastSubregion) { gfx.setFont(defaultFont); gfx.setColor(eolMarkerColor); gfx.drawString(":",Math.max(x, textArea.getHorizontalOffset() + textArea.wrapMargin + textArea.charWidth), baseLine); x += textArea.charWidth; } else if(physicalLine < buffer.getLineCount() - 1 && buffer.isFoldStart(physicalLine) && !textArea.displayManager .isLineVisible(physicalLine + 1)) { int level = buffer.getFoldLevel(physicalLine + 1); if(buffer.getFoldHandler() instanceof IndentFoldHandler) level = Math.max(1,level / buffer.getIndentSize()); if(level > 3) level = 0; SyntaxStyle foldLineStyle = TextAreaPainter.this.foldLineStyle[level]; Font font = foldLineStyle.getFont(); gfx.setFont(font); gfx.setColor(foldLineStyle.getForegroundColor()); int nextLine; int nextScreenLine = screenLine + 1; if(nextScreenLine < textArea.getVisibleLines()) { nextLine = textArea.chunkCache.getLineInfo(nextScreenLine) .physicalLine; } else { nextLine = textArea.displayManager .getNextVisibleLine(physicalLine); } if(nextLine == -1) nextLine = textArea.getLineCount(); int count = nextLine - physicalLine - 1; String str = " [" + count + " lines]"; float width = getStringWidth(str); gfx.drawString(str,x,baseLine); x += width; } else if(eolMarkers) { gfx.setFont(defaultFont); gfx.setColor(eolMarkerColor); gfx.drawString(eolMarkerChar,x,baseLine); x += textArea.charWidth; } lineInfo.width = x - originalX; } } //}}} //{{{ PaintCaret class private class PaintCaret extends TextAreaExtension { @Override public void paintValidLine(Graphics2D gfx, int screenLine, int physicalLine, int start, int end, int y) { if(!textArea.isCaretVisible()) return; int caret = textArea.getCaretPosition(); if(caret < start || caret >= end) return; int offset = caret - textArea.getLineStartOffset(physicalLine); textArea.offsetToXY(physicalLine, offset, textArea.offsetXY); int caretX = textArea.offsetXY.x; int lineHeight = getLineHeight(); int charHeight = Math.min(lineHeight, getFontHeight()); int charOffset = Math.max(lineHeight - charHeight, 0); gfx.setColor(caretColor); if(textArea.isOverwriteEnabled()) { if (thickCaret) gfx.fillRect(caretX, y + lineHeight - 4, textArea.charWidth, 3); else gfx.drawLine(caretX, y + lineHeight - 1, caretX + textArea.charWidth, y + lineHeight - 1); } else if(blockCaret) gfx.drawRect(caretX, y + charOffset, textArea.charWidth - 1, charHeight - 1); else { if (thickCaret) gfx.fillRect(caretX, y + charOffset, 3, charHeight - 1); else gfx.drawLine(caretX, y + charOffset, caretX, y + charOffset + charHeight - 1); } } } //}}} //}}} //}}} }