/*
* Copyright (C) 2012 Martin Leopold <m@martinleopold.com>
*
* 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 (at your option) 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 processing.mode.experimental;
import static processing.mode.experimental.ExperimentalMode.log;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import processing.app.syntax.TextAreaDefaults;
import processing.app.syntax.TokenMarker;
/**
* Customized line painter. Adds support for background colors, left hand gutter
* area with background color and text.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class TextAreaPainter extends processing.app.syntax.TextAreaPainter {
protected TextArea ta; // we need the subclassed textarea
protected ErrorCheckerService errorCheckerService;
/**
* Error line underline color
*/
public Color errorColor = new Color(0xED2630);
/**
* Warning line underline color
*/
public Color warningColor = new Color(0xFFC30E);
/**
* Color of Error Marker
*/
public Color errorMarkerColor = new Color(0xED2630);
/**
* Color of Warning Marker
*/
public Color warningMarkerColor = new Color(0xFFC30E);
static int ctrlMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
public TextAreaPainter(TextArea textArea, TextAreaDefaults defaults) {
super(textArea, defaults);
ta = textArea;
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
// log( " Meta,Ctrl "+ (evt.getModifiers() & ctrlMask));
if (evt.getButton() == MouseEvent.BUTTON1) {
if (evt.isControlDown() || evt.isMetaDown())
handleCtrlClick(evt);
}
}
});
}
// public void processKeyEvent(KeyEvent evt) {
// log(evt);
// }
void handleCtrlClick(MouseEvent evt) {
log("--handleCtrlClick--");
int off = ta.xyToOffset(evt.getX(), evt.getY());
if (off < 0)
return;
int line = ta.getLineOfOffset(off);
if (line < 0)
return;
String s = ta.getLineText(line);
if (s == null)
return;
else if (s.length() == 0)
return;
else {
int x = ta.xToOffset(line, evt.getX()), x2 = x + 1, x1 = x - 1;
log("x="+x);
int xLS = off - ta.getLineStartNonWhiteSpaceOffset(line);
if (x < 0 || x >= s.length())
return;
String word = s.charAt(x) + "";
if (s.charAt(x) == ' ')
return;
if (!(Character.isLetterOrDigit(s.charAt(x)) || s.charAt(x) == '_' || s
.charAt(x) == '$'))
return;
int i = 0;
while (true) {
i++;
if (x1 >= 0 && x1 < s.length()) {
if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') {
word = s.charAt(x1--) + word;
xLS--;
} else
x1 = -1;
} else
x1 = -1;
if (x2 >= 0 && x2 < s.length()) {
if (Character.isLetterOrDigit(s.charAt(x2)) || s.charAt(x2) == '_'
|| s.charAt(x2) == '$')
word = word + s.charAt(x2++);
else
x2 = -1;
} else
x2 = -1;
if (x1 < 0 && x2 < 0)
break;
if (i > 200) {
// time out!
// System.err.println("Whoopsy! :P");
break;
}
}
if (Character.isDigit(word.charAt(0)))
return;
log(errorCheckerService.mainClassOffset + line +
"|" + line + "| offset " + xLS + word + " <= \n");
errorCheckerService.getASTGenerator()
.scrollToDeclaration(line, word, xLS);
}
}
private void loadTheme(ExperimentalMode mode) {
errorColor = mode.getThemeColor("editor.errorcolor", errorColor);
warningColor = mode.getThemeColor("editor.warningcolor", warningColor);
errorMarkerColor = mode.getThemeColor("editor.errormarkercolor",
errorMarkerColor);
warningMarkerColor = mode.getThemeColor("editor.warningmarkercolor",
warningMarkerColor);
}
/**
* 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, TokenMarker tokenMarker, int line,
int x) {
try {
//TODO: This line is causing NPE's randomly ever since I added the toggle for
//Java Mode/Debugger toolbar.
super.paintLine(gfx, tokenMarker, line, x + ta.getGutterWidth());
} catch (Exception e) {
log(e.getMessage());
}
if(ta.editor.debugToolbarEnabled != null && ta.editor.debugToolbarEnabled.get()){
// paint gutter
paintGutterBg(gfx, line, x);
// disabled line background after P5 2.1, since it adds highlight by default
//paintLineBgColor(gfx, line, x + ta.getGutterWidth());
paintGutterLine(gfx, line, x);
// paint gutter symbol
paintGutterText(gfx, line, x);
}
paintErrorLine(gfx, line, x);
}
/**
* Paint the gutter background (solid color).
*
* @param gfx
* the graphics context
* @param line
* 0-based line number
* @param x
* horizontal position
*/
protected void paintGutterBg(Graphics gfx, int line, int x) {
gfx.setColor(ta.gutterBgColor);
int y = ta.lineToY(line) + fm.getLeading() + fm.getMaxDescent();
gfx.fillRect(0, y, ta.getGutterWidth(), fm.getHeight());
}
/**
* Paint the vertical gutter separator line.
*
* @param gfx
* the graphics context
* @param line
* 0-based line number
* @param x
* horizontal position
*/
protected void paintGutterLine(Graphics gfx, int line, int x) {
int y = ta.lineToY(line) + fm.getLeading() + fm.getMaxDescent();
gfx.setColor(ta.gutterLineColor);
gfx.drawLine(ta.getGutterWidth(), y, ta.getGutterWidth(),
y + fm.getHeight());
}
/**
* Paint the gutter text.
*
* @param gfx
* the graphics context
* @param line
* 0-based line number
* @param x
* horizontal position
*/
protected void paintGutterText(Graphics gfx, int line, int x) {
String text = ta.getGutterText(line);
if (text == null) {
return;
}
gfx.setFont(getFont());
Color textColor = ta.getGutterTextColor(line);
if (textColor == null) {
gfx.setColor(getForeground());
} else {
gfx.setColor(textColor);
}
int y = ta.lineToY(line) + fm.getHeight();
// draw 4 times to make it appear bold, displaced 1px to the right, to the bottom and bottom right.
//int len = text.length() > ta.gutterChars ? ta.gutterChars : text.length();
Utilities.drawTabbedText(new Segment(text.toCharArray(), 0, text.length()),
ta.getGutterMargins(), y, gfx, this, 0);
Utilities.drawTabbedText(new Segment(text.toCharArray(), 0, text.length()),
ta.getGutterMargins() + 1, y, gfx, this, 0);
Utilities.drawTabbedText(new Segment(text.toCharArray(), 0, text.length()),
ta.getGutterMargins(), y + 1, gfx, this, 0);
Utilities.drawTabbedText(new Segment(text.toCharArray(), 0, text.length()),
ta.getGutterMargins() + 1, y + 1, gfx, this, 0);
}
/**
* Paint the background color of a line.
*
* @param gfx
* the graphics context
* @param line
* 0-based line number
* @param x
*/
protected void paintLineBgColor(Graphics gfx, int line, int x) {
int y = ta.lineToY(line);
y += fm.getLeading() + fm.getMaxDescent();
int height = fm.getHeight();
// get the color
Color col = ta.getLineBgColor(line);
//System.out.print("bg line " + line + ": ");
// no need to paint anything
if (col == null) {
//log("none");
return;
}
// paint line background
gfx.setColor(col);
gfx.fillRect(0, y, getWidth(), height);
}
/**
* 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) {
if (errorCheckerService == null) {
return;
}
if (errorCheckerService.problemsList == null) {
return;
}
boolean notFound = true;
boolean isWarning = false;
Problem problem = null;
// Check if current line contains an error. If it does, find if it's an
// error or warning
for (ErrorMarker emarker : errorCheckerService.getEditor().errorBar.errorPoints) {
if (emarker.getProblem().getLineNumber() == line) {
notFound = false;
if (emarker.getType() == ErrorMarker.Warning) {
isWarning = true;
}
problem = emarker.getProblem();
//log(problem.toString());
break;
}
}
if (notFound) {
return;
}
// Determine co-ordinates
// log("Hoff " + ta.getHorizontalOffset() + ", " +
// horizontalAdjustment);
int y = ta.lineToY(line);
y += fm.getLeading() + fm.getMaxDescent();
int height = fm.getHeight();
int start = ta.getLineStartOffset(line) + problem.getPDELineStartOffset();
int pLength = problem.getPDELineStopOffset() + 1
- problem.getPDELineStartOffset();
try {
String badCode = null;
String goodCode = null;
try {
badCode = ta.getDocument().getText(start, pLength);
goodCode = ta.getDocument().getText(ta.getLineStartOffset(line),
problem.getPDELineStartOffset());
//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;
}
// Take care of offsets
int aw = fm.stringWidth(trimRight(badCode)) + ta.getHorizontalOffset(); // apparent width. Whitespaces
// to the left of line + text
// width
int rw = fm.stringWidth(badCode.trim()); // real width
int x1 = fm.stringWidth(goodCode) + (aw - rw), y1 = y + fm.getHeight()
- 2, x2 = x1 + rw;
// Adding offsets for the gutter
x1 += ta.getGutterWidth();
x2 += ta.getGutterWidth();
// gfx.fillRect(x1, y, rw, height);
// Let the painting begin!
// Little rect at starting of a line containing errors - disabling it for now
// gfx.setColor(errorMarkerColor);
// if (isWarning) {
// gfx.setColor(warningMarkerColor);
// }
// gfx.fillRect(1, y + 2, 3, height - 2);
gfx.setColor(errorColor);
if (isWarning) {
gfx.setColor(warningColor);
}
int xx = x1;
// Draw the jagged lines
while (xx < x2) {
gfx.drawLine(xx, y1, xx + 2, y1 + 1);
xx += 2;
gfx.drawLine(xx, y1 + 1, xx + 2, y1);
xx += 2;
}
} catch (Exception e) {
System.out
.println("Looks like I messed up! XQTextAreaPainter.paintLine() : "
+ e);
//e.printStackTrace();
}
// Won't highlight the line. Select the text instead.
// gfx.setColor(Color.RED);
// gfx.fillRect(2, y, 3, height);
}
/**
* Trims out trailing whitespaces (to the right)
*
* @param string
* @return - String
*/
private String trimRight(String string) {
String newString = "";
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) != ' ') {
newString = string.substring(0, i) + string.trim();
break;
}
}
return newString;
}
/**
* Sets ErrorCheckerService and loads theme for TextAreaPainter(XQMode)
*
* @param ecs
* @param mode
*/
public void setECSandTheme(ErrorCheckerService ecs, ExperimentalMode mode) {
this.errorCheckerService = ecs;
loadTheme(mode);
}
public String getToolTipText(java.awt.event.MouseEvent evt) {
int off = ta.xyToOffset(evt.getX(), evt.getY());
if (off < 0)
return null;
int line = ta.getLineOfOffset(off);
if (line < 0)
return null;
String s = ta.getLineText(line);
if (s == null)
return evt.toString();
else if (s.length() == 0)
return null;
else {
int x = ta.xToOffset(line, evt.getX()), x2 = x + 1, x1 = x - 1;
int xLS = off - ta.getLineStartNonWhiteSpaceOffset(line);
if (x < 0 || x >= s.length())
return null;
String word = s.charAt(x) + "";
if (s.charAt(x) == ' ')
return null;
if (!(Character.isLetterOrDigit(s.charAt(x)) || s.charAt(x) == '_' || s
.charAt(x) == '$'))
return null;
int i = 0;
while (true) {
i++;
if (x1 >= 0 && x1 < s.length()) {
if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') {
word = s.charAt(x1--) + word;
xLS--;
} else
x1 = -1;
} else
x1 = -1;
if (x2 >= 0 && x2 < s.length()) {
if (Character.isLetterOrDigit(s.charAt(x2)) || s.charAt(x2) == '_'
|| s.charAt(x2) == '$')
word = word + s.charAt(x2++);
else
x2 = -1;
} else
x2 = -1;
if (x1 < 0 && x2 < 0)
break;
if (i > 200) {
// time out!
// System.err.println("Whoopsy! :P");
break;
}
}
if (Character.isDigit(word.charAt(0)))
return null;
String tooltipText = errorCheckerService.getASTGenerator()
.getLabelForASTNode(line, word, xLS);
log(errorCheckerService.mainClassOffset + " MCO "
+ "|" + line + "| offset " + xLS + word + " <= offf: "+off+ "\n");
if (tooltipText != null)
return tooltipText;
return word;
}
}
}