moving things around to merge PDE X

This commit is contained in:
Ben Fry
2015-01-20 14:56:21 -05:00
parent 25099cfbfa
commit 260d131c00
56 changed files with 38 additions and 2 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,827 @@
/*
* Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.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 static processing.mode.experimental.ExperimentalMode.logE;
import static processing.mode.experimental.ExperimentalMode.log2;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.PlainDocument;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
/**
* Wrapper class for ASTNode objects
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
public class ASTNodeWrapper {
private ASTNode Node;
private String label;
private int lineNumber;
//private int apiLevel;
/*
* TODO: Every ASTNode object in ASTGenerator.codetree is stored as a
* ASTNodeWrapper instance. So how resource heavy would it be to store a
* pointer to ECS in every instance of ASTNodeWrapper? Currently I will rather
* pass an ECS pointer in the argument when I need to access a method which
* requires a method defined in ECS, i.e, only on demand.
* Bad design choice for ECS methods? IDK, yet.
*/
public ASTNodeWrapper(ASTNode node) {
if (node == null){
return;
}
this.Node = node;
label = getNodeAsString(node);
if (label == null)
label = node.toString();
lineNumber = getLineNumber(node);
label += " | Line " + lineNumber;
//apiLevel = 0;
}
public ASTNodeWrapper(ASTNode node, String label){
if (node == null){
return;
}
this.Node = node;
if(label != null)
this.label = label;
else{
label = getNodeAsString(node);
if (label == null)
label = node.toString();
label += " | Line " + lineNumber;
}
lineNumber = getLineNumber(node);
}
/**
* For this node, finds various offsets (java code).
* Note that line start offset for this node is int[2] - int[1]
* @return int[]{line number, line number start offset, node start offset,
* node length}
*/
public int[] getJavaCodeOffsets(ErrorCheckerService ecs) {
int nodeOffset = Node.getStartPosition(), nodeLength = Node
.getLength();
log("0.nodeOffset " + nodeOffset);
ASTNode thisNode = Node;
while (thisNode.getParent() != null) {
if (getLineNumber(thisNode.getParent()) == lineNumber) {
thisNode = thisNode.getParent();
} else {
break;
}
}
/*
* There's an edge case here - multiple statements in a single line.
* After identifying the statement with the line number, I'll have to
* look at previous tree nodes in the same level for same line number.
* The correct line start offset would be the line start offset of
* the first node with this line number.
*
* Using linear search for now. P.S: Eclipse AST iterators are messy.
* TODO: binary search might improve speed by 0.001%?
*/
int altStartPos = thisNode.getStartPosition();
log("1.Altspos " + altStartPos);
thisNode = thisNode.getParent();
Javadoc jd = null;
/*
* There's another case that needs to be handled. If a TD, MD or FD
* contains javadoc comments(multi or single line) the starting position
* of the javadoc is treated as the beginning of the declaration by the AST parser.
* But that's clearly not what we need. The true decl begins after the javadoc ends.
* So this offset needs to be found carefully and stored in altStartPos
*
*/
if (thisNode instanceof TypeDeclaration) {
jd = ((TypeDeclaration) thisNode).getJavadoc();
altStartPos = getJavadocOffset((TypeDeclaration) thisNode);
log("Has t jdoc " + ((TypeDeclaration) thisNode).getJavadoc());
} else if (thisNode instanceof MethodDeclaration) {
altStartPos = getJavadocOffset((MethodDeclaration) thisNode);
jd = ((MethodDeclaration) thisNode).getJavadoc();
log("Has m jdoc " + jd);
} else if (thisNode instanceof FieldDeclaration) {
FieldDeclaration fd = ((FieldDeclaration) thisNode);
jd = fd.getJavadoc();
log("Has f jdoc " + fd.getJavadoc());
altStartPos = getJavadocOffset(fd);
//nodeOffset = ((VariableDeclarationFragment)(fd.fragments().get(0))).getName().getStartPosition();
}
if (jd == null) {
log("Visiting children of node " + getNodeAsString(thisNode));
@SuppressWarnings("unchecked")
Iterator<StructuralPropertyDescriptor> it =
thisNode.structuralPropertiesForType().iterator();
boolean flag = true;
while (it.hasNext()) {
StructuralPropertyDescriptor prop = it.next();
if (prop.isChildListProperty()) {
List<ASTNode> nodelist = (List<ASTNode>)
thisNode.getStructuralProperty(prop);
log("prop " + prop);
for (ASTNode cnode : nodelist) {
log("Visiting node " + getNodeAsString(cnode));
if (getLineNumber(cnode) == lineNumber) {
if (flag) {
altStartPos = cnode.getStartPosition();
// log("multi...");
flag = false;
} else {
if (cnode == Node) {
// loop only till the current node.
break;
}
// We've located the first node in the line.
// Now normalize offsets till Node
//altStartPos += normalizeOffsets(cnode);
}
}
}
}
}
log("Altspos " + altStartPos);
}
int pdeoffsets[] = getPDECodeOffsets(ecs);
String pdeCode = ecs.getPDECodeAtLine(pdeoffsets[0],pdeoffsets[1] - 1).trim();
int vals[] = createOffsetMapping(ecs, pdeCode,nodeOffset - altStartPos,nodeLength);
if (vals != null)
return new int[] {
lineNumber, nodeOffset + vals[0] - altStartPos, vals[1] };
else {// no offset mapping needed
log("joff[1] = " + (nodeOffset - altStartPos));
return new int[] { lineNumber, nodeOffset - altStartPos, nodeLength };
}
}
/**
* When FD has javadoc attached, the beginning of FD is marked as the
* start of the javadoc. This kind of screws things when trying to locate
* the exact name of the FD. So, offset compensations...
*
* @param fd
* @return
*/
private int getJavadocOffset(FieldDeclaration fd){
List<ASTNode> list = fd.modifiers();
SimpleName sn = (SimpleName) getNode();
Type tp = fd.getType();
int lineNum = getLineNumber(sn);
log("SN "+sn + ", " + lineNum);
for (ASTNode astNode : list) {
if(getLineNumber(astNode) == lineNum)
{
log("first node in that line " + astNode);
log("diff " + (sn.getStartPosition() - astNode.getStartPosition()));
return (astNode.getStartPosition());
}
}
if(getLineNumber(fd.getType()) == lineNum)
{
log("first node in that line " + tp);
log("diff " + (sn.getStartPosition() - tp.getStartPosition()));
return (tp.getStartPosition());
}
return 0;
}
/**
* When MD has javadoc attached, the beginning of FD is marked as the
* start of the javadoc. This kind of screws things when trying to locate
* the exact name of the MD. So, offset compensations...
*
* @param md
* @return
*/
private int getJavadocOffset(MethodDeclaration md) {
@SuppressWarnings("unchecked")
List<ASTNode> list = md.modifiers();
SimpleName sn = (SimpleName) getNode();
int lineNum = getLineNumber(sn);
log("SN " + sn + ", " + lineNum);
for (ASTNode astNode : list) {
if (getLineNumber(astNode) == lineNum) {
log("first node in that line " + astNode);
log("diff " + (sn.getStartPosition() - astNode.getStartPosition()));
return (astNode.getStartPosition());
}
}
if (!md.isConstructor()) {
Type tp = md.getReturnType2();
if (getLineNumber(tp) == lineNum) {
log("first node in that line " + tp);
log("diff " + (sn.getStartPosition() - tp.getStartPosition()));
return (tp.getStartPosition());
}
}
return 0;
}
/**
* When TD has javadoc attached, the beginning of FD is marked as the
* start of the javadoc. This kind of screws things when trying to locate
* the exact name of the TD. So, offset compensations...
*
* @param td
* @return
*/
private int getJavadocOffset(TypeDeclaration td){
// TODO: This isn't perfect yet. Class \n \n \n className still breaks it.. :'(
@SuppressWarnings("unchecked")
List<ASTNode> list = td.modifiers();
SimpleName sn = (SimpleName) getNode();
int lineNum = getLineNumber(sn);
log("SN "+sn + ", " + lineNum);
for (ASTNode astNode : list) {
if(getLineNumber(astNode) == lineNum)
{
log("first node in that line " + astNode);
log("diff " + (sn.getStartPosition() - astNode.getStartPosition()));
return (astNode.getStartPosition());
}
}
if(td.getJavadoc() != null){
log("diff "
+ (td.getJavadoc().getStartPosition() + td.getJavadoc().getLength() + 1));
return (td.getJavadoc().getStartPosition() + td.getJavadoc().getLength() + 1);
}
log("getJavadocOffset(TypeDeclaration td) "+sn + ", found nothing. Meh.");
return 0;
}
/**
* Finds the difference in pde and java code offsets
* @param source
* @param inpOffset
* @param nodeLen
* @return int[0] - difference in start offset, int[1] - node length
*/
private int[] createOffsetMapping(ErrorCheckerService ecs, String source, int inpOffset, int nodeLen) {
int ret[][] = getOffsetMapping(ecs, source);
if(ret == null){
// no offset mapping needed
return null;
}
int javaCodeMap[] = ret[0];
int pdeCodeMap[] = ret[1];
int pi = 1, pj = 1;
pj = 0;
pi = 0;
int count = 1;
// first find the java code index
pj = inpOffset;
int startIndex = javaCodeMap[pj];
// find beginning
while (pdeCodeMap[pi] != startIndex && pi < pdeCodeMap.length)
pi++;
int startoffDif = pi - pj;
int stopindex = javaCodeMap[pj + nodeLen - 1];
log(startIndex + "SI,St" + stopindex + "sod " + startoffDif);
// count till stopindex
while (pdeCodeMap[pi] < stopindex && pi < pdeCodeMap.length) {
pi++;
count++;
}
// log("PDE maps from " + pdeeCodeMap[pi]);
log("pde len " + count);
return new int[] { startoffDif, count };
}
/**
* Generates offset mapping between java and pde code
*
* @param source
* @return int[0] - java code offsets, int[1] = pde code offsets
*/
public int[][] getOffsetMapping(ErrorCheckerService ecs, String source){
/*
* This is some tricky shiz. So detailed explanation follows:
*
* The main issue here is that pde enhancements like color vars, # literals
* and int() type casting deviate from standard java. But I need to exact
* index matching for pde and java versions of snippets.For ex:
* "color col = #ffaadd;" <-PDE version
* "int col = 0xffffaadd;" <-Converted to Java
*
* For exact index mapping, I need to know at which indices either is
* deviating from the other and by what amount. Turns out, it isn't quite
* easy.(1) First I take the pde version of the code as an argument(pde
* version fetched from the editor directly). I then find all instances
* which need to be converted to pure java, marking those indices and the
* index correction needed. (2) Now all java conversions are applied after
* marking the offsets. This ensures that the index order isn't disturbed by
* one at a time conversions as done in preprocessCode() in ECS. Took me
* sometime to figure out this was a bug. (3) Next I create a table(two
* separate arrays) which allows me to look it up for matching any index
* between pde or java version of the snippet. This also lets me find out
* any difference in length between both versions.
*
* Keep in mind though, dark magic was involved in creating the final lookup
* table.
*
* TODO: This is a work in progress. There may be more bugs here in hiding.
*/
log("Src:" + source);
// Instead of converting pde into java, how can I simply extract the same source
// from the java code? Think. TODO
String sourceAlt = new String(source);
String sourceJava = ecs.astGenerator.getJavaSourceCodeLine(lineNumber);
TreeMap<Integer, Integer> offsetmap = new TreeMap<Integer, Integer>();
if(sourceJava.trim().startsWith("public") && !source.startsWith("public")){
offsetmap.put(0,6);
//TODO: This is a temp fix. You GOTTA rewrite offset matching
}
// Find all #[web color]
// Should be 6 digits only.
final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W";
Pattern webPattern = Pattern.compile(webColorRegexp);
Matcher webMatcher = webPattern.matcher(sourceAlt);
while (webMatcher.find()) {
// log("Found at: " + webMatcher.start());
// log("-> " + found);
offsetmap.put(webMatcher.end() - 1, 3);
}
// Find all color data types
final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())";
Pattern colorPattern = Pattern.compile(colorTypeRegex);
Matcher colorMatcher = colorPattern.matcher(sourceAlt);
while (colorMatcher.find()) {
// System.out.print("Start index: " + colorMatcher.start());
// log(" End index: " + colorMatcher.end() + " ");
// log("-->" + colorMatcher.group() + "<--");
offsetmap.put(colorMatcher.end() - 1, -2);
}
// Find all int(), char()
String dataTypeFunc[] = { "int", "char", "float", "boolean", "byte" };
for (String dataType : dataTypeFunc) {
String dataTypeRegexp = "\\b" + dataType + "\\s*\\(";
Pattern pattern = Pattern.compile(dataTypeRegexp);
Matcher matcher = pattern.matcher(sourceAlt);
while (matcher.find()) {
// System.out.print("Start index: " + matcher.start());
// log(" End index: " + matcher.end() + " ");
// log("-->" + matcher.group() + "<--");
offsetmap.put(matcher.end() - 1, ("PApplet.parse").length());
}
matcher.reset();
sourceAlt = matcher.replaceAll("PApplet.parse"
+ Character.toUpperCase(dataType.charAt(0)) + dataType.substring(1)
+ "(");
}
if(offsetmap.isEmpty()){
log("No offset matching needed.");
return null;
}
// replace with 0xff[webcolor] and others
webMatcher = webPattern.matcher(sourceAlt);
while (webMatcher.find()) {
// log("Found at: " + webMatcher.start());
String found = sourceAlt.substring(webMatcher.start(), webMatcher.end());
// log("-> " + found);
sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1));
webMatcher = webPattern.matcher(sourceAlt);
}
colorMatcher = colorPattern.matcher(sourceAlt);
sourceAlt = colorMatcher.replaceAll("int");
log("From direct source: ");
// sourceAlt = sourceJava;
log(sourceAlt);
// Create code map. Beware! Dark magic ahead.
int javaCodeMap[] = new int[source.length() * 2];
int pdeCodeMap[] = new int[source.length() * 2];
int pi = 1, pj = 1;
int keySum = 0;
for (Integer key : offsetmap.keySet()) {
for (; pi < key +keySum; pi++) {
javaCodeMap[pi] = javaCodeMap[pi - 1] + 1;
}
for (; pj < key; pj++) {
pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1;
}
log(key + ":" + offsetmap.get(key));
int kval = offsetmap.get(key);
if (kval > 0) {
// repeat java offsets
pi--;
pj--;
for (int i = 0; i < kval; i++, pi++, pj++) {
if (pi > 1 && pj > 1) {
javaCodeMap[pi] = javaCodeMap[pi - 1];
pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1;
}
}
} else {
// repeat pde offsets
pi--;
pj--;
for (int i = 0; i < -kval; i++, pi++, pj++) {
if (pi > 1 && pj > 1) {
javaCodeMap[pi] = javaCodeMap[pi - 1] + 1;
pdeCodeMap[pj] = pdeCodeMap[pj - 1];
}
}
}
// after each adjustment, the key values need to keep
// up with changed offset
keySum += kval;
}
javaCodeMap[pi] = javaCodeMap[pi - 1] + 1;
pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1;
while (pi < sourceAlt.length()) {
javaCodeMap[pi] = javaCodeMap[pi - 1] + 1;
pi++;
}
while (pj < source.length()) {
pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1;
pj++;
}
// debug o/p
for (int i = 0; i < pdeCodeMap.length; i++) {
if (pdeCodeMap[i] > 0 || javaCodeMap[i] > 0 || i == 0) {
if (i < source.length())
log2(source.charAt(i));
log2(pdeCodeMap[i] + " - " + javaCodeMap[i]);
if (i < sourceAlt.length())
log2(sourceAlt.charAt(i));
log2(" <-[" + i + "]");
log("");
}
}
log("");
return new int[][]{javaCodeMap,pdeCodeMap};
}
/**
* Highlight the ASTNode in the editor, if it's of type
* SimpleName
* @param astGenerator
* @return - true if highlighting was successful
*/
public boolean highlightNode(ASTGenerator astGenerator){
if(!(Node instanceof SimpleName)){
return false;
}
SimpleName nodeName = (SimpleName) Node;
try {
//TODO: Redundant code. See ASTGenerator.getJavaSourceCodeline()
int javaLineNumber = getLineNumber(nodeName);
int pdeOffs[] = astGenerator.errorCheckerService
.calculateTabIndexAndLineNumber(javaLineNumber);
PlainDocument javaSource = new PlainDocument();
javaSource.insertString(0, astGenerator.errorCheckerService.sourceCode, null);
Element lineElement = javaSource.getDefaultRootElement()
.getElement(javaLineNumber-1);
if(lineElement == null) {
log(lineNumber + " line element null while highlighting " + nodeName);
return false;
}
String javaLine = javaSource.getText(lineElement.getStartOffset(),
lineElement.getEndOffset()
- lineElement.getStartOffset());
astGenerator.editor.getSketch().setCurrentCode(pdeOffs[0]);
String pdeLine = astGenerator.editor.getLineText(pdeOffs[1]);
String lookingFor = nodeName.toString();
log(lookingFor + ", " + nodeName.getStartPosition());
log(javaLineNumber +" JL " + javaLine + " LSO " + lineElement.getStartOffset() + ","
+ lineElement.getEndOffset());
log(pdeOffs[1] + " PL " + pdeLine);
if (!javaLine.contains(lookingFor) || !pdeLine.contains(lookingFor)) {
logE("Logical error in highLightNode(). Please file a bug report.");
return false;
}
OffsetMatcher ofm = new OffsetMatcher(pdeLine, javaLine);
int highlightStart = ofm.getPdeOffForJavaOff(nodeName.getStartPosition()
- lineElement.getStartOffset(),
nodeName.getLength());
if (highlightStart == -1) {
logE("Logical error in highLightNode() during offset matching. " +
"Please file a bug report.");
return false;
}
int lso = astGenerator.editor.ta.getLineStartOffset(pdeOffs[1]);
highlightStart += lso;
astGenerator.editor.setSelection(highlightStart, highlightStart
+ nodeName.getLength());
/*
// First find the name in the java line, and marks its index
Pattern toFind = Pattern.compile("\\b" + nodeName.toString() + "\\b");
Matcher matcher = toFind.matcher(javaLine);
int count = 0, index = 0;
int lsto = lineElement.getStartOffset();
while(matcher.find()){
count++;
//log(matcher.start() + lsto);
if(lsto + matcher.start() == nodeName.getStartPosition())
break;
}
log("count=" + count);
index = 0;
// find the same name in the pde line by its index and get its offsets
matcher = toFind.matcher(pdeLine);
while(matcher.find()){
count--;
if(count == 0){
log("Found on pde line lso: " + matcher.start());
index = matcher.end();
break;
}
}
log("pde lso " + (index - lookingFor.length()));
int lso = astGenerator.editor.ta.getLineStartOffset(pdeOffs[1]);
astGenerator.editor.setSelection(lso + index - lookingFor.length(), lso
+ index);
*/
return true;
} catch (BadLocationException e) {
logE("BLE in highLightNode() for " + nodeName);
e.printStackTrace();
}
return false;
}
/**
* Gets offset mapping between java and pde code
* int[0][x] stores the java code offset and
* int[1][x] is the corresponding offset in pde code
* @param ecs
* @return int[0] - java code offset, int[1] - pde code offset
*/
public int[][] getOffsetMapping(ErrorCheckerService ecs){
int pdeoffsets[] = getPDECodeOffsets(ecs);
String pdeCode = ecs.getPDECodeAtLine(pdeoffsets[0],pdeoffsets[1] - 1).trim();
return getOffsetMapping(ecs, pdeCode);
}
/**
*
* @param ecs
* - ErrorCheckerService instance
* @return int[0] - tab number, int[1] - line number in the int[0] tab, int[2]
* - line start offset, int[3] - offset from line start int[2] and
* int[3] are on TODO
*/
public int[] getPDECodeOffsets(ErrorCheckerService ecs) {
return ecs.JavaToPdeOffsets(lineNumber + 1, Node.getStartPosition());
}
public int getPDECodeOffsetForSN(ASTGenerator astGen){
if (Node instanceof SimpleName) {
Element lineElement = astGen.getJavaSourceCodeElement(lineNumber);
log("Line element off " + lineElement.getStartOffset());
OffsetMatcher ofm = new OffsetMatcher(
astGen
.getPDESourceCodeLine(lineNumber),
astGen
.getJavaSourceCodeLine(lineNumber));
//log("");
int pdeOffset = ofm.getPdeOffForJavaOff(Node.getStartPosition()
- lineElement.getStartOffset(), Node.toString().length());
return pdeOffset;
}
return -1;
}
public String toString() {
return label;
}
public ASTNode getNode() {
return Node;
}
public String getLabel() {
return label;
}
public int getNodeType() {
return Node.getNodeType();
}
public int getLineNumber() {
return lineNumber;
}
/**
* Applies pde enhancements to code.
* TODO: Code reuse happening here. :\
* @param source
* @return
*/
public static String getJavaCode(String source){
log("Src:" + source);
String sourceAlt = new String(source);
// Find all #[web color]
// Should be 6 digits only.
final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W";
Pattern webPattern = Pattern.compile(webColorRegexp);
Matcher webMatcher = webPattern.matcher(sourceAlt);
while (webMatcher.find()) {
// log("Found at: " + webMatcher.start());
// log("-> " + found);
}
// Find all color data types
final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())";
Pattern colorPattern = Pattern.compile(colorTypeRegex);
Matcher colorMatcher = colorPattern.matcher(sourceAlt);
while (colorMatcher.find()) {
// System.out.print("Start index: " + colorMatcher.start());
// log(" End index: " + colorMatcher.end() + " ");
// log("-->" + colorMatcher.group() + "<--");
}
// Find all int(), char()
String dataTypeFunc[] = { "int", "char", "float", "boolean", "byte" };
for (String dataType : dataTypeFunc) {
String dataTypeRegexp = "\\b" + dataType + "\\s*\\(";
Pattern pattern = Pattern.compile(dataTypeRegexp);
Matcher matcher = pattern.matcher(sourceAlt);
while (matcher.find()) {
// System.out.print("Start index: " + matcher.start());
// log(" End index: " + matcher.end() + " ");
// log("-->" + matcher.group() + "<--");
}
matcher.reset();
sourceAlt = matcher.replaceAll("PApplet.parse"
+ Character.toUpperCase(dataType.charAt(0)) + dataType.substring(1)
+ "(");
}
// replace with 0xff[webcolor] and others
webMatcher = webPattern.matcher(sourceAlt);
while (webMatcher.find()) {
// log("Found at: " + webMatcher.start());
String found = sourceAlt.substring(webMatcher.start(), webMatcher.end());
// log("-> " + found);
sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1));
webMatcher = webPattern.matcher(sourceAlt);
}
colorMatcher = colorPattern.matcher(sourceAlt);
sourceAlt = colorMatcher.replaceAll("int");
log("Converted:"+sourceAlt);
return sourceAlt;
}
private static int getLineNumber(ASTNode node) {
return ((CompilationUnit) node.getRoot()).getLineNumber(node
.getStartPosition());
}
/*private static int getLineNumber2(ASTNode thisNode) {
int jdocOffset = 0; Javadoc jd = null;
if(thisNode instanceof TypeDeclaration){
jd = ((TypeDeclaration)thisNode).getJavadoc();
log("Has t jdoc " + ((TypeDeclaration)thisNode).getJavadoc());
} else if(thisNode instanceof MethodDeclaration){
jd = ((MethodDeclaration)thisNode).getJavadoc();
log("Has m jdoc " + jd);
} else if(thisNode instanceof FieldDeclaration){
jd = ((FieldDeclaration)thisNode).getJavadoc();
log("Has f jdoc " + ((FieldDeclaration)thisNode).getJavadoc());
}
if(jd != null){
jdocOffset = 1+jd.getLength();
}
log("ln 2 = " + ((CompilationUnit) thisNode.getRoot()).getLineNumber(thisNode
.getStartPosition() + jdocOffset));
return ((CompilationUnit) thisNode.getRoot()).getLineNumber(thisNode
.getStartPosition() + jdocOffset);
}*/
static private String getNodeAsString(ASTNode node) {
if (node == null)
return "NULL";
String className = node.getClass().getName();
int index = className.lastIndexOf(".");
if (index > 0)
className = className.substring(index + 1);
// if(node instanceof BodyDeclaration)
// return className;
String value = className;
if (node instanceof TypeDeclaration)
value = ((TypeDeclaration) node).getName().toString() + " | " + className;
else if (node instanceof MethodDeclaration)
value = ((MethodDeclaration) node).getName().toString() + " | "
+ className;
else if (node instanceof MethodInvocation)
value = ((MethodInvocation) node).getName().toString() + " | "
+ className;
else if (node instanceof FieldDeclaration)
value = ((FieldDeclaration) node).toString() + " FldDecl| ";
else if (node instanceof SingleVariableDeclaration)
value = ((SingleVariableDeclaration) node).getName() + " - "
+ ((SingleVariableDeclaration) node).getType() + " | SVD ";
else if (node instanceof ExpressionStatement)
value = node.toString() + className;
else if (node instanceof SimpleName)
value = ((SimpleName) node).getFullyQualifiedName() + " | " + className;
else if (node instanceof QualifiedName)
value = node.toString() + " | " + className;
else if (className.startsWith("Variable"))
value = node.toString() + " | " + className;
else if (className.endsWith("Type"))
value = node.toString() + " |" + className;
value += " [" + node.getStartPosition() + ","
+ (node.getStartPosition() + node.getLength()) + "]";
value += " Line: "
+ ((CompilationUnit) node.getRoot()).getLineNumber(node
.getStartPosition());
return value;
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 com.sun.jdi.ArrayReference;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.Value;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Specialized {@link VariableNode} for representing single fields in an array.
* Overrides {@link #setValue} to properly change the value of the encapsulated
* array field.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class ArrayFieldNode extends VariableNode {
protected ArrayReference array;
protected int index;
/**
* Construct an {@link ArrayFieldNode}.
*
* @param name the name
* @param type the type
* @param value the value
* @param array a reference to the array
* @param index the index inside the array
*/
public ArrayFieldNode(String name, String type, Value value, ArrayReference array, int index) {
super(name, type, value);
this.array = array;
this.index = index;
}
@Override
public void setValue(Value value) {
try {
array.setValue(index, value);
} catch (InvalidTypeException ex) {
Logger.getLogger(ArrayFieldNode.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotLoadedException ex) {
Logger.getLogger(ArrayFieldNode.class.getName()).log(Level.SEVERE, null, ex);
}
this.value = value;
}
}

View File

@@ -0,0 +1,353 @@
/*
* Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.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.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Timer;
import processing.app.Base;
import processing.app.Sketch;
/**
* Autosave utility for saving sketch backups in the background after
* certain intervals
* NOTE: This was developed as an experiment, but disabled for now.
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
public class AutoSaveUtil {
private DebugEditor editor;
private Timer timer;
// private int saveTime;
private File autosaveDir, pastSave;
private boolean isSaving;
private boolean isAutoSaveBackup;
private File sketchFolder, sketchBackupFolder;
private static final String AUTOSAVEFOLDER = "__autosave__";
/**
*
* @param dedit
* @param timeOut - in minutes, how frequently should saves occur
*/
public AutoSaveUtil(DebugEditor dedit, int timeOut){
/*
editor = dedit;
if (timeOut < 1) { // less than 1 minute not allowed!
saveTime = -1;
throw new IllegalArgumentException("");
}
else{
saveTime = timeOut * 60 * 1000;
log("AutoSaver Interval(mins): " + timeOut);
}
checkIfBackup();
if(isAutoSaveBackup){
sketchBackupFolder = sketchFolder;
}
else{
autosaveDir = new File(editor.getSketch().getFolder().getAbsolutePath() + File.separator + AUTOSAVEFOLDER);
sketchFolder = editor.getSketch().getFolder();
sketchBackupFolder = autosaveDir;
}*/
}
/**
* If the sketch path looks like ../__autosave__/../FooSketch
* then assume this is a backup sketch
*/
private void checkIfBackup(){
File parent = sketchFolder.getParentFile().getParentFile();
if(parent.isDirectory() && parent.getName().equals(AUTOSAVEFOLDER)){
isAutoSaveBackup = true;
log("IS AUTOSAVE " + sketchFolder.getAbsolutePath());
}
}
public File getActualSketchFolder(){
if(isAutoSaveBackup)
return sketchFolder.getParentFile().getParentFile().getParentFile();
else
return sketchFolder;
}
public boolean isAutoSaveBackup() {
return isAutoSaveBackup;
}
/**
* Check if any previous autosave exists
* @return
*/
public boolean checkForPastSave(){
if(autosaveDir.exists()){
String prevSaves[] = Base.listFiles(autosaveDir, false);
if(prevSaves.length > 0){
File t = new File(Base.listFiles(new File(prevSaves[0]), false)[0]);
sketchBackupFolder = t;
pastSave = new File(t.getAbsolutePath() + File.separator + t.getName() + ".pde");
if(pastSave.exists())
return true;
}
}
return false;
}
/**
* Refresh autosave directory if current sketch location in the editor changes
*/
public void reloadAutosaveDir(){
while(isSaving);
autosaveDir = new File(editor.getSketch().getFolder().getAbsolutePath() + File.separator + AUTOSAVEFOLDER);
}
public File getAutoSaveDir(){
return autosaveDir;
}
/**
* The folder of the original sketch
* @return
*/
public File getSketchFolder(){
return sketchFolder;
}
public File getSketchBackupFolder(){
return sketchBackupFolder;
}
public File getPastSave(){
return pastSave;
}
/**
* Start the auto save service
*/
public void init(){
/*
if(isAutoSaveBackup) {
log("AutoSaver not started");
return;
}
if(saveTime < 10000) saveTime = 10 * 1000;
saveTime = 5 * 1000; //TODO: remove
timer = new Timer();
timer.schedule(new SaveTask(), saveTime, saveTime);
isSaving = false;
log("AutoSaver started");
*/
}
/**
* Stop the autosave service
*/
public void stop(){
while(isSaving); // save operation mustn't be interrupted
if(timer != null) timer.cancel();
Base.removeDir(autosaveDir);
ExperimentalMode.log("Stopping autosaver and deleting backup dir");
}
/**
* Main function that performs the save operation
* Code reused from processing.app.Sketch.saveAs()
* @return
* @throws IOException
*/
private boolean saveSketch() throws IOException{
if(!editor.getSketch().isModified()) return false;
isSaving = true;
Sketch sc = editor.getSketch();
boolean deleteOldSave = false;
String oldSave = null;
if(!autosaveDir.exists()){
autosaveDir = new File(sc.getFolder().getAbsolutePath(), AUTOSAVEFOLDER);
autosaveDir.mkdir();
}
else
{
// delete the previous backup after saving current one.
String prevSaves[] = Base.listFiles(autosaveDir, false);
if(prevSaves.length > 0){
deleteOldSave = true;
oldSave = prevSaves[0];
}
}
String newParentDir = autosaveDir + File.separator + System.currentTimeMillis();
String newName = sc.getName();
// check on the sanity of the name
String sanitaryName = Sketch.checkName(newName);
File newFolder = new File(newParentDir, sanitaryName);
if (!sanitaryName.equals(newName) && newFolder.exists()) {
Base.showMessage("Cannot Save",
"A sketch with the cleaned name\n" +
"" + sanitaryName + "” already exists.");
isSaving = false;
return false;
}
newName = sanitaryName;
// String newPath = newFolder.getAbsolutePath();
// String oldPath = folder.getAbsolutePath();
// if (newPath.equals(oldPath)) {
// return false; // Can't save a sketch over itself
// }
// make sure there doesn't exist a tab with that name already
// but ignore this situation for the first tab, since it's probably being
// resaved (with the same name) to another location/folder.
for (int i = 1; i < sc.getCodeCount(); i++) {
if (newName.equalsIgnoreCase(sc.getCode()[i].getPrettyName())) {
Base.showMessage("Nope",
"You can't save the sketch as \"" + newName + "\"\n" +
"because the sketch already has a tab with that name.");
isSaving = false;
return false;
}
}
// if the new folder already exists, then first remove its contents before
// copying everything over (user will have already been warned).
if (newFolder.exists()) {
Base.removeDir(newFolder);
}
// in fact, you can't do this on Windows because the file dialog
// will instead put you inside the folder, but it happens on OS X a lot.
// now make a fresh copy of the folder
newFolder.mkdirs();
// grab the contents of the current tab before saving
// first get the contents of the editor text area
if (sc.getCurrentCode().isModified()) {
sc.getCurrentCode().setProgram(editor.getText());
}
File[] copyItems = sc.getFolder().listFiles(new FileFilter() {
public boolean accept(File file) {
String name = file.getName();
// just in case the OS likes to return these as if they're legit
if (name.equals(".") || name.equals("..")) {
return false;
}
// list of files/folders to be ignored during "save as"
for (String ignorable : editor.getMode().getIgnorable()) {
if (name.equals(ignorable)) {
return false;
}
}
// ignore the extensions for code, since that'll be copied below
for (String ext : editor.getMode().getExtensions()) {
if (name.endsWith(ext)) {
return false;
}
}
// don't do screen captures, since there might be thousands. kind of
// a hack, but seems harmless. hm, where have i heard that before...
if (name.startsWith("screen-")) {
return false;
}
return true;
}
});
// now copy over the items that make sense
for (File copyable : copyItems) {
if (copyable.isDirectory()) {
Base.copyDir(copyable, new File(newFolder, copyable.getName()));
} else {
Base.copyFile(copyable, new File(newFolder, copyable.getName()));
}
}
// save the other tabs to their new location
for (int i = 1; i < sc.getCodeCount(); i++) {
File newFile = new File(newFolder, sc.getCode()[i].getFileName());
sc.getCode()[i].saveAs(newFile);
}
// While the old path to the main .pde is still set, remove the entry from
// the Recent menu so that it's not sticking around after the rename.
// If untitled, it won't be in the menu, so there's no point.
// if (!isUntitled()) {
// editor.removeRecent();
// }
// save the main tab with its new name
File newFile = new File(newFolder, newName + ".pde");
sc.getCode()[0].saveAs(newFile);
// updateInternal(newName, newFolder);
//
// // Make sure that it's not an untitled sketch
// setUntitled(false);
//
// // Add this sketch back using the new name
// editor.addRecent();
// let Editor know that the save was successful
if(deleteOldSave){
Base.removeDir(new File(oldSave));
}
isSaving = false;
return true;
}
/**
* Timertask used to perform the save operation every X minutes
* @author quarkninja
*
*/
/*
private class SaveTask extends TimerTask{
@Override
public void run() {
try {
if(saveSketch())
ExperimentalMode.log("Backup Saved " + editor.getSketch().getMainFilePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
*/
}

View File

@@ -0,0 +1,37 @@
/*
* 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 com.sun.jdi.ReferenceType;
/**
* Listener to be notified when a class is loaded in the debugger. Used by
* {@link LineBreakpoint}s to activate themselves as soon as the respective
* class is loaded.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public interface ClassLoadListener {
/**
* Event handler called when a class is loaded.
*
* @param theClass the class
*/
public void classLoaded(ReferenceType theClass);
}

View File

@@ -0,0 +1,548 @@
/*
* Copyright (C) 2012-14 Martin Leopold <m@martinleopold.com> and Manindra Moharana <me@mkmoharana.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 java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jface.text.Document;
/**
*
* Provides compilation checking functionality
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class CompilationChecker {
/**
* ICompilationUnit implementation
*/
private class CompilationUnitImpl implements ICompilationUnit {
private CompilationUnit unit;
CompilationUnitImpl(CompilationUnit unit) {
this.unit = unit;
}
public char[] getContents() {
char[] contents = null;
try {
Document doc = new Document();
if (readFromFile)
doc.set(readFile());
else
doc.set(sourceText);
// TextEdit edits = unit.rewrite(doc, null);
// edits.apply(doc);
String sourceCode = doc.get();
if (sourceCode != null)
contents = sourceCode.toCharArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
return contents;
}
public char[] getMainTypeName() {
TypeDeclaration classType = (TypeDeclaration) unit.types().get(0);
return classType.getName().getFullyQualifiedName().toCharArray();
}
public char[][] getPackageName() {
String[] names = getSimpleNames(this.unit.getPackage().getName()
.getFullyQualifiedName());
char[][] packages = new char[names.length][];
for (int i = 0; i < names.length; ++i)
packages[i] = names[i].toCharArray();
return packages;
}
public char[] getFileName() {
TypeDeclaration classType = (TypeDeclaration) unit.types().get(0);
String name = classType.getName().getFullyQualifiedName() + ".java";
return name.toCharArray();
}
@Override
public boolean ignoreOptionalProblems() {
return false;
}
}
/**
* ICompilerRequestor implementation
*/
private class CompileRequestorImpl implements ICompilerRequestor {
private List<IProblem> problems;
private List<ClassFile> classes;
public CompileRequestorImpl() {
this.problems = new ArrayList<IProblem>();
this.classes = new ArrayList<ClassFile>();
}
public void acceptResult(CompilationResult result) {
boolean errors = false;
if (result.hasProblems()) {
IProblem[] problems = result.getProblems();
for (int i = 0; i < problems.length; i++) {
if (problems[i].isError())
errors = true;
this.problems.add(problems[i]);
}
}
if (!errors) {
ClassFile[] classFiles = result.getClassFiles();
for (int i = 0; i < classFiles.length; i++)
this.classes.add(classFiles[i]);
}
}
List<IProblem> getProblems() {
return this.problems;
}
List<ClassFile> getResults() {
//System.out.println("Calling get results");
return this.classes;
}
}
/**
* INameEnvironment implementation
*/
private class NameEnvironmentImpl implements INameEnvironment {
private ICompilationUnit unit;
private String fullName;
NameEnvironmentImpl(ICompilationUnit unit) {
this.unit = unit;
this.fullName = CharOperation.toString(this.unit.getPackageName()) + "."
+ new String(this.unit.getMainTypeName());
}
public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
return findType(CharOperation.toString(compoundTypeName));
}
public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
String fullName = CharOperation.toString(packageName);
if (typeName != null) {
if (fullName.length() > 0)
fullName += ".";
fullName += new String(typeName);
}
return findType(fullName);
}
public boolean isPackage(char[][] parentPackageName, char[] packageName) {
String fullName = CharOperation.toString(parentPackageName);
if (packageName != null) {
if (fullName.length() > 0)
fullName += ".";
fullName += new String(packageName);
}
if (findType(fullName) != null)
return false;
try {
return (getClassLoader().loadClass(fullName) == null);
} catch (ClassNotFoundException e) {
return true;
}
}
public void cleanup() {
}
private NameEnvironmentAnswer findType(String fullName) {
if (this.fullName.equals(fullName))
return new NameEnvironmentAnswer(unit, null);
try {
InputStream is = getClassLoader().getResourceAsStream(fullName
.replace('.',
'/')
+ ".class");
if (is != null) {
// System.out.println("Find type: " + fullName);
byte[] buffer = new byte[8192];
int bytes = 0;
ByteArrayOutputStream os = new ByteArrayOutputStream(buffer.length);
while ((bytes = is.read(buffer, 0, buffer.length)) > 0)
os.write(buffer, 0, bytes);
os.flush();
ClassFileReader classFileReader = new ClassFileReader(
os.toByteArray(),
fullName
.toCharArray(),
true);
return new NameEnvironmentAnswer(classFileReader, null);
}
return null;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassFormatException e) {
throw new RuntimeException(e);
}
}
}
private URLClassLoader urlClassLoader;
private ClassLoader getClassLoader() {
if (urlClassLoader != null) {
return urlClassLoader;
} else {
return getClass().getClassLoader();
}
}
private void prepareClassLoader(ArrayList<File> jarList) {
URL urls[] = new URL[jarList.size()];
for (int i = 0; i < urls.length; i++) {
try {
urls[i] = jarList.get(i).toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
urlClassLoader = new URLClassLoader(urls);
//System.out.println("URL Classloader ready");
}
/**
* ClassLoader implementation
*/
/*
private class CustomClassLoader extends ClassLoader {
private Map classMap;
CustomClassLoader(ClassLoader parent, List classesList) {
this.classMap = new HashMap();
for (int i = 0; i < classesList.size(); i++) {
ClassFile classFile = (ClassFile) classesList.get(i);
String className = CharOperation.toString(classFile.getCompoundName());
this.classMap.put(className, classFile.getBytes());
}
}
public Class findClass(String name) throws ClassNotFoundException {
byte[] bytes = (byte[]) this.classMap.get(name);
if (bytes != null)
return defineClass(name, bytes, 0, bytes.length);
return super.findClass(name);
}
};
*/
private ICompilationUnit generateCompilationUnit() {
ASTParser parser = ASTParser.newParser(AST.JLS4);
try {
parser.setSource("".toCharArray());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Map<String, String> options = JavaCore.getOptions();
// Ben has decided to move on to 1.6. Yay!
JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options);
parser.setCompilerOptions(options);
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
unit.recordModifications();
AST ast = unit.getAST();
// Package statement
// package astexplorer;
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
unit.setPackage(packageDeclaration);
// unit.se
packageDeclaration.setName(ast.newSimpleName(fileName));
// System.out.println("Filename: " + fileName);
// class declaration
// public class SampleComposite extends Composite {
TypeDeclaration classType = ast.newTypeDeclaration();
classType.setInterface(false);
// classType.s
classType.setName(ast.newSimpleName(fileName));
unit.types().add(classType);
// classType.setSuperclass(ast.newSimpleName("Composite"));
return new CompilationUnitImpl(unit);
}
public static String fileName = "HelloPeasy";
public static String readFile() {
BufferedReader reader = null;
System.out.println(fileName);
try {
reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(
new File(
"/media/quarkninja/Work/TestStuff/"
+ fileName
+ ".java"))));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
StringBuilder ret = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
ret.append(line);
ret.append("\n");
}
return ("package " + fileName + ";\n" + ret.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
private void compileMeQuitely(ICompilationUnit unit, Map<String, String> compilerSettings) {
Map<String, String> settings;
if (compilerSettings == null) {
settings = new HashMap<>();
settings.put(CompilerOptions.OPTION_LineNumberAttribute,
CompilerOptions.GENERATE);
settings.put(CompilerOptions.OPTION_SourceFileAttribute,
CompilerOptions.GENERATE);
settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6);
settings.put(CompilerOptions.OPTION_SuppressWarnings,
CompilerOptions.DISABLED);
// settings.put(CompilerOptions.OPTION_ReportUnusedImport,
// CompilerOptions.IGNORE);
// settings.put(CompilerOptions.OPTION_ReportMissingSerialVersion,
// CompilerOptions.IGNORE);
// settings.put(CompilerOptions.OPTION_ReportRawTypeReference,
// CompilerOptions.IGNORE);
// settings.put(CompilerOptions.OPTION_ReportUncheckedTypeOperation,
// CompilerOptions.IGNORE);
} else {
settings = compilerSettings;
}
// CompilerOptions cop = new CompilerOptions();
// cop.set(settings);
CompileRequestorImpl requestor = new CompileRequestorImpl();
Compiler compiler = new Compiler(new NameEnvironmentImpl(unit),
DefaultErrorHandlingPolicies
.proceedWithAllProblems(),
new CompilerOptions(settings), requestor,
new DefaultProblemFactory(Locale
.getDefault()));
compiler.compile(new ICompilationUnit[] { unit });
List problems = requestor.getProblems();
prob = new IProblem[problems.size()];
int count = 0;
for (Iterator it = problems.iterator(); it.hasNext();) {
IProblem problem = (IProblem) it.next();
prob[count++] = problem;
}
}
private void compileMeQuitely(ICompilationUnit unit) {
compileMeQuitely(unit, null);
}
static private String[] getSimpleNames(String qualifiedName) {
StringTokenizer st = new StringTokenizer(qualifiedName, ".");
ArrayList<String> list = new ArrayList<String>();
while (st.hasMoreTokens()) {
String name = st.nextToken().trim();
if (!name.equals("*"))
list.add(name);
}
return list.toArray(new String[0]);
}
public static void main(String[] args) {
ArrayList<File> fl = new ArrayList<File>();
fl.add(new File(
"/home/quarkninja/Workspaces/processing_workspace/processing/core/library/core.jar"));
CompilationChecker cc = new CompilationChecker(fl);
cc.getErrors("Brightness");
cc.display();
}
public void display() {
boolean error = false;
int errorCount = 0, warningCount = 0, count = 0;
for (int i = 0; i < prob.length; i++) {
IProblem problem = prob[i];
if (problem == null)
continue;
StringBuilder sb = new StringBuilder();
sb.append(problem.getMessage());
sb.append(" | line: ");
sb.append(problem.getSourceLineNumber());
String msg = sb.toString();
if (problem.isError()) {
error = true;
msg = "Error: " + msg;
errorCount++;
} else if (problem.isWarning()) {
msg = "Warning: " + msg;
warningCount++;
}
System.out.println(msg);
prob[count++] = problem;
}
if (!error) {
System.out.println("====================================");
System.out.println(" Compiled without any errors. ");
System.out.println("====================================");
} else {
System.out.println("====================================");
System.out.println(" Compilation failed. You erred man! ");
System.out.println("====================================");
}
System.out.print("Total warnings: " + warningCount);
System.out.println(", Total errors: " + errorCount);
}
IProblem[] prob;
public IProblem[] getErrors(String name) {
fileName = name;
compileMeQuitely(generateCompilationUnit());
// System.out.println("getErrors()");
return prob;
}
/**
* Performs compiler error check.
* @param sourceName - name of the class
* @param source - source code
* @param settings - compiler options
* @param classLoader - custom classloader which can load all dependencies
* @return IProblem[] - list of compiler errors and warnings
*/
public IProblem[] getErrors(String sourceName, String source, Map<String, String> settings,
URLClassLoader classLoader) {
fileName = sourceName;
readFromFile = false;
sourceText = "package " + fileName + ";\n" + source;
if (classLoader != null)
this.urlClassLoader = classLoader;
compileMeQuitely(generateCompilationUnit(), settings);
// System.out.println("getErrors(), Done.");
return prob;
}
private boolean readFromFile = true;
String sourceText = "";
public IProblem[] getErrors(String sourceName, String source) {
return getErrors(sourceName, source, null);
}
@SuppressWarnings("rawtypes")
public IProblem[] getErrors(String sourceName, String source, Map<String, String> settings) {
fileName = sourceName;
readFromFile = false;
sourceText = "package " + fileName + ";\n" + source;
compileMeQuitely(generateCompilationUnit(), settings);
// System.out.println("getErrors(), Done.");
return prob;
}
public CompilationChecker() {
// System.out.println("Compilation Checker initialized.");
}
public CompilationChecker(ArrayList<File> fileList) {
prepareClassLoader(fileList);
// System.out.println("Compilation Checker initialized.");
}
}

