All Downloads are FREE. Search and download functionalities are using the official Maven repository.

processing.mode.java.pdex.JavaTextAreaPainter Maven / Gradle / Ivy

Go to download

Processing is a programming language, development environment, and online community. This Java Mode package contains the Java mode for Processing IDE.

There is a newer version: 3.3.7
Show newest version
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 The Processing Foundation

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package processing.mode.java.pdex;

import processing.mode.java.JavaEditor;
import processing.mode.java.tweak.*;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.util.List;

import javax.swing.text.BadLocationException;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;

import processing.app.Messages;
import processing.app.Mode;
import processing.app.SketchCode;
import processing.app.syntax.SyntaxDocument;
import processing.app.syntax.TextAreaDefaults;
import processing.app.syntax.TextAreaPainter;
import processing.app.syntax.TokenMarker;
import processing.app.ui.Editor;


// TODO Most of this needs to be merged into the main TextAreaPainter,
//      since it's not specific to Java. [fry 150821]

/**
 * Customized line painter. Adds support for background colors,
 * left hand gutter area with background color and text.
 */
public class JavaTextAreaPainter extends TextAreaPainter
	implements MouseListener, MouseMotionListener {

  public Color errorUnderlineColor;
  public Color warningUnderlineColor;

  protected Font gutterTextFont;
  protected Color gutterTextColor;
  protected Color gutterLineHighlightColor;


  public JavaTextAreaPainter(final JavaTextArea textArea, TextAreaDefaults defaults) {
    super(textArea, defaults);

    // Handle mouse clicks to toggle breakpoints
    addMouseListener(new MouseAdapter() {
      long lastTime;  // OS X seems to be firing multiple mouse events

      public void mousePressed(MouseEvent event) {
        JavaEditor javaEditor = getJavaEditor();
        // Don't toggle breakpoints when the debugger isn't enabled
        // https://github.com/processing/processing/issues/3306
        if (javaEditor.isDebuggerEnabled()) {
          long thisTime = event.getWhen();
          if (thisTime - lastTime > 100) {
            if (event.getX() < Editor.LEFT_GUTTER) {
              int offset = getJavaTextArea().xyToOffset(event.getX(), event.getY());
              if (offset >= 0) {
                int lineIndex = getJavaTextArea().getLineOfOffset(offset);
                javaEditor.toggleBreakpoint(lineIndex);
              }
            }
            lastTime = thisTime;
          }
        }
      }
    });

    // TweakMode code
    tweakMode = false;
    cursorType = Cursor.DEFAULT_CURSOR;
  }

  /**
   * Paint a line. Paints the gutter (with background color and text) then the
   * line (background color and text).
   *
   * @param gfx
   *          the graphics context
   * @param tokenMarker
   * @param line
   *          0-based line number
   * @param x
   *          horizontal position
   */
  @Override
  protected void paintLine(Graphics gfx, int line, int x,
                           TokenMarker tokenMarker) {
    try {
      // TODO This line is causing NPEs randomly ever since I added the
      // toggle for Java Mode/Debugger toolbar. [Manindra]
      super.paintLine(gfx, line, x + Editor.LEFT_GUTTER, tokenMarker);

    } catch (Exception e) {
      Messages.log(e.getMessage());
    }

    // formerly only when in debug mode
    paintLeftGutter(gfx, line, x);
//    paintGutterBg(gfx, line, x);
//    paintGutterLine(gfx, line, x);
//    paintGutterText(gfx, line, x);

    paintErrorLine(gfx, line, x);
  }


  /**
   * Paint the gutter: draw the background, draw line numbers, break points.
   * @param gfx the graphics context
   * @param line 0-based line number
   * @param x horizontal position
   */
  protected void paintLeftGutter(Graphics gfx, int line, int x) {
    int y = textArea.lineToY(line) + fm.getLeading() + fm.getMaxDescent();
    if (line == textArea.getSelectionStopLine()) {
      gfx.setColor(gutterLineHighlightColor);
      gfx.fillRect(0, y, Editor.LEFT_GUTTER, fm.getHeight());
    } else {
      //gfx.setColor(getJavaTextArea().gutterBgColor);
      gfx.setClip(0, y, Editor.LEFT_GUTTER, fm.getHeight());
      gfx.drawImage(getJavaTextArea().getGutterGradient(), 0, 0, getWidth(), getHeight(), this);
      gfx.setClip(null);  // reset
    }

    String text = null;
    if (getJavaEditor().isDebuggerEnabled()) {
      text = getJavaTextArea().getGutterText(line);
    }

    gfx.setColor(gutterTextColor);
    int textRight = Editor.LEFT_GUTTER - Editor.GUTTER_MARGIN;
    int textBaseline = textArea.lineToY(line) + fm.getHeight();

    if (text != null) {
      if (text.equals(JavaTextArea.BREAK_MARKER)) {
        drawDiamond(gfx, textRight - 8, textBaseline - 8, 8, 8);

      } else if (text.equals(JavaTextArea.STEP_MARKER)) {
        //drawRightArrow(gfx, textRight - 7, textBaseline - 7, 7, 6);
        drawRightArrow(gfx, textRight - 7, textBaseline - 7.5f, 7, 7);
      }
    } else {
      // if no special text for a breakpoint, just show the line number
      text = String.valueOf(line + 1);
      //text = makeOSF(String.valueOf(line + 1));

      gfx.setFont(gutterTextFont);
      // Right-align the text
      char[] txt = text.toCharArray();
      int tx = textRight - gfx.getFontMetrics().charsWidth(txt, 0, txt.length);
      // Using 'fm' here because it's relative to the editor text size,
      // not the numbers in the gutter
      Utilities.drawTabbedText(new Segment(txt, 0, text.length()),
                               tx, textBaseline, gfx, this, 0);
    }
  }


  private void drawDiamond(Graphics g, float x, float y, float w, float h) {
    Graphics2D g2 = (Graphics2D) g;
    GeneralPath path = new GeneralPath();
    path.moveTo(x + w/2, y);
    path.lineTo(x + w, y + h/2);
    path.lineTo(x + w/2, y + h);
    path.lineTo(x, y + h/2);
    path.closePath();
    g2.fill(path);
  }


  private void drawRightArrow(Graphics g, float x, float y, float w, float h) {
    Graphics2D g2 = (Graphics2D) g;
    GeneralPath path = new GeneralPath();
    path.moveTo(x, y);
    path.lineTo(x + w, y + h/2);
    path.lineTo(x, y + h);
    path.closePath();
    g2.fill(path);
  }


  /*
  // Failed attempt to switch line numbers to old-style figures
  String makeOSF(String what) {
    char[] c = what.toCharArray();
    for (int i = 0; i < c.length; i++) {
      c[i] += (char) (c[i] - '0' + 0x362);
    }
    return new String(c);
  }
  */


  /**
   * Paint the background color of a line.
   *
   * @param gfx
   *          the graphics context
   * @param line
   *          0-based line number
   * @param x
  private void paintLineBgColor(Graphics gfx, int line, int x) {
    int y = textArea.lineToY(line);
    y += fm.getLeading() + fm.getMaxDescent();
    int height = fm.getHeight();

    Color col = getJavaTextArea().getLineBgColor(line);
    if (col != null) {
      // paint line background
      gfx.setColor(col);
      gfx.fillRect(0, y, getWidth(), height);
    }
  }
   */


  /**
   * Remove all trailing whitespace from a line
   */
  static private String trimRight(String str) {
    int i = str.length() - 1;
    while (i >= 0 && Character.isWhitespace(str.charAt(i))) {
      i--;
    }
    return str.substring(0, i+1);
  }


  /**
   * Paints the underline for an error/warning line
   *
   * @param gfx
   *          the graphics context
   * @param tokenMarker
   * @param line
   *          0-based line number: NOTE
   * @param x
   */
  protected void paintErrorLine(Graphics gfx, int line, int x) {
    List problems = getJavaEditor().findProblems(line);
    for (Problem problem : problems) {
      int startOffset = problem.getStartOffset();
      int stopOffset = problem.getStopOffset();

      int lineOffset = textArea.getLineStartOffset(line);

      int wiggleStart = Math.max(startOffset, lineOffset);
      int wiggleStop = Math.min(stopOffset, textArea.getLineStopOffset(line));

      int y = textArea.lineToY(line) + fm.getLeading() + fm.getMaxDescent();

      try {
        String badCode = null;
        String goodCode = null;
        try {
          SyntaxDocument doc = textArea.getDocument();
          badCode = doc.getText(wiggleStart, wiggleStop - wiggleStart);
          goodCode = doc.getText(lineOffset, wiggleStart - lineOffset);
          //log("paintErrorLine() LineText GC: " + goodCode);
          //log("paintErrorLine() LineText BC: " + badCode);
        } catch (BadLocationException bl) {
          // Error in the import statements or end of code.
          // System.out.print("BL caught. " + ta.getLineCount() + " ,"
          // + line + " ,");
          // log((ta.getLineStopOffset(line) - start - 1));
          return;
        }

        int trimmedLength = badCode.trim().length();
        int rightTrimmedLength = trimRight(badCode).length();
        int leftTrimLength = rightTrimmedLength - trimmedLength;

        // Fix offsets when bad code is just whitespace
        if (trimmedLength == 0) {
          leftTrimLength = 0;
          rightTrimmedLength = badCode.length();
        }

        int x1 = textArea.offsetToX(line, goodCode.length() + leftTrimLength);
        int x2 = textArea.offsetToX(line, goodCode.length() + rightTrimmedLength);
        if (x1 == x2) x2 += fm.stringWidth(" ");
        int y1 = y + fm.getHeight() - 2;

        if (line != problem.getLineNumber()) {
          x1 = Editor.LEFT_GUTTER; // on the following lines, wiggle extends to the left border
        }

        gfx.setColor(errorUnderlineColor);
        if (problem.isWarning()) {
          gfx.setColor(warningUnderlineColor);
        }
        paintSquiggle(gfx, y1, x1, x2);

      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }


  static private void paintSquiggle(Graphics g, int y, int x1, int x2) {
    int xx = x1;

    while (xx < x2) {
      g.drawLine(xx, y, xx + 2, y + 1);
      xx += 2;
      g.drawLine(xx, y + 1, xx + 2, y);
      xx += 2;
    }
  }


  /**
   * Loads theme for TextAreaPainter(XQMode)
   */
  public void setMode(Mode mode) {
    errorUnderlineColor = mode.getColor("editor.error.underline.color");
    warningUnderlineColor = mode.getColor("editor.warning.underline.color");

    gutterTextFont = mode.getFont("editor.gutter.text.font");
    gutterTextColor = mode.getColor("editor.gutter.text.color");
    gutterLineHighlightColor = mode.getColor("editor.gutter.linehighlight.color");
  }


  @Override
  public String getToolTipText(MouseEvent evt) {
    int line = evt.getY() / getFontMetrics().getHeight() + textArea.getFirstLine();
    if (line >= 0 || line < textArea.getLineCount()) {
      List problems = getJavaEditor().findProblems(line);
      for (Problem problem : problems) {
        int lineStart = textArea.getLineStartOffset(line);
        int lineEnd = textArea.getLineStopOffset(line);

        int errorStart = problem.getStartOffset();
        int errorEnd = problem.getStopOffset() + 1;

        int startOffset = Math.max(errorStart, lineStart) - lineStart;
        int stopOffset = Math.min(errorEnd, lineEnd) - lineStart;

        int x = evt.getX();

        if (x >= getJavaTextArea().offsetToX(line, startOffset) &&
            x <= getJavaTextArea().offsetToX(line, stopOffset)) {
          getJavaEditor().statusToolTip(JavaTextAreaPainter.this,
                                        problem.getMessage(),
                                        problem.isError());
          return super.getToolTipText(evt);
        }
      }
    }
    setToolTipText(null);
    return super.getToolTipText(evt);
  }


  // TweakMode code
	protected int horizontalAdjustment = 0;

	public boolean tweakMode = false;
	public List> handles;
	public List> colorBoxes;

	public Handle mouseHandle = null;
	public ColorSelector colorSelector;

	int cursorType;
	BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
	Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor");


	@Override
	synchronized public void paint(Graphics gfx) {
		super.paint(gfx);

		if (tweakMode && handles != null) {
			int currentTab = getCurrentCodeIndex();
			// enable anti-aliasing
			Graphics2D g2d = (Graphics2D)gfx;
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                				   RenderingHints.VALUE_ANTIALIAS_ON);

			for (Handle n : handles.get(currentTab)) {
				// update n position and width, and draw it
				int lineStartChar = textArea.getLineStartOffset(n.line);
				int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar);
				int y = textArea.lineToY(n.line) + fm.getHeight() + 1;
				int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar);
				n.setPos(x, y);
				n.setWidth(end - x);
				n.draw(g2d, n==mouseHandle);
			}

			// draw color boxes
			for (ColorControlBox cBox: colorBoxes.get(currentTab)) {
				int lineStartChar = textArea.getLineStartOffset(cBox.getLine());
				int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar);
				int y = textArea.lineToY(cBox.getLine()) + fm.getDescent();
				cBox.setPos(x, y+1);
				cBox.draw(g2d);
			}
		}
	}


	protected void startTweakMode() {
	  addMouseListener(this);
	  addMouseMotionListener(this);
	  tweakMode = true;
	  setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		repaint();
	}


	protected void stopTweakMode() {
		tweakMode = false;

		if (colorSelector != null) {
			colorSelector.hide();
			WindowEvent windowEvent =
			  new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING);
			colorSelector.frame.dispatchEvent(windowEvent);
		}

		setCursor(new Cursor(Cursor.TEXT_CURSOR));
		repaint();
	}


	protected void updateInterface(List> handles,
	                               List> colorBoxes) {
		this.handles = handles;
		this.colorBoxes = colorBoxes;

		initInterfacePositions();
		repaint();
	}


	/**
	* Initialize all the number changing interfaces.
	* synchronize this method to prevent the execution of 'paint' in the middle.
	* (don't paint while we make changes to the text of the editor)
	*/
	private synchronized void initInterfacePositions() {
		SketchCode[] code = getEditor().getSketch().getCode();
		int prevScroll = textArea.getVerticalScrollPosition();
		String prevText = textArea.getText();

		for (int tab=0; tab




© 2015 - 2024 Weber Informatics LLC | Privacy Policy