mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
moving things around to merge PDE X
This commit is contained in:
3742
java/src/processing/mode/experimental/ASTGenerator.java
Normal file
3742
java/src/processing/mode/experimental/ASTGenerator.java
Normal file
File diff suppressed because it is too large
Load Diff
827
java/src/processing/mode/experimental/ASTNodeWrapper.java
Normal file
827
java/src/processing/mode/experimental/ASTNodeWrapper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
65
java/src/processing/mode/experimental/ArrayFieldNode.java
Normal file
65
java/src/processing/mode/experimental/ArrayFieldNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
353
java/src/processing/mode/experimental/AutoSaveUtil.java
Normal file
353
java/src/processing/mode/experimental/AutoSaveUtil.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
37
java/src/processing/mode/experimental/ClassLoadListener.java
Normal file
37
java/src/processing/mode/experimental/ClassLoadListener.java
Normal 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);
|
||||
}
|
||||
548
java/src/processing/mode/experimental/CompilationChecker.java
Normal file
548
java/src/processing/mode/experimental/CompilationChecker.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
367
java/src/processing/mode/experimental/Compiler.java
Normal file
367
java/src/processing/mode/experimental/Compiler.java
Normal 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;
|
||||
}
|
||||
}
|
||||
262
java/src/processing/mode/experimental/CompletionCandidate.java
Normal file
262
java/src/processing/mode/experimental/CompletionCandidate.java
Normal 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>");
|
||||
* */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
575
java/src/processing/mode/experimental/CompletionPanel.java
Normal file
575
java/src/processing/mode/experimental/CompletionPanel.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
73
java/src/processing/mode/experimental/DebugBuild.java
Normal file
73
java/src/processing/mode/experimental/DebugBuild.java
Normal 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;
|
||||
}
|
||||
}
|
||||
2126
java/src/processing/mode/experimental/DebugEditor.java
Normal file
2126
java/src/processing/mode/experimental/DebugEditor.java
Normal file
File diff suppressed because it is too large
Load Diff
85
java/src/processing/mode/experimental/DebugRunner.java
Normal file
85
java/src/processing/mode/experimental/DebugRunner.java
Normal 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;
|
||||
}
|
||||
}
|
||||
305
java/src/processing/mode/experimental/DebugToolbar.java
Normal file
305
java/src/processing/mode/experimental/DebugToolbar.java
Normal 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];
|
||||
}
|
||||
}
|
||||
1372
java/src/processing/mode/experimental/Debugger.java
Normal file
1372
java/src/processing/mode/experimental/Debugger.java
Normal file
File diff suppressed because it is too large
Load Diff
317
java/src/processing/mode/experimental/ErrorBar.java
Normal file
317
java/src/processing/mode/experimental/ErrorBar.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1766
java/src/processing/mode/experimental/ErrorCheckerService.java
Normal file
1766
java/src/processing/mode/experimental/ErrorCheckerService.java
Normal file
File diff suppressed because it is too large
Load Diff
59
java/src/processing/mode/experimental/ErrorMarker.java
Normal file
59
java/src/processing/mode/experimental/ErrorMarker.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package processing.mode.experimental;
|
||||
/**
|
||||
* Error markers displayed on the Error Bar.
|
||||
*
|
||||
* @author Manindra Moharana <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
374
java/src/processing/mode/experimental/ErrorWindow.java
Normal file
374
java/src/processing/mode/experimental/ErrorWindow.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
459
java/src/processing/mode/experimental/ExperimentalMode.java
Normal file
459
java/src/processing/mode/experimental/ExperimentalMode.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
65
java/src/processing/mode/experimental/FieldNode.java
Normal file
65
java/src/processing/mode/experimental/FieldNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
79
java/src/processing/mode/experimental/ImportStatement.java
Normal file
79
java/src/processing/mode/experimental/ImportStatement.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
126
java/src/processing/mode/experimental/JavadocHelper.java
Normal file
126
java/src/processing/mode/experimental/JavadocHelper.java
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
228
java/src/processing/mode/experimental/LineBreakpoint.java
Normal file
228
java/src/processing/mode/experimental/LineBreakpoint.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
196
java/src/processing/mode/experimental/LineHighlight.java
Normal file
196
java/src/processing/mode/experimental/LineHighlight.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
269
java/src/processing/mode/experimental/LineID.java
Normal file
269
java/src/processing/mode/experimental/LineID.java
Normal 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.
|
||||
}
|
||||
}
|
||||
35
java/src/processing/mode/experimental/LineListener.java
Normal file
35
java/src/processing/mode/experimental/LineListener.java
Normal 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);
|
||||
}
|
||||
66
java/src/processing/mode/experimental/LocalVariableNode.java
Normal file
66
java/src/processing/mode/experimental/LocalVariableNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
273
java/src/processing/mode/experimental/OffsetMatcher.java
Normal file
273
java/src/processing/mode/experimental/OffsetMatcher.java
Normal 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;");
|
||||
}
|
||||
}
|
||||
211
java/src/processing/mode/experimental/Problem.java
Normal file
211
java/src/processing/mode/experimental/Problem.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
387
java/src/processing/mode/experimental/SketchOutline.java
Normal file
387
java/src/processing/mode/experimental/SketchOutline.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
341
java/src/processing/mode/experimental/TabOutline.java
Normal file
341
java/src/processing/mode/experimental/TabOutline.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
962
java/src/processing/mode/experimental/TextArea.java
Normal file
962
java/src/processing/mode/experimental/TextArea.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
859
java/src/processing/mode/experimental/TextAreaPainter.java
Normal file
859
java/src/processing/mode/experimental/TextAreaPainter.java
Normal 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
285
java/src/processing/mode/experimental/Utils.java
Normal file
285
java/src/processing/mode/experimental/Utils.java
Normal 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;");
|
||||
}
|
||||
}
|
||||
36
java/src/processing/mode/experimental/VMEventListener.java
Normal file
36
java/src/processing/mode/experimental/VMEventListener.java
Normal 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);
|
||||
}
|
||||
68
java/src/processing/mode/experimental/VMEventReader.java
Normal file
68
java/src/processing/mode/experimental/VMEventReader.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
java/src/processing/mode/experimental/VariableInspector.form
Normal file
53
java/src/processing/mode/experimental/VariableInspector.form
Normal 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>
|
||||
931
java/src/processing/mode/experimental/VariableInspector.java
Normal file
931
java/src/processing/mode/experimental/VariableInspector.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
358
java/src/processing/mode/experimental/VariableNode.java
Normal file
358
java/src/processing/mode/experimental/VariableNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
145
java/src/processing/mode/experimental/XQConsoleToggle.java
Normal file
145
java/src/processing/mode/experimental/XQConsoleToggle.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
317
java/src/processing/mode/experimental/XQErrorTable.java
Normal file
317
java/src/processing/mode/experimental/XQErrorTable.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
246
java/src/processing/mode/experimental/XQPreprocessor.java
Normal file
246
java/src/processing/mode/experimental/XQPreprocessor.java
Normal 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 <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
367
java/src/processing/mode/experimental/tweak/ColorControlBox.java
Normal file
367
java/src/processing/mode/experimental/tweak/ColorControlBox.java
Normal 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();
|
||||
}
|
||||
}
|
||||
97
java/src/processing/mode/experimental/tweak/ColorMode.java
Normal file
97
java/src/processing/mode/experimental/tweak/ColorMode.java
Normal 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 + ")";
|
||||
}
|
||||
}
|
||||
31
java/src/processing/mode/experimental/tweak/ColorScheme.java
Normal file
31
java/src/processing/mode/experimental/tweak/ColorScheme.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
398
java/src/processing/mode/experimental/tweak/ColorSelector.java
Normal file
398
java/src/processing/mode/experimental/tweak/ColorSelector.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
262
java/src/processing/mode/experimental/tweak/Handle.java
Normal file
262
java/src/processing/mode/experimental/tweak/Handle.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package galsasson.mode.tweak;
|
||||
|
||||
public class Settings {
|
||||
public static boolean alwaysShowColorBoxes = true;
|
||||
}
|
||||
783
java/src/processing/mode/experimental/tweak/SketchParser.java
Normal file
783
java/src/processing/mode/experimental/tweak/SketchParser.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
182
java/src/processing/mode/experimental/tweak/UDPTweakClient.java
Normal file
182
java/src/processing/mode/experimental/tweak/UDPTweakClient.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user