View File

@@ -0,0 +1,367 @@
/*
* 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 java.io.*;
import java.lang.reflect.Method;
import processing.app.Base;
import processing.app.SketchException;
import processing.core.PApplet;
/**
* Copied from processing.mode.java.Compiler, just added -g switch to generate
* debugging info.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class Compiler extends processing.mode.java.Compiler {
/**
* Compile with ECJ. See http://j.mp/8paifz for documentation.
*
* @return true if successful.
* @throws RunnerException Only if there's a problem. Only then.
*/
// public boolean compile(Sketch sketch,
// File srcFolder,
// File binFolder,
// String primaryClassName,
// String sketchClassPath,
// String bootClassPath) throws RunnerException {
static public boolean compile(DebugBuild build) throws SketchException {
// This will be filled in if anyone gets angry
SketchException exception = null;
boolean success = false;
String baseCommand[] = new String[] {
"-g",
"-Xemacs",
//"-noExit", // not necessary for ecj
"-source", "1.6",
"-target", "1.6",
"-classpath", build.getClassPath(),
"-nowarn", // we're not currently interested in warnings (works in ecj)
"-d", build.getBinFolder().getAbsolutePath() // output the classes in the buildPath
};
//PApplet.println(baseCommand);
// make list of code files that need to be compiled
// String[] sourceFiles = new String[sketch.getCodeCount()];
// int sourceCount = 0;
// sourceFiles[sourceCount++] =
// new File(buildPath, primaryClassName + ".java").getAbsolutePath();
//
// for (SketchCode code : sketch.getCode()) {
// if (code.isExtension("java")) {
// String path = new File(buildPath, code.getFileName()).getAbsolutePath();
// sourceFiles[sourceCount++] = path;
// }
// }
String[] sourceFiles = Base.listFiles(build.getSrcFolder(), false, ".java");
// String[] command = new String[baseCommand.length + sourceFiles.length];
// System.arraycopy(baseCommand, 0, command, 0, baseCommand.length);
// // append each of the files to the command string
// System.arraycopy(sourceFiles, 0, command, baseCommand.length, sourceCount);
String[] command = PApplet.concat(baseCommand, sourceFiles);
//PApplet.println(command);
try {
// Load errors into a local StringBuilder
final StringBuilder errorBuffer = new StringBuilder();
// Create single method dummy writer class to slurp errors from ecj
Writer internalWriter = new Writer() {
public void write(char[] buf, int off, int len) {
errorBuffer.append(buf, off, len);
}
public void flush() { }
public void close() { }
};
// Wrap as a PrintWriter since that's what compile() wants
PrintWriter writer = new PrintWriter(internalWriter);
//result = com.sun.tools.javac.Main.compile(command, writer);
PrintWriter outWriter = new PrintWriter(System.out);
// Version that's not dynamically loaded
//CompilationProgress progress = null;
//success = BatchCompiler.compile(command, outWriter, writer, progress);
// Version that *is* dynamically loaded. First gets the mode class loader
// so that it can grab the compiler JAR files from it.
ClassLoader loader = build.getMode().getJavaModeClassLoader();
//ClassLoader loader = build.getMode().getClassLoader();
try {
Class batchClass =
Class.forName("org.eclipse.jdt.core.compiler.batch.BatchCompiler", false, loader);
Class progressClass =
Class.forName("org.eclipse.jdt.core.compiler.CompilationProgress", false, loader);
Class[] compileArgs =
new Class[] { String[].class, PrintWriter.class, PrintWriter.class, progressClass };
Method compileMethod = batchClass.getMethod("compile", compileArgs);
success = (Boolean)
compileMethod.invoke(null, new Object[] { command, outWriter, writer, null });
} catch (Exception e) {
e.printStackTrace();
throw new SketchException("Unknown error inside the compiler.");
}
// Close out the stream for good measure
writer.flush();
writer.close();
BufferedReader reader =
new BufferedReader(new StringReader(errorBuffer.toString()));
//System.err.println(errorBuffer.toString());
String line = null;
while ((line = reader.readLine()) != null) {
//System.out.println("got line " + line); // debug
// get first line, which contains file name, line number,
// and at least the first line of the error message
String errorFormat = "([\\w\\d_]+.java):(\\d+):\\s*(.*):\\s*(.*)\\s*";
String[] pieces = PApplet.match(line, errorFormat);
//PApplet.println(pieces);
// if it's something unexpected, die and print the mess to the console
if (pieces == null) {
exception = new SketchException("Cannot parse error text: " + line);
exception.hideStackTrace();
// Send out the rest of the error message to the console.
System.err.println(line);
while ((line = reader.readLine()) != null) {
System.err.println(line);
}
break;
}
// translate the java filename and line number into a un-preprocessed
// location inside a source file or tab in the environment.
String dotJavaFilename = pieces[1];
// Line numbers are 1-indexed from javac
int dotJavaLineIndex = PApplet.parseInt(pieces[2]) - 1;
String errorMessage = pieces[4];
exception = build.placeException(errorMessage,
dotJavaFilename,
dotJavaLineIndex);
/*
int codeIndex = 0; //-1;
int codeLine = -1;
// first check to see if it's a .java file
for (int i = 0; i < sketch.getCodeCount(); i++) {
SketchCode code = sketch.getCode(i);
if (code.isExtension("java")) {
if (dotJavaFilename.equals(code.getFileName())) {
codeIndex = i;
codeLine = dotJavaLineIndex;
}
}
}
// if it's not a .java file, codeIndex will still be 0
if (codeIndex == 0) { // main class, figure out which tab
//for (int i = 1; i < sketch.getCodeCount(); i++) {
for (int i = 0; i < sketch.getCodeCount(); i++) {
SketchCode code = sketch.getCode(i);
if (code.isExtension("pde")) {
if (code.getPreprocOffset() <= dotJavaLineIndex) {
codeIndex = i;
//System.out.println("i'm thinkin file " + i);
codeLine = dotJavaLineIndex - code.getPreprocOffset();
}
}
}
}
//System.out.println("code line now " + codeLine);
exception = new RunnerException(errorMessage, codeIndex, codeLine, -1, false);
*/
if (exception == null) {
exception = new SketchException(errorMessage);
}
// for a test case once message parsing is implemented,
// use new Font(...) since that wasn't getting picked up properly.
/*
if (errorMessage.equals("cannot find symbol")) {
handleCannotFindSymbol(reader, exception);
} else if (errorMessage.indexOf("is already defined") != -1) {
reader.readLine(); // repeats the line of code w/ error
int codeColumn = caretColumn(reader.readLine());
exception = new RunnerException(errorMessage,
codeIndex, codeLine, codeColumn);
} else if (errorMessage.startsWith("package") &&
errorMessage.endsWith("does not exist")) {
// Because imports are stripped out and re-added to the 0th line of
// the preprocessed code, codeLine will always be wrong for imports.
exception = new RunnerException("P" + errorMessage.substring(1) +
". You might be missing a library.");
} else {
exception = new RunnerException(errorMessage);
}
*/
if (errorMessage.startsWith("The import ") &&
errorMessage.endsWith("cannot be resolved")) {
// The import poo cannot be resolved
//import poo.shoe.blah.*;
//String what = errorMessage.substring("The import ".length());
String[] m = PApplet.match(errorMessage, "The import (.*) cannot be resolved");
//what = what.substring(0, what.indexOf(' '));
if (m != null) {
// System.out.println("'" + m[1] + "'");
if (m[1].equals("processing.xml")) {
exception.setMessage("processing.xml no longer exists, this code needs to be updated for 2.0.");
System.err.println("The processing.xml library has been replaced " +
"with a new 'XML' class that's built-in.");
handleCrustyCode();
} else {
exception.setMessage("The package " +
"\u201C" + m[1] + "\u201D" +
" does not exist. " +
"You might be missing a library.");
System.err.println("Libraries must be " +
"installed in a folder named 'libraries' " +
"inside the 'sketchbook' folder.");
}
}
// // Actually create the folder and open it for the user
// File sketchbookLibraries = Base.getSketchbookLibrariesFolder();
// if (!sketchbookLibraries.exists()) {
// if (sketchbookLibraries.mkdirs()) {
// Base.openFolder(sketchbookLibraries);
// }
// }
} else if (errorMessage.endsWith("cannot be resolved to a type")) {
// xxx cannot be resolved to a type
//xxx c;
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
if (what.equals("BFont") ||
what.equals("BGraphics") ||
what.equals("BImage")) {
exception.setMessage(what + " has been replaced with P" + what.substring(1));
handleCrustyCode();
} else {
exception.setMessage("Cannot find a class or type " +
"named \u201C" + what + "\u201D");
}
} else if (errorMessage.endsWith("cannot be resolved")) {
// xxx cannot be resolved
//println(xxx);
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
if (what.equals("LINE_LOOP") ||
what.equals("LINE_STRIP")) {
exception.setMessage("LINE_LOOP and LINE_STRIP are not available, " +
"please update your code.");
handleCrustyCode();
} else if (what.equals("framerate")) {
exception.setMessage("framerate should be changed to frameRate.");
handleCrustyCode();
} else if (what.equals("screen")) {
exception.setMessage("Change screen.width and screen.height to " +
"displayWidth and displayHeight.");
handleCrustyCode();
} else if (what.equals("screenWidth") ||
what.equals("screenHeight")) {
exception.setMessage("Change screenWidth and screenHeight to " +
"displayWidth and displayHeight.");
handleCrustyCode();
} else {
exception.setMessage("Cannot find anything " +
"named \u201C" + what + "\u201D");
}
} else if (errorMessage.startsWith("Duplicate")) {
// "Duplicate nested type xxx"
// "Duplicate local variable xxx"
} else {
String[] parts = null;
// The method xxx(String) is undefined for the type Temporary_XXXX_XXXX
//xxx("blah");
// The method xxx(String, int) is undefined for the type Temporary_XXXX_XXXX
//xxx("blah", 34);
// The method xxx(String, int) is undefined for the type PApplet
//PApplet.sub("ding");
String undefined =
"The method (\\S+\\(.*\\)) is undefined for the type (.*)";
parts = PApplet.match(errorMessage, undefined);
if (parts != null) {
if (parts[1].equals("framerate(int)")) {
exception.setMessage("framerate() no longer exists, use frameRate() instead.");
handleCrustyCode();
} else if (parts[1].equals("push()")) {
exception.setMessage("push() no longer exists, use pushMatrix() instead.");
handleCrustyCode();
} else if (parts[1].equals("pop()")) {
exception.setMessage("pop() no longer exists, use popMatrix() instead.");
handleCrustyCode();
} else {
String mess = "The function " + parts[1] + " does not exist.";
exception.setMessage(mess);
}
break;
}
}
if (exception != null) {
// The stack trace just shows that this happened inside the compiler,
// which is a red herring. Don't ever show it for compiler stuff.
exception.hideStackTrace();
break;
}
}
} catch (IOException e) {
String bigSigh = "Error while compiling. (" + e.getMessage() + ")";
exception = new SketchException(bigSigh);
e.printStackTrace();
success = false;
}
// In case there was something else.
if (exception != null) throw exception;
return success;
}
}

View File

@@ -0,0 +1,262 @@
package processing.mode.experimental;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
public class CompletionCandidate implements Comparable<CompletionCandidate>{
private String elementName; //
private String label; // the toString value
private String completionString;
private Object wrappedObject;
private int type;
public static final int PREDEF_CLASS = 0, PREDEF_FIELD = 1,
PREDEF_METHOD = 2, LOCAL_CLASS = 3, LOCAL_METHOD = 4, LOCAL_FIELD = 5,
LOCAL_VAR = 6;
public CompletionCandidate(Method method) {
method.getDeclaringClass().getName();
elementName = method.getName();
StringBuilder label = new StringBuilder("<html>"+method.getName() + "(");
StringBuilder cstr = new StringBuilder(method.getName() + "(");
for (int i = 0; i < method.getParameterTypes().length; i++) {
label.append(method.getParameterTypes()[i].getSimpleName());
if (i < method.getParameterTypes().length - 1) {
label.append(",");
cstr.append(",");
}
}
if(method.getParameterTypes().length == 1) {
cstr.append(' ');
}
label.append(")");
if(method.getReturnType() != null)
label.append(" : " + method.getReturnType().getSimpleName());
label.append(" - <font color=#777777>" + method.getDeclaringClass().getSimpleName() + "</font></html>");
cstr.append(")");
this.label = label.toString();
this.completionString = cstr.toString();
type = PREDEF_METHOD;
wrappedObject = method;
}
public Object getWrappedObject() {
return wrappedObject;
}
public CompletionCandidate(SingleVariableDeclaration svd) {
completionString = svd.getName().toString();
elementName = svd.getName().toString();
if(svd.getParent() instanceof FieldDeclaration)
type = LOCAL_FIELD;
else
type = LOCAL_VAR;
label = svd.getName() + " : " + svd.getType();
wrappedObject = svd;
}
public CompletionCandidate(VariableDeclarationFragment vdf) {
completionString = vdf.getName().toString();
elementName = vdf.getName().toString();
if(vdf.getParent() instanceof FieldDeclaration)
type = LOCAL_FIELD;
else
type = LOCAL_VAR;
label = vdf.getName() + " : " + ASTGenerator.extracTypeInfo2(vdf);
wrappedObject = vdf;
}
public CompletionCandidate(MethodDeclaration method) {
// log("ComCan " + method.getName());
elementName = method.getName().toString();
type = LOCAL_METHOD;
List<ASTNode> params = (List<ASTNode>) method
.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
StringBuilder label = new StringBuilder(elementName + "(");
StringBuilder cstr = new StringBuilder(method.getName() + "(");
for (int i = 0; i < params.size(); i++) {
label.append(params.get(i).toString());
if (i < params.size() - 1) {
label.append(",");
cstr.append(",");
}
}
if (params.size() == 1) {
cstr.append(' ');
}
label.append(")");
if (method.getReturnType2() != null)
label.append(" : " + method.getReturnType2());
cstr.append(")");
this.label = label.toString();
this.completionString = cstr.toString();
wrappedObject = method;
}
public CompletionCandidate(TypeDeclaration td){
type = LOCAL_CLASS;
elementName = td.getName().toString();
label = elementName;
completionString = elementName;
wrappedObject = td;
}
public CompletionCandidate(Field f) {
f.getDeclaringClass().getName();
elementName = f.getName();
type = PREDEF_FIELD;
// "<html>"
// + matchedClass + " : " + "<font color=#777777>"
// + matchedClass2.substring(0, d) + "</font>", matchedClass
// + "</html>"
label = "<html>" + f.getName() + " : " + f.getType().getSimpleName()
+ " - <font color=#777777>" + f.getDeclaringClass().getSimpleName() + "</font></html>";
completionString = elementName;
wrappedObject = f;
}
public CompletionCandidate(String name, String labelStr, String completionStr, int type) {
elementName = name;
label = labelStr;
completionString = completionStr;
this.type = type;
}
public CompletionCandidate(String name, int type) {
elementName = name;
label = name;
completionString = name;
this.type = type;
}
public String getElementName() {
return elementName;
}
public String getCompletionString() {
return completionString;
}
public String toString() {
return label;
}
public int getType() {
return type;
}
public String getLabel() {
return label;
}
public String getNoHtmlLabel(){
if(!label.contains("<html>")) {
return label;
}
else {
StringBuilder ans = new StringBuilder(label);
while(ans.indexOf("<") > -1) {
int a = ans.indexOf("<"), b = ans.indexOf(">");
if(a > b) break;
ans.replace(a, b+1, "");
// System.out.println(ans.replace(a, b+1, ""));
// System.out.println(ans + "--");
}
return ans.toString();
}
}
public void setLabel(String label) {
this.label = label;
}
public void setCompletionString(String completionString) {
this.completionString = completionString;
}
public int compareTo(CompletionCandidate cc) {
if(type != cc.getType()){
return cc.getType() - type;
}
return (elementName.compareTo(cc.getElementName()));
}
public void regenerateCompletionString(){
if(wrappedObject instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration)wrappedObject;
List<ASTNode> params = (List<ASTNode>) method
.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
StringBuilder label = new StringBuilder(elementName + "(");
StringBuilder cstr = new StringBuilder(method.getName() + "(");
for (int i = 0; i < params.size(); i++) {
label.append(params.get(i).toString());
if (i < params.size() - 1) {
label.append(",");
cstr.append(",");
}
}
if (params.size() == 1) {
cstr.append(' ');
}
label.append(")");
if (method.getReturnType2() != null)
label.append(" : " + method.getReturnType2());
cstr.append(")");
this.label = label.toString();
this.completionString = cstr.toString();
}
else if (wrappedObject instanceof Method) {
Method method = (Method)wrappedObject;
StringBuilder label = new StringBuilder("<html>" + method.getName() + "(");
StringBuilder cstr = new StringBuilder(method.getName() + "(");
for (int i = 0; i < method.getParameterTypes().length; i++) {
label.append(method.getParameterTypes()[i].getSimpleName());
if (i < method.getParameterTypes().length - 1) {
label.append(",");
cstr.append(",");
}
}
if(method.getParameterTypes().length == 1) {
cstr.append(' ');
}
label.append(")");
if(method.getReturnType() != null)
label.append(" : " + method.getReturnType().getSimpleName());
label.append(" - <font color=#777777>" + method.getDeclaringClass().getSimpleName() + "</font></html>");
cstr.append(")");
this.label = label.toString();
this.completionString = cstr.toString();
/*
* StringBuilder label = new StringBuilder("<html>"+method.getName() + "(");
StringBuilder cstr = new StringBuilder(method.getName() + "(");
for (int i = 0; i < method.getParameterTypes().length; i++) {
label.append(method.getParameterTypes()[i].getSimpleName());
if (i < method.getParameterTypes().length - 1) {
label.append(",");
cstr.append(",");
}
}
if(method.getParameterTypes().length == 1) {
cstr.append(' ');
}
label.append(")");
if(method.getReturnType() != null)
label.append(" : " + method.getReturnType().getSimpleName());
label.append(" - <font color=#777777>" + method.getDeclaringClass().getSimpleName() + "</font></html>");
* */
}
}
}

View File

@@ -0,0 +1,575 @@
/*
* Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.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 static processing.mode.experimental.ExperimentalMode.log2;
import static processing.mode.experimental.ExperimentalMode.logE;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.Painter;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.basic.BasicScrollBarUI;
import javax.swing.text.BadLocationException;
import processing.app.syntax.JEditTextArea;
/**
* Manages the actual suggestion popup that gets displayed
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
public class CompletionPanel {
/**
* The completion list generated by ASTGenerator
*/
private JList<CompletionCandidate> completionList;
/**
* The popup menu in which the suggestion list is shown
*/
private JPopupMenu popupMenu;
/**
* Partial word which triggered the code completion and which needs to be completed
*/
private String subWord;
/**
* Postion where the completion has to be inserted
*/
private int insertionPosition;
private TextArea textarea;
/**
* Scroll pane in which the completion list is displayed
*/
private JScrollPane scrollPane;
protected DebugEditor editor;
public static final int MOUSE_COMPLETION = 10, KEYBOARD_COMPLETION = 20;
/**
* Triggers the completion popup
* @param textarea
* @param position - insertion position(caret pos)
* @param subWord - Partial word which triggered the code completion and which needs to be completed
* @param items - completion candidates
* @param location - Point location where popup list is to be displayed
* @param dedit
*/
public CompletionPanel(final JEditTextArea textarea, int position, String subWord,
DefaultListModel<CompletionCandidate> items, final Point location, DebugEditor dedit) {
this.textarea = (TextArea) textarea;
editor = dedit;
this.insertionPosition = position;
if (subWord.indexOf('.') != -1)
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
else
this.subWord = subWord;
popupMenu = new JPopupMenu();
popupMenu.removeAll();
popupMenu.setOpaque(false);
popupMenu.setBorder(null);
scrollPane = new JScrollPane();
styleScrollPane();
scrollPane.setViewportView(completionList = createSuggestionList(position, items));
popupMenu.add(scrollPane, BorderLayout.CENTER);
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize())); //TODO: Eradicate this evil
this.textarea.errorCheckerService.getASTGenerator().updateJavaDoc(completionList.getSelectedValue());
textarea.requestFocusInWindow();
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
+ location.y);
//log("Suggestion shown: " + System.currentTimeMillis());
}
private void styleScrollPane() {
String laf = UIManager.getLookAndFeel().getID();
if (!laf.equals("Nimbus") && !laf.equals("Windows")) return;
String thumbColor = null;
if (laf.equals("Nimbus")) {
UIDefaults defaults = new UIDefaults();
defaults.put("PopupMenu.contentMargins", new InsetsUIResource(0, 0, 0, 0));
defaults.put("ScrollPane[Enabled].borderPainter", new Painter<JComponent>() {
public void paint(Graphics2D g, JComponent t, int w, int h) {}
});
popupMenu.putClientProperty("Nimbus.Overrides", defaults);
scrollPane.putClientProperty("Nimbus.Overrides", defaults);
thumbColor = "nimbusBlueGrey";
} else if (laf.equals("Windows")) {
thumbColor = "ScrollBar.thumbShadow";
}
scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(Integer.MAX_VALUE, 8));
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(8, Integer.MAX_VALUE));
scrollPane.getHorizontalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
scrollPane.getVerticalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
}
public static class CompletionScrollBarUI extends BasicScrollBarUI {
private String thumbColorName;
protected CompletionScrollBarUI(String thumbColorName) {
this.thumbColorName = thumbColorName;
}
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle trackBounds) {
g.setColor((Color) UIManager.get(thumbColorName));
g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
}
@Override
protected JButton createDecreaseButton(int orientation) {
return createZeroButton();
}
@Override
protected JButton createIncreaseButton(int orientation) {
return createZeroButton();
}
private JButton createZeroButton() {
JButton jbutton = new JButton();
jbutton.setPreferredSize(new Dimension(0, 0));
jbutton.setMinimumSize(new Dimension(0, 0));
jbutton.setMaximumSize(new Dimension(0, 0));
return jbutton;
}
}
public boolean isVisible() {
return popupMenu.isVisible();
}
public void setVisible(boolean v){
//log("Pred popup visible.");
popupMenu.setVisible(v);
}
/**
* Dynamic height of completion panel depending on item count
* @param itemCount
* @return - height
*/
private int calcHeight(int itemCount) {
int maxHeight = 250;
FontMetrics fm = textarea.getGraphics().getFontMetrics();
float itemHeight = Math.max((fm.getHeight() + (fm.getDescent()) * 0.5f),
editor.dmode.classIcon.getIconHeight() * 1.2f);
if (horizontalScrollBarVisible)
itemCount++;
if (itemCount < 4)
itemHeight *= 1.3f; //Sorry, but it works.
float h = itemHeight * (itemCount);
if (itemCount >= 4)
h += itemHeight * 0.3; // a bit of offset
return Math.min(maxHeight, (int) h); // popup menu height
}
private boolean horizontalScrollBarVisible = false;
/**
* Dynamic width of completion panel
* @return - width
*/
private int calcWidth() {
horizontalScrollBarVisible = false;
int maxWidth = 300;
float min = 0;
FontMetrics fm = textarea.getGraphics().getFontMetrics();
for (int i = 0; i < completionList.getModel().getSize(); i++) {
float h = fm.stringWidth(completionList.getModel().getElementAt(i).getLabel());
min = Math.max(min, h);
}
int w = Math.min((int) min, maxWidth);
if(w == maxWidth)
horizontalScrollBarVisible = true;
w += editor.dmode.classIcon.getIconWidth(); // add icon width too!
w += fm.stringWidth(" "); // a bit of offset
//log("popup width " + w);
return w; // popup menu width
}
/**
* Created the popup list to be displayed
* @param position
* @param items
* @return
*/
private JList<CompletionCandidate> createSuggestionList(final int position,
final DefaultListModel<CompletionCandidate> items) {
JList<CompletionCandidate> list = new JList<CompletionCandidate>(items);
//list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
insertSelection(MOUSE_COMPLETION);
hide();
}
}
});
list.setCellRenderer(new CustomListRenderer());
list.setFocusable(false);
return list;
}
// possibly defunct
public boolean updateList(final DefaultListModel<CompletionCandidate> items, String newSubword,
final Point location, int position) {
this.subWord = new String(newSubword);
if (subWord.indexOf('.') != -1)
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
insertionPosition = position;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
scrollPane.getViewport().removeAll();
completionList.setModel(items);
completionList.setSelectedIndex(0);
scrollPane.setViewportView(completionList);
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize()));
//log("Suggestion updated" + System.nanoTime());
textarea.requestFocusInWindow();
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
+ location.y);
completionList.validate();
scrollPane.validate();
popupMenu.validate();
}
});
return true;
}
/**
* Inserts the CompletionCandidate chosen from the suggestion list
* @param completionSource - whether being completed via keypress or mouse click.
* @return true - if code was successfully inserted at the caret position
*/
public boolean insertSelection(int completionSource) {
if (completionList.getSelectedValue() != null) {
try {
// If user types 'abc.', subword becomes '.' and null is returned
String currentSubword = fetchCurrentSubword();
int currentSubwordLen = currentSubword == null ? 0 : currentSubword
.length();
//logE(currentSubword + " <= subword,len => " + currentSubword.length());
String selectedSuggestion =
completionList.getSelectedValue().getCompletionString();
if (currentSubword != null) {
selectedSuggestion = selectedSuggestion.substring(currentSubwordLen);
} else {
currentSubword = "";
}
String completionString =
completionList.getSelectedValue().getCompletionString();
if (selectedSuggestion.endsWith(" )")) { // the case of single param methods
// selectedSuggestion = ")";
if (completionString.endsWith(" )")) {
completionString = completionString.substring(0, completionString
.length() - 2)
+ ")";
}
}
boolean mouseClickOnOverloadedMethods = false;
if (completionSource == MOUSE_COMPLETION) {
// The case of overloaded methods, displayed as 'foo(...)'
// They have completion strings as 'foo('. See #2755
if (completionString.endsWith("(")) {
mouseClickOnOverloadedMethods = true;
}
}
logE(subWord + " <= subword, Inserting suggestion=> "
+ selectedSuggestion + " Current sub: " + currentSubword);
if (currentSubword.length() > 0) {
textarea.getDocument().remove(insertionPosition - currentSubwordLen,
currentSubwordLen);
}
textarea.getDocument()
.insertString(insertionPosition - currentSubwordLen,
completionString, null);
if (selectedSuggestion.endsWith(")") && !selectedSuggestion.endsWith("()")) {
// place the caret between '( and first ','
int x = selectedSuggestion.indexOf(',');
if(x == -1) {
// the case of single param methods, containing no ','
textarea.setCaretPosition(textarea.getCaretPosition() - 1); // just before ')'
} else {
textarea.setCaretPosition(insertionPosition + x);
}
}
log("Suggestion inserted: " + System.currentTimeMillis());
if (completionList.getSelectedValue().getLabel().contains("...")) {
// log("No hide");
// Why not hide it? Coz this is the case of
// overloaded methods. See #2755
} else {
hide();
}
if(mouseClickOnOverloadedMethods) {
// See #2755
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
editor.ta.fetchPhrase(null);
return null;
}
};
worker.execute();
}
return true;
} catch (BadLocationException e1) {
e1.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
hide();
}
return false;
}
private String fetchCurrentSubword() {
//log("Entering fetchCurrentSubword");
TextArea ta = editor.ta;
int off = ta.getCaretPosition();
//log2("off " + off);
if (off < 0)
return null;
int line = ta.getCaretLine();
if (line < 0)
return null;
String s = ta.getLineText(line);
//log2("lin " + line);
//log2(s + " len " + s.length());
int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1;
if(x >= s.length() || x < 0)
return null; //TODO: Does this check cause problems? Verify.
log2(" x char: " + s.charAt(x));
//int xLS = off - getLineStartNonWhiteSpaceOffset(line);
String word = (x < s.length() ? s.charAt(x) : "") + "";
if (s.trim().length() == 1) {
// word = ""
// + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
//word = (x < s.length()?s.charAt(x):"") + "";
word = word.trim();
if (word.endsWith("."))
word = word.substring(0, word.length() - 1);
return word;
}
//log("fetchCurrentSubword 1 " + word);
if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.'
// if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE)
// ; // accepted these keys
// else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$'))
// return null;
int i = 0;
while (true) {
i++;
//TODO: currently works on single line only. "a. <new line> b()" won't be detected
if (x1 >= 0) {
// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_') {
word = s.charAt(x1--) + word;
} else {
break;
}
} else {
break;
}
if (i > 200) {
// time out!
break;
}
}
// if (keyChar != KeyEvent.CHAR_UNDEFINED)
//log("fetchCurrentSubword 2 " + word);
if (Character.isDigit(word.charAt(0)))
return null;
word = word.trim();
if (word.endsWith("."))
word = word.substring(0, word.length() - 1);
//log("fetchCurrentSubword 3 " + word);
//showSuggestionLater();
return word;
//}
}
/**
* Hide the suggestion list
*/
public void hide() {
popupMenu.setVisible(false);
//log("Suggestion hidden" + System.nanoTime());
//textarea.errorCheckerService.getASTGenerator().jdocWindowVisible(false);
}
/**
* When up arrow key is pressed, moves the highlighted selection up in the list
*/
public void moveUp() {
if (completionList.getSelectedIndex() == 0) {
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
selectIndex(completionList.getModel().getSize() - 1);
return;
} else {
int index = Math.max(completionList.getSelectedIndex() - 1, 0);
selectIndex(index);
}
int step = scrollPane.getVerticalScrollBar().getMaximum()
/ completionList.getModel().getSize();
scrollPane.getVerticalScrollBar().setValue(scrollPane
.getVerticalScrollBar()
.getValue()
- step);
textarea.errorCheckerService.getASTGenerator().updateJavaDoc(completionList.getSelectedValue());
}
/**
* When down arrow key is pressed, moves the highlighted selection down in the list
*/
public void moveDown() {
if (completionList.getSelectedIndex() == completionList.getModel().getSize() - 1) {
scrollPane.getVerticalScrollBar().setValue(0);
selectIndex(0);
return;
} else {
int index = Math.min(completionList.getSelectedIndex() + 1, completionList.getModel()
.getSize() - 1);
selectIndex(index);
}
textarea.errorCheckerService.getASTGenerator().updateJavaDoc(completionList.getSelectedValue());
int step = scrollPane.getVerticalScrollBar().getMaximum()
/ completionList.getModel().getSize();
scrollPane.getVerticalScrollBar().setValue(scrollPane
.getVerticalScrollBar()
.getValue()
+ step);
}
private void selectIndex(int index) {
completionList.setSelectedIndex(index);
// final int position = textarea.getCaretPosition();
// SwingUtilities.invokeLater(new Runnable() {
// @Override
// public void run() {
// textarea.setCaretPosition(position);
// };
// });
}
/**
* Custom cell renderer to display icons along with the completion candidates
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
private class CustomListRenderer extends
javax.swing.DefaultListCellRenderer {
//protected final ImageIcon classIcon, fieldIcon, methodIcon;
public Component getListCellRendererComponent(JList<?> list, Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value,
index,
isSelected,
cellHasFocus);
if (value instanceof CompletionCandidate) {
CompletionCandidate cc = (CompletionCandidate) value;
switch (cc.getType()) {
case CompletionCandidate.LOCAL_VAR:
label.setIcon(editor.dmode.localVarIcon);
break;
case CompletionCandidate.LOCAL_FIELD:
case CompletionCandidate.PREDEF_FIELD:
label.setIcon(editor.dmode.fieldIcon);
break;
case CompletionCandidate.LOCAL_METHOD:
case CompletionCandidate.PREDEF_METHOD:
label.setIcon(editor.dmode.methodIcon);
break;
case CompletionCandidate.LOCAL_CLASS:
case CompletionCandidate.PREDEF_CLASS:
label.setIcon(editor.dmode.classIcon);
break;
default:
log("(CustomListRenderer)Unknown CompletionCandidate type " + cc.getType());
break;
}
}
else
log("(CustomListRenderer)Unknown CompletionCandidate object " + value);
return label;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 java.io.File;
import processing.app.Sketch;
import processing.app.SketchException;
import processing.mode.java.JavaBuild;
/**
* Copied from processing.mode.java.JavaBuild, just changed compiler.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class DebugBuild extends JavaBuild {
public DebugBuild(Sketch sketch) {
super(sketch);
}
/**
* Preprocess and compile sketch. Copied from
* processing.mode.java.JavaBuild, just changed compiler.
*
* @param srcFolder
* @param binFolder
* @param sizeWarning
* @return main class name or null on compile failure
* @throws SketchException
*/
@Override
public String build(File srcFolder, File binFolder, boolean sizeWarning) throws SketchException {
this.srcFolder = srcFolder;
this.binFolder = binFolder;
// Base.openFolder(srcFolder);
// Base.openFolder(binFolder);
// run the preprocessor
String classNameFound = preprocess(srcFolder, sizeWarning);
// compile the program. errors will happen as a RunnerException
// that will bubble up to whomever called build().
// Compiler compiler = new Compiler(this);
// String bootClasses = System.getProperty("sun.boot.class.path");
// if (compiler.compile(this, srcFolder, binFolder, primaryClassName, getClassPath(), bootClasses)) {
if (Compiler.compile(this)) { // use compiler with debug info enabled (-g switch flicked)
sketchClassName = classNameFound;
return classNameFound;
}
return null;
}
public ExperimentalMode getMode() {
return (ExperimentalMode)mode;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
/*
* 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 com.sun.jdi.VirtualMachine;
import processing.app.RunnerListener;
import processing.app.SketchException;
import processing.app.exec.StreamRedirectThread;
import processing.mode.java.JavaBuild;
import processing.mode.java.runner.MessageSiphon;
/**
* Runs a {@link JavaBuild}. Launches the build in a new debuggee VM.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class DebugRunner extends processing.mode.java.runner.Runner {
// important inherited fields
// protected VirtualMachine vm;
public DebugRunner(JavaBuild build, RunnerListener listener) throws SketchException {
super(build, listener);
}
/**
* Launch the virtual machine. Simple non-blocking launch. VM starts
* suspended.
*
* @return debuggee VM or null on failure
*/
public VirtualMachine launch() {
// String[] machineParamList = getMachineParams();
// String[] sketchParamList = getSketchParams(false);
// /*
// * System.out.println("vm launch sketch params:"); for (int i=0;
// * i<sketchParamList.length; i++) {
// * System.out.println(sketchParamList[i]); } System.out.println("vm
// * launch machine params:"); for (int i=0; i<machineParamList.length;
// * i++) { System.out.println(machineParamList[i]); }
// *
// */
// vm = launchVirtualMachine(machineParamList, sketchParamList); // will return null on failure
if (launchVirtualMachine(false)) { // will return null on failure
redirectStreams(vm);
}
return vm;
}
/**
* Redirect a VMs output and error streams to System.out and System.err
*
* @param vm the VM
*/
protected void redirectStreams(VirtualMachine vm) {
MessageSiphon ms = new MessageSiphon(process.getErrorStream(), this);
errThread = ms.getThread();
outThread = new StreamRedirectThread("VM output reader", process.getInputStream(), System.out);
errThread.start();
outThread.start();
}
/**
* Additional access to the virtual machine. TODO: may not be needed
*
* @return debugge VM or null if not running
*/
public VirtualMachine vm() {
return vm;
}
}

