TextAreaPainter.java :  » Database-Client » SQL-Workbench » workbench » gui » editor » Java Open Source

Java Open Source » Database Client » SQL Workbench 
SQL Workbench » workbench » gui » editor » TextAreaPainter.java
/*
 * TextAreaPainter.java - Paints the text area
 * Copyright (C) 1999 Slava Pestov
 *
 * You may use and modify this package for any purpose. Redistribution is
 * permitted, in both source and binary form, provided that this notice
 * remains intact in all source distributions of this package.
 */
package workbench.gui.editor;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;
import workbench.resource.Settings;
import workbench.util.NumberStringCache;
import workbench.util.StringUtil;

/**
 * The text area repaint manager. It performs double buffering and paints
 * lines of text.
 * @author Slava Pestov
 */
public class TextAreaPainter 
  extends JComponent 
  implements TabExpander, PropertyChangeListener
{
  int currentLineIndex;
  Token currentLineTokens;
  Segment currentLine;

  // protected members
  protected JEditTextArea textArea;
  protected SyntaxStyle[] styles;
  protected Color caretColor;
  protected Color selectionColor;
  protected Color bracketHighlightColor;
  protected Color errorColor;
  protected Color currentLineColor;

  protected boolean bracketHighlight;

  protected int tabSize = -1;
  protected FontMetrics fm;

  protected boolean showLineNumbers = false;
  protected int gutterWidth = 0;
  protected int gutterCharWidth = 0;
  
  protected static final int GUTTER_MARGIN = 2;
  private static final Color GUTTER_BACKGROUND = new Color(238,240,238);
  private static final Color GUTTER_COLOR = Color.DARK_GRAY;

  public TextAreaPainter(JEditTextArea textArea)
  {
    this.textArea = textArea;

    setDoubleBuffered(true);
    setOpaque(true);

    currentLine = new Segment();
    currentLineIndex = -1;

    setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));

    setFont(new Font("Monospaced",Font.PLAIN,12));
    setForeground(Color.BLACK);
    setBackground(Color.WHITE);

    caretColor = Color.BLACK;
    errorColor = Settings.getInstance().getEditorErrorColor();
    selectionColor = Settings.getInstance().getEditorSelectionColor();
    currentLineColor = Settings.getInstance().getEditorCurrentLineColor();
    bracketHighlightColor = Color.BLACK;
    bracketHighlight = true;
    Settings.getInstance().addPropertyChangeListener(this, Settings.PROPERTY_EDITOR_TAB_WIDTH, Settings.PROPERTY_EDITOR_CURRENT_LINE_COLOR);
  }

  public void dispose()
  {
    Settings.getInstance().removePropertyChangeListener(this);
  }
  
  public void propertyChange(PropertyChangeEvent evt)
  {
    if (Settings.PROPERTY_EDITOR_TAB_WIDTH.equals(evt.getPropertyName()))
    {
      this.calculateTabSize();
    }
    else if (Settings.PROPERTY_EDITOR_CURRENT_LINE_COLOR.equals(evt.getPropertyName()))
    {
      this.currentLineColor = Settings.getInstance().getEditorCurrentLineColor();
      invalidate();
    }
  }
   
  /**
   * Switches display of line numbers in the left gutter area.
   *
   * @param aFlag
   */
  public final void setShowLineNumbers(boolean aFlag)
  {
    this.showLineNumbers = aFlag;
    this.invalidate();
  }

  public final boolean getShowLineNumbers() 
  { 
    return this.showLineNumbers; 
  }

  /**
   * Returns if this component can be traversed by pressing the
   * Tab key. This returns false.
   */
  @SuppressWarnings("deprecation")
  public final boolean isManagingFocus()
  {
    return false;
  }

  /**
   * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
   * will be used to paint tokens with id = <i>n</i>.
   * @see Token
   */
  public final SyntaxStyle[] getStyles()
  {
    return styles;
  }

  /**
   * 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 Token
   */
  public final void setStyles(SyntaxStyle[] styles)
  {
    this.styles = styles;
    repaint();
  }

  /**
   * Returns the caret color.
   */
  public final Color getCaretColor()
  {
    return caretColor;
  }

  /**
   * Sets the caret color.
   * @param caretColor The caret color
   */
  public final void setCaretColor(Color caretColor)
  {
    this.caretColor = caretColor;
    invalidateSelectedLines();
  }

  /**
   * Returns the selection color.
   */
  public final Color getSelectionColor()
  {
    return selectionColor;
  }

  /**
   * Sets the selection color.
   * @param selectionColor The selection color
   */
  public final void setSelectionColor(Color selectionColor)
  {
    this.selectionColor = selectionColor;
    invalidateSelectedLines();
  }

  /**
   * Returns the bracket highlight color.
   */
  public final Color getBracketHighlightColor()
  {
    return bracketHighlightColor;
  }

  /**
   * Sets the bracket highlight color.
   * @param bracketHighlightColor The bracket highlight color
   */
  public final void setBracketHighlightColor(Color bracketHighlightColor)
  {
    this.bracketHighlightColor = bracketHighlightColor;
    invalidateLine(textArea.getBracketLine());
  }

  /**
   * Returns true if bracket highlighting is enabled, false otherwise.
   * When bracket highlighting is enabled, the bracket matching the
   * one before the caret (if any) is highlighted.
   */
  public final boolean isBracketHighlightEnabled()
  {
    return bracketHighlight;
  }

  /**
   * Enables or disables bracket highlighting.
   * When bracket highlighting is enabled, the bracket matching the
   * one before the caret (if any) is highlighted.
   * @param bracketHighlight True if bracket highlighting should be
   * enabled, false otherwise
   */
  public final void setBracketHighlightEnabled(boolean bracketHighlight)
  {
    this.bracketHighlight = bracketHighlight;
    invalidateLine(textArea.getBracketLine());
  }

  /**
   * Returns the font metrics used by this component.
   */
  public FontMetrics getFontMetrics()
  {
    if (fm == null)
    {
      this.fm = getFontMetrics(getFont());
    }
    return fm;
  }

  /**
   * 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
   */
  public void setFont(Font font)
  {
    super.setFont(font);
    this.fm = getFontMetrics(font);
    calculateTabSize();
    calculateGutterWidth();
  }

  private void calculateGutterWidth()
  {
    FontMetrics cfm = getFontMetrics();
    if (cfm == null) 
    {
      this.gutterCharWidth = 18;
    }
    else
    {
      this.gutterCharWidth = cfm.charWidth('9');
    }
    if (this.showLineNumbers)
    {
      int lastLine = textArea.getLineCount();
      int chars = StringUtil.numDigits(lastLine);
      this.gutterWidth = (chars * gutterCharWidth) + (GUTTER_MARGIN * 2);
    }
    else
    {
      this.gutterWidth = 0;
    }    
  }
  public void calculateTabSize()
  {
    this.tabSize = -1;
    if (this.textArea == null) return;
    if (this.textArea.getDocument() == null) return;
    FontMetrics cfm = this.getFontMetrics();
    if (cfm == null) return;
    
    Object tab = textArea.getDocument().getProperty(PlainDocument.tabSizeAttribute);
    int t = -1;
    if (tab == null) 
    {
      t = Settings.getInstance().getEditorTabWidth();
    }
    else
    {
      Integer tsize = (Integer)tab;
      t = tsize.intValue();
    }
    this.tabSize = cfm.charWidth(' ') * t;
  }
  
  public void paint(Graphics gfx)
  {

    calculateGutterWidth();

    Rectangle clipRect = gfx.getClipBounds();

    if (clipRect != null)
    {
      gfx.setColor(this.getBackground());
      gfx.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

      if (this.showLineNumbers)
      {
        gfx.setColor(GUTTER_BACKGROUND);
        gfx.fillRect(clipRect.x, clipRect.y, gutterWidth - clipRect.x, clipRect.height);
      }
    }
    
    final int lastLine = textArea.getLineCount();
    final int visibleCount = textArea.getVisibleLines();
    final int firstVisible = textArea.getFirstLine();
    
    int fheight = fm.getHeight();
    int firstInvalid = firstVisible + clipRect.y / fheight;
    int lastInvalid = firstVisible + ((clipRect.y + clipRect.height) / fheight) + 1;
    if (lastInvalid > lastLine) lastInvalid = lastLine;
    
    try 
    {
      TokenMarker tokenMarker = textArea.getDocument().getTokenMarker();
      int x = textArea.getHorizontalOffset();

      int endLine = firstVisible + visibleCount;
      if (endLine > lastLine) endLine = lastLine;
      
      int cw = this.getWidth() - gutterWidth;
      int ch = getHeight();
      int gutterX = this.gutterWidth - GUTTER_MARGIN;

      final int caretLine = textArea.getCaretLine();
      
      for (int line = firstVisible; line <= endLine; line++)
      {
        int y = textArea.lineToY(line);
        
        if (this.showLineNumbers)
        {
          // It seems that the Objects created by Integer.toString()
          // that are passed to drawString() are not collected
          // correctly (as seen in the profiler). So each time
          // the editor gets redrawn a small amount of memory is lost
          // To workaround this, I'm caching (some of) the values 
          // that are needed here.
          
          String s = NumberStringCache.getNumberString(line);
          int w = s.length() * this.gutterCharWidth;
          gfx.setColor(GUTTER_COLOR);
          gfx.drawString(s, gutterX - w, y);
        }
        
        if (line >= firstInvalid && line < lastInvalid) 
        {
          if (this.showLineNumbers)
          {
            gfx.setClip(this.gutterWidth, 0, cw, ch);
            gfx.translate(this.gutterWidth,0);    
          }    
          
          if (line == caretLine && this.currentLineColor != null)
          {
            gfx.setColor(currentLineColor);
            gfx.fillRect(0, y + fm.getLeading() + fm.getMaxDescent(), getWidth(), fheight);
            gfx.setColor(getBackground());
          }
          
          paintLine(gfx, tokenMarker, line, y, x);
          
          if (this.showLineNumbers)
          {
            gfx.translate(-this.gutterWidth,0);        
            gfx.setClip(null);
          }
        }
      }
    }
    catch (Exception e)
    {
      System.err.println("Error repainting line range {" + firstInvalid + "," + lastInvalid + "}:" + e.getMessage());
      //e.printStackTrace();
    }
  }

  /**
   * Marks a line as needing a repaint.
   * @param line The line to invalidate
   */
  public final void invalidateLine(int line)
  {
    repaint(0,textArea.lineToY(line) + fm.getMaxDescent() + fm.getLeading(),getWidth(),fm.getHeight());
  }

  public int getGutterWidth()
  {
    return this.gutterWidth; // + GUTTER_MARGIN;
  }
  /**
   * Marks a range of lines as needing a repaint.
   * @param firstLine The first line to invalidate
   * @param lastLine The last line to invalidate
   */
  public final void invalidateLineRange(int firstLine, int lastLine)
  {
    repaint(0,textArea.lineToY(firstLine) + fm.getMaxDescent() + fm.getLeading(),getWidth(),(lastLine - firstLine + 1) * fm.getHeight());
  }

  /**
   * Repaints the lines containing the selection.
   */
  public final void invalidateSelectedLines()
  {
    invalidateLineRange(textArea.getSelectionStartLine(),textArea.getSelectionEndLine());
  }

  /**
   * 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)
  {
    if (tabSize == -1)
    {
      this.calculateTabSize();
    }
    int offset = textArea.getHorizontalOffset();
    int ntabs = ((int)x - offset) / tabSize;
    return (ntabs + 1) * tabSize + offset;
  }

  protected void paintLine(Graphics gfx, TokenMarker tokenMarker,  int line, int y, int x)
  {
    Font defaultFont = getFont();
    Color defaultColor = getForeground();

    currentLineIndex = line;

    if(tokenMarker == null)
    {
      paintPlainLine(gfx,line,defaultFont,defaultColor,x,y);
    }
    else
    {
      paintSyntaxLine(gfx,tokenMarker,line,defaultFont,defaultColor,x,y);
    }
  }

  protected void paintPlainLine(Graphics gfx, int line, Font defaultFont, Color defaultColor, int x, int y)
  {
    textArea.getLineText(line,currentLine);
    
    paintHighlight(gfx,line,y);

    gfx.setFont(defaultFont);
    gfx.setColor(defaultColor);

    y += fm.getHeight();
    Utilities.drawTabbedText(currentLine,x,y,gfx,this,0);
  }

  protected void paintSyntaxLine(Graphics gfx, TokenMarker tokenMarker,
    int line, Font defaultFont, Color defaultColor, int x, int y)
  {
    textArea.getLineText(line,currentLine);
    
    currentLineTokens = tokenMarker.markTokens(currentLine,  line);

    paintHighlight(gfx,line,y);

    gfx.setFont(defaultFont);
    gfx.setColor(defaultColor);
    y += fm.getHeight();
    SyntaxUtilities.paintSyntaxLine(currentLine,currentLineTokens,styles,this,gfx,x,y,0);
  }

  protected void paintHighlight(Graphics gfx, int line, int y)
  {
    if (line >= textArea.getSelectionStartLine()  && line <= textArea.getSelectionEndLine())
    {
      paintLineHighlight(gfx,line,y);
    }

    if (bracketHighlight && line == textArea.getBracketLine())
    {
      paintBracketHighlight(gfx,line,y);
    }
    
    if (line == textArea.getCaretLine())
    {
      paintCaret(gfx,line,y);
    }
  }

  protected void paintLineHighlight(Graphics gfx, int line, int y)
  {
    int selectionStart = textArea.getSelectionStart();
    int selectionEnd = textArea.getSelectionEnd();
    
    if (selectionStart == selectionEnd) return;
    
    int height = fm.getHeight();
    y += fm.getLeading() + fm.getMaxDescent();

    Color c = this.textArea.getAlternateSelectionColor();
    if (c != null)
    {
      gfx.setColor(c);
    }
    else
    {
      gfx.setColor(selectionColor);
    }

    int selectionStartLine = textArea.getSelectionStartLine();
    int selectionEndLine = textArea.getSelectionEndLine();
    int lineStart = textArea.getLineStartOffset(line);

    int x1, x2;
    if (textArea.isSelectionRectangular())
    {
      int lineLen = textArea.getLineLength(line);
      x1 = textArea._offsetToX(line,Math.min(lineLen,selectionStart - textArea.getLineStartOffset(selectionStartLine)));// + this.gutterWidth;
      x2 = textArea._offsetToX(line,Math.min(lineLen,selectionEnd - textArea.getLineStartOffset(selectionEndLine)));// + this.gutterWidth;
      if(x1 == x2) x2++;
    }
    else if (selectionStartLine == selectionEndLine)
    {
      x1 = textArea._offsetToX(line,selectionStart - lineStart);
      x2 = textArea._offsetToX(line,selectionEnd - lineStart);
    }
    else if (line == selectionStartLine)
    {
      x1 = textArea._offsetToX(line,selectionStart - lineStart);
      x2 = getWidth();
    }
    else if (line == selectionEndLine)
    {
      x1 = 0;
      x2 = textArea._offsetToX(line,selectionEnd - lineStart);
    }
    else
    {
      x1 = 0; //gutterWidth;
      x2 = getWidth();
    }

    
    // "inlined" min/max()
    gfx.fillRect(x1 > x2 ? x2 : x1,y,x1 > x2 ? (x1 - x2) : (x2 - x1),height);

  }

  protected void paintBracketHighlight(Graphics gfx, int line, int y)
  {
    int position = textArea.getBracketPosition();
    if(position == -1) return;

    y += fm.getLeading() + fm.getMaxDescent();
    int x = textArea._offsetToX(line,position);// + this.gutterWidth;
    gfx.setColor(bracketHighlightColor);
    // Hack!!! Since there is no fast way to get the character
    // from the bracket matching routine, we use ( since all
    // brackets probably have the same width anyway
    gfx.drawRect(x,y,fm.charWidth('(') - 1,  fm.getHeight() - 1);
  }

  protected void paintCaret(Graphics gfx, int line, int y)
  {
    if (textArea.isCaretVisible())
    {
      int offset = textArea.getCaretPosition() - textArea.getLineStartOffset(line);
      int caretX = textArea._offsetToX(line,offset);// + this.gutterWidth;
      int caretWidth = (textArea.isOverwriteEnabled() ? fm.charWidth('w') : 2);
      y += fm.getLeading() + fm.getMaxDescent();
      int height = fm.getHeight();

      gfx.setColor(caretColor);

      if(textArea.isOverwriteEnabled())
      {
        gfx.fillRect(caretX,y + height - 1,  caretWidth,1);
      }
      else
      {
        gfx.drawRect(caretX,y,caretWidth - 1,height - 1);
      }
    }
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.