processing.mode.java.pdex.JavaTextAreaPainter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-mode Show documentation
Show all versions of java-mode Show documentation
Processing is a programming language, development environment, and online community.
This Java Mode package contains the Java mode for Processing IDE.
/* -*- 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