View File

@@ -0,0 +1,305 @@
/*
* 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 java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import processing.app.Base;
import processing.app.Editor;
import processing.app.Language;
import processing.app.Toolkit;
import processing.mode.java.JavaToolbar;
/**
* Custom toolbar for the editor window. Preserves original button numbers
* ({@link JavaToolbar#RUN}, {@link JavaToolbar#STOP}, {@link JavaToolbar#NEW},
* {@link JavaToolbar#OPEN}, {@link JavaToolbar#SAVE}, {@link JavaToolbar#EXPORT})
* which can be used e.g. in {@link #activate} and
* {@link #deactivate}.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class DebugToolbar extends JavaToolbar {
// preserve original button id's, but re-define so they are accessible
// (they are used by DebugEditor, so they want to be public)
static protected final int RUN = 100; // change this, to be able to get it's name via getTitle()
static protected final int DEBUG = JavaToolbar.RUN;
static protected final int CONTINUE = 101;
static protected final int STEP = 102;
static protected final int TOGGLE_BREAKPOINT = 103;
static protected final int TOGGLE_VAR_INSPECTOR = 104;
static protected final int STOP = JavaToolbar.STOP;
// static protected final int NEW = JavaToolbar.NEW;
// static protected final int OPEN = JavaToolbar.OPEN;
// static protected final int SAVE = JavaToolbar.SAVE;
// static protected final int EXPORT = JavaToolbar.EXPORT;
// the sequence of button ids. (this maps button position = index to button ids)
static protected final int[] buttonSequence = {
DEBUG, CONTINUE, STEP, STOP, TOGGLE_BREAKPOINT, TOGGLE_VAR_INSPECTOR
// NEW, OPEN, SAVE, EXPORT
};
public DebugToolbar(Editor editor, Base base) {
super(editor, base);
}
public Image[][] loadDebugImages() {
int res = Toolkit.highResDisplay() ? 2 : 1;
String suffix = null;
Image allButtons = null;
// Some modes may not have a 2x version. If a mode doesn't have a 1x
// version, this will cause an error... they should always have 1x.
if (res == 2) {
suffix = "-2x.png";
allButtons = mode.loadImage("theme/buttons-debug" + suffix);
if (allButtons == null) {
res = 1; // take him down a notch
}
}
if (res == 1) {
suffix = ".png";
allButtons = mode.loadImage("theme/buttons-debug" + suffix);
if (allButtons == null) {
// use the old (pre-2.0b9) file name
suffix = ".gif";
allButtons = mode.loadImage("theme/buttons-debug" + suffix);
}
}
// The following three final fields were not accessible, so just copied the values here
// for the time being. TODO: inform Ben, make these fields public
/** Width of each toolbar button. */
final int BUTTON_WIDTH = 27;
/** Size (both width and height) of the buttons in the source image. */
final int BUTTON_IMAGE_SIZE = 33;
int count = allButtons.getWidth(this) / BUTTON_WIDTH*res;
final int GRID_SIZE = 32;
Image[][] buttonImages = new Image[count][3];
for (int i = 0; i < count; i++) {
for (int state = 0; state < 3; state++) {
Image image = new BufferedImage(BUTTON_WIDTH*res, GRID_SIZE*res, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.drawImage(allButtons,
-(i*BUTTON_IMAGE_SIZE*res) - 3,
(state-2)*BUTTON_IMAGE_SIZE*res, null);
g.dispose();
buttonImages[i][state] = image;
}
}
return buttonImages;
}
/**
* Initialize buttons. Loads images and adds the buttons to the toolbar.
*/
@Override
public void init() {
Image[][] images = loadDebugImages();
for (int idx = 0; idx < buttonSequence.length; idx++) {
int id = buttonId(idx);
//addButton(getTitle(id, false), getTitle(id, true), images[idx], id == NEW || id == TOGGLE_BREAKPOINT);
addButton(getTitle(id, false), getTitle(id, true), images[idx], id == TOGGLE_BREAKPOINT);
}
}
/**
* Get the title for a toolbar button. Displayed in the toolbar when
* hovering over a button.
* @param id id of the toolbar button
* @param shift true if shift is pressed
* @return the title
*/
public static String getTitle(int id, boolean shift) {
switch (id) {
case DebugToolbar.RUN:
return JavaToolbar.getTitle(JavaToolbar.RUN, shift);
case STOP:
return JavaToolbar.getTitle(JavaToolbar.STOP, shift);
// case NEW:
// return JavaToolbar.getTitle(JavaToolbar.NEW, shift);
// case OPEN:
// return JavaToolbar.getTitle(JavaToolbar.OPEN, shift);
// case SAVE:
// return JavaToolbar.getTitle(JavaToolbar.SAVE, shift);
// case EXPORT:
// return JavaToolbar.getTitle(JavaToolbar.EXPORT, shift);
case DEBUG:
if (shift) {
return Language.text("toolbar.run");
} else {
return Language.text("toolbar.debug.debug");
}
case CONTINUE:
return Language.text("toolbar.debug.continue");
case TOGGLE_BREAKPOINT:
return Language.text("toolbar.debug.toggle_breakpoints");
case STEP:
if (shift) {
return Language.text("toolbar.debug.step_into");
} else {
return Language.text("toolbar.debug.step");
}
case TOGGLE_VAR_INSPECTOR:
return Language.text("toolbar.debug.variable_inspector");
}
return null;
}
/**
* Event handler called when a toolbar button is clicked.
* @param e the mouse event
* @param idx index (i.e. position) of the toolbar button clicked
*/
@Override
public void handlePressed(MouseEvent e, int idx) {
boolean shift = e.isShiftDown();
DebugEditor deditor = (DebugEditor) editor;
int id = buttonId(idx); // convert index/position to button id
switch (id) {
// case DebugToolbar.RUN:
// super.handlePressed(e, JavaToolbar.RUN);
// break;
case STOP:
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Stop' toolbar button");
super.handlePressed(e, JavaToolbar.STOP);
break;
// case NEW:
// Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'New' toolbar button");
// super.handlePressed(e, JavaToolbar.NEW);
// break;
// case OPEN:
// Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Open' toolbar button");
// super.handlePressed(e, JavaToolbar.OPEN);
// break;
// case SAVE:
// Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Save' toolbar button");
// super.handlePressed(e, JavaToolbar.SAVE);
// break;
// case EXPORT:
// Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Export' toolbar button");
// super.handlePressed(e, JavaToolbar.EXPORT);
// break;
case DEBUG:
deditor.handleStop(); // Close any running sketches
deditor.showProblemListView(XQConsoleToggle.CONSOLE);
if (shift) {
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Run' toolbar button");
deditor.handleRun();
} else {
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Debug' toolbar button");
deditor.dbg.startDebug();
}
break;
case CONTINUE:
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Continue' toolbar button");
deditor.dbg.continueDebug();
break;
case TOGGLE_BREAKPOINT:
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Toggle Breakpoint' toolbar button");
deditor.dbg.toggleBreakpoint();
break;
case STEP:
if (shift) {
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Step Into' toolbar button");
deditor.dbg.stepInto();
} else {
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Step' toolbar button");
deditor.dbg.stepOver();
}
break;
// case STEP_INTO:
// deditor.dbg.stepInto();
// break;
// case STEP_OUT:
// deditor.dbg.stepOut();
// break;
case TOGGLE_VAR_INSPECTOR:
Logger.getLogger(DebugToolbar.class.getName()).log(Level.INFO, "Invoked 'Variable Inspector' toolbar button");
deditor.toggleVariableInspector();
break;
}
}
/**
* Activate (light up) a button.
* @param id the button id
*/
@Override
public void activate(int id) {
//System.out.println("activate button idx: " + buttonIndex(id));
super.activate(buttonIndex(id));
}
/**
* Set a button to be inactive.
* @param id the button id
*/
@Override
public void deactivate(int id) {
//System.out.println("deactivate button idx: " + buttonIndex(id));
super.deactivate(buttonIndex(id));
}
/**
* Get button position (index) from it's id.
* @param buttonId the button id
* ({@link #RUN}, {@link #DEBUG}, {@link #CONTINUE}), {@link #STEP}, ...)
* @return the button index
*/
protected int buttonIndex(int buttonId) {
for (int i = 0; i < buttonSequence.length; i++) {
if (buttonSequence[i] == buttonId) {
return i;
}
}
return -1;
}
/**
* Get the button id from its position (index).
* @param buttonIdx the button index
* @return the button id
* ({@link #RUN}, {@link #DEBUG}, {@link #CONTINUE}), {@link #STEP}, ...)
*/
protected int buttonId(int buttonIdx) {
return buttonSequence[buttonIdx];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,317 @@
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.text.BadLocationException;
import processing.app.Base;
import processing.app.SketchCode;
/**
* The bar on the left of the text area which displays all errors as rectangles. <br>
* <br>
* All errors and warnings of a sketch are drawn on the bar, clicking on one,
* scrolls to the tab and location. Error messages displayed on hover. Markers
* are not in sync with the error line. Similar to eclipse's right error bar
* which displays the overall errors in a document
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class ErrorBar extends JPanel {
/**
* Preferred height of the component
*/
protected int preferredHeight;
/**
* Preferred height of the component
*/
protected int preferredWidth = 12;
/**
* Height of marker
*/
public static final int errorMarkerHeight = 4;
/**
* Color of Error Marker
*/
public Color errorColor = new Color(0xED2630);
/**
* Color of Warning Marker
*/
public Color warningColor = new Color(0xFFC30E);
/**
* Background color of the component
*/
public Color backgroundColor = new Color(0x2C343D);
/**
* DebugEditor instance
*/
protected DebugEditor editor;
/**
* ErrorCheckerService instance
*/
protected ErrorCheckerService errorCheckerService;
/**
* Stores error markers displayed PER TAB along the error bar.
*/
protected ArrayList<ErrorMarker> errorPoints = new ArrayList<ErrorMarker>();
/**
* Stores previous list of error markers.
*/
protected ArrayList<ErrorMarker> errorPointsOld = new ArrayList<ErrorMarker>();
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(backgroundColor);
g.fillRect(0, 0, getWidth(), getHeight());
for (ErrorMarker emarker : errorPoints) {
if (emarker.getType() == ErrorMarker.Error) {
g.setColor(errorColor);
} else {
g.setColor(warningColor);
}
g.fillRect(2, emarker.getY(), (getWidth() - 3), errorMarkerHeight);
}
}
public Dimension getPreferredSize() {
return new Dimension(preferredWidth, preferredHeight);
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public ErrorBar(DebugEditor editor, int height, ExperimentalMode mode) {
this.editor = editor;
this.preferredHeight = height;
this.errorCheckerService = editor.errorCheckerService;
errorColor = mode.getThemeColor("errorbar.errorcolor", errorColor);
warningColor = mode
.getThemeColor("errorbar.warningcolor", warningColor);
backgroundColor = mode.getThemeColor("errorbar.backgroundcolor",
backgroundColor);
addListeners();
}
/**
* Update error markers in the error bar.
*
* @param problems
* - List of problems.
*/
synchronized public void updateErrorPoints(final ArrayList<Problem> problems) {
// NOTE TO SELF: ErrorMarkers are calculated for the present tab only
// Error Marker index in the arraylist is LOCALIZED for current tab.
// Also, need to do the update in the UI thread via SwingWorker to prevent
// concurrency issues.
final int fheight = this.getHeight();
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
SketchCode sc = editor.getSketch().getCurrentCode();
int totalLines = 0, currentTab = editor.getSketch()
.getCurrentCodeIndex();
try {
totalLines = Base.countLines(sc.getDocument()
.getText(0, sc.getDocument().getLength())) + 1;
} catch (BadLocationException e) {
e.printStackTrace();
}
// System.out.println("Total lines: " + totalLines);
synchronized (errorPoints) {
errorPointsOld.clear();
for (ErrorMarker marker : errorPoints) {
errorPointsOld.add(marker);
}
errorPoints.clear();
// Each problem.getSourceLine() will have an extra line added
// because of
// class declaration in the beginning as well as default imports
synchronized (problems) {
for (Problem problem : problems) {
if (problem.getTabIndex() == currentTab) {
// Ratio of error line to total lines
float y = (problem.getLineNumber() + 1)
/ ((float) totalLines);
// Ratio multiplied by height of the error bar
y *= fheight - 15; // -15 is just a vertical offset
errorPoints
.add(new ErrorMarker(problem, (int) y,
problem.isError() ? ErrorMarker.Error
: ErrorMarker.Warning));
// System.out.println("Y: " + y);
}
}
}
}
return null;
}
protected void done() {
repaint();
}
};
try {
worker.execute(); // I eat concurrency bugs for breakfast.
} catch (Exception exp) {
System.out.println("Errorbar update markers is slacking."
+ exp.getMessage());
// e.printStackTrace();
}
}
/**
* Check if new errors have popped up in the sketch since the last check
*
* @return true - if errors have changed
*/
public boolean errorPointsChanged() {
if (errorPointsOld.size() != errorPoints.size()) {
editor.getTextArea().repaint();
// System.out.println("2 Repaint " + System.currentTimeMillis());
return true;
}
else {
for (int i = 0; i < errorPoints.size(); i++) {
if (errorPoints.get(i).getY() != errorPointsOld.get(i).getY()) {
editor.getTextArea().repaint();
// System.out.println("3 Repaint " +
// System.currentTimeMillis());
return true;
}
}
}
return false;
}
/**
* Add various mouse listeners.
*/
protected void addListeners() {
this.addMouseListener(new MouseAdapter() {
// Find out which error/warning the user has clicked
// and then scroll to that
@Override
public void mouseClicked(final MouseEvent e) {
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
for (ErrorMarker eMarker : errorPoints) {
// -2 and +2 are extra allowance, clicks in the
// vicinity of the markers register that way
if (e.getY() >= eMarker.getY() - 2
&& e.getY() <= eMarker.getY() + 2 + errorMarkerHeight) {
errorCheckerService.scrollToErrorLine(eMarker.getProblem());
return null;
}
}
return null;
}
};
try {
worker.execute();
} catch (Exception exp) {
System.out.println("Errorbar mouseClicked is slacking."
+ exp.getMessage());
// e.printStackTrace();
}
}
});
// Tooltip on hover
this.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseMoved(final MouseEvent evt) {
// System.out.println(e);
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
for (ErrorMarker eMarker : errorPoints) {
if (evt.getY() >= eMarker.getY() - 2
&& evt.getY() <= eMarker.getY() + 2 + errorMarkerHeight) {
Problem p = eMarker.getProblem();
String msg = (p.isError() ? "Error: " : "Warning: ")
+ p.getMessage();
setToolTipText(msg);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
break;
}
}
return null;
}
};
try {
worker.execute();
} catch (Exception exp) {
System.out
.println("Errorbar mousemoved Worker is slacking."
+ exp.getMessage());
// e.printStackTrace();
}
}
@Override
public void mouseDragged(MouseEvent arg0) {
}
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
package processing.mode.experimental;
/**
* Error markers displayed on the Error Bar.
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class ErrorMarker {
/**
* y co-ordinate of the marker
*/
private int y;
/**
* Type of marker: Error or Warning?
*/
private int type = -1;
/**
* Error Type constant
*/
public static final int Error = 1;
/**
* Warning Type constant
*/
public static final int Warning = 2;
/**
* Problem that the error marker represents
* @see Problem
*/
private Problem problem;
public ErrorMarker(Problem problem, int y, int type) {
this.problem = problem;
this.y = y;
this.type = type;
}
/**
* y co-ordinate of the marker
*/
public int getY() {
return y;
}
/**
* Type of marker: ErrorMarker.Error or ErrorMarker.Warning?
*/
public int getType() {
return type;
}
/**
* Problem that the error marker represents
* @see Problem
*/
public Problem getProblem() {
return problem;
}
}

View File

@@ -0,0 +1,242 @@
package processing.mode.experimental;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.TreeMap;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
public class ErrorMessageSimplifier {
/**
* Mapping between ProblemID constant and the constant name. Holds about 650
* of them. Also, this is just temporary, will be used to find the common
* error types, cos you know, identifying String names is easier than
* identifying 8 digit int constants!
* TODO: this is temporary
*/
private static TreeMap<Integer, String> constantsMap;
public ErrorMessageSimplifier() {
new Thread() {
public void run() {
prepareConstantsList();
}
}.start();
}
private static void prepareConstantsList() {
constantsMap = new TreeMap<Integer, String>();
Class<DefaultProblem> probClass = DefaultProblem.class;
Field f[] = probClass.getFields();
for (Field field : f) {
if (Modifier.isStatic(field.getModifiers()))
try {
//System.out.println(field.getName() + " :" + field.get(null));
Object val = field.get(null);
if (val instanceof Integer) {
constantsMap.put((Integer) (val), field.getName());
}
} catch (Exception e) {
e.printStackTrace();
break;
}
}
//System.out.println("Total items: " + constantsMap.size());
}
public static String getIDName(int id) {
if (constantsMap == null){
prepareConstantsList();
}
return constantsMap.get(id);
}
/**
* Tones down the jargon in the ecj reported errors.
*
* @param problem
* @return
*/
public static String getSimplifiedErrorMessage(Problem problem) {
if (problem == null)
return null;
IProblem iprob = problem.getIProblem();
String args[] = iprob.getArguments();
// log("Simplifying message: " + problem.getMessage() + " ID: "
// + getIDName(iprob.getID()));
// log("Arg count: " + args.length);
// for (int i = 0; i < args.length; i++) {
// log("Arg " + args[i]);
// }
String result = null;
switch (iprob.getID()) {
case IProblem.ParsingError:
if (args.length > 0) {
result = "Error on \"" + args[0] + "\"";
}
break;
case IProblem.ParsingErrorDeleteToken:
if (args.length > 0) {
result = "Error on \"" + args[0] + "\"";
}
break;
case IProblem.ParsingErrorInsertToComplete:
if (args.length > 0) {
if (args[0].length() == 1) {
result = getErrorMessageForBracket(args[0].charAt(0));
}
else {
if(args[0].equals("AssignmentOperator Expression")){
result = "Consider adding a \"=\"";
}
else {
result = "Error on \"" + args[0] + "\"";
}
}
}
break;
case IProblem.ParsingErrorInvalidToken:
if (args.length > 0) {
if (args[1].equals("VariableDeclaratorId")) {
if(args[0].equals("int")) {
result = "\"color\" and \"int\" are reserved words & can't be used as variable names";
}
else {
result = "Error on \"" + args[0] + "\"";
}
}
else {
result = "Error on \"" + args[0] + "\"";
}
}
break;
case IProblem.ParsingErrorInsertTokenAfter:
if (args.length > 0) {
if (args[1].length() == 1) {
result = getErrorMessageForBracket(args[1].charAt(0));
}
else {
result = "Error on \"" + args[0] + "\"Consider adding a \"" + args[1] + "\"";
}
}
break;
case IProblem.UndefinedMethod:
if (args.length > 2) {
result = "The method \"" + args[args.length - 2] + "("
+ getSimpleName(args[args.length - 1]) + ")\" doesn't exist";
}
break;
case IProblem.ParameterMismatch:
if (args.length > 3) {
// 2nd arg is method name, 3rd arg is correct param list
if (args[2].trim().length() == 0) {
// the case where no params are needed.
result = "The method \"" + args[1]
+ "\" doesn't expect any parameters";
} else {
result = "The method \"" + args[1]
+ "\" expects parameters like this: " + args[1] + "("
+ getSimpleName(args[2]) + ")";
}
}
break;
case IProblem.UndefinedField:
if (args.length > 0) {
result = "The global variable \"" + args[0] + "\" doesn't exist";
}
break;
case IProblem.UndefinedType:
if (args.length > 0) {
result = "The class \"" + args[0] + "\" doesn't exist";
}
break;
case IProblem.UnresolvedVariable:
if (args.length > 0) {
result = "The variable \"" + args[0] + "\" doesn't exist";
}
break;
case IProblem.UndefinedName:
if (args.length > 0) {
result = "The name \"" + args[0] + "\" can't be recognized";
}
break;
case IProblem.TypeMismatch:
if (args.length > 1) {
result = "Type mismatch, \"" + getSimpleName(args[0])
+ "\" doesn't match with \"" + getSimpleName(args[1]) + "\"";
}
break;
}
// log("Simplified Error Msg: " + result);
if (result == null)
result = problem.getMessage();
return result;
}
/**
* Converts java.lang.String into String, etc
*
* @param inp
* @return
*/
private static String getSimpleName(String inp) {
if (inp.indexOf('.') < 0)
return inp;
String res = "";
ArrayList<String> names = new ArrayList<String>();
if (inp.indexOf(',') >= 0) {
String arr[] = inp.split(",");
for (int i = 0; i < arr.length; i++) {
names.add(arr[i]);
}
} else
names.add(inp);
for (String n : names) {
int x = n.lastIndexOf('.');
if (x >= 0) {
n = n.substring(x + 1, n.length());
}
res = res + ", " + n;
}
return res.substring(2, res.length());
}
private static String getErrorMessageForBracket(char c){
String result = null;
switch (c) {
case ';':
result = "Missing a semi-colon \";\"";
break;
case '[':
result = "Missing opening square bracket \"[\"";
break;
case ']':
result = "Missing closing square bracket \"]\"";
break;
case '(':
result = "Missing opening parentheses \"(\"";
break;
case ')':
result = "Missing closing parentheses \")\"";
break;
case '{':
result = "Missing opening curly bracket \"{\"";
break;
case '}':
result = "Missing closing curly bracket \"}\"";
break;
default:
result = "Consider adding a \"" + c + "\"";
}
return result;
}
}

View File

@@ -0,0 +1,374 @@
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.table.TableModel;
import processing.app.Editor;
import processing.app.Toolkit;
/**
* Error Window that displays a tablular list of errors. Clicking on an error
* scrolls to its location in the code.
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class ErrorWindow extends JFrame {
private JPanel contentPane;
/**
* The table displaying the errors
*/
protected XQErrorTable errorTable;
/**
* Scroll pane that contains the Error Table
*/
protected JScrollPane scrollPane;
protected DebugEditor thisEditor;
private JFrame thisErrorWindow;
/**
* Handles the sticky Problem window
*/
private DockTool2Base Docker;
protected ErrorCheckerService errorCheckerService;
/**
* Preps up ErrorWindow
*
* @param editor
* - Editor
* @param ecs - ErrorCheckerService
*/
public ErrorWindow(DebugEditor editor, ErrorCheckerService ecs) {
thisErrorWindow = this;
errorCheckerService = ecs;
thisEditor = editor;
setTitle("Problems");
prepareFrame();
}
/**
* Sets up ErrorWindow
*/
protected void prepareFrame() {
Toolkit.setIcon(this);
setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
// Default size: setBounds(100, 100, 458, 160);
setBounds(100, 100, 458, 160); // Yeah, I hardcode such things sometimes. Hate me.
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(new BorderLayout(0, 0));
scrollPane = new JScrollPane();
contentPane.add(scrollPane);
errorTable = new XQErrorTable(errorCheckerService);
scrollPane.setViewportView(errorTable);
try {
Docker = new DockTool2Base();
addListeners();
} catch (Exception e) {
System.out.println("addListeners() acted silly.");
e.printStackTrace();
}
if (thisEditor != null) {
setLocation(new Point(thisEditor.getLocation().x
+ thisEditor.getWidth(), thisEditor.getLocation().y));
}
}
/**
* Updates the error table with new data(Table Model). Called from Error
* Checker Service.
*
* @param tableModel
* - Table Model
* @return True - If error table was updated successfully.
*/
synchronized public boolean updateTable(final TableModel tableModel) {
// XQErrorTable handles evrything now
return errorTable.updateTable(tableModel);
}
/**
* Adds various listeners to components of EditorWindow and to the Editor
* window
*/
protected void addListeners() {
if (thisErrorWindow == null)
System.out.println("ERW null");
thisErrorWindow.addComponentListener(new ComponentListener() {
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentResized(ComponentEvent e) {
Docker.tryDocking();
}
@Override
public void componentMoved(ComponentEvent e) {
Docker.tryDocking();
}
@Override
public void componentHidden(ComponentEvent e) {
}
});
thisErrorWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
thisEditor.problemWindowMenuCB.setSelected(false);
}
@Override
public void windowDeiconified(WindowEvent e) {
thisEditor.setExtendedState(Frame.NORMAL);
}
});
if (thisEditor == null) {
System.out.println("Editor null");
return;
}
/*thisEditor.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
errorCheckerService.pauseThread();
errorCheckerService.stopThread(); // Bye bye thread.
thisErrorWindow.dispose();
}
@Override
public void windowIconified(WindowEvent e) {
thisErrorWindow.setExtendedState(Frame.ICONIFIED);
}
@Override
public void windowDeiconified(WindowEvent e) {
thisErrorWindow.setExtendedState(Frame.NORMAL);
}
});*/
thisEditor.addComponentListener(new ComponentListener() {
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentResized(ComponentEvent e) {
if (Docker.isDocked()) {
Docker.dock();
} else {
Docker.tryDocking();
}
}
@Override
public void componentMoved(ComponentEvent e) {
if (Docker.isDocked()) {
Docker.dock();
} else {
Docker.tryDocking();
}
}
@Override
public void componentHidden(ComponentEvent e) {
// System.out.println("ed hidden");
}
});
}
/**
* Implements the docking feature of the tool - The frame sticks to the
* editor and once docked, moves along with it as the editor is resized,
* moved, or closed.
*
* This class has been borrowed from Tab Manager tool by Thomas Diewald. It
* has been slightly modified and used here.
*
* @author Thomas Diewald , http://thomasdiewald.com
*/
private class DockTool2Base {
private int docking_border = 0;
private int dock_on_editor_y_offset_ = 0;
private int dock_on_editor_x_offset_ = 0;
// ///////////////////////////////
// ____2____
// | |
// | |
// 0 | editor | 1
// | |
// |_________|
// 3
// ///////////////////////////////
// public void reset() {
// dock_on_editor_y_offset_ = 0;
// dock_on_editor_x_offset_ = 0;
// docking_border = 0;
// }
public boolean isDocked() {
return (docking_border >= 0);
}
private final int MAX_GAP_ = 20;
//
public void tryDocking() {
if (thisEditor == null)
return;
Editor editor = thisEditor;
Frame frame = thisErrorWindow;
int ex = editor.getX();
int ey = editor.getY();
int ew = editor.getWidth();
int eh = editor.getHeight();
int fx = frame.getX();
int fy = frame.getY();
int fw = frame.getWidth();
int fh = frame.getHeight();
if (((fy > ey) && (fy < ey + eh))
|| ((fy + fh > ey) && (fy + fh < ey + eh))) {
int dis_border_left = Math.abs(ex - (fx + fw));
int dis_border_right = Math.abs((ex + ew) - (fx));
if (dis_border_left < MAX_GAP_ || dis_border_right < MAX_GAP_) {
docking_border = (dis_border_left < dis_border_right) ? 0
: 1;
dock_on_editor_y_offset_ = fy - ey;
dock();
return;
}
}
if (((fx > ex) && (fx < ex + ew))
|| ((fx + fw > ey) && (fx + fw < ex + ew))) {
int dis_border_top = Math.abs(ey - (fy + fh));
int dis_border_bot = Math.abs((ey + eh) - (fy));
if (dis_border_top < MAX_GAP_ || dis_border_bot < MAX_GAP_) {
docking_border = (dis_border_top < dis_border_bot) ? 2 : 3;
dock_on_editor_x_offset_ = fx - ex;
dock();
return;
}
}
docking_border = -1;
}
public void dock() {
if (thisEditor == null)
return;
Editor editor = thisEditor;
Frame frame = thisErrorWindow;
int ex = editor.getX();
int ey = editor.getY();
int ew = editor.getWidth();
int eh = editor.getHeight();
// int fx = frame.getX();
// int fy = frame.getY();
int fw = frame.getWidth();
int fh = frame.getHeight();
int x = 0, y = 0;
if (docking_border == -1) {
return;
}
if (docking_border == 0) {
x = ex - fw;
y = ey + dock_on_editor_y_offset_;
}
if (docking_border == 1) {
x = ex + ew;
y = ey + dock_on_editor_y_offset_;
}
if (docking_border == 2) {
x = ex + dock_on_editor_x_offset_;
y = ey - fh;
}
if (docking_border == 3) {
x = ex + dock_on_editor_x_offset_;
y = ey + eh;
}
frame.setLocation(x, y);
}
}
}

View File

