diff --git a/app/.classpath b/app/.classpath
index 51adbc14f..12fc327eb 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -16,5 +16,6 @@
+
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index bbf0f52a5..0c1e901b0 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -276,11 +276,17 @@ public class Base {
Mode javaScriptMode =
ModeContribution.load(this, getContentFile("modes/javascript"),
"processing.mode.javascript.JavaScriptMode").getMode();
-
- coreModes = new Mode[] { javaMode, androidMode, javaScriptMode };
+
+// coreModes = new Mode[] { javaMode, androidMode, javaScriptMode };
+
+ Mode debugMode =
+ ModeContribution.load(this, getContentFile("modes/java2"),
+ "processing.mode.java2.DebugMode").getMode();
+ coreModes = new Mode[] { javaMode, androidMode, javaScriptMode, debugMode };
+
// for (Mode mode : coreModes) { // already called by load() above
// mode.setupGUI();
-// }
+// }
}
@@ -648,8 +654,10 @@ public class Base {
public ArrayList getModeList() {
ArrayList allModes = new ArrayList();
allModes.addAll(Arrays.asList(coreModes));
- for (ModeContribution contrib : modeContribs) {
- allModes.add(contrib.getMode());
+ if (modeContribs != null) {
+ for (ModeContribution contrib : modeContribs) {
+ allModes.add(contrib.getMode());
+ }
}
return allModes;
}
diff --git a/app/src/processing/app/macosx/ThinkDifferent.java b/app/src/processing/app/macosx/ThinkDifferent.java
index 9512a49cd..1c5442b7f 100644
--- a/app/src/processing/app/macosx/ThinkDifferent.java
+++ b/app/src/processing/app/macosx/ThinkDifferent.java
@@ -23,6 +23,7 @@ package processing.app.macosx;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
@@ -30,6 +31,7 @@ import javax.swing.JMenuItem;
import processing.app.Base;
import processing.app.Toolkit;
+import processing.core.PApplet;
import com.apple.eawt.*;
@@ -77,21 +79,28 @@ public class ThinkDifferent implements ApplicationListener {
// JavaSE6_AppleExtensionsRef/api/com/apple/eawt/Application.html
// Only available since Java for Mac OS X 10.6 Update 1, and
// Java for Mac OS X 10.5 Update 6, so need to load this dynamically
- try {
- // com.apple.eawt.Application.setDefaultMenuBar(JMenuBar)
- Class> appClass = Application.class;
- Method method =
- appClass.getMethod("setDefaultMenuBar", new Class[] { JMenuBar.class });
- if (method != null) {
- JMenuBar defaultMenuBar = new JMenuBar();
- JMenu fileMenu = buildFileMenu(base);
- defaultMenuBar.add(fileMenu);
- method.invoke(application, new Object[] { defaultMenuBar });
- // This is kind of a gross way to do this, but the alternatives? Hrm.
- Base.defaultFileMenu = fileMenu;
+ if (PApplet.javaVersion <= 1.6f) { // doesn't work on Oracle's Java
+ try {
+ // com.apple.eawt.Application.setDefaultMenuBar(JMenuBar)
+ Class> appClass = Application.class;
+ Method method =
+ appClass.getMethod("setDefaultMenuBar", new Class[] { JMenuBar.class });
+ if (method != null) {
+ JMenuBar defaultMenuBar = new JMenuBar();
+ JMenu fileMenu = buildFileMenu(base);
+ defaultMenuBar.add(fileMenu);
+ method.invoke(application, new Object[] { defaultMenuBar });
+ // This is kind of a gross way to do this, but the alternatives? Hrm.
+ Base.defaultFileMenu = fileMenu;
+ }
+ } catch (InvocationTargetException ite) {
+ ite.getTargetException().printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace(); // oh well nevermind
}
- } catch (Exception e) {
- e.printStackTrace(); // oh well nevermind
+ } else {
+ // http://java.net/jira/browse/MACOSX_PORT-775?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel
+ System.out.println("Skipping default menu bar due to apparent Oracle Java bug.");
}
}
diff --git a/app/src/processing/app/syntax/PdeTextAreaDefaults.java b/app/src/processing/app/syntax/PdeTextAreaDefaults.java
index 1cde60428..4eac90358 100644
--- a/app/src/processing/app/syntax/PdeTextAreaDefaults.java
+++ b/app/src/processing/app/syntax/PdeTextAreaDefaults.java
@@ -33,21 +33,22 @@ public class PdeTextAreaDefaults extends TextAreaDefaults {
inputHandler = new DefaultInputHandler();
//inputHandler.addDefaultKeyBindings(); // 0122
- // use option on mac for text edit controls that are ctrl on windows/linux
+ // Use option on mac for text edit controls that are ctrl on Windows/Linux.
+ // (i.e. ctrl-left/right is option-left/right on OS X)
String mod = Base.isMacOS() ? "A" : "C";
// right now, ctrl-up/down is select up/down, but mod should be
// used instead, because the mac expects it to be option(alt)
inputHandler.addKeyBinding("BACK_SPACE", InputHandler.BACKSPACE);
- // for 0122, shift-backspace is delete, for 0176, it's now a preference,
+ // for 0122, shift-backspace is delete, for 0176, it's now a preference,
// to prevent holy warriors from attacking me for it.
if (Preferences.getBoolean("editor.keys.shift_backspace_is_delete")) {
inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.DELETE);
} else {
inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.BACKSPACE);
}
-
+
inputHandler.addKeyBinding("DELETE", InputHandler.DELETE);
inputHandler.addKeyBinding("S+DELETE", InputHandler.DELETE);
@@ -60,9 +61,9 @@ public class PdeTextAreaDefaults extends TextAreaDefaults {
//inputHandler.addKeyBinding("TAB", InputHandler.INSERT_TAB);
inputHandler.addKeyBinding("INSERT", InputHandler.OVERWRITE);
-
+
// http://dev.processing.org/bugs/show_bug.cgi?id=162
- // added for 0176, though the bindings do not appear relevant for osx
+ // added for 0176, though the bindings do not appear relevant for osx
if (Preferences.getBoolean("editor.keys.alternative_cut_copy_paste")) {
inputHandler.addKeyBinding("C+INSERT", InputHandler.CLIPBOARD_COPY);
inputHandler.addKeyBinding("S+INSERT", InputHandler.CLIPBOARD_PASTE);
@@ -93,6 +94,12 @@ public class PdeTextAreaDefaults extends TextAreaDefaults {
inputHandler.addKeyBinding("CS+END", InputHandler.SELECT_DOC_END);
}
+ if (Base.isMacOS()) {
+ //inputHandler.addKeyBinding("C+A", InputHandler.HOME);
+ //inputHandler.addKeyBinding("C+E", InputHandler.END);
+ //inputHandler.addKeyBinding("C+D", InputHandler.DELETE); // option-delete (fwd)
+ }
+
if (Base.isMacOS()) {
inputHandler.addKeyBinding("M+LEFT", InputHandler.HOME);
inputHandler.addKeyBinding("M+RIGHT", InputHandler.END);
diff --git a/app/src/processing/app/tools/CreateFont.java b/app/src/processing/app/tools/CreateFont.java
index f6b29c926..d5cea13ba 100644
--- a/app/src/processing/app/tools/CreateFont.java
+++ b/app/src/processing/app/tools/CreateFont.java
@@ -52,7 +52,7 @@ public class CreateFont extends JFrame implements Tool {
Editor editor;
//Sketch sketch;
- Dimension windowSize;
+// Dimension windowSize;
JList fontSelector;
JTextField sizeSelector;
@@ -242,19 +242,20 @@ public class CreateFont extends JFrame implements Tool {
Toolkit.registerWindowCloseKeys(root, disposer);
Toolkit.setIcon(this);
- setResizable(false);
pack();
+ setResizable(false);
+ //System.out.println(getPreferredSize());
// do this after pack so it doesn't affect layout
sample.setFont(new Font(list[0], Font.PLAIN, 48));
fontSelector.setSelectedIndex(0);
- Dimension screen = Toolkit.getScreenSize();
- windowSize = getSize();
-
- setLocation((screen.width - windowSize.width) / 2,
- (screen.height - windowSize.height) / 2);
+// Dimension screen = Toolkit.getScreenSize();
+// windowSize = getSize();
+// setLocation((screen.width - windowSize.width) / 2,
+// (screen.height - windowSize.height) / 2);
+ setLocationRelativeTo(null);
// create this behind the scenes
charSelector = new CharacterSelector();
@@ -352,17 +353,17 @@ public class CreateFont extends JFrame implements Tool {
}
- /**
- * make the window vertically resizable
- */
- public Dimension getMaximumSize() {
- return new Dimension(windowSize.width, 2000);
- }
-
-
- public Dimension getMinimumSize() {
- return windowSize;
- }
+// /**
+// * make the window vertically resizable
+// */
+// public Dimension getMaximumSize() {
+// return new Dimension(windowSize.width, 2000);
+// }
+//
+//
+// public Dimension getMinimumSize() {
+// return windowSize;
+// }
/*
diff --git a/app/src/processing/mode/java/runner/Runner.java b/app/src/processing/mode/java/runner/Runner.java
index f082b945b..e2cf52a83 100644
--- a/app/src/processing/mode/java/runner/Runner.java
+++ b/app/src/processing/mode/java/runner/Runner.java
@@ -128,6 +128,8 @@ public class Runner implements MessageConsumer {
vm = launchVirtualMachine(machineParamList, sketchParamList);
if (vm != null) {
generateTrace(null);
+ //redirectStreams(vm);
+
// try {
// generateTrace(new PrintWriter("/Users/fry/Desktop/output.txt"));
// } catch (Exception e) {
@@ -352,6 +354,7 @@ public class Runner implements MessageConsumer {
// OS X at this point, because we require 10.6.8 and higher. That also
// means we don't need to check for any other OS versions, unless
// is a douchebag and modifies Info.plist to get around the restriction.
+ addr = "" + (8000 + (int) (Math.random() * 1000));
commandArgs =
"/usr/libexec/java_home " +
(System.getProperty("os.version").startsWith("10.6") ? "" : "--request ") +
@@ -453,6 +456,20 @@ public class Runner implements MessageConsumer {
}
+// /**
+// * 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(vm.process().getErrorStream(), this);
+// errThread = ms.getThread();
+// outThread = new StreamRedirectThread("VM output reader", vm.process().getInputStream(), System.out);
+// errThread.start();
+// outThread.start();
+// }
+
+
/**
* Generate the trace.
* Enable events, start thread to display events,
@@ -465,8 +482,8 @@ public class Runner implements MessageConsumer {
EventThread eventThread = null;
//if (writer != null) {
eventThread = new EventThread(this, vm, excludes, writer);
- eventThread.setEventRequests(watchFields);
eventThread.start();
+ eventThread.setEventRequests(watchFields);
//}
//redirectOutput();
@@ -521,6 +538,8 @@ public class Runner implements MessageConsumer {
protected Connector findConnector(String connectorName) {
List connectors = Bootstrap.virtualMachineManager().allConnectors();
+// List connectors =
+// org.eclipse.jdi.Bootstrap.virtualMachineManager().allConnectors();
// debug: code to list available connectors
// Iterator iter2 = connectors.iterator();
@@ -529,9 +548,13 @@ public class Runner implements MessageConsumer {
// System.out.println("connector name is " + connector.name());
// }
- Iterator iter = connectors.iterator();
- while (iter.hasNext()) {
- Connector connector = (Connector)iter.next();
+ for (Object c : connectors) {
+ Connector connector = (Connector) c;
+// System.out.println(connector.name());
+// }
+// Iterator iter = connectors.iterator();
+// while (iter.hasNext()) {
+// Connector connector = (Connector)iter.next();
if (connector.name().equals(connectorName)) {
return connector;
}
diff --git a/app/src/processing/mode/java2/ArrayFieldNode.java b/app/src/processing/mode/java2/ArrayFieldNode.java
new file mode 100755
index 000000000..0037a84ec
--- /dev/null
+++ b/app/src/processing/mode/java2/ArrayFieldNode.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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;
+ }
+}
diff --git a/app/src/processing/mode/java2/ClassLoadListener.java b/app/src/processing/mode/java2/ClassLoadListener.java
new file mode 100755
index 000000000..03c474e15
--- /dev/null
+++ b/app/src/processing/mode/java2/ClassLoadListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+public interface ClassLoadListener {
+
+ /**
+ * Event handler called when a class is loaded.
+ *
+ * @param theClass the class
+ */
+ public void classLoaded(ReferenceType theClass);
+}
diff --git a/app/src/processing/mode/java2/Compiler.java b/app/src/processing/mode/java2/Compiler.java
new file mode 100755
index 000000000..ae1c9c09f
--- /dev/null
+++ b/app/src/processing/mode/java2/Compiler.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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 StringBuffer
+ final StringBuffer errorBuffer = new StringBuffer();
+
+ // 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;
+ }
+}
diff --git a/app/src/processing/mode/java2/DebugBuild.java b/app/src/processing/mode/java2/DebugBuild.java
new file mode 100755
index 000000000..7ef1b38fa
--- /dev/null
+++ b/app/src/processing/mode/java2/DebugBuild.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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 DebugMode getMode() {
+ return (DebugMode)mode;
+ }
+}
diff --git a/app/src/processing/mode/java2/DebugEditor.java b/app/src/processing/mode/java2/DebugEditor.java
new file mode 100755
index 000000000..9a96bc544
--- /dev/null
+++ b/app/src/processing/mode/java2/DebugEditor.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.text.Document;
+import processing.app.*;
+import processing.app.syntax.JEditTextArea;
+import processing.app.syntax.PdeTextAreaDefaults;
+import processing.core.PApplet;
+import processing.mode.java.JavaEditor;
+
+/**
+ * Main View Class. Handles the editor window including tool bar and menu. Has
+ * access to the Sketch. Provides line highlighting (for breakpoints and the
+ * debuggers current line).
+ *
+ * @author Martin Leopold
+ */
+public class DebugEditor extends JavaEditor implements ActionListener {
+ // important fields from superclass
+ //protected Sketch sketch;
+ //private JMenu fileMenu;
+ //protected EditorToolbar toolbar;
+
+ // highlighting
+ protected Color breakpointColor = new Color(240, 240, 240); // the background color for highlighting lines
+ protected Color currentLineColor = new Color(255, 255, 150); // the background color for highlighting lines
+ protected Color breakpointMarkerColor = new Color(74, 84, 94); // the color of breakpoint gutter markers
+ protected Color currentLineMarkerColor = new Color(226, 117, 0); // the color of current line gutter markers
+ protected List breakpointedLines = new ArrayList(); // breakpointed lines
+ protected LineHighlight currentLine; // line the debugger is currently suspended at
+ protected final String breakpointMarkerComment = " //<>//"; // breakpoint marker comment
+ // menus
+ protected JMenu debugMenu; // the debug menu
+ // debugger control
+ protected JMenuItem debugMenuItem;
+ protected JMenuItem continueMenuItem;
+ protected JMenuItem stopMenuItem;
+ // breakpoints
+ protected JMenuItem toggleBreakpointMenuItem;
+ protected JMenuItem listBreakpointsMenuItem;
+ // stepping
+ protected JMenuItem stepOverMenuItem;
+ protected JMenuItem stepIntoMenuItem;
+ protected JMenuItem stepOutMenuItem;
+ // info
+ protected JMenuItem printStackTraceMenuItem;
+ protected JMenuItem printLocalsMenuItem;
+ protected JMenuItem printThisMenuItem;
+ protected JMenuItem printSourceMenuItem;
+ protected JMenuItem printThreads;
+ // variable inspector
+ protected JMenuItem toggleVariableInspectorMenuItem;
+ // references
+ protected DebugMode dmode; // the mode
+ protected Debugger dbg; // the debugger
+ protected VariableInspector vi; // the variable inspector frame
+ protected TextArea ta; // the text area
+
+ public DebugEditor(Base base, String path, EditorState state, Mode mode) {
+ super(base, path, state, mode);
+
+ // get mode
+ dmode = (DebugMode) mode;
+
+ // init controller class
+ dbg = new Debugger(this);
+
+ // variable inspector window
+ vi = new VariableInspector(this);
+
+ // access to customized (i.e. subclassed) text area
+ ta = (TextArea) textarea;
+
+ // set action on frame close
+// addWindowListener(new WindowAdapter() {
+// @Override
+// public void windowClosing(WindowEvent e) {
+// onWindowClosing(e);
+// }
+// });
+
+ // load settings from theme.txt
+ DebugMode theme = dmode;
+ breakpointColor = theme.loadColorFromTheme("breakpoint.bgcolor", breakpointColor);
+ breakpointMarkerColor = theme.loadColorFromTheme("breakpoint.marker.color", breakpointMarkerColor);
+ currentLineColor = theme.loadColorFromTheme("currentline.bgcolor", currentLineColor);
+ currentLineMarkerColor = theme.loadColorFromTheme("currentline.marker.color", currentLineMarkerColor);
+
+ // set breakpoints from marker comments
+ for (LineID lineID : stripBreakpointComments()) {
+ //System.out.println("setting: " + lineID);
+ dbg.setBreakpoint(lineID);
+ }
+ getSketch().setModified(false); // setting breakpoints will flag sketch as modified, so override this here
+ }
+
+// /**
+// * Event handler called when closing the editor window. Kills the variable
+// * inspector window.
+// *
+// * @param e the event object
+// */
+// protected void onWindowClosing(WindowEvent e) {
+// // remove var.inspector
+// vi.dispose();
+// // quit running debug session
+// dbg.stopDebug();
+// }
+ /**
+ * Used instead of the windowClosing event handler, since it's not called on
+ * mode switch. Called when closing the editor window. Stops running debug
+ * sessions and kills the variable inspector window.
+ */
+ @Override
+ public void dispose() {
+ //System.out.println("window dispose");
+ // quit running debug session
+ dbg.stopDebug();
+ // remove var.inspector
+ vi.dispose();
+ // original dispose
+ super.dispose();
+ }
+
+ /**
+ * Overrides sketch menu creation to change keyboard shortcuts from "Run".
+ *
+ * @return the sketch menu
+ */
+ @Override
+ public JMenu buildSketchMenu() {
+ JMenuItem runItem = Toolkit.newJMenuItemShift(DebugToolbar.getTitle(DebugToolbar.RUN, false), KeyEvent.VK_R);
+ runItem.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleRun();
+ }
+ });
+
+ JMenuItem presentItem = new JMenuItem(DebugToolbar.getTitle(DebugToolbar.RUN, true));
+ presentItem.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handlePresent();
+ }
+ });
+
+ JMenuItem stopItem = new JMenuItem(DebugToolbar.getTitle(DebugToolbar.STOP, false));
+ stopItem.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ handleStop();
+ }
+ });
+ return buildSketchMenu(new JMenuItem[]{runItem, presentItem, stopItem});
+ }
+
+ /**
+ * Creates the debug menu. Includes ActionListeners for the menu items.
+ * Intended for adding to the menu bar.
+ *
+ * @return The debug menu
+ */
+ protected JMenu buildDebugMenu() {
+ debugMenu = new JMenu("Debug");
+
+ debugMenuItem = Toolkit.newJMenuItem("Debug", KeyEvent.VK_R);
+ debugMenuItem.addActionListener(this);
+ continueMenuItem = Toolkit.newJMenuItem("Continue", KeyEvent.VK_U);
+ continueMenuItem.addActionListener(this);
+ stopMenuItem = new JMenuItem("Stop");
+ stopMenuItem.addActionListener(this);
+
+ toggleBreakpointMenuItem = Toolkit.newJMenuItem("Toggle Breakpoint", KeyEvent.VK_B);
+ toggleBreakpointMenuItem.addActionListener(this);
+ listBreakpointsMenuItem = new JMenuItem("List Breakpoints");
+ listBreakpointsMenuItem.addActionListener(this);
+
+ stepOverMenuItem = Toolkit.newJMenuItem("Step", KeyEvent.VK_J);
+ stepOverMenuItem.addActionListener(this);
+ stepIntoMenuItem = Toolkit.newJMenuItemShift("Step Into", KeyEvent.VK_J);
+ stepIntoMenuItem.addActionListener(this);
+ stepOutMenuItem = Toolkit.newJMenuItemAlt("Step Out", KeyEvent.VK_J);
+ stepOutMenuItem.addActionListener(this);
+
+ printStackTraceMenuItem = new JMenuItem("Print Stack Trace");
+ printStackTraceMenuItem.addActionListener(this);
+ printLocalsMenuItem = new JMenuItem("Print Locals");
+ printLocalsMenuItem.addActionListener(this);
+ printThisMenuItem = new JMenuItem("Print Fields");
+ printThisMenuItem.addActionListener(this);
+ printSourceMenuItem = new JMenuItem("Print Source Location");
+ printSourceMenuItem.addActionListener(this);
+ printThreads = new JMenuItem("Print Threads");
+ printThreads.addActionListener(this);
+
+ toggleVariableInspectorMenuItem = Toolkit.newJMenuItem("Toggle Variable Inspector", KeyEvent.VK_I);
+ toggleVariableInspectorMenuItem.addActionListener(this);
+
+ debugMenu.add(debugMenuItem);
+ debugMenu.add(continueMenuItem);
+ debugMenu.add(stopMenuItem);
+ debugMenu.addSeparator();
+ debugMenu.add(toggleBreakpointMenuItem);
+ debugMenu.add(listBreakpointsMenuItem);
+ debugMenu.addSeparator();
+ debugMenu.add(stepOverMenuItem);
+ debugMenu.add(stepIntoMenuItem);
+ debugMenu.add(stepOutMenuItem);
+ debugMenu.addSeparator();
+ debugMenu.add(printStackTraceMenuItem);
+ debugMenu.add(printLocalsMenuItem);
+ debugMenu.add(printThisMenuItem);
+ debugMenu.add(printSourceMenuItem);
+ debugMenu.add(printThreads);
+ debugMenu.addSeparator();
+ debugMenu.add(toggleVariableInspectorMenuItem);
+ return debugMenu;
+ }
+
+ @Override
+ public JMenu buildModeMenu() {
+ return buildDebugMenu();
+ }
+
+ /**
+ * Callback for menu items. Implementation of Swing ActionListener.
+ *
+ * @param ae Action event
+ */
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ //System.out.println("ActionEvent: " + ae.toString());
+
+ JMenuItem source = (JMenuItem) ae.getSource();
+ if (source == debugMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Debug' menu item");
+ //dmode.handleDebug(sketch, this);
+ dbg.startDebug();
+ } else if (source == stopMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Stop' menu item");
+ //dmode.handleDebug(sketch, this);
+ dbg.stopDebug();
+ } else if (source == continueMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Continue' menu item");
+ //dmode.handleDebug(sketch, this);
+ dbg.continueDebug();
+ } else if (source == stepOverMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Step Over' menu item");
+ dbg.stepOver();
+ } else if (source == stepIntoMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Step Into' menu item");
+ dbg.stepInto();
+ } else if (source == stepOutMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Step Out' menu item");
+ dbg.stepOut();
+ } else if (source == printStackTraceMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Print Stack Trace' menu item");
+ dbg.printStackTrace();
+ } else if (source == printLocalsMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Print Locals' menu item");
+ dbg.printLocals();
+ } else if (source == printThisMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Print This' menu item");
+ dbg.printThis();
+ } else if (source == printSourceMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Print Source' menu item");
+ dbg.printSource();
+ } else if (source == printThreads) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Print Threads' menu item");
+ dbg.printThreads();
+ } else if (source == toggleBreakpointMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Toggle Breakpoint' menu item");
+ dbg.toggleBreakpoint();
+ } else if (source == listBreakpointsMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'List Breakpoints' menu item");
+ dbg.listBreakpoints();
+ } else if (source == toggleVariableInspectorMenuItem) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.INFO, "Invoked 'Toggle Variable Inspector' menu item");
+ toggleVariableInspector();
+ }
+ }
+
+// @Override
+// public void handleRun() {
+// dbg.continueDebug();
+// }
+ /**
+ * Event handler called when hitting the stop button. Stops a running debug
+ * session or performs standard stop action if not currently debugging.
+ */
+ @Override
+ public void handleStop() {
+ if (dbg.isStarted()) {
+ dbg.stopDebug();
+ } else {
+ super.handleStop();
+ }
+ }
+
+ /**
+ * Event handler called when loading another sketch in this editor. Clears
+ * breakpoints of previous sketch.
+ *
+ * @param path
+ * @return true if a sketch was opened, false if aborted
+ */
+ @Override
+ protected boolean handleOpenInternal(String path) {
+ boolean didOpen = super.handleOpenInternal(path);
+ if (didOpen && dbg != null) {
+ // should already been stopped (open calls handleStop)
+ dbg.clearBreakpoints();
+ clearBreakpointedLines(); // force clear breakpoint highlights
+ variableInspector().reset(); // clear contents of variable inspector
+ }
+ return didOpen;
+ }
+
+ /**
+ * Extract breakpointed lines from source code marker comments. This removes
+ * marker comments from the editor text. Intended to be called on loading a
+ * sketch, since re-setting the sketches contents after removing the markers
+ * will clear all breakpoints.
+ *
+ * @return the list of {@link LineID}s where breakpoint marker comments were
+ * removed from.
+ */
+ protected List stripBreakpointComments() {
+ List bps = new ArrayList();
+ // iterate over all tabs
+ Sketch sketch = getSketch();
+ for (int i = 0; i < sketch.getCodeCount(); i++) {
+ SketchCode tab = sketch.getCode(i);
+ String code = tab.getProgram();
+ String lines[] = code.split("\\r?\\n"); // newlines not included
+ //System.out.println(code);
+
+ // scan code for breakpoint comments
+ int lineIdx = 0;
+ for (String line : lines) {
+ //System.out.println(line);
+ if (line.endsWith(breakpointMarkerComment)) {
+ LineID lineID = new LineID(tab.getFileName(), lineIdx);
+ bps.add(lineID);
+ //System.out.println("found breakpoint: " + lineID);
+ // got a breakpoint
+ //dbg.setBreakpoint(lineID);
+ int index = line.lastIndexOf(breakpointMarkerComment);
+ lines[lineIdx] = line.substring(0, index);
+ }
+ lineIdx++;
+ }
+ //tab.setProgram(code);
+ code = PApplet.join(lines, "\n");
+ setTabContents(tab.getFileName(), code);
+ }
+ return bps;
+ }
+
+ /**
+ * Add breakpoint marker comments to the source file of a specific tab. This
+ * acts on the source file on disk, not the editor text. Intended to be
+ * called just after saving the sketch.
+ *
+ * @param tabFilename the tab file name
+ */
+ protected void addBreakpointComments(String tabFilename) {
+ SketchCode tab = getTab(tabFilename);
+ List bps = dbg.getBreakpoints(tab.getFileName());
+
+ // load the source file
+ File sourceFile = new File(sketch.getFolder(), tab.getFileName());
+ //System.out.println("file: " + sourceFile);
+ try {
+ String code = Base.loadFile(sourceFile);
+ //System.out.println("code: " + code);
+ String lines[] = code.split("\\r?\\n"); // newlines not included
+ for (LineBreakpoint bp : bps) {
+ //System.out.println("adding bp: " + bp.lineID());
+ lines[bp.lineID().lineIdx()] += breakpointMarkerComment;
+ }
+ code = PApplet.join(lines, "\n");
+ //System.out.println("new code: " + code);
+ Base.saveFile(code, sourceFile);
+ } catch (IOException ex) {
+ Logger.getLogger(DebugEditor.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ public boolean handleSave(boolean immediately) {
+ //System.out.println("handleSave " + immediately);
+
+ // note modified tabs
+ final List modified = new ArrayList();
+ for (int i = 0; i < getSketch().getCodeCount(); i++) {
+ SketchCode tab = getSketch().getCode(i);
+ if (tab.isModified()) {
+ modified.add(tab.getFileName());
+ }
+ }
+
+ boolean saved = super.handleSave(immediately);
+ if (saved) {
+ if (immediately) {
+ for (String tabFilename : modified) {
+ addBreakpointComments(tabFilename);
+ }
+ } else {
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ for (String tabFilename : modified) {
+ addBreakpointComments(tabFilename);
+ }
+ }
+ });
+ }
+ }
+ return saved;
+ }
+
+ @Override
+ public boolean handleSaveAs() {
+ //System.out.println("handleSaveAs");
+ String oldName = getSketch().getCode(0).getFileName();
+ //System.out.println("old name: " + oldName);
+ boolean saved = super.handleSaveAs();
+ if (saved) {
+ // re-set breakpoints in first tab (name has changed)
+ List bps = dbg.getBreakpoints(oldName);
+ dbg.clearBreakpoints(oldName);
+ String newName = getSketch().getCode(0).getFileName();
+ //System.out.println("new name: " + newName);
+ for (LineBreakpoint bp : bps) {
+ LineID line = new LineID(newName, bp.lineID().lineIdx());
+ //System.out.println("setting: " + line);
+ dbg.setBreakpoint(line);
+ }
+ // add breakpoint marker comments to source file
+ for (int i = 0; i < getSketch().getCodeCount(); i++) {
+ addBreakpointComments(getSketch().getCode(i).getFileName());
+ }
+
+ // set new name of variable inspector
+ vi.setTitle(getSketch().getName());
+ }
+ return saved;
+ }
+
+ /**
+ * Set text contents of a specific tab. Updates underlying document and text
+ * area. Clears Breakpoints.
+ *
+ * @param tabFilename the tab file name
+ * @param code the text to set
+ */
+ protected void setTabContents(String tabFilename, String code) {
+ // remove all breakpoints of this tab
+ dbg.clearBreakpoints(tabFilename);
+
+ SketchCode currentTab = getCurrentTab();
+
+ // set code of tab
+ SketchCode tab = getTab(tabFilename);
+ if (tab != null) {
+ tab.setProgram(code);
+ // this updates document and text area
+ // TODO: does this have any negative effects? (setting the doc to null)
+ tab.setDocument(null);
+ setCode(tab);
+
+ // switch back to original tab
+ setCode(currentTab);
+ }
+ }
+
+ /**
+ * Clear the console.
+ */
+ public void clearConsole() {
+ console.clear();
+ }
+
+ /**
+ * Clear current text selection.
+ */
+ public void clearSelection() {
+ setSelection(getCaretOffset(), getCaretOffset());
+ }
+
+ /**
+ * Select a line in the current tab.
+ *
+ * @param lineIdx 0-based line number
+ */
+ public void selectLine(int lineIdx) {
+ setSelection(getLineStartOffset(lineIdx), getLineStopOffset(lineIdx));
+ }
+
+ /**
+ * Set the cursor to the start of a line.
+ *
+ * @param lineIdx 0-based line number
+ */
+ public void cursorToLineStart(int lineIdx) {
+ setSelection(getLineStartOffset(lineIdx), getLineStartOffset(lineIdx));
+ }
+
+ /**
+ * Set the cursor to the end of a line.
+ *
+ * @param lineIdx 0-based line number
+ */
+ public void cursorToLineEnd(int lineIdx) {
+ setSelection(getLineStopOffset(lineIdx), getLineStopOffset(lineIdx));
+ }
+
+ /**
+ * Switch to a tab.
+ *
+ * @param tabFileName the file name identifying the tab. (as in
+ * {@link SketchCode#getFileName()})
+ */
+ public void switchToTab(String tabFileName) {
+ Sketch s = getSketch();
+ for (int i = 0; i < s.getCodeCount(); i++) {
+ if (tabFileName.equals(s.getCode(i).getFileName())) {
+ s.setCurrentCode(i);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Access the debugger.
+ *
+ * @return the debugger controller object
+ */
+ public Debugger dbg() {
+ return dbg;
+ }
+
+ /**
+ * Access the mode.
+ *
+ * @return the mode object
+ */
+ public DebugMode mode() {
+ return dmode;
+ }
+
+ /**
+ * Access the custom text area object.
+ *
+ * @return the text area object
+ */
+ public TextArea textArea() {
+ return ta;
+ }
+
+ /**
+ * Access variable inspector window.
+ *
+ * @return the variable inspector object
+ */
+ public VariableInspector variableInspector() {
+ return vi;
+ }
+
+ public DebugToolbar toolbar() {
+ return (DebugToolbar) toolbar;
+ }
+
+ /**
+ * Show the variable inspector window.
+ */
+ public void showVariableInspector() {
+ vi.setVisible(true);
+ }
+
+ /**
+ * Set visibility of the variable inspector window.
+ *
+ * @param visible true to set the variable inspector visible, false for
+ * invisible.
+ */
+ public void showVariableInspector(boolean visible) {
+ vi.setVisible(visible);
+ }
+
+ /**
+ * Hide the variable inspector window.
+ */
+ public void hideVariableInspector() {
+ vi.setVisible(true);
+ }
+
+ /**
+ * Toggle visibility of the variable inspector window.
+ */
+ public void toggleVariableInspector() {
+ vi.setFocusableWindowState(false); // to not get focus when set visible
+ vi.setVisible(!vi.isVisible());
+ vi.setFocusableWindowState(true); // allow to get focus again
+ }
+
+ /**
+ * Text area factory method. Instantiates the customized TextArea.
+ *
+ * @return the customized text area object
+ */
+ @Override
+ protected JEditTextArea createTextArea() {
+ //System.out.println("overriding creation of text area");
+ return new TextArea(new PdeTextAreaDefaults(mode), this);
+ }
+
+ /**
+ * Set the line to highlight as currently suspended at. Will override the
+ * breakpoint color, if set. Switches to the appropriate tab and scroll to
+ * the line by placing the cursor there.
+ *
+ * @param line the line to highlight as current suspended line
+ */
+ public void setCurrentLine(LineID line) {
+ clearCurrentLine();
+ if (line == null) {
+ return; // safety, e.g. when no line mapping is found and the null line is used.
+ }
+ switchToTab(line.fileName());
+ // scroll to line, by setting the cursor
+ cursorToLineStart(line.lineIdx());
+ // highlight line
+ currentLine = new LineHighlight(line.lineIdx(), currentLineColor, this);
+ currentLine.setMarker(ta.currentLineMarker, currentLineMarkerColor);
+ currentLine.setPriority(10); // fixes current line being hidden by the breakpoint when moved down
+ }
+
+ /**
+ * Clear the highlight for the debuggers current line.
+ */
+ public void clearCurrentLine() {
+ if (currentLine != null) {
+ currentLine.clear();
+ currentLine.dispose();
+
+ // revert to breakpoint color if any is set on this line
+ for (LineHighlight hl : breakpointedLines) {
+ if (hl.lineID().equals(currentLine.lineID())) {
+ hl.paint();
+ break;
+ }
+ }
+ currentLine = null;
+ }
+ }
+
+ /**
+ * Add highlight for a breakpointed line.
+ *
+ * @param lineID the line id to highlight as breakpointed
+ */
+ public void addBreakpointedLine(LineID lineID) {
+ LineHighlight hl = new LineHighlight(lineID, breakpointColor, this);
+ hl.setMarker(ta.breakpointMarker, breakpointMarkerColor);
+ breakpointedLines.add(hl);
+ // repaint current line if it's on this line
+ if (currentLine != null && currentLine.lineID().equals(lineID)) {
+ currentLine.paint();
+ }
+ }
+
+ /**
+ * Add highlight for a breakpointed line on the current tab.
+ *
+ * @param lineIdx the line index on the current tab to highlight as
+ * breakpointed
+ */
+ //TODO: remove and replace by {@link #addBreakpointedLine(LineID lineID)}
+ public void addBreakpointedLine(int lineIdx) {
+ addBreakpointedLine(getLineIDInCurrentTab(lineIdx));
+ }
+
+ /**
+ * Remove a highlight for a breakpointed line. Needs to be on the current
+ * tab.
+ *
+ * @param lineIdx the line index on the current tab to remove a breakpoint
+ * highlight from
+ */
+ public void removeBreakpointedLine(int lineIdx) {
+ LineID line = getLineIDInCurrentTab(lineIdx);
+ //System.out.println("line id: " + line.fileName() + " " + line.lineIdx());
+ LineHighlight foundLine = null;
+ for (LineHighlight hl : breakpointedLines) {
+ if (hl.lineID.equals(line)) {
+ foundLine = hl;
+ break;
+ }
+ }
+ if (foundLine != null) {
+ foundLine.clear();
+ breakpointedLines.remove(foundLine);
+ foundLine.dispose();
+ // repaint current line if it's on this line
+ if (currentLine != null && currentLine.lineID().equals(line)) {
+ currentLine.paint();
+ }
+ }
+ }
+
+ /**
+ * Remove all highlights for breakpointed lines.
+ */
+ public void clearBreakpointedLines() {
+ for (LineHighlight hl : breakpointedLines) {
+ hl.clear();
+ hl.dispose();
+ }
+ breakpointedLines.clear(); // remove all breakpoints
+ // fix highlights not being removed when tab names have changed due to opening a new sketch in same editor
+ ta.clearLineBgColors(); // force clear all highlights
+ ta.clearGutterText();
+
+ // repaint current line
+ if (currentLine != null) {
+ currentLine.paint();
+ }
+ }
+
+ /**
+ * Retrieve a {@link LineID} object for a line on the current tab.
+ *
+ * @param lineIdx the line index on the current tab
+ * @return the {@link LineID} object representing a line index on the
+ * current tab
+ */
+ public LineID getLineIDInCurrentTab(int lineIdx) {
+ return new LineID(getSketch().getCurrentCode().getFileName(), lineIdx);
+ }
+
+ /**
+ * Retrieve line of sketch where the cursor currently resides.
+ *
+ * @return the current {@link LineID}
+ */
+ protected LineID getCurrentLineID() {
+ String tab = getSketch().getCurrentCode().getFileName();
+ int lineNo = getTextArea().getCaretLine();
+ return new LineID(tab, lineNo);
+ }
+
+ /**
+ * Check whether a {@link LineID} is on the current tab.
+ *
+ * @param line the {@link LineID}
+ * @return true, if the {@link LineID} is on the current tab.
+ */
+ public boolean isInCurrentTab(LineID line) {
+ return line.fileName().equals(getSketch().getCurrentCode().getFileName());
+ }
+
+ /**
+ * Event handler called when switching between tabs. Loads all line
+ * background colors set for the tab.
+ *
+ * @param code tab to switch to
+ */
+ @Override
+ protected void setCode(SketchCode code) {
+ //System.out.println("tab switch: " + code.getFileName());
+ super.setCode(code); // set the new document in the textarea, etc. need to do this first
+
+ // set line background colors for tab
+ if (ta != null) { // can be null when setCode is called the first time (in constructor)
+ // clear all line backgrounds
+ ta.clearLineBgColors();
+ // clear all gutter text
+ ta.clearGutterText();
+ // load appropriate line backgrounds for tab
+ // first paint breakpoints
+ for (LineHighlight hl : breakpointedLines) {
+ if (isInCurrentTab(hl.lineID())) {
+ hl.paint();
+ }
+ }
+ // now paint current line (if any)
+ if (currentLine != null) {
+ if (isInCurrentTab(currentLine.lineID())) {
+ currentLine.paint();
+ }
+ }
+ }
+ if (dbg() != null && dbg().isStarted()) {
+ dbg().startTrackingLineChanges();
+ }
+ }
+
+ /**
+ * Get a tab by its file name.
+ *
+ * @param fileName the filename to search for.
+ * @return the {@link SketchCode} object representing the tab, or null if
+ * not found
+ */
+ public SketchCode getTab(String fileName) {
+ Sketch s = getSketch();
+ for (SketchCode c : s.getCode()) {
+ if (c.getFileName().equals(fileName)) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the current tab.
+ *
+ * @return the {@link SketchCode} representing the current tab
+ */
+ public SketchCode getCurrentTab() {
+ return getSketch().getCurrentCode();
+ }
+
+ /**
+ * Access the currently edited document.
+ *
+ * @return the document object
+ */
+ public Document currentDocument() {
+ //return ta.getDocument();
+ return getCurrentTab().getDocument();
+ }
+
+ /**
+ * Factory method for the editor toolbar. Instantiates the customized
+ * toolbar.
+ *
+ * @return the toolbar
+ */
+ @Override
+ public EditorToolbar createToolbar() {
+ return new DebugToolbar(this, base);
+ }
+
+ /**
+ * Event Handler for double clicking in the left hand gutter area.
+ *
+ * @param lineIdx the line (0-based) that was double clicked
+ */
+ public void gutterDblClicked(int lineIdx) {
+ if (dbg != null) {
+ dbg.toggleBreakpoint(lineIdx);
+ }
+ }
+
+ public void statusBusy() {
+ statusNotice("Debugger busy...");
+ }
+
+ public void statusHalted() {
+ statusNotice("Debugger halted.");
+ }
+}
diff --git a/app/src/processing/mode/java2/DebugMode.java b/app/src/processing/mode/java2/DebugMode.java
new file mode 100755
index 000000000..ef0f26c0b
--- /dev/null
+++ b/app/src/processing/mode/java2/DebugMode.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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 processing.app.Base;
+import processing.app.EditorState;
+import processing.app.Mode;
+import processing.mode.java.JavaMode;
+
+/**
+ * Debug Mode for Processing. Built on top of JavaMode.
+ *
+ * @author Martin Leopold
+ */
+public class DebugMode extends JavaMode {
+
+ public static final boolean VERBOSE_LOGGING = true;
+ public static final int LOG_SIZE = 524288; // max log file size (in bytes)
+
+ // important inherited fields:
+ // protected Base base;
+ public DebugMode(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 {
+ File logFile = getContentFile("logs/DebugMode.%g.log");
+ File logFolder = logFile.getParentFile();
+ if (!logFolder.exists()) {
+ logFolder.mkdir();
+ }
+ Handler handler = new FileHandler(logFile.getAbsolutePath(), LOG_SIZE, 10, false);
+ globalLogger.addHandler(handler);
+ } catch (IOException ex) {
+ Logger.getLogger(DebugMode.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (SecurityException ex) {
+ Logger.getLogger(DebugMode.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ // output version from manifest file
+ Package p = DebugMode.class.getPackage();
+ String titleAndVersion = p.getImplementationTitle() + " (v" + p.getImplementationVersion() + ")";
+ //System.out.println(titleAndVersion);
+ Logger.getLogger(DebugMode.class.getName()).log(Level.INFO, titleAndVersion);
+ }
+
+ /**
+ * Return the pretty/printable/menu name for this mode. This is separate
+ * from the single word name of the folder that contains this mode. It could
+ * even have spaces, though that might result in sheer madness or total
+ * mayhem.
+ */
+ @Override
+ public String getTitle() {
+ return "Debug";
+ }
+
+ /**
+ * Create a new editor associated with this mode.
+ */
+ @Override
+ public processing.app.Editor createEditor(Base base, String path, EditorState state) {
+ return new DebugEditor(base, path, state, this);
+ }
+
+ /**
+ * Returns the default extension for this editor setup.
+ */
+ /*
+ * @Override public String getDefaultExtension() { return null; }
+ */
+ /**
+ * Returns a String[] array of proper extensions.
+ */
+ /*
+ * @Override public String[] getExtensions() { return null; }
+ */
+ /**
+ * Get array of file/directory names that needn't be copied during "Save
+ * As".
+ */
+ /*
+ * @Override public String[] getIgnorable() { return null; }
+ */
+ /**
+ * 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 loadStringFromTheme(String attribute, String defaultValue) {
+ String newString = theme.get(attribute);
+ if (newString != null) {
+ return newString;
+ }
+ Logger.getLogger(DebugMode.class.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 loadColorFromTheme(String attribute, Color defaultValue) {
+ Color newColor = theme.getColor(attribute);
+ if (newColor != null) {
+ return newColor;
+ }
+ System.out.println("error loading color: " + attribute);
+ Logger.getLogger(DebugMode.class.getName()).log(Level.WARNING, "Error loading Color: {0}", attribute);
+ return defaultValue;
+ }
+
+ public ClassLoader getJavaModeClassLoader() {
+ //return super.getClassLoader();
+ for (Mode m : base.getModeList()) {
+ if (m.getClass() == JavaMode.class) {
+ JavaMode jMode = (JavaMode) m;
+ return jMode.getClassLoader();
+ }
+ }
+ // badness
+ return null;
+ }
+}
diff --git a/app/src/processing/mode/java2/DebugRunner.java b/app/src/processing/mode/java2/DebugRunner.java
new file mode 100755
index 000000000..8f67b187a
--- /dev/null
+++ b/app/src/processing/mode/java2/DebugRunner.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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();
+ /*
+ * System.out.println("vm launch sketch params:"); for (int i=0;
+ * i
+ *
+ * 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.java2;
+
+import java.awt.Image;
+import java.awt.event.MouseEvent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import processing.app.Base;
+import processing.app.Editor;
+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
+ */
+public class DebugToolbar extends JavaToolbar {
+ // preserve original button id's, but re-define so they are accessible (they are protected)
+
+ static protected final int RUN = 100; // change this, to be able to get it's name via getTitle()
+ 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;
+
+ 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[] buttonSequence = {
+ DEBUG, CONTINUE, STEP, STOP, TOGGLE_BREAKPOINT, TOGGLE_VAR_INSPECTOR, NEW, OPEN, SAVE, EXPORT
+ }; // the sequence of button ids. (this maps button position = index to button ids)
+
+ public DebugToolbar(Editor editor, Base base) {
+ super(editor, base);
+ }
+
+ /**
+ * Initialize buttons. Loads images and adds the buttons to the toolbar.
+ */
+ @Override
+ public void init() {
+ Image[][] images = loadImages();
+ 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);
+ }
+ }
+
+ /**
+ * 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 "Run";
+ } else {
+ return "Debug";
+ }
+ case CONTINUE:
+ return "Continue";
+ case TOGGLE_BREAKPOINT:
+ return "Toggle Breakpoint";
+ case STEP:
+ if (shift) {
+ return "Step Into";
+ } else {
+ return "Step";
+ }
+ case TOGGLE_VAR_INSPECTOR:
+ return "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:
+ 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];
+ }
+}
diff --git a/app/src/processing/mode/java2/Debugger.java b/app/src/processing/mode/java2/Debugger.java
new file mode 100755
index 000000000..5459784a8
--- /dev/null
+++ b/app/src/processing/mode/java2/Debugger.java
@@ -0,0 +1,1367 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import com.sun.jdi.*;
+import com.sun.jdi.event.*;
+import com.sun.jdi.request.BreakpointRequest;
+import com.sun.jdi.request.ClassPrepareRequest;
+import com.sun.jdi.request.EventRequestManager;
+import com.sun.jdi.request.StepRequest;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JTree; // needed for javadocs
+import javax.swing.tree.DefaultMutableTreeNode;
+import processing.app.Sketch;
+import processing.app.SketchCode;
+
+/**
+ * Main controller class for debugging mode. Mainly works with DebugEditor as
+ * the corresponding "view". Uses DebugRunner to launch a VM.
+ *
+ * @author Martin Leopold
+ */
+public class Debugger implements VMEventListener {
+
+ protected DebugEditor editor; // editor window, acting as main view
+ protected DebugRunner runtime; // the runtime, contains debuggee VM
+ protected boolean started = false; // debuggee vm has started, VMStartEvent received, main class loaded
+ protected boolean paused = false; // currently paused at breakpoint or step
+ protected ThreadReference currentThread; // thread the last breakpoint or step occured in
+ protected String mainClassName; // name of the main class that's currently being debugged
+ protected ReferenceType mainClass; // the debuggee's main class
+ protected Set classes = new HashSet(); // holds all loaded classes in the debuggee VM
+ protected List classLoadListeners = new ArrayList(); // listeners for class load events
+ protected String srcPath; // path to the src folder of the current build
+ protected List breakpoints = new ArrayList(); // list of current breakpoints
+ protected StepRequest requestedStep; // the step request we are currently in, or null if not in a step
+ protected Map runtimeLineChanges = new HashMap(); // maps line number changes at runtime (orig -> changed)
+ protected Set runtimeTabsTracked = new HashSet(); // contains tab filenames which already have been tracked for runtime changes
+
+ /**
+ * Construct a Debugger object.
+ *
+ * @param editor The Editor that will act as primary view
+ */
+ public Debugger(DebugEditor editor) {
+ this.editor = editor;
+ }
+
+ /**
+ * Access the VM.
+ *
+ * @return the virtual machine object or null if not available.
+ */
+ public VirtualMachine vm() {
+ if (runtime != null) {
+ return runtime.vm();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Access the editor associated with this debugger.
+ *
+ * @return the editor object
+ */
+ public DebugEditor editor() {
+ return editor;
+ }
+
+ /**
+ * Retrieve the main class of the debuggee VM.
+ *
+ * @return the main classes {@link ReferenceType} or null if the debugger is
+ * not started.
+ */
+ public ReferenceType getMainClass() {
+ if (isStarted()) {
+ return mainClass;
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Get the {@link ReferenceType} for a class name.
+ *
+ * @param name the class name
+ * @return the {@link ReferenceType} or null if not found (e.g. not yet
+ * loaded)
+ */
+ public ReferenceType getClass(String name) {
+ if (name == null) {
+ return null;
+ }
+ if (name.equals(mainClassName)) {
+ return mainClass;
+ }
+ for (ReferenceType rt : classes) {
+ if (rt.name().equals(name)) {
+ return rt;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a class load listener. Will be notified when a class is loaded in the
+ * debuggee VM.
+ *
+ * @param listener the {@link ClassLoadListener}
+ */
+ public void addClassLoadListener(ClassLoadListener listener) {
+ classLoadListeners.add(listener);
+ }
+
+ /**
+ * Remove a class load listener. Cease to be notified when classes are
+ * loaded in the debuggee VM.
+ *
+ * @param listener {@link ClassLoadListener}
+ */
+ public void removeClassLoadListener(ClassLoadListener listener) {
+ classLoadListeners.remove(listener);
+ }
+
+ /**
+ * Start a debugging session. Builds the sketch and launches a VM to run it.
+ * VM starts suspended. Should produce a VMStartEvent.
+ */
+ public synchronized void startDebug() {
+ //stopDebug(); // stop any running sessions
+ if (isStarted()) {
+ return; // do nothing
+ }
+
+ // we are busy now
+ editor.statusBusy();
+
+ // clear console
+ editor.clearConsole();
+
+ // clear variable inspector (also resets expanded states)
+ editor.variableInspector().reset();
+
+ // load edits into sketch obj, etc...
+ editor.prepareRun();
+
+ editor.toolbar().activate(DebugToolbar.DEBUG); // after prepareRun, since this removes highlights
+
+ try {
+ Sketch sketch = editor.getSketch();
+ DebugBuild build = new DebugBuild(sketch);
+
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "building sketch: {0}", sketch.getName());
+ //LineMapping.addLineNumbers(sketch); // annotate
+ mainClassName = build.build(false);
+ //LineMapping.removeLineNumbers(sketch); // annotate
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "class: {0}", mainClassName);
+
+ // folder with assembled/preprocessed src
+ srcPath = build.getSrcFolder().getPath();
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "build src: {0}", srcPath);
+ // folder with compiled code (.class files)
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "build bin: {0}", build.getBinFolder().getPath());
+
+ if (mainClassName != null) {
+ // generate the source line mapping
+ //lineMap = LineMapping.generateMapping(srcPath + File.separator + mainClassName + ".java");
+
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "launching debuggee runtime");
+ runtime = new DebugRunner(build, editor);
+ VirtualMachine vm = runtime.launch(); // non-blocking
+ if (vm == null) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, "error 37: launch failed");
+ }
+
+ // start receiving vm events
+ VMEventReader eventThread = new VMEventReader(vm.eventQueue(), this);
+ eventThread.start();
+
+ //return runtime;
+
+ /*
+ * // launch runner in new thread new Thread(new Runnable() {
+ *
+ * @Override public void run() { runtime.launch(false); // this
+ * blocks until finished } }).start(); return runtime;
+ */
+
+ startTrackingLineChanges();
+ editor.statusBusy();
+ }
+ } catch (Exception e) {
+ editor.statusError(e);
+ }
+ }
+
+ /**
+ * End debugging session. Stops and disconnects VM. Should produce
+ * VMDisconnectEvent.
+ */
+ public synchronized void stopDebug() {
+ editor.variableInspector().lock();
+ if (runtime != null) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "closing runtime");
+ runtime.close();
+ runtime = null;
+ //build = null;
+ classes.clear();
+ // need to clear highlight here because, VMDisconnectedEvent seems to be unreliable. TODO: likely synchronization problem
+ editor.clearCurrentLine();
+ }
+ stopTrackingLineChanges();
+ started = false;
+ editor.toolbar().deactivate(DebugToolbar.DEBUG);
+ editor.toolbar().deactivate(DebugToolbar.CONTINUE);
+ editor.toolbar().deactivate(DebugToolbar.STEP);
+ editor.statusEmpty();
+ }
+
+ /**
+ * Resume paused debugging session. Resumes VM.
+ */
+ public synchronized void continueDebug() {
+ editor.toolbar().activate(DebugToolbar.CONTINUE);
+ editor.variableInspector().lock();
+ //editor.clearSelection();
+ //clearHighlight();
+ editor.clearCurrentLine();
+ if (!isStarted()) {
+ startDebug();
+ } else if (isPaused()) {
+ runtime.vm().resume();
+ paused = false;
+ editor.statusBusy();
+ }
+ }
+
+ /**
+ * Step through source code lines.
+ *
+ * @param stepDepth the step depth ({@link StepRequest#STEP_OVER},
+ * {@link StepRequest#STEP_INTO} or {@link StepRequest#STEP_OUT})
+ */
+ protected void step(int stepDepth) {
+ if (!isStarted()) {
+ startDebug();
+ } else if (isPaused()) {
+ editor.variableInspector().lock();
+ editor.toolbar().activate(DebugToolbar.STEP);
+
+ // use global to mark that there is a step request pending
+ requestedStep = runtime.vm().eventRequestManager().createStepRequest(currentThread, StepRequest.STEP_LINE, stepDepth);
+ requestedStep.addCountFilter(1); // valid for one step only
+ requestedStep.enable();
+ paused = false;
+ runtime.vm().resume();
+ editor.statusBusy();
+ }
+ }
+
+ /**
+ * Step over current statement.
+ */
+ public synchronized void stepOver() {
+ step(StepRequest.STEP_OVER);
+ }
+
+ /**
+ * Step into current statement.
+ */
+ public synchronized void stepInto() {
+ step(StepRequest.STEP_INTO);
+ }
+
+ /**
+ * Step out of current function.
+ */
+ public synchronized void stepOut() {
+ step(StepRequest.STEP_OUT);
+ }
+
+ /**
+ * Print the current stack trace.
+ */
+ public synchronized void printStackTrace() {
+ if (isStarted()) {
+ printStackTrace(currentThread);
+ }
+ }
+
+ /**
+ * Print local variables. Outputs type, name and value of each variable.
+ */
+ public synchronized void printLocals() {
+ if (isStarted()) {
+ printLocalVariables(currentThread);
+ }
+ }
+
+ /**
+ * Print fields of current {@code this}-object. Outputs type, name and value
+ * of each field.
+ */
+ public synchronized void printThis() {
+ if (isStarted()) {
+ printThis(currentThread);
+ }
+ }
+
+ /**
+ * Print a source code snippet of the current location.
+ */
+ public synchronized void printSource() {
+ if (isStarted()) {
+ printSourceLocation(currentThread);
+ }
+ }
+
+ /**
+ * Set a breakpoint on the current line.
+ */
+ public synchronized void setBreakpoint() {
+ setBreakpoint(editor.getCurrentLineID());
+ }
+
+ /**
+ * Set a breakpoint on a line in the current tab.
+ *
+ * @param lineIdx the line index (0-based) of the current tab to set the
+ * breakpoint on
+ */
+ public synchronized void setBreakpoint(int lineIdx) {
+ setBreakpoint(editor.getLineIDInCurrentTab(lineIdx));
+ }
+
+ /**
+ * Set a breakpoint.
+ *
+ * @param line the line id to set the breakpoint on
+ */
+ public synchronized void setBreakpoint(LineID line) {
+ // do nothing if we are kinda busy
+ if (isStarted() && !isPaused()) {
+ return;
+ }
+ // do nothing if there already is a breakpoint on this line
+ if (hasBreakpoint(line)) {
+ return;
+ }
+ breakpoints.add(new LineBreakpoint(line, this));
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "set breakpoint on line {0}", line);
+ }
+
+ /**
+ * Remove a breakpoint from the current line (if set).
+ */
+ public synchronized void removeBreakpoint() {
+ removeBreakpoint(editor.getCurrentLineID().lineIdx());
+ }
+
+ /**
+ * Remove a breakpoint from a line in the current tab.
+ *
+ * @param lineIdx the line index (0-based) in the current tab to remove the
+ * breakpoint from
+ */
+ protected void removeBreakpoint(int lineIdx) {
+ // do nothing if we are kinda busy
+ if (isBusy()) {
+ return;
+ }
+
+ LineBreakpoint bp = breakpointOnLine(editor.getLineIDInCurrentTab(lineIdx));
+ if (bp != null) {
+ bp.remove();
+ breakpoints.remove(bp);
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "removed breakpoint {0}", bp);
+ }
+ }
+
+ /**
+ * Remove all breakpoints.
+ */
+ public synchronized void clearBreakpoints() {
+ //TODO: handle busy-ness correctly
+ if (isBusy()) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.WARNING, "busy");
+ return;
+ }
+
+ for (LineBreakpoint bp : breakpoints) {
+ bp.remove();
+ }
+ breakpoints.clear();
+ }
+
+ /**
+ * Clear breakpoints in a specific tab.
+ *
+ * @param tabFilename the tab's file name
+ */
+ public synchronized void clearBreakpoints(String tabFilename) {
+ //TODO: handle busy-ness correctly
+ if (isBusy()) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.WARNING, "busy");
+ return;
+ }
+
+ Iterator i = breakpoints.iterator();
+ while (i.hasNext()) {
+ LineBreakpoint bp = i.next();
+ if (bp.lineID().fileName().equals(tabFilename)) {
+ bp.remove();
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Get the breakpoint on a certain line, if set.
+ *
+ * @param line the line to get the breakpoint from
+ * @return the breakpoint, or null if no breakpoint is set on the specified
+ * line.
+ */
+ protected LineBreakpoint breakpointOnLine(LineID line) {
+ for (LineBreakpoint bp : breakpoints) {
+ if (bp.isOnLine(line)) {
+ return bp;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Toggle a breakpoint on the current line.
+ */
+ public synchronized void toggleBreakpoint() {
+ toggleBreakpoint(editor.getCurrentLineID().lineIdx());
+ }
+
+ /**
+ * Toggle a breakpoint on a line in the current tab.
+ *
+ * @param lineIdx the line index (0-based) in the current tab
+ */
+ public synchronized void toggleBreakpoint(int lineIdx) {
+ LineID line = editor.getLineIDInCurrentTab(lineIdx);
+ if (!hasBreakpoint(line)) {
+ setBreakpoint(line.lineIdx());
+ } else {
+ removeBreakpoint(line.lineIdx());
+ }
+ }
+
+ /**
+ * Check if there's a breakpoint on a particular line.
+ *
+ * @param line the line id
+ * @return true if a breakpoint is set on the given line, otherwise false
+ */
+ protected boolean hasBreakpoint(LineID line) {
+ LineBreakpoint bp = breakpointOnLine(line);
+ return bp != null;
+ }
+
+ /**
+ * Print a list of currently set breakpoints.
+ */
+ public synchronized void listBreakpoints() {
+ if (breakpoints.isEmpty()) {
+ System.out.println("no breakpoints");
+ } else {
+ System.out.println("line breakpoints:");
+ for (LineBreakpoint bp : breakpoints) {
+ System.out.println(bp);
+ }
+ }
+ }
+
+ /**
+ * Retrieve a list of breakpoint in a particular tab.
+ *
+ * @param tabFilename the tab's file name
+ * @return the list of breakpoints in the given tab
+ */
+ public synchronized List getBreakpoints(String tabFilename) {
+ List list = new ArrayList();
+ for (LineBreakpoint bp : breakpoints) {
+ if (bp.lineID().fileName().equals(tabFilename)) {
+ list.add(bp);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Callback for VM events. Will be called from another thread.
+ * ({@link VMEventReader})
+ *
+ * @param es Incoming set of events from VM
+ */
+ @Override
+ public synchronized void vmEvent(EventSet es) {
+ for (Event e : es) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "*** VM Event: {0}", e.toString());
+ if (e instanceof VMStartEvent) {
+ //initialThread = ((VMStartEvent) e).thread();
+ ThreadReference t = ((VMStartEvent) e).thread();
+ //printStackTrace(t);
+
+ // break on main class load
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "requesting event on main class load: {0}", mainClassName);
+ ClassPrepareRequest mainClassPrepare = runtime.vm().eventRequestManager().createClassPrepareRequest();
+ mainClassPrepare.addClassFilter(mainClassName);
+ mainClassPrepare.enable();
+
+ // break on loading custom classes
+ for (SketchCode tab : editor.getSketch().getCode()) {
+ if (tab.isExtension("java")) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "requesting event on class load: {0}", tab.getPrettyName());
+ ClassPrepareRequest customClassPrepare = runtime.vm().eventRequestManager().createClassPrepareRequest();
+ customClassPrepare.addClassFilter(tab.getPrettyName());
+ customClassPrepare.enable();
+ }
+ }
+
+ runtime.vm().resume();
+ } else if (e instanceof ClassPrepareEvent) {
+ ClassPrepareEvent ce = (ClassPrepareEvent) e;
+ ReferenceType rt = ce.referenceType();
+ currentThread = ce.thread();
+ paused = true; // for now we're paused
+
+ if (rt.name().equals(mainClassName)) {
+ //printType(rt);
+ mainClass = rt;
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "main class load: {0}", rt.name());
+ started = true; // now that main class is loaded, we're started
+ } else {
+ classes.add(rt); // save loaded classes
+ Logger.getLogger(Debugger.class.getName()).log(Level.INFO, "class load: {0}", rt.name());
+ }
+
+ // notify listeners
+ for (ClassLoadListener listener : classLoadListeners) {
+ if (listener != null) {
+ listener.classLoaded(rt);
+ }
+ }
+
+ paused = false; // resuming now
+ runtime.vm().resume();
+ } else if (e instanceof BreakpointEvent) {
+ BreakpointEvent be = (BreakpointEvent) e;
+ currentThread = be.thread(); // save this thread
+ BreakpointRequest br = (BreakpointRequest) be.request();
+
+ //printSourceLocation(currentThread);
+ updateVariableInspector(currentThread); // this is already on the EDT
+ final LineID newCurrentLine = locationToLineID(be.location());
+ javax.swing.SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ editor.setCurrentLine(newCurrentLine);
+ editor.toolbar().deactivate(DebugToolbar.STEP);
+ editor.toolbar().deactivate(DebugToolbar.CONTINUE);
+ }
+ });
+
+ // hit a breakpoint during a step, need to cancel the step.
+ if (requestedStep != null) {
+ runtime.vm().eventRequestManager().deleteEventRequest(requestedStep);
+ requestedStep = null;
+ }
+
+ // fix canvas update issue
+ // TODO: is this a good solution?
+ resumeOtherThreads(currentThread);
+
+ paused = true;
+ editor.statusHalted();
+ } else if (e instanceof StepEvent) {
+ StepEvent se = (StepEvent) e;
+ currentThread = se.thread();
+
+ //printSourceLocation(currentThread);
+ updateVariableInspector(currentThread); // this is already on the EDT
+ final LineID newCurrentLine = locationToLineID(se.location());
+ javax.swing.SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ editor.setCurrentLine(newCurrentLine);
+ editor.toolbar().deactivate(DebugToolbar.STEP);
+ editor.toolbar().deactivate(DebugToolbar.CONTINUE);
+ }
+ });
+
+ // delete the steprequest that triggered this step so new ones can be placed (only one per thread)
+ EventRequestManager mgr = runtime.vm().eventRequestManager();
+ mgr.deleteEventRequest(se.request());
+ requestedStep = null; // mark that there is no step request pending
+ paused = true;
+ editor.statusHalted();
+
+ // disallow stepping into invisible lines
+ if (!locationIsVisible(se.location())) {
+ stepOutIntoViewOrContinue(); // TODO: this leads to stepping, should it run on the EDT?
+ }
+ } else if (e instanceof VMDisconnectEvent) {
+// started = false;
+// // clear line highlight
+// editor.clearCurrentLine();
+ stopDebug();
+ } else if (e instanceof VMDeathEvent) {
+ started = false;
+ editor.statusEmpty();
+ }
+ }
+ }
+
+ /**
+ * Check whether a location corresponds to a code line in the editor.
+ *
+ * @param l the location
+ * @return true if the location corresponds to a line in the editor
+ */
+ protected boolean locationIsVisible(Location l) {
+ return locationToLineID(l) != null;
+ }
+
+ /**
+ * Step out if this results in a visible location, otherwise continue.
+ */
+ protected void stepOutIntoViewOrContinue() {
+ try {
+ List frames = currentThread.frames();
+ if (frames.size() > 1) {
+ if (locationIsVisible(frames.get(1).location())) {
+ //System.out.println("stepping out to: " + locationToString(frames.get(1).location()));
+ stepOut();
+ return;
+ }
+ }
+ continueDebug();
+
+// //Step out to the next visible location on the stack frame
+// if (thread.frames(i, i1))
+// for (StackFrame f : thread.frames()) {
+// Location l = f.location();
+// if (locationIsVisible(l)) {
+// System.out.println("need to step out to: " + locationToString(l));
+// }
+// }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Check whether a debugging session is running. i.e. the debugger is
+ * connected to a debuggee VM, VMStartEvent has been received and main class
+ * is loaded.
+ *
+ * @return true if the debugger is started.
+ */
+ public synchronized boolean isStarted() {
+ return started && runtime != null && runtime.vm() != null;
+ }
+
+ /**
+ * Check whether the debugger is paused. i.e. it is currently suspended at a
+ * breakpoint or step.
+ *
+ * @return true if the debugger is paused, false otherwise or if not started
+ * ({@link #isStarted()})
+ */
+ public synchronized boolean isPaused() {
+ return isStarted() && paused && currentThread != null && currentThread.isSuspended();
+ }
+
+ /**
+ * Check whether the debugger is currently busy. i.e. running (not
+ * suspended).
+ *
+ * @return true if the debugger is currently running and not suspended.
+ */
+ public synchronized boolean isBusy() {
+ return isStarted() && !isPaused();
+ }
+
+ /**
+ * Print call stack trace of a thread. Only works on suspended threads.
+ *
+ * @param t suspended thread to print stack trace of
+ */
+ protected void printStackTrace(ThreadReference t) {
+ if (!t.isSuspended()) {
+ return;
+ }
+ try {
+ System.out.println("stack trace for thread " + t.name() + ":");
+ int i = 0;
+ for (StackFrame f : t.frames()) {
+ Location l = f.location();
+ System.out.println(i++ + ": " + f.toString());
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Resume all other threads except the one given as parameter. Useful e.g.
+ * to just keep the thread suspended a breakpoint occurred in.
+ *
+ * @param t the thread not to resume
+ */
+ protected void resumeOtherThreads(ThreadReference t) {
+ if (!isStarted()) {
+ return;
+ }
+ for (ThreadReference other : vm().allThreads()) {
+ if (!other.equals(t) && other.isSuspended()) {
+ other.resume();
+ }
+ }
+ }
+
+ /**
+ * Print info about all current threads. Includes name, status, isSuspended,
+ * isAtBreakpoint.
+ */
+ public synchronized void printThreads() {
+ if (!isPaused()) {
+ return;
+ }
+ System.out.println("threads:");
+ for (ThreadReference t : vm().allThreads()) {
+ printThread(t);
+ }
+ }
+
+ /**
+ * Print info about a thread. Includes name, status, isSuspended,
+ * isAtBreakpoint.
+ *
+ * @param t the thread to print info about
+ */
+ protected void printThread(ThreadReference t) {
+ System.out.println(t.name());
+ System.out.println(" is suspended: " + t.isSuspended());
+ System.out.println(" is at breakpoint: " + t.isAtBreakpoint());
+ System.out.println(" status: " + threadStatusToString(t.status()));
+ }
+
+ /**
+ * Convert a status code returned by {@link ThreadReference#status() } to a
+ * human readable form.
+ *
+ * @param status {@link ThreadReference#THREAD_STATUS_MONITOR},
+ * {@link ThreadReference#THREAD_STATUS_NOT_STARTED},
+ * {@link ThreadReference#THREAD_STATUS_RUNNING},
+ * {@link ThreadReference#THREAD_STATUS_SLEEPING},
+ * {@link ThreadReference#THREAD_STATUS_UNKNOWN},
+ * {@link ThreadReference#THREAD_STATUS_WAIT} or
+ * {@link ThreadReference#THREAD_STATUS_ZOMBIE}
+ * @return String containing readable status code.
+ */
+ protected String threadStatusToString(int status) {
+ switch (status) {
+ case ThreadReference.THREAD_STATUS_MONITOR:
+ return "THREAD_STATUS_MONITOR";
+ case ThreadReference.THREAD_STATUS_NOT_STARTED:
+ return "THREAD_STATUS_NOT_STARTED";
+ case ThreadReference.THREAD_STATUS_RUNNING:
+ return "THREAD_STATUS_RUNNING";
+ case ThreadReference.THREAD_STATUS_SLEEPING:
+ return "THREAD_STATUS_SLEEPING";
+ case ThreadReference.THREAD_STATUS_UNKNOWN:
+ return "THREAD_STATUS_UNKNOWN";
+ case ThreadReference.THREAD_STATUS_WAIT:
+ return "THREAD_STATUS_WAIT";
+ case ThreadReference.THREAD_STATUS_ZOMBIE:
+ return "THREAD_STATUS_ZOMBIE";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Print local variables on a suspended thread. Takes the topmost stack
+ * frame and lists all local variables and their values.
+ *
+ * @param t suspended thread
+ */
+ protected void printLocalVariables(ThreadReference t) {
+ if (!t.isSuspended()) {
+ return;
+ }
+ try {
+ if (t.frameCount() == 0) {
+ System.out.println("call stack empty");
+ } else {
+ StackFrame sf = t.frame(0);
+ List locals = sf.visibleVariables();
+ if (locals.isEmpty()) {
+ System.out.println("no local variables");
+ return;
+ }
+ for (LocalVariable lv : locals) {
+ System.out.println(lv.typeName() + " " + lv.name() + " = " + sf.getValue(lv));
+ }
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (AbsentInformationException ex) {
+ System.out.println("local variable information not available");
+ }
+ }
+
+ /**
+ * Update variable inspector window. Displays local variables and this
+ * fields.
+ *
+ * @param t suspended thread to retrieve locals and this
+ */
+ protected void updateVariableInspector(ThreadReference t) {
+ if (!t.isSuspended()) {
+ return;
+ }
+ try {
+ if (t.frameCount() == 0) {
+ // TODO: needs to be handled in a better way:
+ Logger.getLogger(Debugger.class.getName()).log(Level.WARNING, "call stack empty");
+ } else {
+ final VariableInspector vi = editor.variableInspector();
+ // first get data
+ final List stackTrace = getStackTrace(t);
+ final List locals = getLocals(t, 0);
+ final String currentLocation = currentLocation(t);
+ final List thisFields = getThisFields(t, 0, true);
+ final List declaredThisFields = getThisFields(t, 0, false);
+ final String thisName = thisName(t);
+ // now update asynchronously
+ javax.swing.SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ //System.out.println("updating vi. from EDT: " + javax.swing.SwingUtilities.isEventDispatchThread());
+ vi.updateCallStack(stackTrace, "Call Stack");
+ vi.updateLocals(locals, "Locals at " + currentLocation);
+ vi.updateThisFields(thisFields, "Class " + thisName);
+ vi.updateDeclaredThisFields(declaredThisFields, "Class " + thisName);
+ vi.unlock(); // need to do this before rebuilding, otherwise we get these ... dots in the labels
+ vi.rebuild();
+ }
+ });
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Get the class name of the current this object in a suspended thread.
+ *
+ * @param t a suspended thread
+ * @return the class name of this
+ */
+ protected String thisName(ThreadReference t) {
+ try {
+ if (!t.isSuspended() || t.frameCount() == 0) {
+ return "";
+ }
+ return t.frame(0).thisObject().referenceType().name();
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ return "";
+ }
+ }
+
+ /**
+ * Get a description of the current location in a suspended thread. Format:
+ * class.method:translated_line_number
+ *
+ * @param t a suspended thread
+ * @return descriptive string for the given location
+ */
+ protected String currentLocation(ThreadReference t) {
+ try {
+ if (!t.isSuspended() || t.frameCount() == 0) {
+ return "";
+ }
+ return locationToString(t.frame(0).location());
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ return "";
+ }
+ }
+
+ /**
+ * Get a string describing a location. Format:
+ * class.method:translated_line_number
+ *
+ * @param l a location
+ * @return descriptive string for the given location
+ */
+ protected String locationToString(Location l) {
+ LineID line = locationToLineID(l);
+ int lineNumber;
+ if (line != null) {
+ lineNumber = line.lineIdx() + 1;
+ } else {
+ lineNumber = l.lineNumber();
+ }
+ return l.declaringType().name() + "." + l.method().name() + ":" + lineNumber;
+ }
+
+ /**
+ * Compile a list of current locals usable for insertion into a
+ * {@link JTree}. Recursively resolves object references.
+ *
+ * @param t the suspended thread to get locals for
+ * @param depth how deep to resolve nested object references. 0 will not
+ * resolve nested objects.
+ * @return the list of current locals
+ */
+ protected List getLocals(ThreadReference t, int depth) {
+ //System.out.println("getting locals");
+ List vars = new ArrayList();
+ try {
+ if (t.frameCount() > 0) {
+ StackFrame sf = t.frame(0);
+ for (LocalVariable lv : sf.visibleVariables()) {
+ //System.out.println("local var: " + lv.name());
+ Value val = sf.getValue(lv);
+ VariableNode var = new LocalVariableNode(lv.name(), lv.typeName(), val, lv, sf);
+ if (depth > 0) {
+ var.addChildren(getFields(val, depth - 1, true));
+ }
+ vars.add(var);
+ }
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (AbsentInformationException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.WARNING, "local variable information not available", ex);
+ }
+ return vars;
+ }
+
+ /**
+ * Compile a list of fields in the current this object usable for insertion
+ * into a {@link JTree}. Recursively resolves object references.
+ *
+ * @param t the suspended thread to get locals for
+ * @param depth how deep to resolve nested object references. 0 will not
+ * resolve nested objects.
+ * @return the list of fields in the current this object
+ */
+ protected List getThisFields(ThreadReference t, int depth, boolean includeInherited) {
+ //System.out.println("getting this");
+ try {
+ if (t.frameCount() > 0) {
+ StackFrame sf = t.frame(0);
+ ObjectReference thisObj = sf.thisObject();
+ return getFields(thisObj, depth, includeInherited);
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return new ArrayList();
+ }
+
+ /**
+ * Recursively get the fields of a {@link Value} for insertion into a
+ * {@link JTree}.
+ *
+ * @param value must be an instance of {@link ObjectReference}
+ * @param depth the current depth
+ * @param maxDepth the depth to stop at (inclusive)
+ * @return list of child fields of the given value
+ */
+ protected List getFields(Value value, int depth, int maxDepth, boolean includeInherited) {
+ // remember: Value <- ObjectReference, ArrayReference
+ List vars = new ArrayList();
+ if (depth <= maxDepth) {
+ if (value instanceof ArrayReference) {
+ return getArrayFields((ArrayReference) value);
+ } else if (value instanceof ObjectReference) {
+ ObjectReference obj = (ObjectReference) value;
+ // get the fields of this object
+ List fields = includeInherited ? obj.referenceType().visibleFields() : obj.referenceType().fields();
+ for (Field field : fields) {
+ Value val = obj.getValue(field); // get the value, may be null
+ VariableNode var = new FieldNode(field.name(), field.typeName(), val, field, obj);
+ // recursively add children
+ if (val != null) {
+ var.addChildren(getFields(val, depth + 1, maxDepth, includeInherited));
+ }
+ vars.add(var);
+ }
+ }
+ }
+ return vars;
+ }
+
+ /**
+ * Recursively get the fields of a {@link Value} for insertion into a
+ * {@link JTree}.
+ *
+ * @param value must be an instance of {@link ObjectReference}
+ * @param maxDepth max recursion depth. 0 will give only direct children
+ * @return list of child fields of the given value
+ */
+ protected List getFields(Value value, int maxDepth, boolean includeInherited) {
+ return getFields(value, 0, maxDepth, includeInherited);
+ }
+
+ /**
+ * Get the fields of an array for insertion into a {@link JTree}.
+ *
+ * @param array the array reference
+ * @return list of array fields
+ */
+ protected List getArrayFields(ArrayReference array) {
+ List fields = new ArrayList();
+ if (array != null) {
+ String arrayType = array.type().name();
+ if (arrayType.endsWith("[]")) {
+ arrayType = arrayType.substring(0, arrayType.length() - 2);
+ }
+ int i = 0;
+ for (Value val : array.getValues()) {
+ VariableNode var = new ArrayFieldNode("[" + i + "]", arrayType, val, array, i);
+ fields.add(var);
+ i++;
+ }
+ }
+ return fields;
+ }
+
+ /**
+ * Get the current call stack trace usable for insertion into a
+ * {@link JTree}.
+ *
+ * @param t the suspended thread to retrieve the call stack from
+ * @return call stack as list of {@link DefaultMutableTreeNode}s
+ */
+ protected List getStackTrace(ThreadReference t) {
+ List stack = new ArrayList();
+ try {
+ int i = 0;
+ for (StackFrame f : t.frames()) {
+ stack.add(new DefaultMutableTreeNode(locationToString(f.location())));
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return stack;
+ }
+
+ /**
+ * Print visible fields of current "this" object on a suspended thread.
+ * Prints type, name and value.
+ *
+ * @param t suspended thread
+ */
+ protected void printThis(ThreadReference t) {
+ if (!t.isSuspended()) {
+ return;
+ }
+ try {
+ if (t.frameCount() == 0) {
+ // TODO: needs to be handled in a better way
+ System.out.println("call stack empty");
+ } else {
+ StackFrame sf = t.frame(0);
+ ObjectReference thisObject = sf.thisObject();
+ if (this != null) {
+ ReferenceType type = thisObject.referenceType();
+ System.out.println("fields in this (" + type.name() + "):");
+ for (Field f : type.visibleFields()) {
+ System.out.println(f.typeName() + " " + f.name() + " = " + thisObject.getValue(f));
+ }
+ } else {
+ System.out.println("can't get this (in native or static method)");
+ }
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Print source code snippet of current location in a suspended thread.
+ *
+ * @param t suspended thread
+ */
+ protected void printSourceLocation(ThreadReference t) {
+ try {
+ if (t.frameCount() == 0) {
+ // TODO: needs to be handled in a better way
+ System.out.println("call stack empty");
+ } else {
+ Location l = t.frame(0).location(); // current stack frame location
+ printSourceLocation(l);
+ }
+ } catch (IncompatibleThreadStateException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Print source code snippet.
+ *
+ * @param l {@link Location} object to print source code for
+ */
+ protected void printSourceLocation(Location l) {
+ try {
+ //System.out.println(l.sourceName() + ":" + l.lineNumber());
+ System.out.println("in method " + l.method() + ":");
+ System.out.println(getSourceLine(l.sourcePath(), l.lineNumber(), 2));
+
+ } catch (AbsentInformationException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Read a line from the given file in the builds src folder. 1-based i.e.
+ * first line has line no. 1
+ *
+ * @param filePath
+ * @param lineNo
+ * @return the requested source line
+ */
+ protected String getSourceLine(String filePath, int lineNo, int radius) {
+ if (lineNo == -1) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, "invalid line number: {0}", lineNo);
+ return "";
+ }
+ //System.out.println("getting line: " + lineNo);
+ File f = new File(srcPath + File.separator + filePath);
+ String output = "";
+ try {
+ BufferedReader r = new BufferedReader(new FileReader(f));
+ int i = 1;
+ //String line = "";
+ while (i <= lineNo + radius) {
+ String line = r.readLine(); // line no. i
+ if (line == null) {
+ break; // end of file
+ }
+ if (i >= lineNo - radius) {
+ if (i > lineNo - radius) {
+ output += "\n"; // add newlines before all lines but the first
+ }
+ output += f.getName() + ":" + i + (i == lineNo ? " => " : " ") + line;
+ }
+ i++;
+ }
+ r.close();
+ return output;
+ } catch (FileNotFoundException ex) {
+ //System.err.println(ex);
+ return f.getName() + ":" + lineNo;
+ } catch (IOException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ return "";
+ }
+ }
+
+ /**
+ * Print info about a ReferenceType. Prints class name, source file name,
+ * lists methods.
+ *
+ * @param rt the reference type to print out
+ */
+ protected void printType(ReferenceType rt) {
+ System.out.println("ref.type: " + rt);
+ System.out.println("name: " + rt.name());
+ try {
+ System.out.println("sourceName: " + rt.sourceName());
+ } catch (AbsentInformationException ex) {
+ System.out.println("sourceName: unknown");
+ }
+ System.out.println("methods:");
+ for (Method m : rt.methods()) {
+ System.out.println(m.toString());
+ }
+ }
+
+ /**
+ * Translate a java source location to a sketch line id.
+ *
+ * @param l the location to translate
+ * @return the corresponding line id, or null if not found
+ */
+ protected LineID locationToLineID(Location l) {
+ try {
+ //return lineMap.get(LineID.create(l.sourceName(), l.lineNumber() - 1));
+ return javaToSketchLine(new LineID(l.sourceName(), l.lineNumber() - 1));
+
+ } catch (AbsentInformationException ex) {
+ Logger.getLogger(Debugger.class.getName()).log(Level.SEVERE, null, ex);
+ return null;
+ }
+ }
+
+ /**
+ * Translate a line (index) from java space to sketch space.
+ *
+ * @param javaLine the java line id
+ * @return the corresponding sketch line id or null if failed to translate
+ */
+ public LineID javaToSketchLine(LineID javaLine) {
+ Sketch sketch = editor.getSketch();
+
+ // it may belong to a pure java file created in the sketch
+ // try to find an exact filename match and check the extension
+ SketchCode tab = editor.getTab(javaLine.fileName());
+ if (tab != null && tab.isExtension("java")) {
+ // can translate 1:1
+ return originalToRuntimeLine(javaLine);
+ }
+
+ // check if it is the preprocessed/assembled file for this sketch
+ // java file name needs to match the sketches filename
+ if (!javaLine.fileName().equals(sketch.getName() + ".java")) {
+ return null;
+ }
+
+ // find the tab (.pde file) this line belongs to
+ // get the last tab that has an offset not greater than the java line number
+ for (int i = sketch.getCodeCount() - 1; i >= 0; i--) {
+ tab = sketch.getCode(i);
+ // ignore .java files
+ // the tab's offset must not be greater than the java line number
+ if (tab.isExtension("pde") && tab.getPreprocOffset() <= javaLine.lineIdx()) {
+ return originalToRuntimeLine(new LineID(tab.getFileName(), javaLine.lineIdx() - tab.getPreprocOffset()));
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the runtime-changed line id for an original sketch line. Used to
+ * translate line numbers from the VM (which runs on the original line
+ * numbers) to their current (possibly changed) counterparts.
+ *
+ * @param line the original line id (at compile time)
+ * @return the changed version or the line given as parameter if not found
+ */
+ protected LineID originalToRuntimeLine(LineID line) {
+ LineID transformed = runtimeLineChanges.get(line);
+ if (transformed == null) {
+ return line;
+ }
+ return transformed;
+ }
+
+ /**
+ * Get the original line id for a sketch line that was changed at runtime.
+ * Used to translate line numbers from the UI at runtime (which can differ
+ * from the ones the VM runs on) to their original counterparts.
+ *
+ * @param line the (possibly) changed runtime line
+ * @return the original line or the line given as parameter if not found
+ */
+ protected LineID runtimeToOriginalLine(LineID line) {
+ for (Entry entry : runtimeLineChanges.entrySet()) {
+ if (entry.getValue().equals(line)) {
+ return entry.getKey();
+ }
+ }
+ return line;
+ }
+
+ /**
+ * Translate a line (index) from sketch space to java space.
+ *
+ * @param sketchLine the sketch line id
+ * @return the corresponding java line id or null if failed to translate
+ */
+ public LineID sketchToJavaLine(LineID sketchLine) {
+ sketchLine = runtimeToOriginalLine(sketchLine); // transform back to orig (before changes at runtime)
+
+ // check if there is a tab for this line
+ SketchCode tab = editor.getTab(sketchLine.fileName());
+ if (tab == null) {
+ return null;
+ }
+
+ // check if the tab is a pure java file anyway
+ if (tab.isExtension("java")) {
+ // 1:1 translation
+ return sketchLine;
+ }
+
+ // the java file has a name sketchname.java
+ // just add the tab's offset to get the java name
+ LineID javaLine = new LineID(editor.getSketch().getName() + ".java", sketchLine.lineIdx() + tab.getPreprocOffset());
+ return javaLine;
+ }
+
+ /**
+ * Start tracking all line changes (due to edits) in the current tab.
+ */
+ // TODO: maybe move this to the editor?
+ protected void startTrackingLineChanges() {
+ SketchCode tab = editor.getSketch().getCurrentCode();
+ if (runtimeTabsTracked.contains(tab.getFileName())) {
+ return;
+ }
+
+ for (int i = 0; i < tab.getLineCount(); i++) {
+ LineID old = new LineID(tab.getFileName(), i);
+ LineID tracked = new LineID(tab.getFileName(), i);
+ tracked.startTracking(editor.currentDocument());
+ runtimeLineChanges.put(old, tracked);
+ }
+ runtimeTabsTracked.add(tab.getFileName());
+ //System.out.println("tracking tab: " + tab.getFileName());
+ }
+
+ /**
+ * Stop tracking line changes in all tabs.
+ */
+ protected void stopTrackingLineChanges() {
+ //System.out.println("stop tracking line changes");
+ for (LineID tracked : runtimeLineChanges.values()) {
+ tracked.stopTracking();
+ }
+ runtimeLineChanges.clear();
+ runtimeTabsTracked.clear();
+ }
+}
diff --git a/app/src/processing/mode/java2/FieldNode.java b/app/src/processing/mode/java2/FieldNode.java
new file mode 100755
index 000000000..3fc6e2adb
--- /dev/null
+++ b/app/src/processing/mode/java2/FieldNode.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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;
+ }
+}
diff --git a/app/src/processing/mode/java2/LineBreakpoint.java b/app/src/processing/mode/java2/LineBreakpoint.java
new file mode 100755
index 000000000..84ae65d94
--- /dev/null
+++ b/app/src/processing/mode/java2/LineBreakpoint.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import com.sun.jdi.AbsentInformationException;
+import com.sun.jdi.Location;
+import com.sun.jdi.ReferenceType;
+import com.sun.jdi.request.BreakpointRequest;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * 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
+ */
+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)
+ }
+
+ /**
+ * 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 {
+ List 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();
+ 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
+ if (theClass.name().equals(className())) {
+ this.theClass = theClass;
+ attach();
+ }
+ }
+}
diff --git a/app/src/processing/mode/java2/LineHighlight.java b/app/src/processing/mode/java2/LineHighlight.java
new file mode 100755
index 000000000..b92d3aaa5
--- /dev/null
+++ b/app/src/processing/mode/java2/LineHighlight.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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 allHighlights = new HashSet();
+
+ 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());
+ }
+ }
+}
diff --git a/app/src/processing/mode/java2/LineID.java b/app/src/processing/mode/java2/LineID.java
new file mode 100755
index 000000000..52098abe2
--- /dev/null
+++ b/app/src/processing/mode/java2/LineID.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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 listeners = new HashSet(); // 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.
+ }
+}
diff --git a/app/src/processing/mode/java2/LineListener.java b/app/src/processing/mode/java2/LineListener.java
new file mode 100755
index 000000000..05864678c
--- /dev/null
+++ b/app/src/processing/mode/java2/LineListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+/**
+ * A Listener for line number changes.
+ *
+ * @author Martin Leopold
+ */
+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);
+}
diff --git a/app/src/processing/mode/java2/LocalVariableNode.java b/app/src/processing/mode/java2/LocalVariableNode.java
new file mode 100755
index 000000000..857ea697b
--- /dev/null
+++ b/app/src/processing/mode/java2/LocalVariableNode.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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;
+ }
+}
diff --git a/app/src/processing/mode/java2/TextArea.java b/app/src/processing/mode/java2/TextArea.java
new file mode 100755
index 000000000..98e4042fd
--- /dev/null
+++ b/app/src/processing/mode/java2/TextArea.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.FontMetrics;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.HashMap;
+import java.util.Map;
+import processing.app.syntax.JEditTextArea;
+import processing.app.syntax.TextAreaDefaults;
+
+/**
+ * Customized text area. Adds support for line background colors.
+ *
+ * @author Martin Leopold
+ */
+public class TextArea extends JEditTextArea {
+
+ protected MouseListener[] mouseListeners; // cached mouselisteners, these are wrapped by MouseHandler
+ protected DebugEditor editor; // the editor
+ // line properties
+ protected Map lineColors = new HashMap(); // 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 gutterText = new HashMap(); // maps line index to gutter text
+ protected Map gutterTextColors = new HashMap(); // maps line index to gutter text color
+
+ 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
+ painter = new TextAreaPainter(this, defaults);
+
+ // 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);
+
+ add(CENTER, painter);
+
+ // load settings from theme.txt
+ DebugMode theme = (DebugMode) editor.getMode();
+ gutterBgColor = theme.loadColorFromTheme("gutter.bgcolor", gutterBgColor);
+ gutterLineColor = theme.loadColorFromTheme("gutter.linecolor", gutterLineColor);
+ gutterPadding = theme.getInteger("gutter.padding");
+ breakpointMarker = theme.loadStringFromTheme("breakpoint.marker", breakpointMarker);
+ currentLineMarker = theme.loadStringFromTheme("currentline.marker", currentLineMarker);
+ }
+
+ /**
+ * Retrieve the total width of the gutter area.
+ *
+ * @return gutter width in pixels
+ */
+ protected int getGutterWidth() {
+ FontMetrics fm = painter.getFontMetrics();
+// System.out.println("fm: " + (fm == null));
+// System.out.println("editor: " + (editor == null));
+ //System.out.println("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() {
+ 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);
+ }
+ }
+ } else {
+ // 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();
+ }
+ }
+}
diff --git a/app/src/processing/mode/java2/TextAreaPainter.java b/app/src/processing/mode/java2/TextAreaPainter.java
new file mode 100755
index 000000000..99fd0ef25
--- /dev/null
+++ b/app/src/processing/mode/java2/TextAreaPainter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import javax.swing.text.Segment;
+import javax.swing.text.Utilities;
+import processing.app.syntax.TextAreaDefaults;
+import processing.app.syntax.TokenMarker;
+
+/**
+ * Customized line painter. Adds support for background colors, left hand gutter
+ * area with background color and text.
+ *
+ * @author Martin Leopold
+ */
+public class TextAreaPainter extends processing.app.syntax.TextAreaPainter {
+
+ protected TextArea ta; // we need the subclassed textarea
+
+ public TextAreaPainter(TextArea textArea, TextAreaDefaults defaults) {
+ super(textArea, defaults);
+ ta = textArea;
+ }
+
+ /**
+ * 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) {
+
+ // paint gutter
+ paintGutterBg(gfx, line, x);
+
+ paintLineBgColor(gfx, line, x + ta.getGutterWidth());
+
+ paintGutterLine(gfx, line, x);
+
+ // paint gutter symbol
+ paintGutterText(gfx, line, x);
+
+ super.paintLine(gfx, tokenMarker, line, x + ta.getGutterWidth());
+ }
+
+ /**
+ * 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) {
+ //System.out.println("none");
+ return;
+ }
+ // paint line background
+ gfx.setColor(col);
+ gfx.fillRect(0, y, getWidth(), height);
+ }
+}
diff --git a/app/src/processing/mode/java2/VMEventListener.java b/app/src/processing/mode/java2/VMEventListener.java
new file mode 100755
index 000000000..1e9d33042
--- /dev/null
+++ b/app/src/processing/mode/java2/VMEventListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+import com.sun.jdi.event.EventSet;
+
+/**
+ * Interface for VM callbacks.
+ *
+ * @author Martin Leopold
+ */
+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);
+}
diff --git a/app/src/processing/mode/java2/VMEventReader.java b/app/src/processing/mode/java2/VMEventReader.java
new file mode 100755
index 000000000..ad9178bb6
--- /dev/null
+++ b/app/src/processing/mode/java2/VMEventReader.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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
+ */
+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);
+ }
+ }
+}
diff --git a/app/src/processing/mode/java2/VariableInspector.form b/app/src/processing/mode/java2/VariableInspector.form
new file mode 100755
index 000000000..a5f40f1d3
--- /dev/null
+++ b/app/src/processing/mode/java2/VariableInspector.form
@@ -0,0 +1,53 @@
+
+
+
diff --git a/app/src/processing/mode/java2/VariableInspector.java b/app/src/processing/mode/java2/VariableInspector.java
new file mode 100755
index 000000000..ea53ea807
--- /dev/null
+++ b/app/src/processing/mode/java2/VariableInspector.java
@@ -0,0 +1,930 @@
+/*
+ * Copyright (C) 2012 Martin Leopold
+ *
+ * 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.java2;
+
+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.AbstractLayoutCache;
+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
+ */
+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 callStack; // the call stack
+ protected List locals; // current local variables
+ protected List thisFields; // all fields of the current this-object
+ protected List declaredThisFields; // declared i.e. non-inherited fields of this
+ protected DebugEditor editor; // the editor
+ protected Debugger dbg; // the debugger
+ protected List expandedNodes = new ArrayList(); // 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();
+ locals = new ArrayList();
+ thisFields = new ArrayList();
+ declaredThisFields = new ArrayList();
+
+ this.setTitle(editor.getSketch().getName());
+
+// for (Entry