@@ -0,0 +1,459 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2012 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
import galsasson.mode.tweak.SketchParser;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import processing.app.Base;
import processing.app.Editor;
import processing.app.EditorState;
import processing.app.Mode;
import processing.app.Preferences;
import processing.app.RunnerListener;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.SketchException;
import processing.mode.java.JavaBuild;
import processing.mode.java.JavaMode;
import processing.mode.java.runner.Runner;
/**
* Experimental Mode for Processing, combines Debug Mode and XQMode and
* starts us working toward our next generation editor/debugger setup.
*/
public class ExperimentalMode extends JavaMode {
public static final boolean VERBOSE_LOGGING = true;
//public static final boolean VERBOSE_LOGGING = false;
public static final int LOG_SIZE = 512 * 1024; // max log file size (in bytes)
public static boolean DEBUG = !true;
public ExperimentalMode(Base base, File folder) {
super(base, folder);
// use libraries folder from javamode. will make sketches using core libraries work, as well as import libraries and examples menus
for (Mode m : base.getModeList()) {
if (m.getClass() == JavaMode.class) {
JavaMode jMode = (JavaMode) m;
librariesFolder = jMode.getLibrariesFolder();
rebuildLibraryList();
break;
}
}
// Fetch examples and reference from java mode
// thx to Manindra (https://github.com/martinleopold/DebugMode/issues/4)
examplesFolder = Base.getContentFile("modes/java/examples");
// https://github.com/martinleopold/DebugMode/issues/6
referenceFolder = Base.getContentFile("modes/java/reference");
// set logging level
Logger globalLogger = Logger.getLogger("");
//Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // doesn't work on os x
if (VERBOSE_LOGGING) {
globalLogger.setLevel(Level.INFO);
} else {
globalLogger.setLevel(Level.WARNING);
}
// enable logging to file
try {
// settings is writable for built-in modes, mode folder is not writable
File logFolder = Base.getSettingsFile("debug");
if (!logFolder.exists()) {
logFolder.mkdir();
}
File logFile = new File(logFolder, "DebugMode.%g.log");
Handler handler = new FileHandler(logFile.getAbsolutePath(), LOG_SIZE, 10, false);
globalLogger.addHandler(handler);
} catch (IOException ex) {
Logger.getLogger(ExperimentalMode.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
Logger.getLogger(ExperimentalMode.class.getName()).log(Level.SEVERE, null, ex);
}
// disable initial chattiness for now
// // output version from manifest file
// Package p = ExperimentalMode.class.getPackage();
// String titleAndVersion = p.getImplementationTitle() + " (v" + p.getImplementationVersion() + ")";
// //log(titleAndVersion);
// Logger.getLogger(ExperimentalMode.class.getName()).log(Level.INFO, titleAndVersion);
loadPreferences();
loadIcons();
}
@Override
public String getTitle() {
//return "PDE X";
return "Java";
}
public File[] getKeywordFiles() {
return new File[] {
Base.getContentFile("modes/java/keywords.txt")
};
}
public File getContentFile(String path) {
File file = new File(folder, path);
if (!file.exists()) {
// check to see if it's part of the parent Java Mode
file = new File(Base.getContentFile("modes/java"), path);
}
return file;
}
volatile public static boolean errorCheckEnabled = true,
warningsEnabled = true, codeCompletionsEnabled = true,
debugOutputEnabled = false, errorLogsEnabled = false,
autoSaveEnabled = true, autoSavePromptEnabled = true,
defaultAutoSaveEnabled = true, // ,untitledAutoSaveEnabled;
ccTriggerEnabled = false, importSuggestEnabled = true;
public static int autoSaveInterval = 3; //in minutes
/**
* After how many typed characters, code completion is triggered
*/
volatile public static int codeCompletionTriggerLength = 1;
static public final String prefErrorCheck = "pdex.errorCheckEnabled";
static public final String prefWarnings = "pdex.warningsEnabled";
static public final String prefCodeCompletionEnabled = "pdex.completion";
static public final String prefCCTriggerEnabled = "pdex.completion.trigger";
static public final String prefDebugOP = "pdex.dbgOutput";
static public final String prefErrorLogs = "pdex.writeErrorLogs";
static public final String prefAutoSaveInterval = "pdex.autoSaveInterval";
static public final String prefAutoSave = "pdex.autoSave.autoSaveEnabled";
static public final String prefAutoSavePrompt = "pdex.autoSave.promptDisplay";
static public final String prefDefaultAutoSave = "pdex.autoSave.autoSaveByDefault";
static public final String prefImportSuggestEnabled = "pdex.importSuggestEnabled";
static volatile public boolean enableTweak = false;
public void loadPreferences() {
log("Load PDEX prefs");
ensurePrefsExist();
errorCheckEnabled = Preferences.getBoolean(prefErrorCheck);
warningsEnabled = Preferences.getBoolean(prefWarnings);
codeCompletionsEnabled = Preferences.getBoolean(prefCodeCompletionEnabled);
DEBUG = Preferences.getBoolean(prefDebugOP);
errorLogsEnabled = Preferences.getBoolean(prefErrorLogs);
autoSaveInterval = Preferences.getInteger(prefAutoSaveInterval);
// untitledAutoSaveEnabled = Preferences.getBoolean(prefUntitledAutoSave);
autoSaveEnabled = Preferences.getBoolean(prefAutoSave);
autoSavePromptEnabled = Preferences.getBoolean(prefAutoSavePrompt);
defaultAutoSaveEnabled = Preferences.getBoolean(prefDefaultAutoSave);
ccTriggerEnabled = Preferences.getBoolean(prefCCTriggerEnabled);
importSuggestEnabled = Preferences.getBoolean(prefImportSuggestEnabled);
}
public void savePreferences() {
log("Saving PDEX prefs");
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
Preferences.setBoolean(prefWarnings, warningsEnabled);
Preferences.setBoolean(prefCodeCompletionEnabled, codeCompletionsEnabled);
Preferences.setBoolean(prefDebugOP, DEBUG);
Preferences.setBoolean(prefErrorLogs, errorLogsEnabled);
Preferences.setInteger(prefAutoSaveInterval, autoSaveInterval);
// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled);
Preferences.setBoolean(prefAutoSave, autoSaveEnabled);
Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled);
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
Preferences.setBoolean(prefCCTriggerEnabled, ccTriggerEnabled);
Preferences.setBoolean(prefImportSuggestEnabled, importSuggestEnabled);
}
public void ensurePrefsExist() {
//TODO: Need to do a better job of managing prefs. Think lists.
if (Preferences.get(prefErrorCheck) == null)
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
if (Preferences.get(prefWarnings) == null)
Preferences.setBoolean(prefWarnings, warningsEnabled);
if (Preferences.get(prefCodeCompletionEnabled) == null)
Preferences.setBoolean(prefCodeCompletionEnabled, codeCompletionsEnabled);
if (Preferences.get(prefDebugOP) == null)
Preferences.setBoolean(prefDebugOP, DEBUG);
if (Preferences.get(prefErrorLogs) == null)
Preferences.setBoolean(prefErrorLogs, errorLogsEnabled);
if (Preferences.get(prefAutoSaveInterval) == null)
Preferences.setInteger(prefAutoSaveInterval, autoSaveInterval);
// if(Preferences.get(prefUntitledAutoSave) == null)
// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled);
if (Preferences.get(prefAutoSave) == null)
Preferences.setBoolean(prefAutoSave, autoSaveEnabled);
if (Preferences.get(prefAutoSavePrompt) == null)
Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled);
if (Preferences.get(prefDefaultAutoSave) == null)
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
if (Preferences.get(prefCCTriggerEnabled) == null)
Preferences.setBoolean(prefCCTriggerEnabled, ccTriggerEnabled);
if (Preferences.get(prefImportSuggestEnabled) == null)
Preferences.setBoolean(prefImportSuggestEnabled, importSuggestEnabled);
}
/**
* Create a new editor associated with this mode.
*/
@Override
public Editor createEditor(Base base, String path, EditorState state) {
return new DebugEditor(base, path, state, this);
}
/**
* Load a String value from theme.txt
*
* @param attribute the attribute key to load
* @param defaultValue the default value
* @return the attributes value, or the default value if the attribute
* couldn't be loaded
*/
public String loadThemeString(String attribute, String defaultValue) {
String newString = theme.get(attribute);
if (newString != null) {
return newString;
}
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Error loading String: {0}", attribute);
return defaultValue;
}
/**
* Load a Color value from theme.txt
*
* @param attribute the attribute key to load
* @param defaultValue the default value
* @return the attributes value, or the default value if the attribute
* couldn't be loaded
*/
public Color getThemeColor(String attribute, Color defaultValue) {
Color newColor = theme.getColor(attribute);
if (newColor != null) {
return newColor;
}
log("error loading color: " + attribute);
Logger.getLogger(ExperimentalMode.class.getName()).log(Level.WARNING, "Error loading Color: {0}", attribute);
return defaultValue;
}
protected ImageIcon classIcon, fieldIcon, methodIcon, localVarIcon;
protected void loadIcons(){
String iconPath = getContentFile("data")
.getAbsolutePath()
+ File.separator + "icons";
classIcon = new ImageIcon(iconPath + File.separator + "class_obj.png");
methodIcon = new ImageIcon(iconPath + File.separator
+ "methpub_obj.png");
fieldIcon = new ImageIcon(iconPath + File.separator
+ "field_protected_obj.png");
localVarIcon = new ImageIcon(iconPath + File.separator
+ "field_default_obj.png");
// log("Icons loaded");
}
public ClassLoader getJavaModeClassLoader() {
for (Mode m : base.getModeList()) {
if (m.getClass() == JavaMode.class) {
JavaMode jMode = (JavaMode) m;
return jMode.getClassLoader();
}
}
// badness
return null;
}
/**
* System.out.println()
*/
public static final void log(Object message){
if(ExperimentalMode.DEBUG)
System.out.println(message);
}
/**
* System.err.println()
*/
public static final void logE(Object message){
if(ExperimentalMode.DEBUG)
System.err.println(message);
}
/**
* System.out.print
*/
public static final void log2(Object message){
if(ExperimentalMode.DEBUG)
System.out.print(message);
}
public String[] getIgnorable() {
return new String[] {
"applet",
"application.macosx",
"application.windows",
"application.linux",
"_autosave"
};
}
// TweakMode code
@Override
public Runner handleRun(Sketch sketch, RunnerListener listener) throws SketchException
{
final DebugEditor editor = (DebugEditor)listener;
editor.errorCheckerService.quickErrorCheck();
if (enableTweak) {
enableTweak = false;
return handleTweakPresentOrRun(sketch, listener, false);
}
else {
/* Do the usual (JavaMode style) */
JavaBuild build = new JavaBuild(sketch);
String appletClassName = build.build(false);
if (appletClassName != null) {
final Runner runtime = new Runner(build, listener);
new Thread(new Runnable() {
public void run() {
runtime.launch(false); // this blocks until finished
}
}).start();
return runtime;
}
return null;
}
}
@Override
public Runner handlePresent(Sketch sketch, RunnerListener listener) throws SketchException
{
final DebugEditor editor = (DebugEditor)listener;
editor.errorCheckerService.quickErrorCheck();
if (enableTweak) {
enableTweak = false;
return handleTweakPresentOrRun(sketch, listener, true);
}
else {
/* Do the usual (JavaMode style) */
JavaBuild build = new JavaBuild(sketch);
String appletClassName = build.build(false);
if (appletClassName != null) {
final Runner runtime = new Runner(build, listener);
new Thread(new Runnable() {
public void run() {
runtime.launch(true);
}
}).start();
return runtime;
}
return null;
}
}
public Runner handleTweakPresentOrRun(Sketch sketch, RunnerListener listener, boolean present) throws SketchException
{
final DebugEditor editor = (DebugEditor)listener;
final boolean toPresent = present;
boolean launchInteractive = false;
if (isSketchModified(sketch)) {
editor.deactivateRun();
Base.showMessage("Save", "Please save the sketch before running in Tweak Mode.");
return null;
}
/* first try to build the unmodified code */
JavaBuild build = new JavaBuild(sketch);
String appletClassName = build.build(false);
if (appletClassName == null) {
// unmodified build failed, so fail
return null;
}
/* if compilation passed, modify the code and build again */
// save the original sketch code of the user
editor.initBaseCode();
// check for "// tweak" comment in the sketch
boolean requiresTweak = SketchParser.containsTweakComment(editor.baseCode);
// parse the saved sketch to get all (or only with "//tweak" comment) numbers
final SketchParser parser = new SketchParser(editor.baseCode, requiresTweak);
// add our code to the sketch
launchInteractive = editor.automateSketch(sketch, parser.allHandles);
build = new JavaBuild(sketch);
appletClassName = build.build(false);
if (appletClassName != null) {
final Runner runtime = new Runner(build, listener);
new Thread(new Runnable() {
public void run() {
runtime.launch(toPresent); // this blocks until finished
// executed when the sketch quits
editor.initEditorCode(parser.allHandles, false);
editor.stopInteractiveMode(parser.allHandles);
}
}).start();
if (launchInteractive) {
// replace editor code with baseCode
editor.initEditorCode(parser.allHandles, false);
editor.updateInterface(parser.allHandles, parser.colorBoxes);
editor.startInteractiveMode();
}
return runtime;
}
return null;
}
private boolean isSketchModified(Sketch sketch)
{
for (SketchCode sc : sketch.getCode()) {
if (sc.isModified()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.Field;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Value;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Specialized {@link VariableNode} for representing fields. Overrides
* {@link #setValue} to properly change the value of the encapsulated field.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class FieldNode extends VariableNode {
protected Field field;
protected ObjectReference obj;
/**
* Construct a {@link FieldNode}.
*
* @param name the name
* @param type the type
* @param value the value
* @param field the field
* @param obj a reference to the object containing the field
*/
public FieldNode(String name, String type, Value value, Field field, ObjectReference obj) {
super(name, type, value);
this.field = field;
this.obj = obj;
}
@Override
public void setValue(Value value) {
try {
obj.setValue(field, value);
} catch (InvalidTypeException ex) {
Logger.getLogger(FieldNode.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotLoadedException ex) {
Logger.getLogger(FieldNode.class.getName()).log(Level.SEVERE, null, ex);
}
this.value = value;
}
}

View File

@@ -0,0 +1,79 @@
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
/**
* Wrapper for import statements
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class ImportStatement {
/**
* Ex: processing.opengl.*, java.util.*
*/
private String importName;
/**
* Which tab does it belong to?
*/
private int tab;
/**
* Line number(pde code) of the import
*/
private int lineNumber;
/**
*
* @param importName - Ex: processing.opengl.*, java.util.*
* @param tab - Which tab does it belong to?
* @param lineNumber - Line number(pde code) of the import
*/
public ImportStatement(String importName, int tab, int lineNumber) {
this.importName = importName;
this.tab = tab;
this.lineNumber = lineNumber;
}
public String getImportName() {
return importName;
}
public String getPackageName(){
String ret = new String(importName.trim());
if(ret.startsWith("import "))
ret = ret.substring(7);
if(ret.endsWith(";"))
ret = ret.substring(0, ret.length() - 1);
return ret;
}
public int getTab() {
return tab;
}
public int getLineNumber() {
return lineNumber;
}
}

View File

@@ -0,0 +1,126 @@
package processing.mode.experimental;
import java.io.File;
import java.io.FileFilter;
import java.util.Iterator;
import java.util.TreeMap;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JavadocHelper {
public static void loadJavaDoc(TreeMap<String, String> jdocMap, File p5Ref){
Document doc;
//Pattern pat = Pattern.compile("\\w+");
try {
if (p5Ref == null) {
System.out.println("P5 Ref location null");
p5Ref = new File(
"/home/quarkninja/Workspaces/processing-workspace/processing/build/linux/work/modes/java/reference");
}
FileFilter fileFilter = new FileFilter() {
public boolean accept(File file) {
if(!file.getName().endsWith("_.html"))
return false;
int k = 0;
for (int i = 0; i < file.getName().length(); i++) {
if(file.getName().charAt(i)== '_')
k++;
if(k > 1)
return false;
}
return true;
}
};
for (File docFile : p5Ref.listFiles(fileFilter)) {
doc = Jsoup.parse(docFile, null);
Elements elm = doc.getElementsByClass("ref-item");
String msg = "";
String methodName = docFile.getName().substring(0, docFile.getName().indexOf('_'));
//System.out.println(methodName);
for (Iterator it = elm.iterator(); it.hasNext();) {
Element ele = (Element) it.next();
msg = "<html><body> <strong><div style=\"width: 300px; text-justification: justify;\"></strong><table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"ref-item\">"
+ ele.html() + "</table></div></html></body></html>";
//mat.replaceAll("");
msg = msg.replaceAll("img src=\"", "img src=\""
+ p5Ref.toURI().toURL().toString() + "/");
//System.out.println(ele.text());
}
jdocMap.put(methodName, msg);
}
System.out.println("JDoc loaded "+jdocMap.size());
/* File javaDocFile = new File(
"/home/quarkninja/Workspaces/processing-workspace/processing/build/javadoc/core/processing/core/PApplet.html");
//SimpleOpenNI.SimpleOpenNI
doc = Jsoup.parse(javaDocFile, null);
String msg = "";
Elements elm = doc.getElementsByTag("pre");
// Elements desc = doc.getElementsByTag("dl");
//System.out.println(elm.toString());
for (Iterator iterator = elm.iterator(); iterator.hasNext();) {
Element element = (Element) iterator.next();
//System.out.println(element.text());
// if (element.nextElementSibling() != null)
// System.out.println(element.nextElementSibling().text());
//System.out.println("-------------------");
msg = "<html><body> <strong><div style=\"width: 300px; text-justification: justify;\"></strong>"
+ element.html()
+ element.nextElementSibling()
+ "</div></html></body></html>";
int k = 0;
Matcher matcher = pat.matcher(element.text());
ArrayList<String> parts = new ArrayList<String>();
while (matcher.find()) {
// System.out.print("Start index: " + matcher.start());
// System.out.print(" End index: " + matcher.end() + " ");
if (k == 0 && !matcher.group().equals("public")) {
k = -1;
break;
}
// System.out.print(matcher.group() + " ");
parts.add(matcher.group());
k++;
}
if (k <= 0 || parts.size() < 3)
continue;
int i = 0;
if (parts.get(i).equals("public"))
i++;
if (parts.get(i).equals("static") || parts.get(i).equals("final")
|| parts.get(i).equals("class"))
i++;
if (parts.get(i).equals("static") || parts.get(i).equals("final"))
i++;
// System.out.println("Ret Type " + parts.get(i));
i++; // return type
//System.out.println("Name " + parts.get(i));
jdocMap.put(parts.get(i), msg);
}
// for (String key : jdocMap.keySet()) {
// System.out.println("Method: " + key);
// System.out.println("Method: " + jdocMap.get(key));
// }
*
*/
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,228 @@
/*
* 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.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.request.BreakpointRequest;
/**
* Model/Controller of a line breakpoint. Can be set before or while debugging.
* Adds a highlight using the debuggers view ({@link DebugEditor}).
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class LineBreakpoint implements ClassLoadListener {
protected Debugger dbg; // the debugger
protected LineID line; // the line this breakpoint is set on
protected BreakpointRequest bpr; // the request on the VM's event request manager
protected ReferenceType theClass; // the class containing this breakpoint, null when not yet loaded
/**
* Create a {@link LineBreakpoint}. If in a debug session, will try to
* immediately set the breakpoint. If not in a debug session or the
* corresponding class is not yet loaded the breakpoint will activate on
* class load.
*
* @param line the line id to create the breakpoint on
* @param dbg the {@link Debugger}
*/
public LineBreakpoint(LineID line, Debugger dbg) {
this.line = line;
line.startTracking(dbg.editor().getTab(line.fileName()).getDocument());
this.dbg = dbg;
theClass = dbg.getClass(className()); // try to get the class immediately, may return null if not yet loaded
set(); // activate the breakpoint (show highlight, attach if debugger is running)
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.INFO, "LBP Created " +toString() + " class: " + className(), new Object[]{});
}
/**
* Create a {@link LineBreakpoint} on a line in the current tab.
*
* @param lineIdx the line index of the current tab to create the breakpoint
* on
* @param dbg the {@link Debugger}
*/
// TODO: remove and replace by {@link #LineBreakpoint(LineID line, Debugger dbg)}
public LineBreakpoint(int lineIdx, Debugger dbg) {
this(dbg.editor().getLineIDInCurrentTab(lineIdx), dbg);
}
/**
* Get the line id this breakpoint is on.
*
* @return the line id
*/
public LineID lineID() {
return line;
}
/**
* Test if this breakpoint is on a certain line.
*
* @param testLine the line id to test
* @return true if this breakpoint is on the given line
*/
public boolean isOnLine(LineID testLine) {
return line.equals(testLine);
}
/**
* Attach this breakpoint to the VM. Creates and enables a
* {@link BreakpointRequest}. VM needs to be paused.
*/
protected void attach() {
if (!dbg.isPaused()) {
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "can't attach breakpoint, debugger not paused");
return;
}
if (theClass == null) {
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "can't attach breakpoint, class not loaded: {0}", className());
return;
}
// find line in java space
LineID javaLine = dbg.sketchToJavaLine(line);
if (javaLine == null) {
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "couldn't find line {0} in the java code", line);
return;
}
try {
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "BPs of class: {0} , line " + (javaLine.lineIdx() + 1), new Object[]{theClass});
List<Location> locations = theClass.locationsOfLine(javaLine.lineIdx() + 1);
if (locations.isEmpty()) {
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "no location found for line {0} -> {1}", new Object[]{line, javaLine});
return;
}
// use first found location
bpr = dbg.vm().eventRequestManager().createBreakpointRequest(locations.get(0));
bpr.enable();
Logger.getLogger(LineBreakpoint.class.getName()).log(Level.INFO, "attached breakpoint to {0} -> {1}", new Object[]{line, javaLine});
} catch (AbsentInformationException ex) {
Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Detach this breakpoint from the VM. Deletes the
* {@link BreakpointRequest}.
*/
protected void detach() {
if (bpr != null) {
dbg.vm().eventRequestManager().deleteEventRequest(bpr);
bpr = null;
}
}
/**
* Set this breakpoint. Adds the line highlight. If Debugger is paused also
* attaches the breakpoint by calling {@link #attach()}.
*/
protected void set() {
dbg.addClassLoadListener(this); // class may not yet be loaded
dbg.editor().addBreakpointedLine(line);
if (theClass != null && dbg.isPaused()) { // class is loaded
// immediately activate the breakpoint
attach();
}
if (dbg.editor().isInCurrentTab(line)) {
dbg.editor().getSketch().setModified(true);
}
}
/**
* Remove this breakpoint. Clears the highlight and detaches the breakpoint
* if the debugger is paused.
*/
public void remove() {
dbg.removeClassLoadListener(this);
//System.out.println("removing " + line.lineIdx());
dbg.editor().removeBreakpointedLine(line.lineIdx());
if (dbg.isPaused()) {
// immediately remove the breakpoint
detach();
}
line.stopTracking();
if (dbg.editor().isInCurrentTab(line)) {
dbg.editor().getSketch().setModified(true);
}
}
// public void enable() {
// }
//
// public void disable() {
// }
@Override
public String toString() {
return line.toString();
}
/**
* Get the name of the class this breakpoint belongs to. Needed for fetching
* the right location to create a breakpoint request.
*
* @return the class name
*/
protected String className() {
if (line.fileName().endsWith(".pde")) {
// standard tab
ReferenceType mainClass = dbg.getMainClass();
//System.out.println(dbg.getMainClass().name());
if (mainClass == null) {
return null;
}
return dbg.getMainClass().name();
}
if (line.fileName().endsWith(".java")) {
// pure java tab
return line.fileName().substring(0, line.fileName().lastIndexOf(".java"));
}
return null;
}
/**
* Event handler called when a class is loaded in the debugger. Causes the
* breakpoint to be attached, if its class was loaded.
*
* @param theClass the class that was just loaded.
*/
@Override
public void classLoaded(ReferenceType theClass) {
// check if our class is being loaded
log("Class Loaded: " + theClass.name());
if (theClass.name().equals(className())) {
this.theClass = theClass;
attach();
}
for (ReferenceType ct : theClass.nestedTypes()) {
log("Nested " + ct.name());
}
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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 java.awt.Color;
import java.util.HashSet;
import java.util.Set;
/**
* Model/Controller for a highlighted source code line. Implements a custom
* background color and a text based marker placed in the left-hand gutter area.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class LineHighlight implements LineListener {
protected DebugEditor editor; // the view, used for highlighting lines by setting a background color
protected Color bgColor; // the background color for highlighting lines
protected LineID lineID; // the id of the line
protected String marker; //
protected Color markerColor;
protected int priority = 0;
protected static Set<LineHighlight> allHighlights = new HashSet<LineHighlight>();
protected static boolean isHighestPriority(LineHighlight hl) {
for (LineHighlight check : allHighlights) {
if (check.lineID().equals(hl.lineID()) && check.priority() > hl.priority()) {
return false;
}
}
return true;
}
/**
* Create a {@link LineHighlight}.
*
* @param lineID the line id to highlight
* @param bgColor the background color used for highlighting
* @param editor the {@link DebugEditor}
*/
public LineHighlight(LineID lineID, Color bgColor, DebugEditor editor) {
this.lineID = lineID;
this.bgColor = bgColor;
this.editor = editor;
lineID.addListener(this);
lineID.startTracking(editor.getTab(lineID.fileName()).getDocument()); // TODO: overwrite a previous doc?
paint(); // already checks if on current tab
allHighlights.add(this);
}
public void setPriority(int p) {
this.priority = p;
}
public int priority() {
return priority;
}
/**
* Create a {@link LineHighlight} on the current tab.
*
* @param lineIdx the line index on the current tab to highlight
* @param bgColor the background color used for highlighting
* @param editor the {@link DebugEditor}
*/
// TODO: Remove and replace by {@link #LineHighlight(LineID lineID, Color bgColor, DebugEditor editor)}
public LineHighlight(int lineIdx, Color bgColor, DebugEditor editor) {
this(editor.getLineIDInCurrentTab(lineIdx), bgColor, editor);
}
/**
* Set a text based marker displayed in the left hand gutter area of this
* highlighted line.
*
* @param marker the marker text
*/
public void setMarker(String marker) {
this.marker = marker;
paint();
}
/**
* Set a text based marker displayed in the left hand gutter area of this
* highlighted line. Also use a custom text color.
*
* @param marker the marker text
* @param markerColor the text color
*/
public void setMarker(String marker, Color markerColor) {
this.markerColor = markerColor;
setMarker(marker);
}
/**
* Retrieve the line id of this {@link LineHighlight}.
*
* @return the line id
*/
public LineID lineID() {
return lineID;
}
/**
* Retrieve the color for highlighting this line.
*
* @return the highlight color.
*/
public Color getColor() {
return bgColor;
}
/**
* Test if this highlight is on a certain line.
*
* @param testLine the line to test
* @return true if this highlight is on the given line
*/
public boolean isOnLine(LineID testLine) {
return lineID.equals(testLine);
}
/**
* Event handler for line number changes (due to editing). Will remove the
* highlight from the old line number and repaint it at the new location.
*
* @param line the line that has changed
* @param oldLineIdx the old line index (0-based)
* @param newLineIdx the new line index (0-based)
*/
@Override
public void lineChanged(LineID line, int oldLineIdx, int newLineIdx) {
// clear old line
if (editor.isInCurrentTab(new LineID(line.fileName(), oldLineIdx))) {
editor.textArea().clearLineBgColor(oldLineIdx);
editor.textArea().clearGutterText(oldLineIdx);
}
// paint new line
// but only if it's on top -> fixes current line being hidden by breakpoint moving it down.
// lineChanged events seem to come in inverse order of startTracking the LineID. (and bp is created first...)
if (LineHighlight.isHighestPriority(this)) {
paint();
}
}
/**
* Notify this line highlight that it is no longer used. Call this for
* cleanup before the {@link LineHighlight} is discarded.
*/
public void dispose() {
lineID.removeListener(this);
lineID.stopTracking();
allHighlights.remove(this);
}
/**
* (Re-)paint this line highlight.
*/
public void paint() {
if (editor.isInCurrentTab(lineID)) {
editor.textArea().setLineBgColor(lineID.lineIdx(), bgColor);
if (marker != null) {
if (markerColor != null) {
editor.textArea().setGutterText(lineID.lineIdx(), marker, markerColor);
} else {
editor.textArea().setGutterText(lineID.lineIdx(), marker);
}
}
}
}
/**
* Clear this line highlight.
*/
public void clear() {
if (editor.isInCurrentTab(lineID)) {
editor.textArea().clearLineBgColor(lineID.lineIdx());
editor.textArea().clearGutterText(lineID.lineIdx());
}
}
}

View File

@@ -0,0 +1,269 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Position;
/**
* Describes an ID for a code line. Comprised of a file name and a (0-based)
* line number. Can track changes to the line number due to text editing by
* attaching a {@link Document}. Registered {@link LineListener}s are notified
* of changes to the line number.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class LineID implements DocumentListener {
protected String fileName; // the filename
protected int lineIdx; // the line number, 0-based
protected Document doc; // the Document to use for line number tracking
protected Position pos; // the Position acquired during line number tracking
protected Set<LineListener> listeners = new HashSet<LineListener>(); // listeners for line number changes
public LineID(String fileName, int lineIdx) {
this.fileName = fileName;
this.lineIdx = lineIdx;
}
/**
* Get the file name of this line.
*
* @return the file name
*/
public String fileName() {
return fileName;
}
/**
* Get the (0-based) line number of this line.
*
* @return the line index (i.e. line number, starting at 0)
*/
public synchronized int lineIdx() {
return lineIdx;
}
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Test whether this {@link LineID} is equal to another object. Two
* {@link LineID}'s are equal when both their fileName and lineNo are equal.
*
* @param obj the object to test for equality
* @return {@code true} if equal
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LineID other = (LineID) obj;
if ((this.fileName == null) ? (other.fileName != null) : !this.fileName.equals(other.fileName)) {
return false;
}
if (this.lineIdx != other.lineIdx) {
return false;
}
return true;
}
/**
* Output a string representation in the form fileName:lineIdx+1. Note this
* uses a 1-based line number as is customary for human-readable line
* numbers.
*
* @return the string representation of this line ID
*/
@Override
public String toString() {
return fileName + ":" + (lineIdx + 1);
}
// /**
// * Retrieve a copy of this line ID.
// *
// * @return the copy
// */
// @Override
// public LineID clone() {
// return new LineID(fileName, lineIdx);
// }
/**
* Attach a {@link Document} to enable line number tracking when editing.
* The position to track is before the first non-whitespace character on the
* line. Edits happening before that position will cause the line number to
* update accordingly. Multiple {@link #startTracking} calls will replace
* the tracked document. Whoever wants a tracked line should track it and
* add itself as listener if necessary.
* ({@link LineHighlight}, {@link LineBreakpoint})
*
* @param doc the {@link Document} to use for line number tracking
*/
public synchronized void startTracking(Document doc) {
//System.out.println("tracking: " + this);
if (doc == null) {
return; // null arg
}
if (doc == this.doc) {
return; // already tracking that doc
}
try {
Element line = doc.getDefaultRootElement().getElement(lineIdx);
if (line == null) {
return; // line doesn't exist
}
String lineText = doc.getText(line.getStartOffset(), line.getEndOffset() - line.getStartOffset());
// set tracking position at (=before) first non-white space character on line
pos = doc.createPosition(line.getStartOffset() + nonWhiteSpaceOffset(lineText));
this.doc = doc;
doc.addDocumentListener(this);
} catch (BadLocationException ex) {
Logger.getLogger(LineID.class.getName()).log(Level.SEVERE, null, ex);
pos = null;
this.doc = null;
}
}
/**
* Notify this {@link LineID} that it is no longer in use. Will stop
* position tracking. Call this when this {@link LineID} is no longer
* needed.
*/
public synchronized void stopTracking() {
if (doc != null) {
doc.removeDocumentListener(this);
doc = null;
}
}
/**
* Update the tracked position. Will notify listeners if line number has
* changed.
*/
protected synchronized void updatePosition() {
if (doc != null && pos != null) {
// track position
int offset = pos.getOffset();
int oldLineIdx = lineIdx;
lineIdx = doc.getDefaultRootElement().getElementIndex(offset); // offset to lineNo
if (lineIdx != oldLineIdx) {
for (LineListener l : listeners) {
if (l != null) {
l.lineChanged(this, oldLineIdx, lineIdx);
} else {
listeners.remove(l); // remove null listener
}
}
}
}
}
/**
* Add listener to be notified when the line number changes.
*
* @param l the listener to add
*/
public void addListener(LineListener l) {
listeners.add(l);
}
/**
* Remove a listener for line number changes.
*
* @param l the listener to remove
*/
public void removeListener(LineListener l) {
listeners.remove(l);
}
/**
* Calculate the offset of the first non-whitespace character in a string.
*
* @param str the string to examine
* @return offset of first non-whitespace character in str
*/
protected static int nonWhiteSpaceOffset(String str) {
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return i;
}
}
return str.length();
}
/**
* Called when the {@link Document} registered using {@link #startTracking}
* is edited. This happens when text is inserted or removed.
*
* @param de
*/
protected void editEvent(DocumentEvent de) {
//System.out.println("document edit @ " + de.getOffset());
if (de.getOffset() <= pos.getOffset()) {
updatePosition();
//System.out.println("updating, new line no: " + lineNo);
}
}
/**
* {@link DocumentListener} callback. Called when text is inserted.
*
* @param de
*/
@Override
public void insertUpdate(DocumentEvent de) {
editEvent(de);
}
/**
* {@link DocumentListener} callback. Called when text is removed.
*
* @param de
*/
@Override
public void removeUpdate(DocumentEvent de) {
editEvent(de);
}
/**
* {@link DocumentListener} callback. Called when attributes are changed.
* Not used.
*
* @param de
*/
@Override
public void changedUpdate(DocumentEvent de) {
// not needed.
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
/**
* A Listener for line number changes.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public interface LineListener {
/**
* Event handler for line number changes (due to editing).
*
* @param line the line that has changed
* @param oldLineIdx the old line index (0-based)
* @param newLineIdx the new line index (0-based)
*/
void lineChanged(LineID line, int oldLineIdx, int newLineIdx);
}

View File

@@ -0,0 +1,66 @@
/*
* 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 com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Value;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Specialized {@link VariableNode} for representing local variables. Overrides
* {@link #setValue} to properly change the value of the encapsulated local
* variable.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class LocalVariableNode extends VariableNode {
protected LocalVariable var;
protected StackFrame frame;
/**
* Construct a {@link LocalVariableNode}.
*
* @param name the name
* @param type the type
* @param value the value
* @param var the local variable
* @param frame the stack frame containing the local variable
*/
public LocalVariableNode(String name, String type, Value value, LocalVariable var, StackFrame frame) {
super(name, type, value);
this.var = var;
this.frame = frame;
}
@Override
public void setValue(Value value) {
try {
frame.setValue(var, value);
} catch (InvalidTypeException ex) {
Logger.getLogger(LocalVariableNode.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotLoadedException ex) {
Logger.getLogger(LocalVariableNode.class.getName()).log(Level.SEVERE, null, ex);
}
this.value = value;
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.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 java.util.ArrayList;
import static processing.mode.experimental.ExperimentalMode.log;
/**
* Performs offset matching between PDE and Java code (one line of code only)
*
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
public class OffsetMatcher {
public ArrayList<OffsetMatcher.OffsetPair> offsetMatch;
String pdeCodeLine, javaCodeLine;
boolean matchingNeeded = false;
public OffsetMatcher(String pdeCode, String javaCode) {
this.pdeCodeLine = pdeCode;
this.javaCodeLine = javaCode;
if(pdeCodeLine.trim().equals(javaCodeLine.trim())){ //TODO: trim() needed here?
matchingNeeded = false;
offsetMatch = new ArrayList<OffsetMatcher.OffsetPair>();
//log("Offset Matching not needed");
}
else
{
matchingNeeded = true;
minDistance();
}
// log("PDE <-> Java");
// for (int i = 0; i < offsetMatch.size(); i++) {
// log(offsetMatch.get(i).pdeOffset + " <-> "
// + offsetMatch.get(i).javaOffset +
// ", " + pdeCodeLine.charAt(offsetMatch.get(i).pdeOffset)
// + " <-> " + javaCodeLine.charAt(offsetMatch.get(i).javaOffset));
// }
// log("Length " + offsetMatch.size());
}
public int getPdeOffForJavaOff(int start, int length) {
// log("PDE :" + pdeCodeLine + "\nJAVA:" + javaCodeLine);
// log("getPdeOffForJavaOff() start:" + start + ", len " + length);
if(!matchingNeeded) return start;
int ans = getPdeOffForJavaOff(start);
int end = getPdeOffForJavaOff(start + length - 1);
if(ans == -1 || end == -1){
// log("ans: " + ans + " end: " + end);
}
else {
// log(start + " java start off, pde start off "
// + ans);
// log((start + length - 1) + " java end off, pde end off "
// + end);
// log("J: " + javaCodeLine.substring(start, start + length) + "\nP: "
// + pdeCodeLine.substring(ans, end + 1));
}
return ans;
}
public int getJavaOffForPdeOff(int start, int length) {
if(!matchingNeeded) return start;
int ans = getJavaOffForPdeOff(start);
// log(start + " pde start off, java start off "
// + getJavaOffForPdeOff(start));
// log((start + length - 1) + " pde end off, java end off "
// + getJavaOffForPdeOff(start + length - 1));
return ans;
}
public int getPdeOffForJavaOff(int javaOff) {
if (!matchingNeeded)
return javaOff;
for (int i = offsetMatch.size() - 1; i >= 0; i--) {
if (offsetMatch.get(i).javaOffset < javaOff) {
continue;
} else if (offsetMatch.get(i).javaOffset == javaOff) {
// int j = i;
// sometimes there are multiple repeated j offsets for a single pde offset
// so go to the last one, with bound check
while (i > 0 && offsetMatch.get(--i).javaOffset == javaOff) {
// log("MP " + offsetMatch.get(i).javaOffset + " "
// + offsetMatch.get(i).pdeOffset);
}
if (i + 1 < offsetMatch.size()) { // bounds check, see #2664
int pdeOff = offsetMatch.get(++i).pdeOffset;
while (i > 0 && offsetMatch.get(--i).pdeOffset == pdeOff) {
}
}
int j = i + 1;
if (j > -1 && j < offsetMatch.size())
return offsetMatch.get(j).pdeOffset;
}
}
return -1;
}
public int getJavaOffForPdeOff(int pdeOff) {
if(!matchingNeeded) return pdeOff;
for (int i = offsetMatch.size() - 1; i >= 0; i--) {
if (offsetMatch.get(i).pdeOffset < pdeOff) {
continue;
} else if (offsetMatch.get(i).pdeOffset == pdeOff) {
// int j = i;
while (i > 0 && offsetMatch.get(--i).pdeOffset == pdeOff) {
// log("MP " + offsetMatch.get(i).javaOffset + " "
// + offsetMatch.get(i).pdeOffset);
}
if (i + 1 < offsetMatch.size()) { // bounds check, see #2664
int javaOff = offsetMatch.get(++i).javaOffset;
while (i > 0 && offsetMatch.get(--i).javaOffset == javaOff) {
}
}
int j = i + 1;
if (j > -1 && j < offsetMatch.size())
return offsetMatch.get(j).javaOffset;
}
}
return -1;
}
/**
* Finds 'distance' between two Strings.
* See Edit Distance Problem
* https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/styles/pages/editdistance.html
* http://www.stanford.edu/class/cs124/lec/med.pdf
*
*/
private int minDistance() {
// word1 = reverse(word1);
// word2 = reverse(word2);
int len1 = pdeCodeLine.length();
int len2 = javaCodeLine.length();
// log(pdeCodeLine + " len: " + len1);
// log(javaCodeLine + " len: " + len2);
// len1+1, len2+1, because finally return dp[len1][len2]
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 0; i <= len1; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= len2; j++) {
dp[0][j] = j;
}
//iterate though, and check last char
for (int i = 0; i < len1; i++) {
char c1 = pdeCodeLine.charAt(i);
for (int j = 0; j < len2; j++) {
char c2 = javaCodeLine.charAt(j);
//System.out.print(c1 + "<->" + c2);
//if last two chars equal
if (c1 == c2) {
//update dp value for +1 length
dp[i + 1][j + 1] = dp[i][j];
// log();
} else {
int replace = dp[i][j] + 1;
int insert = dp[i][j + 1] + 1;
int delete = dp[i + 1][j] + 1;
// if (replace < delete) {
// log(" --- D");
// } else
// log(" --- R");
int min = replace > insert ? insert : replace;
min = delete > min ? min : delete;
dp[i + 1][j + 1] = min;
}
}
}
ArrayList<OffsetPair> alist = new ArrayList<OffsetMatcher.OffsetPair>();
offsetMatch = alist;
minDistInGrid(dp, len1, len2, 0, 0, pdeCodeLine.toCharArray(),
javaCodeLine.toCharArray(), alist);
return dp[len1][len2];
}
private void minDistInGrid(int g[][], int i, int j, int fi, int fj,
char s1[], char s2[], ArrayList<OffsetPair> set) {
// if(i < s1.length)System.out.print(s1[i] + " <->");
// if(j < s2.length)System.out.print(s2[j]);
if (i < s1.length && j < s2.length) {
// pdeCodeMap[k] = i;
// javaCodeMap[k] = j;
//System.out.print(s1[i] + " " + i + " <-> " + j + " " + s2[j]);
set.add(new OffsetPair(i, j));
// if (s1[i] != s2[j])
// System.out.println("--");
}
//System.out.println();
if (i == fi && j == fj) {
//System.out.println("Reached end.");
} else {
int a = Integer.MAX_VALUE, b = a, c = a;
if (i > 0)
a = g[i - 1][j];
if (j > 0)
b = g[i][j - 1];
if (i > 0 && j > 0)
c = g[i - 1][j - 1];
int mini = Math.min(a, Math.min(b, c));
if (mini == a) {
//System.out.println(s1[i + 1] + " " + s2[j]);
minDistInGrid(g, i - 1, j, fi, fj, s1, s2, set);
} else if (mini == b) {
//System.out.println(s1[i] + " " + s2[j + 1]);
minDistInGrid(g, i, j - 1, fi, fj, s1, s2, set);
} else if (mini == c) {
//System.out.println(s1[i + 1] + " " + s2[j + 1]);
minDistInGrid(g, i - 1, j - 1, fi, fj, s1, s2, set);
}
}
}
private class OffsetPair {
public final int pdeOffset, javaOffset;
public OffsetPair(int pde, int java) {
pdeOffset = pde;
javaOffset = java;
}
}
public static void main(String[] args) {
// minDistance("c = #qwerty;", "c = 0xffqwerty;");
OffsetMatcher a;
// a = new OffsetMatcher("int a = int(can); int ball;",
// "int a = PApplet.parseInt(can); int ball;");
// a.getPdeOffForJavaOff(25, 3);
// a.getJavaOffForPdeOff(12, 3);
// minDistance("static void main(){;", "public static void main(){;");
// minDistance("#bb00aa", "0xffbb00aa");
// a = new OffsetMatcher("void test(ArrayList<Boid> boids){",
// "public void test(ArrayList<Boid> boids){");
// a.getJavaOffForPdeOff(20,4);
a = new OffsetMatcher("}", "\n");
a.getPdeOffForJavaOff(0,1);
log("--");
a = new OffsetMatcher("color abc = #qwerty;", "int abc = 0xffqwerty;");
a.getPdeOffForJavaOff(4, 3);
// a.getJavaOffForPdeOff(6, 3);
// distance("c = #bb00aa;", "c = 0xffbb00aa;");
}
}

View File

@@ -0,0 +1,211 @@
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.core.compiler.IProblem;
/**
* Wrapper class for IProblem.
*
* Stores the tabIndex and line number according to its tab, including the
* original IProblem object
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class Problem {
/**
* The IProblem which is being wrapped
*/
private IProblem iProblem;
/**
* The tab number to which the error belongs to
*/
private int tabIndex;
/**
* Line number(pde code) of the error
*/
private int lineNumber;
private int lineStartOffset;
private int lineStopOffset;
/**
* Error Message. Processed form of IProblem.getMessage()
*/
private String message;
/**
* The type of error - WARNING or ERROR.
*/
private int type;
/**
* If the error is a 'cannot find type' contains the list of suggested imports
*/
private String[] importSuggestions;
public static final int ERROR = 1, WARNING = 2;
/**
*
* @param iProblem - The IProblem which is being wrapped
* @param tabIndex - The tab number to which the error belongs to
* @param lineNumber - Line number(pde code) of the error
*/
public Problem(IProblem iProblem, int tabIndex, int lineNumber) {
this.iProblem = iProblem;
if(iProblem.isError()) {
type = ERROR;
}
else if(iProblem.isWarning()) {
type = WARNING;
}
this.tabIndex = tabIndex;
this.lineNumber = lineNumber;
this.message = process(iProblem);
this.message = ErrorMessageSimplifier.getSimplifiedErrorMessage(this);
//ErrorMessageSimplifier.getSimplifiedErrorMessage(this);
}
public void setPDEOffsets(int startOffset, int stopOffset){
lineStartOffset = startOffset;
lineStopOffset = stopOffset;
}
public int getPDELineStartOffset(){
return lineStartOffset;
}
public int getPDELineStopOffset(){
return lineStopOffset;
}
public String toString() {
return new String("TAB " + tabIndex + ",LN " + lineNumber + "LN START OFF: "
+ lineStartOffset + ",LN STOP OFF: " + lineStopOffset + ",PROB: "
+ message);
}
public boolean isError(){
return type == ERROR;
}
public boolean isWarning(){
return type == WARNING;
}
public String getMessage(){
return message;
}
public IProblem getIProblem(){
return iProblem;
}
public int getTabIndex(){
return tabIndex;
}
public int getLineNumber(){
return lineNumber;
}
/**
* Remember to subtract a -1 to line number because in compile check code an
* extra package statement is added, so all line numbers are increased by 1
*
* @return
*/
public int getSourceLineNumber(){
return iProblem.getSourceLineNumber();
}
public void setType(int ProblemType){
if(ProblemType == ERROR)
type = ERROR;
else if(ProblemType == WARNING)
type = WARNING;
else throw new IllegalArgumentException("Illegal Problem type passed to Problem.setType(int)");
}
public String[] getImportSuggestions() {
return importSuggestions;
}
public void setImportSuggestions(String[] a) {
importSuggestions = a;
}
private static Pattern pattern;
private static Matcher matcher;
private static final String tokenRegExp = "\\b token\\b";
public static String process(IProblem problem) {
return process(problem.getMessage());
}
/**
* Processes error messages and attempts to make them a bit more english like.
* Currently performs:
* <li>Remove all instances of token. "Syntax error on token 'blah', delete this token"
* becomes "Syntax error on 'blah', delete this"
* @param message - The message to be processed
* @return String - The processed message
*/
public static String process(String message) {
// Remove all instances of token
// "Syntax error on token 'blah', delete this token"
if(message == null) return null;
pattern = Pattern.compile(tokenRegExp);
matcher = pattern.matcher(message);
message = matcher.replaceAll("");
return message;
}
// Split camel case words into separate words.
// "VaraibleDeclaration" becomes "Variable Declaration"
// But sadly "PApplet" become "P Applet" and so on.
public static String splitCamelCaseWord(String word) {
String newWord = "";
for (int i = 1; i < word.length(); i++) {
if (Character.isUpperCase(word.charAt(i))) {
// System.out.println(word.substring(0, i) + " "
// + word.substring(i));
newWord += word.substring(0, i) + " ";
word = word.substring(i);
i = 1;
}
}
newWord += word;
// System.out.println(newWord);
return newWord.trim();
}
}

View File

@@ -0,0 +1,387 @@
package processing.mode.experimental;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
public class SketchOutline {
protected JFrame frmOutlineView;
protected ErrorCheckerService errorCheckerService;
protected JScrollPane jsp;
protected DefaultMutableTreeNode soNode, tempNode;
protected final JTree soTree;
protected JTextField searchField;
protected DebugEditor editor;
protected boolean internalSelection = false;
public SketchOutline(DefaultMutableTreeNode codeTree, ErrorCheckerService ecs) {
errorCheckerService = ecs;
editor = ecs.getEditor();
soNode = new DefaultMutableTreeNode();
generateSketchOutlineTree(soNode, codeTree);
soNode = (DefaultMutableTreeNode) soNode.getChildAt(0);
tempNode = soNode;
soTree = new JTree(soNode);
createGUI();
}
private void createGUI(){
frmOutlineView = new JFrame();
frmOutlineView.setAlwaysOnTop(true);
frmOutlineView.setUndecorated(true);
Point tp = errorCheckerService.getEditor().ta.getLocationOnScreen();
// frmOutlineView.setBounds(tp.x
// + errorCheckerService.getEditor().ta
// .getWidth() - 300, tp.y, 300,
// errorCheckerService.getEditor().ta.getHeight());
//TODO: ^Absolute dimensions are bad bro
int minWidth = (int) (editor.getMinimumSize().width * 0.7f),
maxWidth = (int) (editor.getMinimumSize().width * 0.9f);
frmOutlineView.setLayout(new BoxLayout(frmOutlineView.getContentPane(),
BoxLayout.Y_AXIS));
JPanel panelTop = new JPanel(), panelBottom = new JPanel();
panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS));
panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.Y_AXIS));
searchField = new JTextField();
searchField.setMinimumSize(new Dimension(minWidth, 25));
panelTop.add(searchField);
jsp = new JScrollPane();
soTree.getSelectionModel()
.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
soTree.setRootVisible(false);
soTree.setCellRenderer(new CustomCellRenderer());
for (int i = 0; i < soTree.getRowCount(); i++) {
soTree.expandRow(i);
}
soTree.setSelectionRow(0);
jsp.setViewportView(soTree);
jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jsp.setMinimumSize(new Dimension(minWidth, editor.ta.getHeight() - 10));
jsp.setMaximumSize(new Dimension(maxWidth, editor.ta.getHeight() - 10));
panelBottom.add(jsp);
frmOutlineView.add(panelTop);
frmOutlineView.add(panelBottom);
frmOutlineView.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frmOutlineView.pack();
frmOutlineView.setBounds(tp.x
+ errorCheckerService.getEditor().ta
.getWidth() - minWidth, tp.y, minWidth,
Math.min(editor.ta.getHeight(), frmOutlineView.getHeight()));
frmOutlineView.setMinimumSize(new Dimension(minWidth, Math
.min(errorCheckerService.getEditor().ta.getHeight(), frmOutlineView.getHeight())));
frmOutlineView.setLocation(tp.x
+ errorCheckerService.getEditor().ta
.getWidth()/2 - frmOutlineView.getWidth()/2,
frmOutlineView.getY()
+ (editor.ta.getHeight() - frmOutlineView
.getHeight()) / 2);
addListeners();
}
protected void addListeners() {
searchField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
if (soTree.getRowCount() == 0)
return;
internalSelection = true;
if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
close();
}
else if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
if (soTree.getLastSelectedPathComponent() != null) {
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) soTree
.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ASTNodeWrapper) {
ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject();
awrap.highlightNode(errorCheckerService.astGenerator);
//errorCheckerService.highlightNode(awrap);
close();
}
}
}
else if (evt.getKeyCode() == KeyEvent.VK_UP) {
if (soTree.getLastSelectedPathComponent() == null) {
soTree.setSelectionRow(0);
return;
}
int x = soTree.getLeadSelectionRow() - 1;
int step = jsp.getVerticalScrollBar().getMaximum()
/ soTree.getRowCount();
if (x == -1) {
x = soTree.getRowCount() - 1;
jsp.getVerticalScrollBar().setValue(jsp.getVerticalScrollBar().getMaximum());
} else {
jsp.getVerticalScrollBar().setValue((jsp.getVerticalScrollBar()
.getValue() - step));
}
soTree.setSelectionRow(x);
}
else if (evt.getKeyCode() == KeyEvent.VK_DOWN) {
if (soTree.getLastSelectedPathComponent() == null) {
soTree.setSelectionRow(0);
return;
}
int x = soTree.getLeadSelectionRow() + 1;
int step = jsp.getVerticalScrollBar().getMaximum()
/ soTree.getRowCount();
if (x == soTree.getRowCount()) {
x = 0;
jsp.getVerticalScrollBar().setValue(jsp.getVerticalScrollBar().getMinimum());
} else {
jsp.getVerticalScrollBar().setValue((jsp.getVerticalScrollBar()
.getValue() + step));
}
soTree.setSelectionRow(x);
}
}
});
searchField.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateSelection();
}
public void removeUpdate(DocumentEvent e) {
updateSelection();
}
public void changedUpdate(DocumentEvent e) {
updateSelection();
}
private void updateSelection(){
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
String text = searchField.getText().toLowerCase();
tempNode = new DefaultMutableTreeNode();
filterTree(text, tempNode, soNode);
return null;
}
protected void done() {
soTree.setModel(new DefaultTreeModel(tempNode));
((DefaultTreeModel) soTree.getModel()).reload();
for (int i = 0; i < soTree.getRowCount(); i++) {
soTree.expandRow(i);
}
internalSelection = true;
soTree.setSelectionRow(0);
}
};
worker.execute();
}
});
frmOutlineView.addWindowFocusListener(new WindowFocusListener() {
public void windowLostFocus(WindowEvent e) {
close();
}
public void windowGainedFocus(WindowEvent e) {
}
});
soTree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
if (internalSelection) {
internalSelection = (false);
return;
}
// log(e);
scrollToNode();
}
});
soTree.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent me) {
scrollToNode();
}
});
}
private void scrollToNode(){
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
return null;
}
protected void done() {
if (soTree.getLastSelectedPathComponent() == null) {
return;
}
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) soTree
.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ASTNodeWrapper) {
ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject();
awrap.highlightNode(errorCheckerService.astGenerator);
// log(awrap);
//errorCheckerService.highlightNode(awrap);
close();
}
}
};
worker.execute();
}
protected boolean filterTree(String prefix, DefaultMutableTreeNode tree,
DefaultMutableTreeNode mainTree) {
if (mainTree.isLeaf()) {
return (mainTree.getUserObject().toString().toLowerCase()
.startsWith(prefix));
}
boolean found = false;
for (int i = 0; i < mainTree.getChildCount(); i++) {
DefaultMutableTreeNode tNode = new DefaultMutableTreeNode(
((DefaultMutableTreeNode) mainTree
.getChildAt(i))
.getUserObject());
if (filterTree(prefix, tNode,
(DefaultMutableTreeNode) mainTree.getChildAt(i))) {
found = true;
tree.add(tNode);
}
}
return found;
}
protected void generateSketchOutlineTree(DefaultMutableTreeNode node,
DefaultMutableTreeNode codetree) {
if (codetree == null)
return;
//log("Visi " + codetree + codetree.getUserObject().getClass().getSimpleName());
if (!(codetree.getUserObject() instanceof ASTNodeWrapper))
return;
ASTNodeWrapper awnode = (ASTNodeWrapper) codetree.getUserObject(), aw2 = null;
if (awnode.getNode() instanceof TypeDeclaration) {
aw2 = new ASTNodeWrapper( ((TypeDeclaration) awnode.getNode()).getName(),
((TypeDeclaration) awnode.getNode()).getName()
.toString());
} else if (awnode.getNode() instanceof MethodDeclaration) {
aw2 = new ASTNodeWrapper(
((MethodDeclaration) awnode.getNode()).getName(),
new CompletionCandidate(
((MethodDeclaration) awnode
.getNode()))
.toString());
} else if (awnode.getNode() instanceof FieldDeclaration) {
FieldDeclaration fd = (FieldDeclaration) awnode.getNode();
for (VariableDeclarationFragment vdf : (List<VariableDeclarationFragment>) fd.fragments()) {
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
new ASTNodeWrapper(
vdf.getName(),
new CompletionCandidate(
vdf)
.toString()));
node.add(newNode);
}
return;
}
if (aw2 == null)
return;
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(aw2);
node.add(newNode);
for (int i = 0; i < codetree.getChildCount(); i++) {
generateSketchOutlineTree(newNode,
(DefaultMutableTreeNode) codetree.getChildAt(i));
}
}
public void show() {
frmOutlineView.setVisible(true);
}
public void close(){
frmOutlineView.setVisible(false);
frmOutlineView.dispose();
}
public boolean isVisible(){
return frmOutlineView.isVisible();
}
protected class CustomCellRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded,
leaf, row, hasFocus);
if (value instanceof DefaultMutableTreeNode)
setIcon(getTreeIcon(value));
return this;
}
public javax.swing.Icon getTreeIcon(Object o) {
if (((DefaultMutableTreeNode) o).getUserObject() instanceof ASTNodeWrapper) {
ASTNodeWrapper awrap = (ASTNodeWrapper) ((DefaultMutableTreeNode) o)
.getUserObject();
int type = awrap.getNode().getParent().getNodeType();
if (type == ASTNode.METHOD_DECLARATION)
return editor.dmode.methodIcon;
if (type == ASTNode.TYPE_DECLARATION)
return editor.dmode.classIcon;
if (type == ASTNode.VARIABLE_DECLARATION_FRAGMENT)
return editor.dmode.fieldIcon;
}
return null;
}
}
}

View File

@@ -0,0 +1,341 @@
package processing.mode.experimental;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import processing.app.SketchCode;
public class TabOutline {
protected JFrame frmOutlineView;
protected JScrollPane jsp;
protected DefaultMutableTreeNode tabNode, tempNode;
protected JTree tabTree;
protected JTextField searchField;
protected JLabel lblCaption;
protected DebugEditor editor;
protected ErrorCheckerService errorCheckerService;
protected boolean internalSelection = false;
public TabOutline(ErrorCheckerService ecs) {
errorCheckerService = ecs;
editor = ecs.getEditor();
createGUI();
}
private void createGUI() {
frmOutlineView = new JFrame();
frmOutlineView.setAlwaysOnTop(true);
frmOutlineView.setUndecorated(true);
Point tp = errorCheckerService.getEditor().ta.getLocationOnScreen();
lblCaption = new JLabel("Tabs List (type to filter)");
// int minWidth = (int) (editor.getMinimumSize().width * 0.7f), maxWidth = (int) (editor
// .getMinimumSize().width * 0.9f);
int minWidth = estimateFrameWidth(), maxWidth = (int) (editor
.getMinimumSize().width * 0.9f);
frmOutlineView.setLayout(new BoxLayout(frmOutlineView.getContentPane(),
BoxLayout.Y_AXIS));
JPanel panelTop = new JPanel(), panelMiddle = new JPanel(), panelBottom = new JPanel();
panelTop.setLayout(new GridBagLayout());
panelMiddle.setLayout(new BoxLayout(panelMiddle, BoxLayout.Y_AXIS));
panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.Y_AXIS));
lblCaption.setAlignmentX(Component.LEFT_ALIGNMENT);
panelTop.add(lblCaption);
searchField = new JTextField();
searchField.setMinimumSize(new Dimension(minWidth, 25));
panelMiddle.add(searchField);
jsp = new JScrollPane();
populateTabTree();
jsp.setViewportView(tabTree);
jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jsp.setMinimumSize(new Dimension(minWidth, editor.ta.getHeight() - 10));
jsp.setMaximumSize(new Dimension(maxWidth, editor.ta.getHeight() - 10));
panelBottom.add(jsp);
frmOutlineView.add(panelTop);
frmOutlineView.add(panelMiddle);
frmOutlineView.add(panelBottom);
frmOutlineView.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frmOutlineView.pack();
frmOutlineView.setBounds(tp.x
+ errorCheckerService.getEditor().ta
.getWidth() - minWidth,
tp.y,
minWidth,
estimateFrameHeight());
frmOutlineView.setMinimumSize(new Dimension(minWidth, Math
.min(errorCheckerService.getEditor().ta.getHeight(),
frmOutlineView.getHeight())));
frmOutlineView.setLocation(tp.x
+ errorCheckerService.getEditor().ta
.getWidth()/2 - frmOutlineView.getWidth()/2,
frmOutlineView.getY()
+ (editor.ta.getHeight() - frmOutlineView
.getHeight()) / 2);
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tabTree.getCellRenderer();
renderer.setLeafIcon(null);
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
addListeners();
}
private void addListeners() {
searchField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
if (tabTree.getRowCount() == 0)
return;
internalSelection = true;
if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
close();
} else if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
if (tabTree.getLastSelectedPathComponent() != null) {
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) tabTree
.getLastSelectedPathComponent();
//log("Enter Key, Tab: " + tnode);
switchToTab(tnode.toString());
close();
}
} else if (evt.getKeyCode() == KeyEvent.VK_UP) {
if (tabTree.getLastSelectedPathComponent() == null) {
tabTree.setSelectionRow(0);
return;
}
int x = tabTree.getLeadSelectionRow() - 1;
int step = jsp.getVerticalScrollBar().getMaximum()
/ tabTree.getRowCount();
if (x == -1) {
x = tabTree.getRowCount() - 1;
jsp.getVerticalScrollBar().setValue(jsp.getVerticalScrollBar()
.getMaximum());
} else {
jsp.getVerticalScrollBar().setValue((jsp.getVerticalScrollBar()
.getValue() - step));
}
tabTree.setSelectionRow(x);
} else if (evt.getKeyCode() == KeyEvent.VK_DOWN) {
if (tabTree.getLastSelectedPathComponent() == null) {
tabTree.setSelectionRow(0);
return;
}
int x = tabTree.getLeadSelectionRow() + 1;
int step = jsp.getVerticalScrollBar().getMaximum()
/ tabTree.getRowCount();
if (x == tabTree.getRowCount()) {
x = 0;
jsp.getVerticalScrollBar().setValue(jsp.getVerticalScrollBar()
.getMinimum());
} else {
jsp.getVerticalScrollBar().setValue((jsp.getVerticalScrollBar()
.getValue() + step));
}
tabTree.setSelectionRow(x);
}
}
});
searchField.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateSelection();
}
public void removeUpdate(DocumentEvent e) {
updateSelection();
}
public void changedUpdate(DocumentEvent e) {
updateSelection();
}
private void updateSelection() {
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
String text = searchField.getText().toLowerCase();
tempNode = new DefaultMutableTreeNode();
filterTree(text, tempNode, tabNode);
return null;
}
protected void done() {
tabTree.setModel(new DefaultTreeModel(tempNode));
((DefaultTreeModel) tabTree.getModel()).reload();
// for (int i = 0; i < tabTree.getRowCount(); i++) {
// tabTree.expandRow(i);
// }
internalSelection = true;
tabTree.setSelectionRow(0);
}
};
worker.execute();
}
});
tabTree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
if (internalSelection) {
//log("Internal selection");
internalSelection = (false);
return;
}
// log(e);
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
return null;
}
protected void done() {
if (tabTree.getLastSelectedPathComponent() == null) {
return;
}
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) tabTree
.getLastSelectedPathComponent();
//log("Clicked " + tnode);
switchToTab(tnode.toString());
close();
}
};
worker.execute();
}
});
tabTree.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent me) {
if (tabTree.getLastSelectedPathComponent() == null) {
return;
}
DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) tabTree
.getLastSelectedPathComponent();
//log("Clicked " + tnode);
switchToTab(tnode.toString());
close();
}
});
frmOutlineView.addWindowFocusListener(new WindowFocusListener() {
public void windowLostFocus(WindowEvent e) {
close();
}
public void windowGainedFocus(WindowEvent e) {
}
});
}
private void switchToTab(String tabName) {
for (SketchCode sc : editor.getSketch().getCode()) {
if (sc.getPrettyName().equals(tabName)) {
editor.getSketch().setCurrentCode(editor.getSketch().getCodeIndex(sc));
}
}
}
private void populateTabTree() {
tabNode = new DefaultMutableTreeNode("Tabs");
for (SketchCode sc : editor.getSketch().getCode()) {
DefaultMutableTreeNode tab = new DefaultMutableTreeNode(
sc.getPrettyName());
tabNode.add(tab);
}
tempNode = tabNode;
tabTree = new JTree(tabNode);
tabTree.getSelectionModel()
.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tabTree.setRootVisible(false);
tabTree.setSelectionRow(editor.getSketch().getCurrentCodeIndex());
}
protected boolean filterTree(String prefix, DefaultMutableTreeNode tree,
DefaultMutableTreeNode mainTree) {
if (mainTree.isLeaf()) {
return (mainTree.getUserObject().toString().toLowerCase()
.startsWith(prefix));
}
boolean found = false;
for (int i = 0; i < mainTree.getChildCount(); i++) {
DefaultMutableTreeNode tNode = new DefaultMutableTreeNode(
((DefaultMutableTreeNode) mainTree
.getChildAt(i))
.getUserObject());
if (filterTree(prefix, tNode,
(DefaultMutableTreeNode) mainTree.getChildAt(i))) {
found = true;
tree.add(tNode);
}
}
return found;
}
private int estimateFrameWidth() {
FontMetrics fm = editor.ta.getGraphics().getFontMetrics();
int w = fm.stringWidth(lblCaption.getText()) + 10;
for (int i = 0; i < editor.getSketch().getCodeCount(); i++) {
w = Math.max(w, fm.stringWidth(editor.getSketch().getCode(i)
.getPrettyName()) + 10);
}
return w;
}
private int estimateFrameHeight() {
int textHeight = jsp.getGraphics().getFontMetrics().getHeight() + 2;
int t = Math.max(4, editor.getSketch().getCodeCount() + 3);
return Math.min(textHeight * t, frmOutlineView.getHeight());
}
public void show() {
frmOutlineView.setVisible(true);
}
public void close() {
frmOutlineView.setVisible(false);
frmOutlineView.dispose();
}
public boolean isVisible() {
return frmOutlineView.isVisible();
}
}

View File

@@ -0,0 +1,962 @@
/*
* Copyright (C) 2012-14 Martin Leopold <m@martinleopold.com> and Manindra Moharana <me@mkmoharana.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 static processing.mode.experimental.ExperimentalMode.log2;
import galsasson.mode.tweak.ColorControlBox;
import galsasson.mode.tweak.Handle;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FontMetrics;
import java.awt.Point;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultListModel;
import javax.swing.SwingWorker;
import processing.app.Base;
import processing.app.syntax.JEditTextArea;
import processing.app.syntax.TextAreaDefaults;
/**
* Customized text area. Adds support for line background colors.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class TextArea extends JEditTextArea {
protected MouseListener[] mouseListeners; // cached mouselisteners, these are wrapped by MouseHandler
protected DebugEditor editor; // the editor
// line properties
protected Map<Integer, Color> lineColors = new HashMap<Integer, Color>(); // contains line background colors
// left-hand gutter properties
protected int gutterPadding = 3; // [px] space added to the left and right of gutter chars
protected Color gutterBgColor = new Color(252, 252, 252); // gutter background color
protected Color gutterLineColor = new Color(233, 233, 233); // color of vertical separation line
protected String breakpointMarker = "<>"; // the text marker for highlighting breakpoints in the gutter
protected String currentLineMarker = "->"; // the text marker for highlighting the current line in the gutter
protected Map<Integer, String> gutterText = new HashMap<Integer, String>(); // maps line index to gutter text
protected Map<Integer, Color> gutterTextColors = new HashMap<Integer, Color>(); // maps line index to gutter text color
protected TextAreaPainter customPainter;
protected ErrorCheckerService errorCheckerService;
public TextArea(TextAreaDefaults defaults, DebugEditor editor) {
super(defaults);
this.editor = editor;
// replace the painter:
// first save listeners, these are package-private in JEditTextArea, so not accessible
ComponentListener[] componentListeners = painter.getComponentListeners();
mouseListeners = painter.getMouseListeners();
MouseMotionListener[] mouseMotionListeners = painter
.getMouseMotionListeners();
remove(painter);
// set new painter
customPainter = new TextAreaPainter(this, defaults);
painter = customPainter;
// set listeners
for (ComponentListener cl : componentListeners) {
painter.addComponentListener(cl);
}
for (MouseMotionListener mml : mouseMotionListeners) {
painter.addMouseMotionListener(mml);
}
// use a custom mouse handler instead of directly using mouseListeners
MouseHandler mouseHandler = new MouseHandler();
painter.addMouseListener(mouseHandler);
painter.addMouseMotionListener(mouseHandler);
//addCompletionPopupListner();
add(CENTER, painter);
// load settings from theme.txt
ExperimentalMode theme = (ExperimentalMode) editor.getMode();
gutterBgColor = theme.getThemeColor("gutter.bgcolor", gutterBgColor);
gutterLineColor = theme.getThemeColor("gutter.linecolor", gutterLineColor);
gutterPadding = theme.getInteger("gutter.padding");
breakpointMarker = theme.loadThemeString("breakpoint.marker",
breakpointMarker);
currentLineMarker = theme.loadThemeString("currentline.marker",
currentLineMarker);
// TweakMode code
prevCompListeners = painter
.getComponentListeners();
prevMouseListeners = painter.getMouseListeners();
prevMMotionListeners = painter
.getMouseMotionListeners();
prevKeyListeners = editor.getKeyListeners();
interactiveMode = false;
addPrevListeners();
}
/**
* Sets ErrorCheckerService and loads theme for TextArea(XQMode)
*
* @param ecs
* @param mode
*/
public void setECSandThemeforTextArea(ErrorCheckerService ecs,
ExperimentalMode mode) {
errorCheckerService = ecs;
customPainter.setECSandTheme(ecs, mode);
}
/**
* Handles KeyEvents for TextArea
* Code completion begins from here.
*/
public void processKeyEvent(KeyEvent evt) {
//if(Base.isMacOS() && evt.isControlDown()) System.out.println("Ctrl down: " + evt);
if(evt.getKeyCode() == KeyEvent.VK_ESCAPE){
if(suggestion != null){
if(suggestion.isVisible()){
log("esc key");
hideSuggestion();
evt.consume();
return;
}
}
}
else if(evt.getKeyCode() == KeyEvent.VK_ENTER && evt.getID() == KeyEvent.KEY_PRESSED){
if (suggestion != null) {
if (suggestion.isVisible()) {
if (suggestion.insertSelection(CompletionPanel.KEYBOARD_COMPLETION)) {
//hideSuggestion(); // Kill it!
evt.consume();
// Still try to show suggestions after inserting if it's
// the case of overloaded methods. See #2755
if(suggestion.isVisible())
prepareSuggestions(evt);
return;
}
}
}
}
if (evt.getID() == KeyEvent.KEY_PRESSED) {
switch (evt.getKeyCode()) {
case KeyEvent.VK_DOWN:
if (suggestion != null)
if (suggestion.isVisible()) {
//log("KeyDown");
suggestion.moveDown();
return;
}
break;
case KeyEvent.VK_UP:
if (suggestion != null)
if (suggestion.isVisible()) {
//log("KeyUp");
suggestion.moveUp();
return;
}
break;
case KeyEvent.VK_BACK_SPACE:
log("BK Key");
break;
case KeyEvent.VK_SPACE:
if (suggestion != null)
if (suggestion.isVisible()) {
log("Space bar, hide completion list");
suggestion.hide();
}
break;
default:
break;
}
}
super.processKeyEvent(evt);
if (editor.hasJavaTabs) return; // code completion disabled if java tabs
if (evt.getID() == KeyEvent.KEY_TYPED) {
char keyChar = evt.getKeyChar();
if (keyChar == KeyEvent.VK_ENTER ||
keyChar == KeyEvent.VK_ESCAPE ||
keyChar == KeyEvent.VK_TAB ||
keyChar == KeyEvent.CHAR_UNDEFINED) {
return;
}
else if (keyChar == ')') {
hideSuggestion(); // See #2741
return;
}
final KeyEvent evt2 = evt;
if (keyChar == '.') {
if (ExperimentalMode.codeCompletionsEnabled) {
log("[KeyEvent]" + KeyEvent.getKeyText(evt2.getKeyCode()) + " |Prediction started");
log("Typing: " + fetchPhrase(evt2));
}
} else if (keyChar == ' ') { // Trigger on Ctrl-Space
if (!Base.isMacOS() && ExperimentalMode.codeCompletionsEnabled &&
(evt.isControlDown() || evt.isMetaDown())) {
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
// Provide completions only if it's enabled
if (ExperimentalMode.codeCompletionsEnabled) {
getDocument().remove(getCaretPosition() - 1, 1); // Remove the typed space
log("[KeyEvent]" + evt2.getKeyChar() + " |Prediction started");
log("Typing: " + fetchPhrase(evt2));
}
return null;
}
};
worker.execute();
} else {
hideSuggestion(); // hide on spacebar
}
} else {
if(ExperimentalMode.codeCompletionsEnabled) {
prepareSuggestions(evt2);
}
}
}
// #2699 - Special case for OS X, where Ctrl-Space is not detected as Key_Typed -_-
else if (Base.isMacOS() && evt.getID() == KeyEvent.KEY_RELEASED
&& evt.getKeyCode() == KeyEvent.VK_SPACE && evt.isControlDown()) {
final KeyEvent evt2 = evt;
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
// Provide completions only if it's enabled
if (ExperimentalMode.codeCompletionsEnabled) {
log("[KeyEvent]" + KeyEvent.getKeyText(evt2.getKeyCode()) + " |Prediction started");
log("Typing: " + fetchPhrase(evt2));
}
return null;
}
};
worker.execute();
}
}
/**
* Kickstart auto-complete suggestions
* @param evt - KeyEvent
*/
private void prepareSuggestions(final KeyEvent evt){
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
// Provide completions only if it's enabled
if (ExperimentalMode.codeCompletionsEnabled
&& (ExperimentalMode.ccTriggerEnabled || suggestion.isVisible())) {
log("[KeyEvent]" + evt.getKeyChar() + " |Prediction started");
log("Typing: " + fetchPhrase(evt));
}
return null;
}
};
worker.execute();
}
/**
* Retrieves the word on which the mouse pointer is present
* @param evt - the MouseEvent which triggered this method
* @return
*/
private String fetchPhrase(MouseEvent evt) {
log("--handle Mouse Right Click--");
int off = xyToOffset(evt.getX(), evt.getY());
if (off < 0)
return null;
int line = getLineOfOffset(off);
if (line < 0)
return null;
String s = getLineText(line);
if (s == null)
return null;
else if (s.length() == 0)
return null;
else {
int x = xToOffset(line, evt.getX()), x2 = x + 1, x1 = x - 1;
int xLS = off - getLineStartNonWhiteSpaceOffset(line);
log("x=" + x);
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!
break;
}
}
if (Character.isDigit(word.charAt(0)))
return null;
log("Mouse click, word: " + word.trim());
errorCheckerService.getASTGenerator().setLastClickedWord(line, word, xLS);
return word.trim();
}
}
/**
* Retrieves the current word typed just before the caret.
* Then triggers code completion for that word.
*
* @param evt - the KeyEvent which triggered this method
* @return
*/
public String fetchPhrase(KeyEvent evt) {
int off = getCaretPosition();
log2("off " + off);
if (off < 0)
return null;
int line = getCaretLine();
if (line < 0)
return null;
String s = getLineText(line);
log2("lin " + line);
//log2(s + " len " + s.length());
int x = getCaretPosition() - getLineStartOffset(line) - 1, x1 = x - 1;
if(x >= s.length() || x < 0) {
//log("X is " + x + ". Returning null");
hideSuggestion();
return null; //TODO: Does this check cause problems? Verify.
}
log2(" x char: " + s.charAt(x));
if (!(Character.isLetterOrDigit(s.charAt(x)) || s.charAt(x) == '_'
|| s.charAt(x) == '(' || s.charAt(x) == '.')) {
//log("Char before caret isn't a letter/digit/_(. so no predictions");
hideSuggestion();
return null;
} else if (x > 0 && (s.charAt(x - 1) == ' ' || s.charAt(x - 1) == '(')
&& Character.isDigit(s.charAt(x))) {
//log("Char before caret isn't a letter, but ' ' or '(', so no predictions");
hideSuggestion(); // See #2755, Option 2 comment
return null;
} else if (x == 0){
//log("X is zero");
hideSuggestion();
return null;
}
//int xLS = off - getLineStartNonWhiteSpaceOffset(line);
String word = (x < s.length() ? s.charAt(x) : "") + "";
if (s.trim().length() == 1) {
// word = ""
// + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
//word = (x < s.length()?s.charAt(x):"") + "";
word = word.trim();
if (word.endsWith("."))
word = word.substring(0, word.length() - 1);
errorCheckerService.getASTGenerator().preparePredictions(word, line
+ errorCheckerService.mainClassOffset,0);
return word;
}
int i = 0;
int closeB = 0;
while (true) {
i++;
//TODO: currently works on single line only. "a. <new line> b()" won't be detected
if (x1 >= 0) {
// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_'
|| s.charAt(x1) == '.' || s.charAt(x1) == ')' || s.charAt(x1) == ']') {
if (s.charAt(x1) == ')') {
word = s.charAt(x1--) + word;
closeB++;
while (x1 >= 0 && closeB > 0) {
word = s.charAt(x1) + word;
if (s.charAt(x1) == '(')
closeB--;
if (s.charAt(x1) == ')')
closeB++;
x1--;
}
}
else if (s.charAt(x1) == ']') {
word = s.charAt(x1--) + word;
closeB++;
while (x1 >= 0 && closeB > 0) {
word = s.charAt(x1) + word;
if (s.charAt(x1) == '[')
closeB--;
if (s.charAt(x1) == ']')
closeB++;
x1--;
}
}
else {
word = s.charAt(x1--) + word;
}
} else {
break;
}
} else {
break;
}
if (i > 200) {
// time out!
break;
}
}
if (Character.isDigit(word.charAt(0)))
return null;
word = word.trim();
// if (word.endsWith("."))
// word = word.substring(0, word.length() - 1);
int lineStartNonWSOffset = 0;
if (word.length() >= ExperimentalMode.codeCompletionTriggerLength) {
errorCheckerService.getASTGenerator()
.preparePredictions(word, line + errorCheckerService.mainClassOffset,
lineStartNonWSOffset);
}
return word;
}
/**
* Retrieve the total width of the gutter area.
*
* @return gutter width in pixels
*/
protected int getGutterWidth() {
if(editor.debugToolbarEnabled == null || !editor.debugToolbarEnabled.get()){
return 0;
}
FontMetrics fm = painter.getFontMetrics();
// log("fm: " + (fm == null));
// log("editor: " + (editor == null));
//log("BPBPBPBPB: " + (editor.breakpointMarker == null));
int textWidth = Math.max(fm.stringWidth(breakpointMarker),
fm.stringWidth(currentLineMarker));
return textWidth + 2 * gutterPadding;
}
/**
* Retrieve the width of margins applied to the left and right of the gutter
* text.
*
* @return margins in pixels
*/
protected int getGutterMargins() {
if(editor.debugToolbarEnabled == null || !editor.debugToolbarEnabled.get()){
return 0;
}
return gutterPadding;
}
/**
* Set the gutter text of a specific line.
*
* @param lineIdx
* the line index (0-based)
* @param text
* the text
*/
public void setGutterText(int lineIdx, String text) {
gutterText.put(lineIdx, text);
painter.invalidateLine(lineIdx);
}
/**
* Set the gutter text and color of a specific line.
*
* @param lineIdx
* the line index (0-based)
* @param text
* the text
* @param textColor
* the text color
*/
public void setGutterText(int lineIdx, String text, Color textColor) {
gutterTextColors.put(lineIdx, textColor);
setGutterText(lineIdx, text);
}
/**
* Clear the gutter text of a specific line.
*
* @param lineIdx
* the line index (0-based)
*/
public void clearGutterText(int lineIdx) {
gutterText.remove(lineIdx);
painter.invalidateLine(lineIdx);
}
/**
* Clear all gutter text.
*/
public void clearGutterText() {
for (int lineIdx : gutterText.keySet()) {
painter.invalidateLine(lineIdx);
}
gutterText.clear();
}
/**
* Retrieve the gutter text of a specific line.
*
* @param lineIdx
* the line index (0-based)
* @return the gutter text
*/
public String getGutterText(int lineIdx) {
return gutterText.get(lineIdx);
}
/**
* Retrieve the gutter text color for a specific line.
*
* @param lineIdx
* the line index
* @return the gutter text color
*/
public Color getGutterTextColor(int lineIdx) {
return gutterTextColors.get(lineIdx);
}
/**
* Set the background color of a line.
*
* @param lineIdx
* 0-based line number
* @param col
* the background color to set
*/
public void setLineBgColor(int lineIdx, Color col) {
lineColors.put(lineIdx, col);
painter.invalidateLine(lineIdx);
}
/**
* Clear the background color of a line.
*
* @param lineIdx
* 0-based line number
*/
public void clearLineBgColor(int lineIdx) {
lineColors.remove(lineIdx);
painter.invalidateLine(lineIdx);
}
/**
* Clear all line background colors.
*/
public void clearLineBgColors() {
for (int lineIdx : lineColors.keySet()) {
painter.invalidateLine(lineIdx);
}
lineColors.clear();
}
/**
* Get a lines background color.
*
* @param lineIdx
* 0-based line number
* @return the color or null if no color was set for the specified line
*/
public Color getLineBgColor(int lineIdx) {
return lineColors.get(lineIdx);
}
/**
* Convert a character offset to a horizontal pixel position inside the text
* area. Overridden to take gutter width into account.
*
* @param line
* the 0-based line number
* @param offset
* the character offset (0 is the first character on a line)
* @return the horizontal position
*/
@Override
public int _offsetToX(int line, int offset) {
return super._offsetToX(line, offset) + getGutterWidth();
}
/**
* Convert a horizontal pixel position to a character offset. Overridden to
* take gutter width into account.
*
* @param line
* the 0-based line number
* @param x
* the horizontal pixel position
* @return he character offset (0 is the first character on a line)
*/
@Override
public int xToOffset(int line, int x) {
return super.xToOffset(line, x - getGutterWidth());
}
/**
* Custom mouse handler. Implements double clicking in the gutter area to
* toggle breakpoints, sets default cursor (instead of text cursor) in the
* gutter area.
*/
protected class MouseHandler implements MouseListener, MouseMotionListener {
protected int lastX; // previous horizontal positon of the mouse cursor
@Override
public void mouseClicked(MouseEvent me) {
// forward to standard listeners
for (MouseListener ml : mouseListeners) {
ml.mouseClicked(me);
}
}
@Override
public void mousePressed(MouseEvent me) {
// check if this happened in the gutter area
if (me.getX() < getGutterWidth()) {
if (me.getButton() == MouseEvent.BUTTON1 && me.getClickCount() == 2) {
int line = me.getY() / painter.getFontMetrics().getHeight()
+ firstLine;
if (line >= 0 && line <= getLineCount() - 1) {
editor.gutterDblClicked(line);
}
}
return;
}
if (me.getButton() == MouseEvent.BUTTON3) {
if(!editor.hasJavaTabs){ // tooltips, etc disabled for java tabs
fetchPhrase(me);
}
}
// forward to standard listeners
for (MouseListener ml : mouseListeners) {
ml.mousePressed(me);
}
}
@Override
public void mouseReleased(MouseEvent me) {
// forward to standard listeners
for (MouseListener ml : mouseListeners) {
ml.mouseReleased(me);
}
}
@Override
public void mouseEntered(MouseEvent me) {
// forward to standard listeners
for (MouseListener ml : mouseListeners) {
ml.mouseEntered(me);
}
}
@Override
public void mouseExited(MouseEvent me) {
// forward to standard listeners
for (MouseListener ml : mouseListeners) {
ml.mouseExited(me);
}
}
@Override
public void mouseDragged(MouseEvent me) {
// No need to forward since the standard MouseMotionListeners are called anyway
// nop
}
@Override
public void mouseMoved(MouseEvent me) {
// No need to forward since the standard MouseMotionListeners are called anyway
if (me.getX() < getGutterWidth()) {
if (lastX >= getGutterWidth()) {
painter.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
} else {
if (lastX < getGutterWidth()) {
painter.setCursor(new Cursor(Cursor.TEXT_CURSOR));
}
}
lastX = me.getX();
}
}
private CompletionPanel suggestion;
//JEditTextArea textarea;
/* No longer used
private void addCompletionPopupListner() {
this.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
if (Character.isLetterOrDigit(e.getKeyChar())
|| e.getKeyChar() == KeyEvent.VK_BACK_SPACE
|| e.getKeyChar() == KeyEvent.VK_DELETE) {
// SwingUtilities.invokeLater(new Runnable() {
// @Override
// public void run() {
// showSuggestion();
// }
//
// });
} else if (Character.isWhitespace(e.getKeyChar())
|| e.getKeyChar() == KeyEvent.VK_ESCAPE) {
hideSuggestion();
}
}
@Override
public void keyPressed(KeyEvent e) {
}
});
}*/
// appears unused, removed when looking to change completion trigger [fry 140801]
/*
public void showSuggestionLater(final DefaultListModel defListModel, final String word) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showSuggestion(defListModel,word);
}
});
}
*/
/**
* Calculates location of caret and displays the suggestion popup at the location.
*
* @param defListModel
* @param subWord
*/
protected void showSuggestion(DefaultListModel<CompletionCandidate> defListModel,String subWord) {
hideSuggestion();
if (defListModel.size() == 0) {
log("TextArea: No suggestions to show.");
return;
}
int position = getCaretPosition();
Point location = new Point();
try {
location.x = offsetToX(getCaretLine(), position
- getLineStartOffset(getCaretLine()));
location.y = lineToY(getCaretLine())
+ getPainter().getFontMetrics().getHeight() + getPainter().getFontMetrics().getDescent();
//log("TA position: " + location);
} catch (Exception e2) {
e2.printStackTrace();
return;
}
if (subWord.length() < 2) {
return;
}
//if (suggestion == null)
suggestion = new CompletionPanel(this, position, subWord, defListModel,
location,editor);
// else
// suggestion.updateList(defListModel, subWord, location, position);
//
// suggestion.setVisible(true);
requestFocusInWindow();
// SwingUtilities.invokeLater(new Runnable() {
// @Override
// public void run() {
// requestFocusInWindow();
// }
// });
}
/**
* Hides suggestion popup
*/
protected void hideSuggestion() {
if (suggestion != null) {
suggestion.hide();
//log("Suggestion hidden.");
suggestion = null;
}
}
// TweakMode code
// save input listeners to stop/start text edit
ComponentListener[] prevCompListeners;
MouseListener[] prevMouseListeners;
MouseMotionListener[] prevMMotionListeners;
KeyListener[] prevKeyListeners;
boolean interactiveMode;
/* remove all standard interaction listeners */
public void removeAllListeners()
{
ComponentListener[] componentListeners = painter
.getComponentListeners();
MouseListener[] mouseListeners = painter.getMouseListeners();
MouseMotionListener[] mouseMotionListeners = painter
.getMouseMotionListeners();
KeyListener[] keyListeners = editor.getKeyListeners();
for (ComponentListener cl : componentListeners)
painter.removeComponentListener(cl);
for (MouseListener ml : mouseListeners)
painter.removeMouseListener(ml);
for (MouseMotionListener mml : mouseMotionListeners)
painter.removeMouseMotionListener(mml);
for (KeyListener kl : keyListeners) {
editor.removeKeyListener(kl);
}
}
public void startInteractiveMode()
{
// ignore if we are already in interactiveMode
if (interactiveMode)
return;
removeAllListeners();
// add our private interaction listeners
customPainter.addMouseListener(customPainter);
customPainter.addMouseMotionListener(customPainter);
customPainter.startInterativeMode();
customPainter.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
this.editable = false;
this.caretBlinks = false;
this.setCaretVisible(false);
interactiveMode = true;
}
public void stopInteractiveMode()
{
// ignore if we are not in interactive mode
if (!interactiveMode)
return;
removeAllListeners();
addPrevListeners();
customPainter.stopInteractiveMode();
customPainter.setCursor(new Cursor(Cursor.TEXT_CURSOR));
this.editable = true;
this.caretBlinks = true;
this.setCaretVisible(true);
interactiveMode = false;
}
public int getHorizontalScroll()
{
return horizontal.getValue();
}
private void addPrevListeners()
{
// add the original text-edit listeners
for (ComponentListener cl : prevCompListeners) {
customPainter.addComponentListener(cl);
}
for (MouseListener ml : prevMouseListeners) {
customPainter.addMouseListener(ml);
}
for (MouseMotionListener mml : prevMMotionListeners) {
customPainter.addMouseMotionListener(mml);
}
for (KeyListener kl : prevKeyListeners) {
editor.addKeyListener(kl);
}
}
//public void updateInterface(ArrayList<Handle> handles[], ArrayList<ColorControlBox> colorBoxes[]) {
public void updateInterface(List<List<Handle>> handles, List<List<ColorControlBox>> colorBoxes) {
customPainter.updateInterface(handles, colorBoxes);
}
}

View File

@@ -0,0 +1,859 @@
/*
* 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 galsasson.mode.tweak.ColorControlBox;
import galsasson.mode.tweak.ColorSelector;
import galsasson.mode.tweak.Handle;
import galsasson.mode.tweak.Settings;
import java.awt.Color;
import java.awt.Cursor;
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.image.BufferedImage;
import java.util.List;
import javax.swing.text.BadLocationException;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import processing.app.SketchCode;
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
implements MouseListener, MouseMotionListener {
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) {
if(ta.editor.hasJavaTabs) return; // Ctrl + Click disabled for java tabs
if (evt.getButton() == MouseEvent.BUTTON1) {
if (evt.isControlDown() || evt.isMetaDown())
handleCtrlClick(evt);
}
}
});
// TweakMode code
interactiveMode = false;
cursorType = Cursor.DEFAULT_CURSOR;
}
// 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) {
if (ta.editor.hasJavaTabs) { // disabled for java tabs
setToolTipText(null);
return super.getToolTipText(evt);
}
int off = ta.xyToOffset(evt.getX(), evt.getY());
if (off < 0) {
setToolTipText(null);
return super.getToolTipText(evt);
}
int line = ta.getLineOfOffset(off);
if (line < 0) {
setToolTipText(null);
return super.getToolTipText(evt);
}
String s = ta.getLineText(line);
if (s == "")
return evt.toString();
else if (s.length() == 0) {
setToolTipText(null);
return super.getToolTipText(evt);
} 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()) {
setToolTipText(null);
return super.getToolTipText(evt);
}
String word = s.charAt(x) + "";
if (s.charAt(x) == ' ') {
setToolTipText(null);
return super.getToolTipText(evt);
}
if (!(Character.isLetterOrDigit(s.charAt(x)) || s.charAt(x) == '_' || s
.charAt(x) == '$')) {
setToolTipText(null);
return super.getToolTipText(evt);
}
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))) {
setToolTipText(null);
return super.getToolTipText(evt);
}
String tooltipText = errorCheckerService.getASTGenerator()
.getLabelForASTNode(line, word, xLS);
// log(errorCheckerService.mainClassOffset + " MCO "
// + "|" + line + "| offset " + xLS + word + " <= offf: "+off+ "\n");
if (tooltipText != null)
return tooltipText;
}
setToolTipText(null);
return super.getToolTipText(evt);
}
// TweakMode code
protected int horizontalAdjustment = 0;
public boolean interactiveMode = false;
// public ArrayList<Handle> handles[];
// public ArrayList<ColorControlBox> colorBoxes[];
public List<List<Handle>> handles;
public List<List<ColorControlBox>> colorBoxes;
public Handle mouseHandle = null;
public ColorSelector colorSelector;
int cursorType;
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
/**
* Repaints the text.
* @param gfx The graphics context
*/
@Override
public synchronized void paint(Graphics gfx)
{
super.paint(gfx);
if (interactiveMode && handles!=null)
{
int currentTab = ta.editor.getSketch().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 = ta.getLineStartOffset(n.line);
int x = ta.offsetToX(n.line, n.newStartChar - lineStartChar);
int y = ta.lineToY(n.line) + fm.getHeight() + 1;
int end = ta.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 = ta.getLineStartOffset(cBox.getLine());
int x = ta.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar);
int y = ta.lineToY(cBox.getLine()) + fm.getDescent();
cBox.setPos(x, y+1);
cBox.draw(g2d);
}
}
}
public void startInterativeMode()
{
interactiveMode = true;
repaint();
}
public void stopInteractiveMode()
{
interactiveMode = false;
if (colorSelector != null) {
// close color selector
colorSelector.hide();
colorSelector.frame.dispatchEvent(new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING));
}
repaint();
}
// Update the interface
//public void updateInterface(ArrayList<Handle> handles[], ArrayList<ColorControlBox> colorBoxes[]) {
public void updateInterface(List<List<Handle>> handles, List<List<ColorControlBox>> 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)
*/
public synchronized void initInterfacePositions() {
SketchCode[] code = ta.editor.getSketch().getCode();
int prevScroll = ta.getVerticalScrollPosition();
String prevText = ta.getText();
for (int tab=0; tab<code.length; tab++) {
String tabCode = ta.editor.baseCode[tab];
ta.setText(tabCode);
for (Handle n : handles.get(tab)) {
int lineStartChar = ta.getLineStartOffset(n.line);
int x = ta.offsetToX(n.line, n.newStartChar - lineStartChar);
int end = ta.offsetToX(n.line, n.newEndChar - lineStartChar);
int y = ta.lineToY(n.line) + fm.getHeight() + 1;
n.initInterface(x, y, end-x, fm.getHeight());
}
for (ColorControlBox cBox : colorBoxes.get(tab)) {
int lineStartChar = ta.getLineStartOffset(cBox.getLine());
int x = ta.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar);
int y = ta.lineToY(cBox.getLine()) + fm.getDescent();
cBox.initInterface(this, x, y+1, fm.getHeight()-2, fm.getHeight()-2);
}
}
ta.setText(prevText);
ta.scrollTo(prevScroll, 0);
}
/**
* Take the saved code of the current tab and replace
* all numbers with their current values.
* Update TextArea with the new code.
*/
public void updateCodeText()
{
int charInc = 0;
int currentTab = ta.editor.getSketch().getCurrentCodeIndex();
SketchCode sc = ta.editor.getSketch().getCode(currentTab);
String code = ta.editor.baseCode[currentTab];
for (Handle n : handles.get(currentTab)) {
int s = n.startChar + charInc;
int e = n.endChar + charInc;
code = replaceString(code, s, e, n.strNewValue);
n.newStartChar = n.startChar + charInc;
charInc += n.strNewValue.length() - n.strValue.length();
n.newEndChar = n.endChar + charInc;
}
replaceTextAreaCode(code);
// update also the sketch code for later
sc.setProgram(code);
}
private synchronized void replaceTextAreaCode(String code)
{
// don't paint while we do the stuff below
/* by default setText will scroll all the way to the end
* remember current scroll position */
int scrollLine = ta.getVerticalScrollPosition();
int scrollHor = ta.getHorizontalScroll();
ta.setText(code);
ta.setOrigin(scrollLine, -scrollHor);
}
public String replaceString(String str, int start, int end, String put)
{
return str.substring(0, start) + put + str.substring(end, str.length());
}
public void updateCursor(int mouseX, int mouseY)
{
int currentTab = ta.editor.getSketch().getCurrentCodeIndex();
for (Handle n : handles.get(currentTab)) {
if (n.pick(mouseX, mouseY))
{
cursorType = Cursor.W_RESIZE_CURSOR;
setCursor(new Cursor(cursorType));
return;
}
}
for (ColorControlBox colorBox : colorBoxes.get(currentTab)) {
if (colorBox.pick(mouseX, mouseY))
{
cursorType = Cursor.HAND_CURSOR;
setCursor(new Cursor(cursorType));
return;
}
}
if (cursorType == Cursor.W_RESIZE_CURSOR ||
cursorType == Cursor.HAND_CURSOR ||
cursorType == -1) {
cursorType = Cursor.DEFAULT_CURSOR;
setCursor(new Cursor(cursorType));
}
}
/* Handle color boxes show/hide
*
* display the box if the mouse if in the same line.
* always keep the color box of the color selector.
*/
private void showHideColorBoxes(int y)
{
int currentTab = ta.editor.getSketch().getCurrentCodeIndex();
boolean change = false;
for (ColorControlBox box : colorBoxes.get(currentTab)) {
if (box.setMouseY(y)) {
change = true;
}
}
if (colorSelector != null) {
colorSelector.colorBox.visible = true;
}
if (change) {
repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (mouseHandle != null) {
// set the current drag amount of the arrows
mouseHandle.setCurrentX(e.getX());
// update code text with the new value
updateCodeText();
if (colorSelector != null) {
colorSelector.refreshColor();
}
repaint();
}
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
int currentTab = ta.editor.getSketch().getCurrentCodeIndex();
// check for clicks on number handles
for (Handle n : handles.get(currentTab)) {
if (n.pick(e.getX(), e.getY())) {
cursorType = -1;
this.setCursor(blankCursor);
mouseHandle = n;
mouseHandle.setCenterX(e.getX());
repaint();
return;
}
}
// check for clicks on color boxes
for (ColorControlBox box : colorBoxes.get(currentTab)) {
if (box.pick(e.getX(), e.getY()))
{
if (colorSelector != null) {
// we already show a color selector, close it
colorSelector.frame.dispatchEvent(
new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING));
}
colorSelector = new ColorSelector(box);
colorSelector.frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
colorSelector.frame.setVisible(false);
colorSelector = null;
}
});
colorSelector.show(this.getLocationOnScreen().x + e.getX() + 30,
this.getLocationOnScreen().y + e.getY() - 130);
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (mouseHandle != null) {
mouseHandle.resetProgress();
mouseHandle = null;
updateCursor(e.getX(), e.getY());
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
updateCursor(e.getX(), e.getY());
if (!Settings.alwaysShowColorBoxes) {
showHideColorBoxes(e.getY());
}
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,285 @@
/*
* Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.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 java.util.ArrayList;
/**
* A class containing multiple utility methods
*
* @author Manindra Moharana <me@mkmoharana.com>
*
*/
public class Utils {
public ArrayList<Utils.OfsSetTemp> offsetMatch;
String word1, word2;
public static String reverse(String s) {
char w[] = s.toCharArray();
for (int i = 0; i < w.length / 2; i++) {
char t = w[i];
w[i] = w[w.length - 1 - i];
w[w.length - 1 - i] = t;
}
return new String(w);
}
public void getPdeOffForJavaOff(int start, int length){
System.out.println("PDE <-> Java" );
for (int i = 0; i < offsetMatch.size(); i++) {
System.out.print(offsetMatch.get(i).pdeOffset + " <-> " + offsetMatch.get(i).javaOffset);
System.out.println(", " + word1.charAt(offsetMatch.get(i).pdeOffset) + " <-> "
+ word2.charAt(offsetMatch.get(i).javaOffset));
}
System.out.println("Length " + offsetMatch.size());
System.out.println(start + " java start off, pde start off "
+ getPdeOffForJavaOff(start));
System.out.println((start + length - 1) + " java end off, pde end off "
+ getPdeOffForJavaOff(start + length - 1));
}
public void getJavaOffForPdeOff(int start, int length){
// System.out.println("PDE <-> Java" );
// for (int i = 0; i < offsetMatch.size(); i++) {
// System.out.print(offsetMatch.get(i).pdeOffset + " <-> " + offsetMatch.get(i).javaOffset);
// System.out.println(", " + word1.charAt(offsetMatch.get(i).pdeOffset) + " <-> "
// + word2.charAt(offsetMatch.get(i).javaOffset));
// }
// System.out.println("Length " + offsetMatch.size());
System.out.println(start + " pde start off, java start off "
+ getJavaOffForPdeOff(start));
System.out.println((start + length - 1) + " pde end off, java end off "
+ getJavaOffForPdeOff(start + length - 1));
}
public int getPdeOffForJavaOff(int javaOff){
for (int i = offsetMatch.size() - 1; i >= 0;i--) {
if(offsetMatch.get(i).javaOffset < javaOff){
continue;
}
else
if(offsetMatch.get(i).javaOffset == javaOff){
// int j = i;
while(offsetMatch.get(--i).javaOffset == javaOff){
System.out.println("MP " + offsetMatch.get(i).javaOffset + " "
+ offsetMatch.get(i).pdeOffset);
}
int pdeOff = offsetMatch.get(++i).pdeOffset;
while(offsetMatch.get(--i).pdeOffset == pdeOff);
int j = i + 1;
if (j > -1 && j < offsetMatch.size())
return offsetMatch.get(j).pdeOffset;
}
}
return -1;
}
public int getJavaOffForPdeOff(int pdeOff){
for (int i = offsetMatch.size() - 1; i >= 0;i--) {
if(offsetMatch.get(i).pdeOffset < pdeOff){
continue;
}
else
if(offsetMatch.get(i).pdeOffset == pdeOff){
// int j = i;
while(offsetMatch.get(--i).pdeOffset == pdeOff){
// System.out.println("MP " + offsetMatch.get(i).javaOffset + " "
// + offsetMatch.get(i).pdeOffset);
}
int javaOff = offsetMatch.get(++i).javaOffset;
while(offsetMatch.get(--i).javaOffset == javaOff);
int j = i + 1;
if (j > -1 && j < offsetMatch.size())
return offsetMatch.get(j).javaOffset;
}
}
return -1;
}
public int minDistance(String word1, String word2) {
this.word1 = word1;
this.word2 = word2;
// word1 = reverse(word1);
// word2 = reverse(word2);
int len1 = word1.length();
int len2 = word2.length();
System.out.println(word1 + " len: " + len1);
System.out.println(word2 + " len: " + len2);
// len1+1, len2+1, because finally return dp[len1][len2]
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 0; i <= len1; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= len2; j++) {
dp[0][j] = j;
}
//iterate though, and check last char
for (int i = 0; i < len1; i++) {
char c1 = word1.charAt(i);
for (int j = 0; j < len2; j++) {
char c2 = word2.charAt(j);
//System.out.print(c1 + "<->" + c2);
//if last two chars equal
if (c1 == c2) {
//update dp value for +1 length
dp[i + 1][j + 1] = dp[i][j];
// System.out.println();
} else {
int replace = dp[i][j] + 1;
int insert = dp[i][j + 1] + 1;
int delete = dp[i + 1][j] + 1;
// if (replace < delete) {
// System.out.println(" --- D");
// } else
// System.out.println(" --- R");
int min = replace > insert ? insert : replace;
min = delete > min ? min : delete;
dp[i + 1][j + 1] = min;
}
}
}
// for (int i = 0; i < dp.length; i++) {
// for (int j = 0; j < dp[0].length; j++) {
// System.out.print(dp[i][j] + " ");
// }
// System.out.println();
// }
// int maxLen = Math.max(len1, len2)+2;
// int pdeCodeMap[] = new int[maxLen], javaCodeMap[] = new int[maxLen];
// System.out.println("Edit distance1: " + dp[len1][len2]);
ArrayList<OfsSetTemp> alist = new ArrayList<Utils.OfsSetTemp>();
offsetMatch = alist;
minDistInGrid(dp, len1, len2, 0, 0, word1.toCharArray(),
word2.toCharArray(), alist);
// System.out.println("PDE-to-Java");
// for (int i = 0; i < maxLen; i++) {
// System.out.print(pdeCodeMap[i] + " <-> " + javaCodeMap[i]);
// System.out.println(", " + word1.charAt(pdeCodeMap[i]) + " <-> "
// + word2.charAt(javaCodeMap[i]));
// }
// for (int i = 0; i < alist.size(); i++) {
// System.out.print(alist.get(i).pdeOffset + " <-> " + alist.get(i).javaOffset);
// System.out.println(", " + word1.charAt(alist.get(i).pdeOffset) + " <-> "
// + word2.charAt(alist.get(i).javaOffset));
// }
// System.out.println("Length " + alist.size());
return dp[len1][len2];
}
public static int distance(String a, String b) {
// a = a.toLowerCase();
// b = b.toLowerCase();
// i == 0
int[] costs = new int[b.length() + 1];
for (int j = 0; j < costs.length; j++)
costs[j] = j;
for (int i = 1; i <= a.length(); i++) {
// j == 0; nw = lev(i - 1, j)
costs[0] = i;
int nw = i - 1;
for (int j = 1; j <= b.length(); j++) {
int cj = Math.min(1 + Math.min(costs[j], costs[j - 1]),
a.charAt(i - 1) == b.charAt(j - 1) ? nw : nw + 1);
nw = costs[j];
costs[j] = cj;
}
}
System.out.println("Edit distance2: " + costs[b.length()]);
return costs[b.length()];
}
public void minDistInGrid(int g[][], int i, int j, int fi, int fj,
char s1[], char s2[], ArrayList<OfsSetTemp> set) {
// if(i < s1.length)System.out.print(s1[i] + " <->");
// if(j < s2.length)System.out.print(s2[j]);
if (i < s1.length && j < s2.length) {
// pdeCodeMap[k] = i;
// javaCodeMap[k] = j;
//System.out.print(s1[i] + " " + i + " <-> " + j + " " + s2[j]);
set.add(new OfsSetTemp(i, j));
// if (s1[i] != s2[j])
// System.out.println("--");
}
//System.out.println();
if (i == fi && j == fj) {
//System.out.println("Reached end.");
} else {
int a = Integer.MAX_VALUE, b = a, c = a;
if (i > 0)
a = g[i - 1][j];
if (j > 0)
b = g[i][j - 1];
if (i > 0 && j > 0)
c = g[i - 1][j - 1];
int mini = Math.min(a, Math.min(b, c));
if (mini == a) {
//System.out.println(s1[i + 1] + " " + s2[j]);
minDistInGrid(g, i - 1, j, fi, fj, s1, s2,set);
} else if (mini == b) {
//System.out.println(s1[i] + " " + s2[j + 1]);
minDistInGrid(g, i, j - 1, fi, fj, s1, s2, set);
} else if (mini == c) {
//System.out.println(s1[i + 1] + " " + s2[j + 1]);
minDistInGrid(g, i - 1, j - 1, fi, fj, s1, s2, set);
}
}
}
public class OfsSetTemp {
public final int pdeOffset, javaOffset;
public OfsSetTemp(int pde, int java){
pdeOffset = pde;
javaOffset = java;
}
}
// public class OffsetMatch{
// public final ArrayList<Integer> pdeOffset, javaOffset;
//
// public OffsetMatch(){
// pdeOffset = new ArrayList<Integer>();
// javaOffset = new ArrayList<Integer>();
// }
// }
public static void main(String[] args) {
// minDistance("c = #qwerty;", "c = 0xffqwerty;");
Utils a = new Utils();
a.minDistance("int a = int(can); int ball;", "int a = PApplet.parseInt(can); int ball;");
a.getPdeOffForJavaOff(25, 3);
a.getJavaOffForPdeOff(12,3);
// minDistance("static void main(){;", "public static void main(){;");
// minDistance("#bb00aa", "0xffbb00aa");
//a.minDistance("color g = #qwerty;", "int g = 0xffqwerty;");
System.out.println("--");
a.minDistance("color abc = #qwerty;", "int abc = 0xffqwerty;");
a.getPdeOffForJavaOff(4, 3);
a.getJavaOffForPdeOff(6,3);
// distance("c = #bb00aa;", "c = 0xffbb00aa;");
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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 com.sun.jdi.event.EventSet;
/**
* Interface for VM callbacks.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public interface VMEventListener {
/**
* Receive an event from the VM. Events are sent in batches. See
* documentation of EventSet for more information.
*
* @param es Set of events
*/
void vmEvent(EventSet es);
}

View File

@@ -0,0 +1,68 @@
/*
* 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 com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Reader Thread for VM Events. Constantly monitors a VMs EventQueue for new
* events and forwards them to an VMEventListener.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class VMEventReader extends Thread {
EventQueue eventQueue;
VMEventListener listener;
/**
* Construct a VMEventReader. Needs to be kicked off with start() once
* constructed.
*
* @param eventQueue The queue to read events from. Can be obtained from a
* VirtualMachine via eventQueue().
* @param listener the listener to forward events to.
*/
public VMEventReader(EventQueue eventQueue, VMEventListener listener) {
super("VM Event Thread");
this.eventQueue = eventQueue;
this.listener = listener;
}
@Override
public void run() {
try {
while (true) {
EventSet eventSet = eventQueue.remove();
listener.vmEvent(eventSet);
/*
* for (Event e : eventSet) { System.out.println("VM Event: " +
* e.toString()); }
*/
}
} catch (VMDisconnectedException e) {
Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect");
} catch (Exception e) {
Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e);
}
}
}

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
</SyntheticProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Component id="scrollPane" alignment="0" pref="400" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Component id="scrollPane" alignment="1" pref="300" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="scrollPane">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="org.netbeans.swing.outline.Outline" name="tree">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="4"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@@ -0,0 +1,931 @@
/*
* 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 com.sun.jdi.Value;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultCellEditor;
import javax.swing.GrayFilter;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.ExtTreeWillExpandListener;
import org.netbeans.swing.outline.OutlineModel;
import org.netbeans.swing.outline.RenderDataProvider;
import org.netbeans.swing.outline.RowModel;
/**
* Variable Inspector window.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class VariableInspector extends javax.swing.JFrame {
protected DefaultMutableTreeNode rootNode; // the root node (invisible)
protected DefaultMutableTreeNode builtins; // node for Processing built-in variables
protected DefaultTreeModel treeModel; // data model for the tree column
protected OutlineModel model; // data model for the whole Outline (tree and other columns)
protected List<DefaultMutableTreeNode> callStack; // the call stack
protected List<VariableNode> locals; // current local variables
protected List<VariableNode> thisFields; // all fields of the current this-object
protected List<VariableNode> declaredThisFields; // declared i.e. non-inherited fields of this
protected DebugEditor editor; // the editor
protected Debugger dbg; // the debugger
protected List<TreePath> expandedNodes = new ArrayList<TreePath>(); // list of expanded tree paths. (using list to maintain the order of expansion)
protected boolean p5mode = true; // processing / "advanced" mode flag (currently not used
/**
* Creates new form VariableInspector
*/
public VariableInspector(DebugEditor editor) {
this.editor = editor;
this.dbg = editor.dbg();
initComponents();
// setup Outline
rootNode = new DefaultMutableTreeNode("root");
builtins = new DefaultMutableTreeNode("Processing");
treeModel = new DefaultTreeModel(rootNode); // model for the tree column
model = DefaultOutlineModel.createOutlineModel(treeModel, new VariableRowModel(), true, "Name"); // model for all columns
ExpansionHandler expansionHandler = new ExpansionHandler();
model.getTreePathSupport().addTreeWillExpandListener(expansionHandler);
model.getTreePathSupport().addTreeExpansionListener(expansionHandler);
tree.setModel(model);
tree.setRootVisible(false);
tree.setRenderDataProvider(new OutlineRenderer());
tree.setColumnHidingAllowed(false); // disable visible columns button (shows by default when right scroll bar is visible)
tree.setAutoscrolls(false);
// set custom renderer and editor for value column, since we are using a custom class for values (VariableNode)
TableColumn valueColumn = tree.getColumnModel().getColumn(1);
valueColumn.setCellRenderer(new ValueCellRenderer());
valueColumn.setCellEditor(new ValueCellEditor());
//System.out.println("renderer: " + tree.getDefaultRenderer(String.class).getClass());
//System.out.println("editor: " + tree.getDefaultEditor(String.class).getClass());
callStack = new ArrayList<DefaultMutableTreeNode>();
locals = new ArrayList<VariableNode>();
thisFields = new ArrayList<VariableNode>();
declaredThisFields = new ArrayList<VariableNode>();
this.setTitle(editor.getSketch().getName());
// for (Entry<Object, Object> entry : UIManager.getDefaults().entrySet()) {
// System.out.println(entry.getKey());
// }
}
@Override
public void setTitle(String title) {
super.setTitle(title + " | Variable Inspector");
}
/**
* Model for a Outline Row (excluding the tree column). Column 0 is "Value".
* Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a
* TableCellRenderer instead of this to also have a different icon based on
* expanded state. See:
* http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm
*/
protected class VariableRowModel implements RowModel {
protected String[] columnNames = {"Value", "Type"};
protected int[] editableTypes = {VariableNode.TYPE_BOOLEAN, VariableNode.TYPE_FLOAT, VariableNode.TYPE_INTEGER, VariableNode.TYPE_STRING, VariableNode.TYPE_FLOAT, VariableNode.TYPE_DOUBLE, VariableNode.TYPE_LONG, VariableNode.TYPE_SHORT, VariableNode.TYPE_CHAR};
@Override
public int getColumnCount() {
if (p5mode) {
return 1; // only show value in p5 mode
} else {
return 2;
}
}
@Override
public Object getValueFor(Object o, int i) {
if (o instanceof VariableNode) {
VariableNode var = (VariableNode) o;
switch (i) {
case 0:
return var; // will be converted to an appropriate String by ValueCellRenderer
case 1:
return var.getTypeName();
default:
return "";
}
} else {
return "";
}
}
@Override
public Class getColumnClass(int i) {
if (i == 0) {
return VariableNode.class;
}
return String.class;
}
@Override
public boolean isCellEditable(Object o, int i) {
if (i == 0 && o instanceof VariableNode) {
VariableNode var = (VariableNode) o;
//System.out.println("type: " + var.getTypeName());
for (int type : editableTypes) {
if (var.getType() == type) {
return true;
}
}
}
return false;
}
@Override
public void setValueFor(Object o, int i, Object o1) {
VariableNode var = (VariableNode) o;
String stringValue = (String) o1;
Value value = null;
try {
switch (var.getType()) {
case VariableNode.TYPE_INTEGER:
value = dbg.vm().mirrorOf(Integer.parseInt(stringValue));
break;
case VariableNode.TYPE_BOOLEAN:
value = dbg.vm().mirrorOf(Boolean.parseBoolean(stringValue));
break;
case VariableNode.TYPE_FLOAT:
value = dbg.vm().mirrorOf(Float.parseFloat(stringValue));
break;
case VariableNode.TYPE_STRING:
value = dbg.vm().mirrorOf(stringValue);
break;
case VariableNode.TYPE_LONG:
value = dbg.vm().mirrorOf(Long.parseLong(stringValue));
break;
case VariableNode.TYPE_BYTE:
value = dbg.vm().mirrorOf(Byte.parseByte(stringValue));
break;
case VariableNode.TYPE_DOUBLE:
value = dbg.vm().mirrorOf(Double.parseDouble(stringValue));
break;
case VariableNode.TYPE_SHORT:
value = dbg.vm().mirrorOf(Short.parseShort(stringValue));
break;
case VariableNode.TYPE_CHAR:
// TODO: better char support
if (stringValue.length() > 0) {
value = dbg.vm().mirrorOf(stringValue.charAt(0));
}
break;
}
} catch (NumberFormatException ex) {
Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "invalid value entered for {0}: {1}", new Object[]{var.getName(), stringValue});
}
if (value != null) {
var.setValue(value);
Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "new value set: {0}", var.getStringValue());
}
}
@Override
public String getColumnName(int i) {
return columnNames[i];
}
}
/**
* Renderer for the tree portion of the outline component. Handles icons,
* text color and tool tips.
*/
protected class OutlineRenderer implements RenderDataProvider {
protected Icon[][] icons;
protected static final int ICON_SIZE = 16; // icon size (square, size=width=height)
public OutlineRenderer() {
// load icons
icons = loadIcons("theme/var-icons.gif");
}
/**
* Load multiple icons (horizotal) with multiple states (vertical) from
* a single file.
*
* @param fileName file path in the mode folder.
* @return a nested array (first index: icon, second index: state) or
* null if the file wasn't found.
*/
protected ImageIcon[][] loadIcons(String fileName) {
ExperimentalMode mode = editor.mode();
File file = mode.getContentFile(fileName);
if (!file.exists()) {
Logger.getLogger(OutlineRenderer.class.getName()).log(Level.SEVERE, "icon file not found: {0}", file.getAbsolutePath());
return null;
}
Image allIcons = mode.loadImage(fileName);
int cols = allIcons.getWidth(null) / ICON_SIZE;
int rows = allIcons.getHeight(null) / ICON_SIZE;
ImageIcon[][] iconImages = new ImageIcon[cols][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
//Image image = createImage(ICON_SIZE, ICON_SIZE);
Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null);
iconImages[i][j] = new ImageIcon(image);
}
}
return iconImages;
}
protected Icon getIcon(int type, int state) {
if (type < 0 || type > icons.length - 1) {
return null;
}
return icons[type][state];
}
protected VariableNode toVariableNode(Object o) {
if (o instanceof VariableNode) {
return (VariableNode) o;
} else {
return null;
}
}
protected Icon toGray(Icon icon) {
if (icon instanceof ImageIcon) {
Image grayImage = GrayFilter.createDisabledImage(((ImageIcon) icon).getImage());
return new ImageIcon(grayImage);
}
// Cannot convert
return icon;
}
@Override
public String getDisplayName(Object o) {
return o.toString(); // VariableNode.toString() returns name; (for sorting)
// VariableNode var = toVariableNode(o);
// if (var != null) {
// return var.getName();
// } else {
// return o.toString();
// }
}
@Override
public boolean isHtmlDisplayName(Object o) {
return false;
}
@Override
public Color getBackground(Object o) {
return null;
}
@Override
public Color getForeground(Object o) {
if (tree.isEnabled()) {
return null; // default
} else {
return Color.GRAY;
}
}
@Override
public String getTooltipText(Object o) {
VariableNode var = toVariableNode(o);
if (var != null) {
return var.getDescription();
} else {
return "";
}
}
@Override
public Icon getIcon(Object o) {
VariableNode var = toVariableNode(o);
if (var != null) {
if (tree.isEnabled()) {
return getIcon(var.getType(), 0);
} else {
return getIcon(var.getType(), 1);
}
} else {
if (o instanceof TreeNode) {
// TreeNode node = (TreeNode) o;
// AbstractLayoutCache layout = tree.getLayoutCache();
UIDefaults defaults = UIManager.getDefaults();
boolean isLeaf = model.isLeaf(o);
Icon icon;
if (isLeaf) {
icon = defaults.getIcon("Tree.leafIcon");
} else {
icon = defaults.getIcon("Tree.closedIcon");
}
if (!tree.isEnabled()) {
return toGray(icon);
}
return icon;
}
}
return null; // use standard icon
//UIManager.getIcon(o);
}
}
// TODO: could probably extend the simpler javax.swing.table.DefaultTableCellRenderer here
/**
* Renderer for the value column. Uses an italic font for null values and
* Object values ("instance of ..."). Uses a gray color when tree is not
* enabled.
*/
protected class ValueCellRenderer extends DefaultOutlineCellRenderer {
public ValueCellRenderer() {
super();
}
protected void setItalic(boolean on) {
if (on) {
setFont(new Font(getFont().getName(), Font.ITALIC, getFont().getSize()));
} else {
setFont(new Font(getFont().getName(), Font.PLAIN, getFont().getSize()));
}
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!tree.isEnabled()) {
setForeground(Color.GRAY);
} else {
setForeground(Color.BLACK);
}
if (value instanceof VariableNode) {
VariableNode var = (VariableNode) value;
if (var.getValue() == null || var.getType() == VariableNode.TYPE_OBJECT) {
setItalic(true);
} else {
setItalic(false);
}
value = var.getStringValue();
}
setValue(value);
return c;
}
}
/**
* Editor for the value column. Will show an empty string when editing
* String values that are null.
*/
protected class ValueCellEditor extends DefaultCellEditor {
public ValueCellEditor() {
super(new JTextField());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (!(value instanceof VariableNode)) {
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
VariableNode var = (VariableNode) value;
if (var.getType() == VariableNode.TYPE_STRING && var.getValue() == null) {
return super.getTableCellEditorComponent(table, "", isSelected, row, column);
} else {
return super.getTableCellEditorComponent(table, var.getStringValue(), isSelected, row, column);
}
}
}
/**
* Handler for expanding and collapsing tree nodes. Implements lazy loading
* of tree data (on expand).
*/
protected class ExpansionHandler implements ExtTreeWillExpandListener, TreeExpansionListener {
@Override
public void treeWillExpand(TreeExpansionEvent tee) throws ExpandVetoException {
//System.out.println("will expand");
Object last = tee.getPath().getLastPathComponent();
if (!(last instanceof VariableNode)) {
return;
}
VariableNode var = (VariableNode) last;
// load children
// if (!dbg.isPaused()) {
// System.out.println("throwing veto");
// //throw new ExpandVetoException(tee, "Debugger busy");
// } else {
var.removeAllChildren(); // TODO: should we only load it once?
// TODO: don't filter in advanced mode
//System.out.println("loading children for: " + var);
// true means include inherited
var.addChildren(filterNodes(dbg.getFields(var.getValue(), 0, true), new ThisFilter()));
// }
}
@Override
public void treeWillCollapse(TreeExpansionEvent tee) throws ExpandVetoException {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void treeExpanded(TreeExpansionEvent tee) {
//System.out.println("expanded: " + tee.getPath());
if (!expandedNodes.contains(tee.getPath())) {
expandedNodes.add(tee.getPath());
}
// TreePath newPath = tee.getPath();
// if (expandedLast != null) {
// // test each node of the path for equality
// for (int i = 0; i < expandedLast.getPathCount(); i++) {
// if (i < newPath.getPathCount()) {
// Object last = expandedLast.getPathComponent(i);
// Object cur = newPath.getPathComponent(i);
// System.out.println(last + " =? " + cur + ": " + last.equals(cur) + "/" + (last.hashCode() == cur.hashCode()));
// }
// }
// }
// System.out.println("path equality: " + newPath.equals(expandedLast));
// expandedLast = newPath;
}
@Override
public void treeCollapsed(TreeExpansionEvent tee) {
//System.out.println("collapsed: " + tee.getPath());
// first remove all children of collapsed path
// this makes sure children do not appear before parents in the list.
// (children can't be expanded before their parents)
List<TreePath> removalList = new ArrayList<TreePath>();
for (TreePath path : expandedNodes) {
if (path.getParentPath().equals(tee.getPath())) {
removalList.add(path);
}
}
for (TreePath path : removalList) {
expandedNodes.remove(path);
}
// remove collapsed path
expandedNodes.remove(tee.getPath());
}
@Override
public void treeExpansionVetoed(TreeExpansionEvent tee, ExpandVetoException eve) {
//System.out.println("expansion vetoed");
// nop
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
scrollPane = new javax.swing.JScrollPane();
tree = new org.netbeans.swing.outline.Outline();
scrollPane.setViewportView(tree);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE))
);
pack();
}// </editor-fold>//GEN-END:initComponents
// /**
// * @param args the command line arguments
// */
// public static void main(String args[]) {
// /*
// * Set the Nimbus look and feel
// */
// //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
// /*
// * If Nimbus (introduced in Java SE 6) is not available, stay with the
// * default look and feel. For details see
// * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
// */
// try {
// javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
// } catch (ClassNotFoundException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (InstantiationException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (IllegalAccessException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (javax.swing.UnsupportedLookAndFeelException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// }
// //</editor-fold>
//
// /*
// * Create and display the form
// */
// run(new VariableInspector());
// }
protected static void run(final VariableInspector vi) {
/*
* Create and display the form
*/
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
vi.setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane scrollPane;
protected org.netbeans.swing.outline.Outline tree;
// End of variables declaration//GEN-END:variables
/**
* Access the root node of the tree.
*
* @return the root node
*/
public DefaultMutableTreeNode getRootNode() {
return rootNode;
}
/**
* Unlock the inspector window. Rebuild after this to avoid ... dots in the
* trees labels
*/
public void unlock() {
tree.setEnabled(true);
}
/**
* Lock the inspector window. Cancels open edits.
*/
public void lock() {
if (tree.getCellEditor() != null) {
//tree.getCellEditor().stopCellEditing(); // force quit open edit
tree.getCellEditor().cancelCellEditing(); // cancel an open edit
}
tree.setEnabled(false);
}
/**
* Reset the inspector windows data. Rebuild after this to make changes
* visible.
*/
public void reset() {
rootNode.removeAllChildren();
// clear local data for good measure (in case someone rebuilds)
callStack.clear();
locals.clear();
thisFields.clear();
declaredThisFields.clear();
expandedNodes.clear();
// update
treeModel.nodeStructureChanged(rootNode);
}
// public void setAdvancedMode() {
// p5mode = false;
// }
//
// public void setP5Mode() {
// p5mode = true;
// }
//
// public void toggleMode() {
// if (p5mode) {
// setAdvancedMode();
// } else {
// setP5Mode();
// }
// }
/**
* Update call stack data.
*
* @param nodes a list of nodes that represent the call stack.
* @param title the title to be used when labeling or otherwise grouping
* call stack data.
*/
public void updateCallStack(List<DefaultMutableTreeNode> nodes, String title) {
callStack = nodes;
}
/**
* Update locals data.
*
* @param nodes a list of {@link VariableNode} to be shown as local
* variables in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* locals data.
*/
public void updateLocals(List<VariableNode> nodes, String title) {
locals = nodes;
}
/**
* Update this-fields data.
*
* @param nodes a list of {@link VariableNode}s to be shown as this-fields
* in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* this-fields data.
*/
public void updateThisFields(List<VariableNode> nodes, String title) {
thisFields = nodes;
}
/**
* Update declared (non-inherited) this-fields data.
*
* @param nodes a list of {@link VariableNode}s to be shown as declared
* this-fields in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* declared this-fields data.
*/
public void updateDeclaredThisFields(List<VariableNode> nodes, String title) {
declaredThisFields = nodes;
}
/**
* Rebuild the outline tree from current data. Uses the data provided by
* {@link #updateCallStack}, {@link #updateLocals}, {@link #updateThisFields}
* and {@link #updateDeclaredThisFields}
*/
public void rebuild() {
rootNode.removeAllChildren();
if (p5mode) {
// add all locals to root
addAllNodes(rootNode, locals);
// add non-inherited this fields
addAllNodes(rootNode, filterNodes(declaredThisFields, new LocalHidesThisFilter(locals, LocalHidesThisFilter.MODE_PREFIX)));
// add p5 builtins in a new folder
builtins.removeAllChildren();
addAllNodes(builtins, filterNodes(thisFields, new P5BuiltinsFilter()));
if (builtins.getChildCount() > 0) { // skip builtins in certain situations e.g. in pure java tabs.
rootNode.add(builtins);
}
// notify tree (using model) changed a node and its children
// http://stackoverflow.com/questions/2730851/how-to-update-jtree-elements
// needs to be done before expanding paths!
treeModel.nodeStructureChanged(rootNode);
// handle node expansions
for (TreePath path : expandedNodes) {
//System.out.println("re-expanding: " + path);
path = synthesizePath(path);
if (path != null) {
tree.expandPath(path);
} else {
//System.out.println("couldn't synthesize path");
}
}
// this expansion causes problems when sorted and stepping
//tree.expandPath(new TreePath(new Object[]{rootNode, builtins}));
} else {
// TODO: implement advanced mode here
}
}
/**
* Re-build a {@link TreePath} from a previous path using equals-checks
* starting at the root node. This is used to use paths from previous trees
* for use on the current tree.
*
* @param path the path to synthesize.
* @return the rebuilt path, usable on the current tree.
*/
protected TreePath synthesizePath(TreePath path) {
//System.out.println("synthesizing: " + path);
if (path.getPathCount() == 0 || !rootNode.equals(path.getPathComponent(0))) {
return null;
}
Object[] newPath = new Object[path.getPathCount()];
newPath[0] = rootNode;
TreeNode currentNode = rootNode;
for (int i = 0; i < path.getPathCount() - 1; i++) {
// get next node
for (int j = 0; j < currentNode.getChildCount(); j++) {
TreeNode nextNode = currentNode.getChildAt(j);
if (nextNode.equals(path.getPathComponent(i + 1))) {
currentNode = nextNode;
newPath[i + 1] = nextNode;
//System.out.println("found node " + (i+1) + ": " + nextNode);
break;
}
}
if (newPath[i + 1] == null) {
//System.out.println("didn't find node");
return null;
}
}
return new TreePath(newPath);
}
/**
* Filter a list of nodes using a {@link VariableNodeFilter}.
*
* @param nodes the list of nodes to filter.
* @param filter the filter to be used.
* @return the filtered list.
*/
protected List<VariableNode> filterNodes(List<VariableNode> nodes, VariableNodeFilter filter) {
List<VariableNode> filtered = new ArrayList<VariableNode>();
for (VariableNode node : nodes) {
if (filter.accept(node)) {
filtered.add(node);
}
}
return filtered;
}
/**
* Add all nodes in a list to a root node.
*
* @param root the root node to add to.
* @param nodes the list of nodes to add.
*/
protected void addAllNodes(DefaultMutableTreeNode root, List<? extends MutableTreeNode> nodes) {
for (MutableTreeNode node : nodes) {
root.add(node);
}
}
/**
* A filter for {@link VariableNode}s.
*/
public interface VariableNodeFilter {
/**
* Check whether the filter accepts a {@link VariableNode}.
*
* @param var the input node
* @return true when the filter accepts the input node otherwise false.
*/
public boolean accept(VariableNode var);
}
/**
* A {@link VariableNodeFilter} that accepts Processing built-in variable
* names.
*/
public class P5BuiltinsFilter implements VariableNodeFilter {
protected String[] p5Builtins = {
"focused",
"frameCount",
"frameRate",
"height",
"online",
"screen",
"width",
"mouseX",
"mouseY",
"pmouseX",
"pmouseY",
"key",
"keyCode",
"keyPressed"
};
@Override
public boolean accept(VariableNode var) {
return Arrays.asList(p5Builtins).contains(var.getName());
}
}
/**
* A {@link VariableNodeFilter} that rejects implicit this references.
* (Names starting with "this$")
*/
public class ThisFilter implements VariableNodeFilter {
@Override
public boolean accept(VariableNode var) {
return !var.getName().startsWith("this$");
}
}
/**
* A {@link VariableNodeFilter} that either rejects this-fields if hidden by
* a local, or prefixes its name with "this."
*/
public class LocalHidesThisFilter implements VariableNodeFilter {
/**
* Reject a this-field if hidden by a local.
*/
public static final int MODE_HIDE = 0; // don't show hidden this fields
/**
* Prefix a this-fields name with "this." if hidden by a local.
*/
public static final int MODE_PREFIX = 1; // prefix hidden this fields with "this."
protected List<VariableNode> locals;
protected int mode;
/**
* Construct a {@link LocalHidesThisFilter}.
*
* @param locals a list of locals to check against
* @param mode either {@link #MODE_HIDE} or {@link #MODE_PREFIX}
*/
public LocalHidesThisFilter(List<VariableNode> locals, int mode) {
this.locals = locals;
this.mode = mode;
}
@Override
public boolean accept(VariableNode var) {
// check if the same name appears in the list of locals i.e. the local hides the field
for (VariableNode local : locals) {
if (var.getName().equals(local.getName())) {
switch (mode) {
case MODE_PREFIX:
var.setName("this." + var.getName());
return true;
case MODE_HIDE:
return false;
}
}
}
return true;
}
}
}

View File

@@ -0,0 +1,358 @@
/*
* 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 com.sun.jdi.ArrayReference;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StringReference;
import com.sun.jdi.Value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
/**
* Model for a variable in the variable inspector. Has a type and name and
* optionally a value. Can have sub-variables (as is the case for objects, and
* arrays).
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class VariableNode implements MutableTreeNode {
public static final int TYPE_UNKNOWN = -1;
public static final int TYPE_OBJECT = 0;
public static final int TYPE_ARRAY = 1;
public static final int TYPE_INTEGER = 2;
public static final int TYPE_FLOAT = 3;
public static final int TYPE_BOOLEAN = 4;
public static final int TYPE_CHAR = 5;
public static final int TYPE_STRING = 6;
public static final int TYPE_LONG = 7;
public static final int TYPE_DOUBLE = 8;
public static final int TYPE_BYTE = 9;
public static final int TYPE_SHORT = 10;
public static final int TYPE_VOID = 11;
protected String type;
protected String name;
protected Value value;
protected List<MutableTreeNode> children = new ArrayList<MutableTreeNode>();
protected MutableTreeNode parent;
/**
* Construct a {@link VariableNode}.
* @param name the name
* @param type the type
* @param value the value
*/
public VariableNode(String name, String type, Value value) {
this.name = name;
this.type = type;
this.value = value;
}
public void setValue(Value value) {
this.value = value;
}
public Value getValue() {
return value;
}
/**
* Get a String representation of this variable nodes value.
*
* @return a String representing the value.
*/
public String getStringValue() {
String str;
if (value != null) {
if (getType() == TYPE_OBJECT) {
str = "instance of " + type;
} else if (getType() == TYPE_ARRAY) {
//instance of int[5] (id=998) --> instance of int[5]
str = value.toString().substring(0, value.toString().lastIndexOf(" "));
} else if (getType() == TYPE_STRING) {
str = ((StringReference) value).value(); // use original string value (without quotes)
} else {
str = value.toString();
}
} else {
str = "null";
}
return str;
}
public String getTypeName() {
return type;
}
public int getType() {
if (type == null) {
return TYPE_UNKNOWN;
}
if (type.endsWith("[]")) {
return TYPE_ARRAY;
}
if (type.equals("int")) {
return TYPE_INTEGER;
}
if (type.equals("long")) {
return TYPE_LONG;
}
if (type.equals("byte")) {
return TYPE_BYTE;
}
if (type.equals("short")) {
return TYPE_SHORT;
}
if (type.equals("float")) {
return TYPE_FLOAT;
}
if (type.equals("double")) {
return TYPE_DOUBLE;
}
if (type.equals("char")) {
return TYPE_CHAR;
}
if (type.equals("java.lang.String")) {
return TYPE_STRING;
}
if (type.equals("boolean")) {
return TYPE_BOOLEAN;
}
if (type.equals("void")) {
return TYPE_VOID; //TODO: check if this is correct
}
return TYPE_OBJECT;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* Add a {@link VariableNode} as child.
*
* @param c the {@link VariableNode} to add.
*/
public void addChild(VariableNode c) {
children.add(c);
c.setParent(this);
}
/**
* Add multiple {@link VariableNode}s as children.
*
* @param children the list of {@link VariableNode}s to add.
*/
public void addChildren(List<VariableNode> children) {
for (VariableNode child : children) {
addChild(child);
}
}
@Override
public TreeNode getChildAt(int i) {
return children.get(i);
}
@Override
public int getChildCount() {
return children.size();
}
@Override
public TreeNode getParent() {
return parent;
}
@Override
public int getIndex(TreeNode tn) {
return children.indexOf(tn);
}
@Override
public boolean getAllowsChildren() {
if (value == null) {
return false;
}
// handle strings
if (getType() == TYPE_STRING) {
return false;
}
// handle arrays
if (getType() == TYPE_ARRAY) {
ArrayReference array = (ArrayReference) value;
return array.length() > 0;
}
// handle objects
if (getType() == TYPE_OBJECT) { // this also rules out null
// check if this object has any fields
ObjectReference obj = (ObjectReference) value;
return !obj.referenceType().visibleFields().isEmpty();
}
return false;
}
/**
* This controls the default icon and disclosure triangle.
*
* @return true, will show "folder" icon and disclosure triangle.
*/
@Override
public boolean isLeaf() {
//return children.size() == 0;
return !getAllowsChildren();
}
@Override
public Enumeration children() {
return Collections.enumeration(children);
}
/**
* Get a String representation of this {@link VariableNode}.
*
* @return the name of the variable (for sorting to work).
*/
@Override
public String toString() {
return getName(); // for sorting
}
/**
* Get a String description of this {@link VariableNode}. Contains the type,
* name and value.
*
* @return the description
*/
public String getDescription() {
String str = "";
if (type != null) {
str += type + " ";
}
str += name;
str += " = " + getStringValue();
return str;
}
@Override
public void insert(MutableTreeNode mtn, int i) {
children.add(i, this);
}
@Override
public void remove(int i) {
MutableTreeNode mtn = children.remove(i);
if (mtn != null) {
mtn.setParent(null);
}
}
@Override
public void remove(MutableTreeNode mtn) {
children.remove(mtn);
mtn.setParent(null);
}
/**
* Remove all children from this {@link VariableNode}.
*/
public void removeAllChildren() {
for (MutableTreeNode mtn : children) {
mtn.setParent(null);
}
children.clear();
}
@Override
public void setUserObject(Object o) {
if (o instanceof Value) {
value = (Value) o;
}
}
@Override
public void removeFromParent() {
parent.remove(this);
this.parent = null;
}
@Override
public void setParent(MutableTreeNode mtn) {
parent = mtn;
}
/**
* Test for equality. To be equal, two {@link VariableNode}s need to have
* equal type, name and value.
*
* @param obj the object to test for equality with this {@link VariableNode}
* @return true if the given object is equal to this {@link VariableNode}
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final VariableNode other = (VariableNode) obj;
if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) {
//System.out.println("type not equal");
return false;
}
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
//System.out.println("name not equal");
return false;
}
if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
//System.out.println("value not equal");
return false;
}
// if (this.parent != other.parent && (this.parent == null || !this.parent.equals(other.parent))) {
// System.out.println("parent not equal: " + this.parent + "/" + other.parent);
// return false;
// }
return true;
}
/**
* Returns a hash code based on type, name and value.
*/
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + (this.type != null ? this.type.hashCode() : 0);
hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 97 * hash + (this.value != null ? this.value.hashCode() : 0);
// hash = 97 * hash + (this.parent != null ? this.parent.hashCode() : 0);
return hash;
}
}

View File

@@ -0,0 +1,145 @@
package processing.mode.experimental;
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JPanel;
import processing.app.Language;
/**
* Toggle Button displayed in the editor line status panel for toggling bewtween
* console and problems list. Glorified JPanel.
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class XQConsoleToggle extends JPanel implements MouseListener {
public static final String CONSOLE = Language.text("editor.footer.console"), ERRORSLIST = Language.text("editor.footer.errors") ;
private boolean toggleText = true;
private boolean toggleBG = true;
/**
* Height of the component
*/
protected int height;
protected DebugEditor editor;
protected String buttonName;
public XQConsoleToggle(DebugEditor editor, String buttonName, int height) {
this.editor = editor;
this.height = height;
this.buttonName = buttonName;
}
public Dimension getPreferredSize() {
return new Dimension(70, height);
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// On mouse hover, text and background color are changed.
if (toggleBG) {
g.setColor(new Color(0xff9DA7B0));
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(new Color(0xff29333D));
g.fillRect(0, 0, 4, this.getHeight());
g.setColor(Color.BLACK);
} else {
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(new Color(0xff29333D));
g.fillRect(0, 0, 4, this.getHeight());
g.setColor(Color.WHITE);
}
g.drawString(buttonName, getWidth() / 2 + 2 // + 2 is a offset
- getFontMetrics(getFont()).stringWidth(buttonName) / 2,
this.getHeight() - 6);
if (drawMarker) {
g.setColor(markerColor);
g.fillRect(4, 0, 2, this.getHeight());
}
}
boolean drawMarker = false;
protected Color markerColor;
public void updateMarker(boolean value, Color color){
drawMarker = value;
markerColor = color;
repaint();
}
@Override
public void mouseClicked(MouseEvent arg0) {
this.repaint();
try {
editor.showProblemListView(buttonName);
} catch (Exception e) {
System.out.println(e);
// e.printStackTrace();
}
toggleText = !toggleText;
}
@Override
public void mouseEntered(MouseEvent arg0) {
toggleBG = !toggleBG;
this.repaint();
}
@Override
public void mouseExited(MouseEvent arg0) {
toggleBG = !toggleBG;
this.repaint();
}
@Override
public void mousePressed(MouseEvent arg0) {
}
@Override
public void mouseReleased(MouseEvent arg0) {
}
}

View File

@@ -0,0 +1,317 @@
package processing.mode.experimental;
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.ToolTipManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;
import javax.swing.text.BadLocationException;
import processing.app.Language;
import static processing.mode.experimental.ExperimentalMode.log;
/**
* Custom JTable implementation for XQMode. Minor tweaks and addtions.
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class XQErrorTable extends JTable {
/**
* Column Names of JTable
*/
public static final String[] columnNames = { Language.text("editor.footer.errors.problem"), Language.text("editor.footer.errors.tab"), Language.text("editor.footer.errors.line") };
/**
* Column Widths of JTable.
*/
public int[] columnWidths = { 600, 100, 50 }; // Default Values
/**
* Is the column being resized?
*/
private boolean columnResizing = false;
/**
* ErrorCheckerService instance
*/
protected ErrorCheckerService errorCheckerService;
@Override
public boolean isCellEditable(int rowIndex, int colIndex) {
return false; // Disallow the editing of any cell
}
public XQErrorTable(final ErrorCheckerService errorCheckerService) {
this.errorCheckerService = errorCheckerService;
for (int i = 0; i < this.getColumnModel().getColumnCount(); i++) {
this.getColumnModel().getColumn(i)
.setPreferredWidth(columnWidths[i]);
}
this.getTableHeader().setReorderingAllowed(false);
this.addMouseListener(new MouseAdapter() {
@Override
synchronized public void mouseClicked(MouseEvent e) {
try {
errorCheckerService.scrollToErrorLine(((XQErrorTable) e
.getSource()).getSelectedRow());
// System.out.print("Row clicked: "
// + ((XQErrorTable) e.getSource()).getSelectedRow());
} catch (Exception e1) {
System.out.println("Exception XQErrorTable mouseReleased "
+ e);
}
}
// public void mouseMoved(MouseEvent evt) {
// log(evt);
//// String tip = null;
//// java.awt.Point p = evt.getPoint();
// int rowIndex = rowAtPoint(evt.getPoint());
// int colIndex = columnAtPoint(evt.getPoint());
// synchronized (errorCheckerService.problemsList) {
// if (rowIndex < errorCheckerService.problemsList.size()) {
// Problem p = errorCheckerService.problemsList.get(rowIndex);
// if (p.getImportSuggestions() != null
// && p.getImportSuggestions().length > 0) {
// log("Import Suggestions available");
// }
// }
// }
//// return super.getToolTipText(evt);
// }
});
final XQErrorTable thisTable = this;
this.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseMoved(MouseEvent evt) {
// log(evt);
// String tip = null;
// java.awt.Point p = evt.getPoint();
int rowIndex = rowAtPoint(evt.getPoint());
// int colIndex = columnAtPoint(evt.getPoint());
synchronized (errorCheckerService.problemsList) {
if (rowIndex < errorCheckerService.problemsList.size()) {
Problem p = errorCheckerService.problemsList.get(rowIndex);
if (p.getImportSuggestions() != null
&& p.getImportSuggestions().length > 0) {
String t = p.getMessage() + "(Import Suggestions available)";
int x1 = thisTable.getFontMetrics(thisTable.getFont())
.stringWidth(p.getMessage()), x2 = thisTable
.getFontMetrics(thisTable.getFont()).stringWidth(t);
if(evt.getX() < x1 || evt.getX() > x2) return;
String[] list = p.getImportSuggestions();
String className = list[0].substring(list[0].lastIndexOf('.') + 1);
String[] temp = new String[list.length];
for (int i = 0; i < list.length; i++) {
temp[i] = "<html>Import '" + className + "' <font color=#777777>(" + list[i] + ")</font></html>";
}
showImportSuggestion(temp, evt.getXOnScreen(), evt.getYOnScreen() - 3 * thisTable.getFont().getSize());
}
}
}
}
@Override
public void mouseDragged(MouseEvent e) {
}
});
// Handles the resizing of columns. When mouse press is detected on
// table header, Stop updating the table, store new values of column
// widths,and resume updating. Updating is disabled as long as
// columnResizing is true
this.getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
columnResizing = true;
}
@Override
public void mouseReleased(MouseEvent e) {
columnResizing = false;
for (int i = 0; i < ((JTableHeader) e.getSource())
.getColumnModel().getColumnCount(); i++) {
columnWidths[i] = ((JTableHeader) e.getSource())
.getColumnModel().getColumn(i).getWidth();
// System.out.println("nw " + columnWidths[i]);
}
}
});
ToolTipManager.sharedInstance().registerComponent(this);
}
/**
* Updates table contents with new data
* @param tableModel - TableModel
* @return boolean - If table data was updated
*/
synchronized public boolean updateTable(final TableModel tableModel) {
// If problems list is not visible, no need to update
if (!this.isVisible()) {
return false;
}
SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>() {
protected Object doInBackground() throws Exception {
return null;
}
protected void done() {
try {
setModel(tableModel);
// Set column widths to user defined widths
for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
getColumnModel().getColumn(i).setPreferredWidth(
columnWidths[i]);
}
getTableHeader().setReorderingAllowed(false);
validate();
repaint();
} catch (Exception e) {
System.out.println("Exception at XQErrorTable.updateTable " + e);
// e.printStackTrace();
}
}
};
try {
if (!columnResizing) {
worker.execute();
}
} catch (Exception e) {
System.out.println("ErrorTable updateTable Worker's slacking."
+ e.getMessage());
// e.printStackTrace();
}
return true;
}
JFrame frmImportSuggest;
private void showImportSuggestion(String list[], int x, int y){
if(frmImportSuggest != null) {
// frmImportSuggest.setVisible(false);
// frmImportSuggest = null;
return;
}
final JList<String> classList = new JList<String>(list);
classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
frmImportSuggest = new JFrame();
frmImportSuggest.setUndecorated(true);
frmImportSuggest.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBackground(Color.WHITE);
frmImportSuggest.setBackground(Color.WHITE);
panel.add(classList);
JLabel label = new JLabel("<html><div alight = \"left\"><font size = \"2\"><br>(Click to insert)</font></div></html>");
label.setBackground(Color.WHITE);
label.setHorizontalTextPosition(SwingConstants.LEFT);
panel.add(label);
panel.validate();
frmImportSuggest.getContentPane().add(panel);
frmImportSuggest.pack();
final DebugEditor editor = errorCheckerService.getEditor();
classList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (classList.getSelectedValue() != null) {
try {
String t = classList.getSelectedValue().trim();
log(t);
int x = t.indexOf('(');
String impString = "import " + t.substring(x + 1, t.indexOf(')')) + ";\n";
int ct = editor.getSketch().getCurrentCodeIndex();
editor.getSketch().setCurrentCode(0);
editor.textArea().getDocument().insertString(0, impString, null);
editor.getSketch().setCurrentCode(ct);
} catch (BadLocationException ble) {
log("Failed to insert import");
ble.printStackTrace();
}
}
frmImportSuggest.setVisible(false);
frmImportSuggest.dispose();
frmImportSuggest = null;
}
});
frmImportSuggest.addWindowFocusListener(new WindowFocusListener() {
@Override
public void windowLostFocus(WindowEvent e) {
if (frmImportSuggest != null) {
frmImportSuggest.dispose();
frmImportSuggest = null;
}
}
@Override
public void windowGainedFocus(WindowEvent e) {
}
});
frmImportSuggest.setLocation(x, y);
frmImportSuggest.setBounds(x, y, 250, 100);
frmImportSuggest.pack();
frmImportSuggest.setVisible(true);
}
}

View File

@@ -0,0 +1,246 @@
/*
Part of the XQMode project - https://github.com/Manindra29/XQMode
Under Google Summer of Code 2012 -
http://www.google-melange.com/gsoc/homepage/google/gsoc2012
Copyright (C) 2012 Manindra Moharana
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.experimental;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import processing.mode.java.preproc.PdePreprocessor;
/**
* My implementation of P5 preprocessor. Uses Eclipse JDT features instead of
* ANTLR. Performance gains mostly and full control over debug output. But needs
* lots and lots of testing. There will always an option to switch back to PDE
* preproc.
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class XQPreprocessor {
private ASTRewrite rewrite = null;
private ArrayList<String> imports;
private ArrayList<ImportStatement> extraImports;
private String[] coreImports, defaultImports;
public XQPreprocessor() {
PdePreprocessor p = new PdePreprocessor(null);
defaultImports = p.getDefaultImports();
coreImports = p.getCoreImports();
}
/**
* The main method that performs preprocessing. Converts code into compilable java.
* @param source - String
* @param programImports - List of import statements
* @return String - Compile ready java code
*/
public String doYourThing(String source,
ArrayList<ImportStatement> programImports) {
this.extraImports = programImports;
//source = prepareImports() + source;
Document doc = new Document(source);
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setSource(doc.get().toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
@SuppressWarnings("unchecked")
Map<String, String> options = JavaCore.getOptions();
// Ben has decided to move on to 1.6. Yay!
JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options);
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_6);
parser.setCompilerOptions(options);
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.recordModifications();
rewrite = ASTRewrite.create(cu.getAST());
cu.accept(new XQASTVisitor());
TextEdit edits = cu.rewrite(doc, null);
try {
edits.apply(doc);
} catch (MalformedTreeException e) {
e.printStackTrace();
} catch (BadLocationException e) {
e.printStackTrace();
}
// System.out.println("------------XQPreProc-----------------");
// System.out.println(doc.get());
// System.out.println("------------XQPreProc End-----------------");
// Calculate main class offset
// removed unused 'lines' tabulation [fry 140726]
// int position = doc.get().indexOf("{") + 1;
// int lines = 0;
// for (int i = 0; i < position; i++) {
// if (doc.get().charAt(i) == '\n') {
// lines++;
// }
// }
// lines += 2;
// System.out.println("Lines: " + lines);
return doc.get();
}
/**
* Returns all import statements as lines of code
*
* @return String - All import statements combined. Each import in a separate line.
*/
public String prepareImports() {
imports = new ArrayList<String>();
for (int i = 0; i < extraImports.size(); i++) {
imports.add(new String(extraImports.get(i).getImportName()));
}
imports.add(new String("// Default Imports"));
for (int i = 0; i < coreImports.length; i++) {
imports.add(new String("import " + coreImports[i] + ";"));
}
for (int i = 0; i < defaultImports.length; i++) {
imports.add(new String("import " + defaultImports[i] + ";"));
}
String totalImports = "";
for (int i = 0; i < imports.size(); i++) {
totalImports += imports.get(i) + "\n";
}
totalImports += "\n";
return totalImports;
}
public String prepareImports(ArrayList<ImportStatement> programImports) {
this.extraImports = programImports;
return prepareImports();
}
/**
* Visitor implementation that does all the substitution dirty work. <br>
* <LI>Any function not specified as being protected or private will be made
* 'public'. This means that <TT>void setup()</TT> becomes
* <TT>public void setup()</TT>.
*
* <LI>Converts doubles into floats, i.e. 12.3 becomes 12.3f so that people
* don't have to add f after their numbers all the time since it's confusing
* for beginners. Also, most functions of p5 core deal with floats only.
*
* @author Manindra Moharana
*
*/
private class XQASTVisitor extends ASTVisitor {
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean visit(MethodDeclaration node) {
if (node.getReturnType2() != null) {
// if return type is color, make it int
// if (node.getReturnType2().toString().equals("color")) {
// System.err.println("color type detected!");
// node.setReturnType2(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// The return type is not void, no need to make it public
// if (!node.getReturnType2().toString().equals("void"))
// return true;
}
// Simple method, make it public
if (node.modifiers().size() == 0 && !node.isConstructor()) {
// rewrite.set(node, node.getModifiersProperty(),
// Modifier.PUBLIC,
// null);
// rewrite.getListRewrite(node,
// node.getModifiersProperty()).insertLast(Modifier., null)
List newMod = rewrite.getAST().newModifiers(Modifier.PUBLIC);
node.modifiers().add(newMod.get(0));
}
return true;
}
public boolean visit(NumberLiteral node) {
if (!node.getToken().endsWith("f")
&& !node.getToken().endsWith("d")) {
for (int i = 0; i < node.getToken().length(); i++) {
if (node.getToken().charAt(i) == '.') {
String s = node.getToken() + "f";
node.setToken(s);
break;
}
}
}
return true;
}
// public boolean visit(FieldDeclaration node) {
// if (node.getType().toString().equals("color")){
// System.err.println("color type detected!");
// node.setType(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// return true;
// }
//
// public boolean visit(VariableDeclarationStatement node) {
// if (node.getType().toString().equals("color")){
// System.err.println("color type detected!");
// node.setType(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// return true;
// }
/**
* This is added just for debugging purposes - to make sure that all
* instances of color type have been substituded as in by the regex
* search in ErrorCheckerService.preprocessCode().
*/
public boolean visit(SimpleType node) {
if (node.toString().equals("color")) {
System.err
.println("color type detected! \nThis shouldn't be happening! Please report this as an issue.");
}
return true;
}
}
}

View File

@@ -0,0 +1,367 @@
package galsasson.mode.tweak;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import processing.mode.experimental.TextAreaPainter;
public class ColorControlBox {
public boolean visible;
ArrayList<Handle> handles;
ColorMode colorMode;
Color color;
boolean ilegalColor = false;
boolean isBW;
boolean isHex;
String drawContext;
// interface
int x, y, width, height;
TextAreaPainter painter;
public ColorControlBox(String context, ColorMode mode, ArrayList<Handle> handles)
{
this.drawContext = context;
this.colorMode = mode;
this.handles = handles;
// add this box to the handles so they can update this color on change
for (Handle h : handles) {
h.setColorBox(this);
}
isBW = isGrayScale();
isHex = isHexColor();
color = getCurrentColor();
visible = Settings.alwaysShowColorBoxes;
}
public void initInterface(TextAreaPainter textAreaPainter, int x, int y, int w, int h)
{
this.painter = textAreaPainter;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
public void setPos(int x, int y)
{
this.x = x;
this.y = y;
}
public void draw(Graphics2D g2d)
{
if (!visible) {
return;
}
AffineTransform trans = g2d.getTransform();
g2d.translate(x, y);
// draw current color
g2d.setColor(color);
g2d.fillRoundRect(0, 0, width, height, 5, 5);
// draw black outline
g2d.setStroke(new BasicStroke(1));
g2d.setColor(Color.BLACK);
g2d.drawRoundRect(0, 0, width, height, 5, 5);
if (ilegalColor) {
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(2));
g2d.drawLine(width-3, 3, 3, height-3);
}
g2d.setTransform(trans);
}
public boolean isGrayScale()
{
if (handles.size() <= 2) {
int value = handles.get(0).newValue.intValue();
if ((value&0xff000000) == 0) {
return true;
}
}
return false;
}
/**
* Check if color is hex or webcolor
* @return
* true if number is hex or webcolor
*/
private boolean isHexColor()
{
if (handles.get(0).type == "hex" || handles.get(0).type == "webcolor") {
int value = handles.get(0).value.intValue();
if ((value&0xff000000) != 0) {
return true;
}
}
return false;
}
public Color getCurrentColor()
{
try {
if (handles.size() == 1)
{
if (isBW) {
// treat as color(gray)
float gray = handles.get(0).newValue.floatValue();
return verifiedGrayColor(gray);
}
else {
// treat as color(argb)
int argb = handles.get(0).newValue.intValue();
return verifiedHexColor(argb);
}
}
else if (handles.size() == 2)
{
if (isBW) {
// color(gray, alpha)
float gray = handles.get(0).newValue.floatValue();
return verifiedGrayColor(gray);
}
else {
// treat as color(argb, a)
int argb = handles.get(0).newValue.intValue();
float a = handles.get(1).newValue.floatValue();
return verifiedHexColor(argb, a);
}
}
else if (handles.size() == 3)
{
// color(v1, v2, v3)
float v1 = handles.get(0).newValue.floatValue();
float v2 = handles.get(1).newValue.floatValue();
float v3 = handles.get(2).newValue.floatValue();
if (colorMode.modeType == ColorMode.RGB) {
return verifiedRGBColor(v1, v2, v3, colorMode.aMax);
}
else {
return verifiedHSBColor(v1, v2, v3, colorMode.aMax);
}
}
else if (handles.size() == 4)
{
// color(v1, v2, v3, alpha)
float v1 = handles.get(0).newValue.floatValue();
float v2 = handles.get(1).newValue.floatValue();
float v3 = handles.get(2).newValue.floatValue();
float a = handles.get(3).newValue.floatValue();
if (colorMode.modeType == ColorMode.RGB) {
return verifiedRGBColor(v1, v2, v3, a);
}
else {
return verifiedHSBColor(v1, v2, v3, a);
}
}
}
catch (Exception e) {
System.out.println("error parsing color value: " + e.toString());
ilegalColor = true;
return Color.WHITE;
}
// couldn't figure out this color, return WHITE color
ilegalColor = true;
return Color.WHITE;
}
private Color verifiedGrayColor(float gray)
{
if (gray < 0 || gray > colorMode.v1Max) {
return colorError();
}
ilegalColor = false;
gray = gray/colorMode.v1Max * 255;
return new Color((int)gray, (int)gray, (int)gray, 255);
}
private Color verifiedHexColor(int argb)
{
int r = (argb>>16)&0xff;
int g = (argb>>8)&0xff;
int b = (argb&0xff);
ilegalColor = false;
return new Color(r, g, b, 255);
}
private Color verifiedHexColor(int argb, float alpha)
{
int r = (argb>>16)&0xff;
int g = (argb>>8)&0xff;
int b = (argb&0xff);
ilegalColor = false;
return new Color(r, g, b, 255);
}
public Color verifiedRGBColor(float r, float g, float b, float a)
{
if (r < 0 || r > colorMode.v1Max ||
g < 0 || g > colorMode.v2Max ||
b < 0 || b > colorMode.v3Max) {
return colorError();
}
ilegalColor = false;
r = r/colorMode.v1Max * 255;
g = g/colorMode.v2Max * 255;
b = b/colorMode.v3Max * 255;
return new Color((int)r, (int)g, (int)b, 255);
}
public Color verifiedHSBColor(float h, float s, float b, float a)
{
if (h < 0 || h > colorMode.v1Max ||
s < 0 || s > colorMode.v2Max ||
b < 0 || b > colorMode.v3Max) {
return colorError();
}
ilegalColor = false;
Color c = Color.getHSBColor(h/colorMode.v1Max, s/colorMode.v2Max, b/colorMode.v3Max);
return new Color(c.getRed(), c.getGreen(), c.getBlue(), 255);
}
private Color colorError()
{
ilegalColor = true;
return Color.WHITE;
}
public void colorChanged()
{
color = getCurrentColor();
}
public int getTabIndex()
{
return handles.get(0).tabIndex;
}
public int getLine()
{
return handles.get(0).line;
}
public int getCharIndex()
{
int lastHandle = handles.size()-1;
return handles.get(lastHandle).newEndChar + 2;
}
/* Check if the point is in the box
*
*/
public boolean pick(int mx, int my)
{
if (!visible) {
return false;
}
if (mx>x && mx < x+width && my>y && my<y+height) {
return true;
}
return false;
}
/* Only show the color box if mouse is on the same line
*
* return true if there was change
*/
public boolean setMouseY(int my)
{
boolean change = false;
if (my>y && my<y+height) {
if (!visible) {
change = true;
}
visible = true;
}
else {
if (visible) {
change = true;
}
visible = false;
}
return change;
}
/* Update the color numbers with the new values that were selected
* in the color selector
*
* hue, saturation and brightness parameters are always 0-255
*/
public void selectorChanged(int hue, int saturation, int brightness)
{
if (isBW) {
// color(gray) or color(gray, alpha)
handles.get(0).setValue((float)hue/255*colorMode.v1Max);
}
else {
if (handles.size() == 1 || handles.size() == 2) {
// color(argb)
int prevVal = handles.get(0).newValue.intValue();
int prevAlpha = (prevVal>>24)&0xff;
Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
int newVal = (prevAlpha<<24) | (c.getRed()<<16) | (c.getGreen()<<8) | (c.getBlue());
handles.get(0).setValue(newVal);
}
else if (handles.size() == 3 || handles.size() == 4) {
// color(v1, v2, v3) or color(v1, v2, v3, alpha)
if (colorMode.modeType == ColorMode.HSB) {
// HSB
float v1 = (float)hue/255 * colorMode.v1Max;
float v2 = (float)saturation/255 * colorMode.v2Max;
float v3 = (float)brightness/255 * colorMode.v3Max;
handles.get(0).setValue(v1);
handles.get(1).setValue(v2);
handles.get(2).setValue(v3);
}
else {
// RGB
Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
handles.get(0).setValue((float)c.getRed()/255*colorMode.v1Max);
handles.get(1).setValue((float)c.getGreen()/255*colorMode.v2Max);
handles.get(2).setValue((float)c.getBlue()/255*colorMode.v3Max);
}
}
}
// update our own color
color = getCurrentColor();
// update code text painter so the user will see the changes
painter.updateCodeText();
painter.repaint();
}
public String toString()
{
return handles.size() + " handles, color mode: " + colorMode.toString();
}
}

View File

@@ -0,0 +1,97 @@
package galsasson.mode.tweak;
public class ColorMode {
final static int RGB = 0;
final static int HSB = 1;
float v1Max, v2Max, v3Max, aMax;
int modeType;
boolean unrecognizedMode;
String drawContext;
public ColorMode(String context)
{
this.drawContext = context;
modeType = RGB;
v1Max = 255;
v2Max = 255;
v3Max = 255;
aMax = 255;
unrecognizedMode = false;
}
public ColorMode(String context, int type, float v1, float v2, float v3, float a)
{
this.drawContext = context;
modeType = type;
v1Max = v1;
v2Max = v2;
v3Max = v3;
aMax = a;
unrecognizedMode = false;
}
public static ColorMode fromString(String context, String mode)
{
try
{
String[] elements = mode.split(",");
// determine the type of the color mode
int type = RGB;
if (elements[0].trim().equals("HSB")) {
type = HSB;
}
if (elements.length == 1) {
// colorMode in the form of colorMode(type)
return new ColorMode(context, type, 255, 255, 255, 255);
}
else if (elements.length == 2) {
// colorMode in the form of colorMode(type, max)
float max = Float.parseFloat(elements[1].trim());
return new ColorMode(context, type, max, max, max, max);
}
else if (elements.length == 4) {
// colorMode in the form of colorMode(type, max1, max2, max3)
float r = Float.parseFloat(elements[1].trim());
float g = Float.parseFloat(elements[2].trim());
float b = Float.parseFloat(elements[3].trim());
return new ColorMode(context, type, r, g, b, 255);
}
else if (elements.length == 5) {
// colorMode in the form of colorMode(type, max1, max2, max3, maxA)
float r = Float.parseFloat(elements[1].trim());
float g = Float.parseFloat(elements[2].trim());
float b = Float.parseFloat(elements[3].trim());
float a = Float.parseFloat(elements[4].trim());
return new ColorMode(context, type, r, g, b, a);
}
}
catch(Exception e) { }
/* if we failed to parse this mode (uses variables etc..)
* we should still keep it so we'll know there is a mode declaration
* and we should mark it as unrecognizable
*/
ColorMode newMode = new ColorMode(context);
newMode.unrecognizedMode = true;
return newMode;
}
public String toString()
{
String type;
if (modeType == RGB) {
type = "RGB";
}
else {
type = "HSB";
}
return "ColorMode: " + type + ": (" + v1Max + ", " + v2Max + ", " + v3Max + ", " + aMax + ")";
}
}

View File

@@ -0,0 +1,31 @@
package galsasson.mode.tweak;
import java.awt.Color;
public class ColorScheme {
private static ColorScheme instance = null;
public Color redStrokeColor;
public Color progressFillColor;
public Color progressEmptyColor;
public Color markerColor;
public Color whitePaneColor;
private ColorScheme()
{
redStrokeColor = new Color(160, 20, 20); // dark red
progressEmptyColor = new Color(180, 180, 180, 200);
progressFillColor = new Color(0, 0, 0, 200);
markerColor = new Color(228, 200, 91, 127);
whitePaneColor = new Color(255, 255, 255, 120);
}
public static ColorScheme getInstance() {
if (instance == null) {
instance = new ColorScheme();
}
return instance;
}
}

View File

@@ -0,0 +1,398 @@
package galsasson.mode.tweak;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import javax.swing.Box;
import javax.swing.JFrame;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PImage;
public class ColorSelector {
int hue, saturation, brightness;
public JFrame frame;
public ColorControlBox colorBox;
ColorSelectorBox selectorBox;
ColorSelectorSlider selectorSlider;
SelectorTopBar topBar;
public ColorSelector(ColorControlBox colorBox)
{
this.colorBox = colorBox;
createFrame();
}
public void createFrame()
{
frame = new JFrame();
frame.setBackground(Color.BLACK);
Box box = Box.createHorizontalBox();
box.setBackground(Color.BLACK);
selectorSlider = new ColorSelectorSlider();
if (!colorBox.isBW) {
selectorBox = new ColorSelectorBox();
box.add(selectorBox.getCanvas());
}
box.add(Box.createHorizontalGlue());
box.add(selectorSlider.getCanvas(), BorderLayout.CENTER);
box.add(Box.createHorizontalGlue());
frame.getContentPane().add(box, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
selectorBox.init();
selectorSlider.init();
}
public void show(int x, int y)
{
frame.setLocation(x, y);
frame.setVisible(true);
frame.repaint();
}
public void hide()
{
this.colorBox = null;
frame.setVisible(false);
}
public void refreshColor()
{
if (colorBox.ilegalColor) {
return;
}
setColor(colorBox.color);
}
public void setColor(Color c)
{
if (selectorBox != null) {
selectorBox.setToColor(c);
}
selectorSlider.setToColor(c);
repaintSelector();
}
public void satBrightChanged()
{
repaintSelector();
}
public void hueChanged()
{
if (selectorBox != null) {
selectorBox.renderBack();
}
repaintSelector();
}
public void repaintSelector()
{
if (selectorBox != null) {
selectorBox.redraw();
}
selectorSlider.redraw();
}
/*
* PApplets for the interactive color fields
*/
public class ColorSelectorBox extends PApplet {
int lastX, lastY;
PImage backImg;
public int sketchWidth() { return 255; }
public int sketchHeight() { return 255; }
public void setup() {
noLoop();
colorMode(HSB, 255, 255, 255);
noFill();
if (!colorBox.ilegalColor) {
setToColor(colorBox.color);
}
renderBack();
}
public void draw() {
image(backImg, 0, 0);
stroke((lastY<128) ? 0 : 255);
pushMatrix();
translate(lastX, lastY);
ellipse(0, 0, 5, 5);
line(-8, 0, -6, 0);
line(6, 0, 8, 0);
line(0, -8, 0, -6);
line(0, 6, 0, 8);
popMatrix();
}
public void renderBack() {
PGraphics buf = createGraphics(255, 255);
buf.colorMode(HSB, 255, 255, 255);
buf.beginDraw();
buf.loadPixels();
int index=0;
for (int j=0; j<255; j++) {
for (int i=0; i<255; i++) {
buf.pixels[index++] = color(hue, i, 255-j);
}
}
buf.updatePixels();
buf.endDraw();
backImg = buf.get();
}
public void setToColor(Color c) {
// set selector color
float hsb[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
saturation = (int)(hsb[1]*255);
brightness = (int)(hsb[2]*255);
lastX = saturation;
lastY = 255 - brightness;
}
public void mousePressed() {
if (mouseX < 0 || mouseX > 255 ||
mouseY < 0 || mouseY > 255) {
return;
}
lastX = mouseX;
lastY = mouseY;
updateColor();
}
public void mouseDragged() {
if (mouseX < 0 || mouseX > 255 ||
mouseY < 0 || mouseY > 255) {
return;
}
lastX = mouseX;
lastY = mouseY;
updateColor();
}
public void updateColor() {
saturation = lastX;
brightness = 255 - lastY;
satBrightChanged();
colorBox.selectorChanged(hue, saturation, brightness);
}
/*
public Dimension getPreferredSize() {
return new Dimension(255, 255);
}
public Dimension getMinimumSize() {
return new Dimension(255, 255);
}
public Dimension getMaximumSize() {
return new Dimension(255, 255);
}
*/
}
public class ColorSelectorSlider extends PApplet {
PImage backImg;
int lastY;
public void setup() {
size(30, 255);
noLoop();
colorMode(HSB, 255, 255, 255);
strokeWeight(1);
noFill();
loadPixels();
if (!colorBox.ilegalColor) {
setToColor(colorBox.color);
}
// draw the slider background
renderBack();
}
public void draw() {
image(backImg, 0, 0);
if (colorBox.isBW) {
stroke(lastY<128 ? 0 : 255);
}
else {
stroke(0);
}
pushMatrix();
translate(0, lastY);
// draw left bracket
beginShape();
vertex(5, -2);
vertex(1, -2);
vertex(1, 2);
vertex(5, 2);
endShape();
// draw middle lines
line(13, 0, 17, 0);
line(15, -2, 15, 2);
// draw right bracket
beginShape();
vertex(24, -2);
vertex(28, -2);
vertex(28, 2);
vertex(24, 2);
endShape();
popMatrix();
if (colorBox.isBW) {
stroke(255);
rect(0, 0, 29, 254);
}
else {
stroke(0);
line(0, 0, 0, 255);
line(29, 0, 29, 255);
}
}
public void renderBack() {
PGraphics buf = createGraphics(30, 255);
buf.beginDraw();
buf.loadPixels();
int index=0;
for (int j=0; j<255; j++) {
for (int i=0; i<30; i++) {
if (colorBox.isBW) {
buf.pixels[index++] = color(255-j);
}
else {
buf.pixels[index++] = color(255-j, 255, 255);
}
}
}
buf.updatePixels();
buf.endDraw();
backImg = buf.get();
}
public void setToColor(Color c)
{
// set slider position
if (colorBox.isBW) {
hue = c.getRed();
}
else {
float hsb[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
hue = (int)(hsb[0]*255);
}
lastY = 255 - hue;
}
public void mousePressed()
{
if (mouseX < 0 || mouseX > 30 ||
mouseY < 0 || mouseY > 255) {
return;
}
lastY = mouseY;
updateColor();
}
public void mouseDragged()
{
if (mouseX < 0 || mouseX > 30 ||
mouseY < 0 || mouseY > 255) {
return;
}
lastY = mouseY;
updateColor();
}
public void updateColor()
{
hue = 255 - lastY;
hueChanged();
colorBox.selectorChanged(hue, saturation, brightness);
}
public Dimension getPreferredSize() {
return new Dimension(30, 255);
}
public Dimension getMinimumSize() {
return new Dimension(30, 255);
}
public Dimension getMaximumSize() {
return new Dimension(30, 255);
}
}
public class SelectorTopBar extends PApplet
{
int barWidth;
int barHeight;
public SelectorTopBar(int w)
{
super();
barWidth = w;
barHeight = 16;
}
public void setup()
{
size(barWidth, barHeight);
noLoop();
}
public void draw()
{
background(128);
}
public Dimension getPreferredSize() {
return new Dimension(barWidth, barHeight);
}
public Dimension getMinimumSize() {
return new Dimension(barWidth, barHeight);
}
public Dimension getMaximumSize() {
return new Dimension(barWidth, barHeight);
}
}
}

View File

@@ -0,0 +1,74 @@
package galsasson.mode.tweak;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
public class HProgressBar {
int x, y, size, width;
int pos;
int lPolyX, rPolyX;
Polygon rightPoly, leftPoly;
public HProgressBar(int size, int width)
{
this.size = size;
this.width = width;
x = 0;
y = 0;
setPos(0);
int xl[] = {0, 0, -(int)(size/1.5)};
int yl[] = {-(int)((float)size/3), (int)((float)size/3), 0};
leftPoly = new Polygon(xl, yl, 3);
int xr[] = {0, (int)(size/1.5), 0};
int yr[] = {-(int)((float)size/3), 0, (int)((float)size/3)};
rightPoly = new Polygon(xr, yr, 3);
}
public void setPos(int pos)
{
this.pos = pos;
lPolyX = 0;
rPolyX = 0;
if (pos > 0) {
rPolyX = pos;
}
else if (pos < 0) {
lPolyX = pos;
}
}
public void setWidth(int width)
{
this.width = width;
}
public void draw(Graphics2D g2d)
{
AffineTransform trans = g2d.getTransform();
g2d.translate(x, y);
// draw white cover on text line
g2d.setColor(ColorScheme.getInstance().whitePaneColor);
g2d.fillRect(-200+lPolyX, -size, 200-lPolyX-width/2, size+1);
g2d.fillRect(width/2, -size, 200+rPolyX, size+1);
// draw left and right triangles and leading line
g2d.setColor(ColorScheme.getInstance().progressFillColor);
AffineTransform tmp = g2d.getTransform();
g2d.translate(-width/2 - 5 + lPolyX, -size/2);
g2d.fillRect(0, -1, -lPolyX, 2);
g2d.fillPolygon(leftPoly);
g2d.setTransform(tmp);
g2d.translate(width/2 + 5 + rPolyX, -size/2);
g2d.fillRect(-rPolyX, -1, rPolyX+1, 2);
g2d.fillPolygon(rightPoly);
g2d.setTransform(tmp);
g2d.setTransform(trans);
}
}

View File

@@ -0,0 +1,262 @@
package galsasson.mode.tweak;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Locale;
public class Handle {
public String type;
public String name;
public String strValue;
public String strNewValue;
public int varIndex;
public int startChar;
public int endChar;
public int newStartChar;
public int newEndChar;
public int line;
int tabIndex;
int decimalPlaces; // number of digits after the decimal point
float incValue;
java.lang.Number value, newValue;
String strDiff;
// connect with color control box
ColorControlBox colorBox;
// interface
int x, y, width, height;
int xCenter, xCurrent, xLast;
HProgressBar progBar = null;
String textFormat;
// the client that sends the changes
UDPTweakClient tweakClient;
public Handle(String t, String n, int vi, String v, int ti, int l, int sc,
int ec, int dp) {
type = t;
name = n;
varIndex = vi;
strValue = v;
tabIndex = ti;
line = l;
startChar = sc;
endChar = ec;
decimalPlaces = dp;
incValue = (float) (1 / Math.pow(10, decimalPlaces));
if (type == "int") {
value = newValue = Integer.parseInt(strValue);
strNewValue = strValue;
textFormat = "%d";
} else if (type == "hex") {
Long val = Long.parseLong(strValue.substring(2, strValue.length()),
16);
value = newValue = val.intValue();
strNewValue = strValue;
textFormat = "0x%x";
} else if (type == "webcolor") {
Long val = Long.parseLong(strValue.substring(1, strValue.length()),
16);
val = val | 0xff000000;
value = newValue = val.intValue();
strNewValue = strValue;
textFormat = "#%06x";
} else if (type == "float") {
value = newValue = Float.parseFloat(strValue);
strNewValue = strValue;
textFormat = "%.0" + decimalPlaces + "f";
}
newStartChar = startChar;
newEndChar = endChar;
}
public void initInterface(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// create drag ball
progBar = new HProgressBar(height, width);
}
public void setCenterX(int mx) {
xLast = xCurrent = xCenter = mx;
}
public void setCurrentX(int mx) {
xLast = xCurrent;
xCurrent = mx;
progBar.setPos(xCurrent - xCenter);
updateValue();
}
public void resetProgress() {
progBar.setPos(0);
}
public void updateValue() {
float change = getChange();
if (type == "int") {
if (newValue.intValue() + (int) change > Integer.MAX_VALUE
|| newValue.intValue() + (int) change < Integer.MIN_VALUE) {
change = 0;
return;
}
setValue(newValue.intValue() + (int) change);
} else if (type == "hex") {
setValue(newValue.intValue() + (int) change);
} else if (type == "webcolor") {
setValue(newValue.intValue() + (int) change);
} else if (type == "float") {
setValue(newValue.floatValue() + change);
}
updateColorBox();
}
public void setValue(Number value) {
if (type == "int") {
newValue = value.intValue();
strNewValue = String.format(Locale.US, textFormat,
newValue.intValue());
} else if (type == "hex") {
newValue = value.intValue();
strNewValue = String.format(Locale.US, textFormat,
newValue.intValue());
} else if (type == "webcolor") {
newValue = value.intValue();
// keep only RGB
int val = (newValue.intValue() & 0xffffff);
strNewValue = String.format(Locale.US, textFormat, val);
} else if (type == "float") {
BigDecimal bd = new BigDecimal(value.floatValue());
bd = bd.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
newValue = bd.floatValue();
strNewValue = String.format(Locale.US, textFormat,
newValue.floatValue());
}
// send new data to the server in the sketch
sendNewValue();
}
public void updateColorBox() {
if (colorBox != null) {
colorBox.colorChanged();
}
}
private float getChange() {
int pixels = xCurrent - xLast;
return pixels * incValue;
}
public void setPos(int nx, int ny) {
x = nx;
y = ny;
}
public void setWidth(int w) {
width = w;
progBar.setWidth(w);
}
public void draw(Graphics2D g2d, boolean hasFocus) {
AffineTransform prevTrans = g2d.getTransform();
g2d.translate(x, y);
// draw underline on the number
g2d.setColor(ColorScheme.getInstance().progressFillColor);
g2d.drawLine(0, 0, width, 0);
if (hasFocus) {
if (progBar != null) {
g2d.translate(width / 2, 2);
progBar.draw(g2d);
}
}
g2d.setTransform(prevTrans);
}
public boolean pick(int mx, int my) {
return pickText(mx, my);
}
public boolean pickText(int mx, int my) {
if (mx > x - 2 && mx < x + width + 2 && my > y - height && my < y) {
return true;
}
return false;
}
public boolean valueChanged() {
if (type == "int") {
return (value.intValue() != newValue.intValue());
} else if (type == "hex") {
return (value.intValue() != newValue.intValue());
} else if (type == "webcolor") {
return (value.intValue() != newValue.intValue());
} else {
return (value.floatValue() != newValue.floatValue());
}
}
public void setColorBox(ColorControlBox box) {
colorBox = box;
}
public void setTweakClient(UDPTweakClient client) {
tweakClient = client;
}
public void sendNewValue() {
int index = varIndex;
try {
if (type == "int") {
tweakClient.sendInt(index, newValue.intValue());
} else if (type == "hex") {
tweakClient.sendInt(index, newValue.intValue());
} else if (type == "webcolor") {
tweakClient.sendInt(index, newValue.intValue());
} else if (type == "float") {
tweakClient.sendFloat(index, newValue.floatValue());
}
} catch (Exception e) {
System.out.println("error sending new value!");
}
}
public String toString() {
return type + " " + name + " = " + strValue + " (tab: " + tabIndex
+ ", line: " + line + ", start: " + startChar + ", end: "
+ endChar + ")";
}
}
/*
* Used for sorting the handles by order of occurrence inside each tab
*/
class HandleComparator implements Comparator<Handle> {
public int compare(Handle handle1, Handle handle2) {
int tab = handle1.tabIndex - handle2.tabIndex;
if (tab == 0) {
return handle1.startChar - handle2.startChar;
} else {
return tab;
}
}
}

View File

@@ -0,0 +1,5 @@
package galsasson.mode.tweak;
public class Settings {
public static boolean alwaysShowColorBoxes = true;
}

View File

@@ -0,0 +1,783 @@
package galsasson.mode.tweak;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SketchParser {
public List<List<ColorControlBox>> colorBoxes;
public List<List<Handle>> allHandles;
int intVarCount;
int floatVarCount;
final String varPrefix = "tweakmode";
String[] codeTabs;
boolean requiresComment;
ArrayList<ColorMode> colorModes;
List<List<Range>> scientificNotations;
public SketchParser(String[] codeTabs, boolean requiresComment) {
this.codeTabs = codeTabs;
this.requiresComment = requiresComment;
intVarCount=0;
floatVarCount=0;
scientificNotations = getAllScientificNotations();
// find, add, and sort all tweakable numbers in the sketch
addAllNumbers();
// handle colors
colorModes = findAllColorModes();
//colorBoxes = new ArrayList[codeTabs.length];
createColorBoxes();
createColorBoxesForLights();
/* If there is more than one color mode per context,
* allow only hex and webcolors in this context.
* Currently there is no notion of order of execution so we
* cannot know which color mode relate to a color.
*/
handleMultipleColorModes();
}
public void addAllNumbers() {
//allHandles = new ArrayList[codeTabs.length]; // moved inside addAllDecimalNumbers
addAllDecimalNumbers();
addAllHexNumbers();
addAllWebColorNumbers();
//for (int i=0; i<codeTabs.length; i++) {
for (List<Handle> handle : allHandles) {
//Collections.sort(allHandles[i], new HandleComparator());
Collections.sort(handle, new HandleComparator());
}
}
/**
* Get a list of all the numbers in this sketch
* @return
* list of all numbers in the sketch (excluding hexadecimals)
*/
private void addAllDecimalNumbers() {
allHandles = new ArrayList<>();
// for every number found:
// save its type (int/float), name, value and position in code.
Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]\\d+\\.?\\d*");
for (int i = 0; i < codeTabs.length; i++) {
//allHandles[i] = new ArrayList<Handle>();
List<Handle> handles = new ArrayList<Handle>();
allHandles.add(handles);
String c = codeTabs[i];
Matcher m = p.matcher(c);
while (m.find()) {
boolean forceFloat = false;
int start = m.start()+1;
int end = m.end();
if (isInComment(start, codeTabs[i])) {
// ignore comments
continue;
}
if (requiresComment) {
// only add numbers that have the "// tweak" comment in their line
if (!lineHasTweakComment(start, c)) {
continue;
}
}
// ignore scientific notation (e.g. 1e-6)
boolean found = false;
for (Range r : scientificNotations.get(i)) {
if (r.contains(start)) {
found=true;
break;
}
}
if (found) {
continue;
}
// remove any 'f' after the number
if (c.charAt(end) == 'f') {
forceFloat = true;
end++;
}
// if its a negative, include the '-' sign
if (c.charAt(start-1) == '-') {
if (isNegativeSign(start-2, c)) {
start--;
}
}
// special case for ignoring (0x...). will be handled later
if (c.charAt(m.end()) == 'x' ||
c.charAt(m.end()) == 'X') {
continue;
}
// special case for ignoring number inside a string ("")
if (isInsideString(start, c))
continue;
// beware of the global assignment (bug from 26.07.2013)
if (isGlobal(m.start(), c))
continue;
int line = countLines(c.substring(0, start)) - 1; // zero based
String value = c.substring(start, end);
//value
if (value.contains(".") || forceFloat) {
// consider this as a float
String name = varPrefix + "_float[" + floatVarCount +"]";
int decimalDigits = getNumDigitsAfterPoint(value);
handles.add(new Handle("float", name, floatVarCount, value, i, line, start, end, decimalDigits));
floatVarCount++;
} else {
// consider this as an int
String name = varPrefix + "_int[" + intVarCount +"]";
handles.add(new Handle("int", name, intVarCount, value, i, line, start, end, 0));
intVarCount++;
}
}
}
}
/**
* Get a list of all the hexadecimal numbers in the code
* @return
* list of all hexadecimal numbers in the sketch
*/
private void addAllHexNumbers()
{
/* for every number found:
* save its type (int/float), name, value and position in code.
*/
Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]0x[A-Fa-f0-9]+");
for (int i=0; i<codeTabs.length; i++)
{
String c = codeTabs[i];
Matcher m = p.matcher(c);
while (m.find())
{
int start = m.start()+1;
int end = m.end();
if (isInComment(start, codeTabs[i])) {
// ignore comments
continue;
}
if (requiresComment) {
// only add numbers that have the "// tweak" comment in their line
if (!lineHasTweakComment(start, c)) {
continue;
}
}
// special case for ignoring number inside a string ("")
if (isInsideString(start, c)) {
continue;
}
// beware of the global assignment (bug from 26.07.2013)
if (isGlobal(m.start(), c)) {
continue;
}
int line = countLines(c.substring(0, start)) - 1; // zero based
String value = c.substring(start, end);
String name = varPrefix + "_int[" + intVarCount + "]";
Handle handle;
try {
handle = new Handle("hex", name, intVarCount, value, i, line, start, end, 0);
}
catch (NumberFormatException e) {
// don't add this number
continue;
}
allHandles.get(i).add(handle);
intVarCount++;
}
}
}
/**
* Get a list of all the webcolors (#) numbers in the code
* @return
* list of all hexadecimal numbers in the sketch
*/
private void addAllWebColorNumbers()
{
Pattern p = Pattern.compile("#[A-Fa-f0-9]{6}");
for (int i=0; i<codeTabs.length; i++)
{
String c = codeTabs[i];
Matcher m = p.matcher(c);
while (m.find())
{
int start = m.start();
int end = m.end();
if (isInComment(start, codeTabs[i])) {
// ignore comments
continue;
}
if (requiresComment) {
// only add numbers that have the "// tweak" comment in their line
if (!lineHasTweakComment(start, c)) {
continue;
}
}
// special case for ignoring number inside a string ("")
if (isInsideString(start, c)) {
continue;
}
// beware of the global assignment (bug from 26.07.2013)
if (isGlobal(m.start(), c)) {
continue;
}
int line = countLines(c.substring(0, start)) - 1; // zero based
String value = c.substring(start, end);
String name = varPrefix + "_int[" + intVarCount + "]";
Handle handle;
try {
handle = new Handle("webcolor", name, intVarCount, value, i, line, start, end, 0);
}
catch (NumberFormatException e) {
// don't add this number
continue;
}
allHandles.get(i).add(handle);
intVarCount++;
}
}
}
private ArrayList<ColorMode> findAllColorModes() {
ArrayList<ColorMode> modes = new ArrayList<ColorMode>();
for (String tab : codeTabs) {
int index = -1;
// search for a call to colorMode function
while ((index = tab.indexOf("colorMode", index+1)) > -1) {
// found colorMode at index
if (isInComment(index, tab)) {
// ignore comments
continue;
}
index += 9;
int parOpen = tab.indexOf('(', index);
if (parOpen < 0) {
continue;
}
int parClose = tab.indexOf(')', parOpen+1);
if (parClose < 0) {
continue;
}
// add this mode
String modeDesc = tab.substring(parOpen+1, parClose);
String context = getObject(index-9, tab);
modes.add(ColorMode.fromString(context, modeDesc));
}
}
return modes;
}
private void createColorBoxes() {
colorBoxes = new ArrayList<>();
// search tab for the functions: 'color', 'fill', 'stroke', 'background', 'tint'
Pattern p = Pattern.compile("color\\(|color\\s\\(|fill[\\(\\s]|stroke[\\(\\s]|background[\\(\\s]|tint[\\(\\s]");
for (int i = 0; i < codeTabs.length; i++) {
//colorBoxes[i] = new ArrayList<ColorControlBox>();
List<ColorControlBox> colorBox = new ArrayList<ColorControlBox>();
colorBoxes.add(colorBox);
String tab = codeTabs[i];
Matcher m = p.matcher(tab);
while (m.find()) {
ArrayList<Handle> colorHandles = new ArrayList<Handle>();
// look for the '(' and ')' positions
int openPar = tab.indexOf("(", m.start());
int closePar = tab.indexOf(")", m.end());
if (openPar < 0 || closePar < 0) {
// ignore this color
continue;
}
if (isInComment(m.start(), tab)) {
// ignore colors in a comment
continue;
}
// look for handles inside the parenthesis
for (Handle handle : allHandles.get(i)) {
if (handle.startChar > openPar &&
handle.endChar <= closePar) {
// we have a match
colorHandles.add(handle);
}
}
if (colorHandles.size() > 0) {
/* make sure there is no other stuff between '()' like variables.
* substract all handle values from string inside parenthesis and
* check there is no garbage left
*/
String insidePar = tab.substring(openPar+1, closePar);
for (Handle h : colorHandles) {
insidePar = insidePar.replaceFirst(h.strValue, "");
}
// make sure there is only ' ' and ',' left in the string.
boolean garbage = false;
for (int j=0; j<insidePar.length(); j++) {
if (insidePar.charAt(j) != ' ' && insidePar.charAt(j) != ',') {
// don't add this color box because we can not know the
// real value of this color
garbage = true;
}
}
// create a new color box
if (!garbage) {
// find the context of the color (e.g. this.fill() or <object>.fill())
String context = getObject(m.start(), tab);
ColorMode cmode = getColorModeForContext(context);
// not adding color operations for modes we couldn't understand
ColorControlBox newCCB = new ColorControlBox(context, cmode, colorHandles);
if (cmode.unrecognizedMode) {
// the color mode is unrecognizable add only if is a hex or webcolor
if (newCCB.isHex) {
colorBox.add(newCCB);
}
} else {
colorBox.add(newCCB);
}
}
}
}
}
}
private void createColorBoxesForLights() {
// search code for light color and material color functions.
Pattern p = Pattern.compile("ambientLight[\\(\\s]|directionalLight[\\(\\s]"+
"|pointLight[\\(\\s]|spotLight[\\(\\s]|lightSpecular[\\(\\s]"+
"|specular[\\(\\s]|ambient[\\(\\s]|emissive[\\(\\s]");
for (int i=0; i<codeTabs.length; i++) {
String tab = codeTabs[i];
Matcher m = p.matcher(tab);
while (m.find()) {
ArrayList<Handle> colorHandles = new ArrayList<Handle>();
// look for the '(' and ')' positions
int openPar = tab.indexOf("(", m.start());
int closePar = tab.indexOf(")", m.end());
if (openPar < 0 || closePar < 0) {
// ignore this color
continue;
}
if (isInComment(m.start(), tab)) {
// ignore colors in a comment
continue;
}
// put 'colorParamsEnd' after three parameters inside the parenthesis or at the close
int colorParamsEnd = openPar;
int commas=3;
while (commas-- > 0) {
colorParamsEnd=tab.indexOf(",", colorParamsEnd+1);
if (colorParamsEnd < 0 ||
colorParamsEnd > closePar) {
colorParamsEnd = closePar;
break;
}
}
for (Handle handle : allHandles.get(i)) {
if (handle.startChar > openPar &&
handle.endChar <= colorParamsEnd) {
// we have a match
colorHandles.add(handle);
}
}
if (colorHandles.size() > 0) {
/* make sure there is no other stuff between '()' like variables.
* substract all handle values from string inside parenthesis and
* check there is no garbage left
*/
String insidePar = tab.substring(openPar+1, colorParamsEnd);
for (Handle h : colorHandles) {
insidePar = insidePar.replaceFirst(h.strValue, "");
}
// make sure there is only ' ' and ',' left in the string.
boolean garbage = false;
for (int j=0; j<insidePar.length(); j++) {
if (insidePar.charAt(j) != ' ' && insidePar.charAt(j) != ',') {
// don't add this color box because we can not know the
// real value of this color
garbage = true;
}
}
// create a new color box
if (!garbage) {
// find the context of the color (e.g. this.fill() or <object>.fill())
String context = getObject(m.start(), tab);
ColorMode cmode = getColorModeForContext(context);
// not adding color operations for modes we couldn't understand
ColorControlBox newCCB = new ColorControlBox(context, cmode, colorHandles);
if (cmode.unrecognizedMode) {
// the color mode is unrecognizable add only if is a hex or webcolor
if (newCCB.isHex) {
colorBoxes.get(i).add(newCCB);
}
} else {
colorBoxes.get(i).add(newCCB);
}
}
}
}
}
}
private ColorMode getColorModeForContext(String context) {
for (ColorMode cm: colorModes) {
if (cm.drawContext.equals(context)) {
return cm;
}
}
// if non found, create the default color mode for this context and return it
ColorMode newCM = new ColorMode(context);
colorModes.add(newCM);
return newCM;
}
private void handleMultipleColorModes()
{
// count how many color modes per context
Map<String, Integer> modeCount = new HashMap<String, Integer>();
for (ColorMode cm : colorModes)
{
Integer prev = modeCount.get(cm.drawContext);
if (prev == null) {
prev=0;
}
modeCount.put(cm.drawContext, prev+1);
}
// find the contexts that have more than one color mode
ArrayList<String> multipleContexts = new ArrayList<String>();
Set<String> allContexts = modeCount.keySet();
for (String context : allContexts) {
if (modeCount.get(context) > 1) {
multipleContexts.add(context);
}
}
/* keep only hex and web color boxes in color calls
* that belong to 'multipleContexts' contexts
*/
for (int i=0; i<codeTabs.length; i++) {
ArrayList<ColorControlBox> toDelete = new ArrayList<ColorControlBox>();
for (String context : multipleContexts) {
for (ColorControlBox ccb : colorBoxes.get(i)) {
if (ccb.drawContext.equals(context) && !ccb.isHex) {
toDelete.add(ccb);
}
}
}
colorBoxes.get(i).removeAll(toDelete);
}
}
public List<List<Range>> getAllScientificNotations() {
//ArrayList<Range> notations[] = new ArrayList[codeTabs.length];
List<List<Range>> notations = new ArrayList<>();
Pattern p = Pattern.compile("[+\\-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?[eE][+\\-]?\\d+");
//for (int i = 0; i < codeTabs.length; i++) {
for (String code : codeTabs) {
List<Range> notation = new ArrayList<Range>();
//notations[i] = new ArrayList<Range>();
//Matcher m = p.matcher(codeTabs[i]);
Matcher m = p.matcher(code);
while (m.find()) {
//notations[i].add(new Range(m.start(), m.end()));
notation.add(new Range(m.start(), m.end()));
}
notations.add(notation);
}
return notations;
}
public static boolean containsTweakComment(String[] codeTabs) {
for (String tab : codeTabs) {
if (hasTweakComment(tab)) {
return true;
}
}
return false;
}
static public boolean lineHasTweakComment(int pos, String code) {
int lineEnd = getEndOfLine(pos, code);
if (lineEnd < 0) {
return false;
}
String line = code.substring(pos, lineEnd);
return hasTweakComment(line);
}
static private boolean hasTweakComment(String code) {
Pattern p = Pattern.compile("\\/\\/.*tweak", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(code);
return m.find();
}
static private boolean isNegativeSign(int pos, String code) {
// go back and look for ,{[(=?+-/*%<>:&|^!~
for (int i = pos; i >= 0; i--) {
char c = code.charAt(i);
if (c != ' ' && c != '\t') {
return (c==',' || c=='{' || c=='[' || c=='(' ||
c=='=' || c=='?' || c=='+' || c=='-' ||
c=='/' || c=='*' || c=='%' || c=='<' ||
c=='>' || c==':' || c=='&' || c=='|' ||
c=='^' || c=='!' || c=='~');
}
}
return false;
}
static private int getNumDigitsAfterPoint(String number) {
Pattern p = Pattern.compile("\\.[0-9]+");
Matcher m = p.matcher(number);
if (m.find()) {
return m.end() - m.start() - 1;
}
return 0;
}
static private int countLines(String str) {
String[] lines = str.split("\r\n|\n\r|\n|\r");
return lines.length;
}
/**
* Are we inside a string? (TODO: ignore comments in the code)
* @param pos
* position in the code
* @param code
* the code
* @return
*/
static private boolean isInsideString(int pos, String code) {
int quoteNum = 0; // count '"'
for (int c = pos; c>=0 && code.charAt(c) != '\n'; c--) {
if (code.charAt(c) == '"') {
quoteNum++;
}
}
if (quoteNum%2 == 1) {
return true;
}
return false;
}
/**
* Is this a global position?
* @param pos position
* @param code code
* @return
* true if the position 'pos' is in global scope in the code 'code'
*/
static private boolean isGlobal(int pos, String code) {
int curlyScope = 0; // count '{-}'
for (int c=pos; c>=0; c--)
{
if (code.charAt(c) == '{') {
// check if a function or an array assignment
for (int cc=c; cc>=0; cc--) {
if (code.charAt(cc)==')') {
curlyScope++;
break;
}
else if (code.charAt(cc)==']') {
break;
}
else if (code.charAt(cc)==';') {
break;
}
}
}
else if (code.charAt(c) == '}') {
// check if a function or an array assignment
for (int cc=c; cc>=0; cc--) {
if (code.charAt(cc)==')') {
curlyScope--;
break;
}
else if (code.charAt(cc)==']') {
break;
}
else if (code.charAt(cc)==';') {
break;
}
}
}
}
if (curlyScope == 0) {
// it is a global position
return true;
}
return false;
};
static private boolean isInComment(int pos, String code) {
// look for one line comment
int lineStart = getStartOfLine(pos, code);
if (lineStart < 0) {
return false;
}
if (code.substring(lineStart, pos).indexOf("//") != -1) {
return true;
}
// TODO: look for block comments
return false;
}
static private int getEndOfLine(int pos, String code) {
return code.indexOf("\n", pos);
}
static private int getStartOfLine(int pos, String code) {
while (pos >= 0) {
if (code.charAt(pos) == '\n') {
return pos+1;
}
pos--;
}
return 0;
}
/** returns the object of the function starting at 'pos'
*
* @param pos
* @param code
* @return
*/
static private String getObject(int pos, String code) {
boolean readObject = false;
String obj = "this";
while (pos-- >= 0) {
if (code.charAt(pos) == '.') {
if (!readObject) {
obj = "";
readObject = true;
}
else {
break;
}
}
else if (code.charAt(pos) == ' ' || code.charAt(pos) == '\t') {
break;
}
else if (readObject) {
obj = code.charAt(pos) + obj;
}
}
return obj;
}
static public int getSetupStart(String code) {
Pattern p = Pattern.compile("void[\\s\\t\\r\\n]*setup[\\s\\t]*\\(\\)[\\s\\t\\r\\n]*\\{");
Matcher m = p.matcher(code);
if (m.find()) {
return m.end();
}
return -1;
}
// private String replaceString(String str, int start, int end, String put) {
// return str.substring(0, start) + put + str.substring(end, str.length());
// }
class Range {
int start;
int end;
public Range(int s, int e) {
start = s;
end = e;
}
public boolean contains(int v) {
return v >= start && v < end;
}
}
}

View File

@@ -0,0 +1,20 @@
package galsasson.mode.tweak;
import processing.app.Base;
import processing.app.Editor;
import processing.mode.java.JavaToolbar;
public class TweakToolbar extends JavaToolbar {
static protected final int RUN = 0;
static protected final int STOP = 1;
static protected final int NEW = 2;
static protected final int OPEN = 3;
static protected final int SAVE = 4;
static protected final int EXPORT = 5;
public TweakToolbar(Editor editor, Base base) {
super(editor, base);
}
}

View File

@@ -0,0 +1,182 @@
package galsasson.mode.tweak;
import java.net.*;
import java.nio.ByteBuffer;
public class UDPTweakClient {
private DatagramSocket socket;
private InetAddress address;
private boolean initialized;
private int sketchPort;
static final int VAR_INT = 0;
static final int VAR_FLOAT = 1;
static final int SHUTDOWN = 0xffffffff;
public UDPTweakClient(int sketchPort)
{
this.sketchPort = sketchPort;
try {
socket = new DatagramSocket();
// only local sketch is allowed
address = InetAddress.getByName("127.0.0.1");
initialized = true;
}
catch (SocketException e) {
initialized = false;
}
catch (UnknownHostException e) {
socket.close();
initialized = false;
}
catch (SecurityException e) {
socket.close();
initialized = false;
}
}
public void shutdown()
{
if (!initialized) {
return;
}
// send shutdown to the sketch
sendShutdown();
initialized = false;
}
public boolean sendInt(int index, int val)
{
if (!initialized) {
return false;
}
try {
byte[] buf = new byte[12];
ByteBuffer bb = ByteBuffer.wrap(buf);
bb.putInt(0, VAR_INT);
bb.putInt(4, index);
bb.putInt(8, val);
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, sketchPort);
socket.send(packet);
}
catch (Exception e) {
return false;
}
return true;
}
public boolean sendFloat(int index, float val)
{
if (!initialized) {
return false;
}
try {
byte[] buf = new byte[12];
ByteBuffer bb = ByteBuffer.wrap(buf);
bb.putInt(0, VAR_FLOAT);
bb.putInt(4, index);
bb.putFloat(8, val);
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, sketchPort);
socket.send(packet);
}
catch (Exception e) {
return false;
}
return true;
}
public boolean sendShutdown()
{
if (!initialized) {
return false;
}
try {
byte[] buf = new byte[12];
ByteBuffer bb = ByteBuffer.wrap(buf);
bb.putInt(0, SHUTDOWN);
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, sketchPort);
socket.send(packet);
}
catch (Exception e) {
return false;
}
return true;
}
public static String getServerCode(int listenPort, boolean hasInts, boolean hasFloats)
{
String serverCode = ""+
"class TweakModeServer extends Thread\n"+
"{\n"+
" protected DatagramSocket socket = null;\n"+
" protected boolean running = true;\n"+
" final int INT_VAR = 0;\n"+
" final int FLOAT_VAR = 1;\n"+
" final int SHUTDOWN = 0xffffffff;\n"+
" public TweakModeServer() {\n"+
" this(\"TweakModeServer\");\n"+
" }\n"+
" public TweakModeServer(String name) {\n"+
" super(name);\n"+
" }\n"+
" public void setup()\n"+
" {\n"+
" try {\n"+
" socket = new DatagramSocket("+listenPort+");\n"+
" socket.setSoTimeout(250);\n"+
" } catch (IOException e) {\n"+
" println(\"error: could not create TweakMode server socket\");\n"+
" }\n"+
" }\n"+
" public void run()\n"+
" {\n"+
" byte[] buf = new byte[256];\n"+
" while(running)\n"+
" {\n"+
" try {\n"+
" DatagramPacket packet = new DatagramPacket(buf, buf.length);\n"+
" socket.receive(packet);\n"+
" ByteBuffer bb = ByteBuffer.wrap(buf);\n"+
" int type = bb.getInt(0);\n"+
" int index = bb.getInt(4);\n";
if (hasInts) {
serverCode +=
" if (type == INT_VAR) {\n"+
" int val = bb.getInt(8);\n"+
" tweakmode_int[index] = val;\n"+
" }\n"+
" else ";
}
if (hasFloats) {
serverCode +=
" if (type == FLOAT_VAR) {\n"+
" float val = bb.getFloat(8);\n"+
" tweakmode_float[index] = val;\n"+
" }\n"+
" else";
}
serverCode +=
" if (type == SHUTDOWN) {\n"+
" running = false;\n"+
" }\n"+
" } catch (SocketTimeoutException e) {\n"+
" // nothing to do here just try receiving again\n"+
" } catch (Exception e) {\n"+
" }\n"+
" }\n"+
" socket.close();\n"+
" }\n"+
"}\n\n\n";
return serverCode;
}
}