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 entry : UIManager.getDefaults().entrySet()) { +// System.out.println(entry.getKey()); +// } + } + + @Override + public void setTitle(String title) { + super.setTitle(title + " | Variable Inspector"); + } + + /** + * Model for a Outline Row (excluding the tree column). Column 0 is "Value". + * Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a + * TableCellRenderer instead of this to also have a different icon based on + * expanded state. See: + * http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm + */ + protected class VariableRowModel implements RowModel { + + protected String[] columnNames = {"Value", "Type"}; + protected int[] editableTypes = {VariableNode.TYPE_BOOLEAN, VariableNode.TYPE_FLOAT, VariableNode.TYPE_INTEGER, VariableNode.TYPE_STRING, VariableNode.TYPE_FLOAT, VariableNode.TYPE_DOUBLE, VariableNode.TYPE_LONG, VariableNode.TYPE_SHORT, VariableNode.TYPE_CHAR}; + + @Override + public int getColumnCount() { + if (p5mode) { + return 1; // only show value in p5 mode + } else { + return 2; + } + } + + @Override + public Object getValueFor(Object o, int i) { + if (o instanceof VariableNode) { + VariableNode var = (VariableNode) o; + switch (i) { + case 0: + return var; // will be converted to an appropriate String by ValueCellRenderer + case 1: + return var.getTypeName(); + default: + return ""; + } + } else { + return ""; + } + } + + @Override + public Class getColumnClass(int i) { + if (i == 0) { + return VariableNode.class; + } + return String.class; + } + + @Override + public boolean isCellEditable(Object o, int i) { + if (i == 0 && o instanceof VariableNode) { + VariableNode var = (VariableNode) o; + //System.out.println("type: " + var.getTypeName()); + for (int type : editableTypes) { + if (var.getType() == type) { + return true; + } + } + } + return false; + } + + @Override + public void setValueFor(Object o, int i, Object o1) { + VariableNode var = (VariableNode) o; + String stringValue = (String) o1; + + Value value = null; + try { + switch (var.getType()) { + case VariableNode.TYPE_INTEGER: + value = dbg.vm().mirrorOf(Integer.parseInt(stringValue)); + break; + case VariableNode.TYPE_BOOLEAN: + value = dbg.vm().mirrorOf(Boolean.parseBoolean(stringValue)); + break; + case VariableNode.TYPE_FLOAT: + value = dbg.vm().mirrorOf(Float.parseFloat(stringValue)); + break; + case VariableNode.TYPE_STRING: + value = dbg.vm().mirrorOf(stringValue); + break; + case VariableNode.TYPE_LONG: + value = dbg.vm().mirrorOf(Long.parseLong(stringValue)); + break; + case VariableNode.TYPE_BYTE: + value = dbg.vm().mirrorOf(Byte.parseByte(stringValue)); + break; + case VariableNode.TYPE_DOUBLE: + value = dbg.vm().mirrorOf(Double.parseDouble(stringValue)); + break; + case VariableNode.TYPE_SHORT: + value = dbg.vm().mirrorOf(Short.parseShort(stringValue)); + break; + case VariableNode.TYPE_CHAR: + // TODO: better char support + if (stringValue.length() > 0) { + value = dbg.vm().mirrorOf(stringValue.charAt(0)); + } + break; + } + } catch (NumberFormatException ex) { + Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "invalid value entered for {0}: {1}", new Object[]{var.getName(), stringValue}); + } + if (value != null) { + var.setValue(value); + Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "new value set: {0}", var.getStringValue()); + } + } + + @Override + public String getColumnName(int i) { + return columnNames[i]; + } + } + + /** + * Renderer for the tree portion of the outline component. Handles icons, + * text color and tool tips. + */ + protected class OutlineRenderer implements RenderDataProvider { + + protected Icon[][] icons; + protected static final int ICON_SIZE = 16; // icon size (square, size=width=height) + + public OutlineRenderer() { + // load icons + icons = loadIcons("theme/var-icons.gif"); + } + + /** + * Load multiple icons (horizotal) with multiple states (vertical) from + * a single file. + * + * @param fileName file path in the mode folder. + * @return a nested array (first index: icon, second index: state) or + * null if the file wasn't found. + */ + protected ImageIcon[][] loadIcons(String fileName) { + DebugMode mode = editor.mode(); + File file = mode.getContentFile(fileName); + if (!file.exists()) { + Logger.getLogger(OutlineRenderer.class.getName()).log(Level.SEVERE, "icon file not found: {0}", file.getAbsolutePath()); + return null; + } + Image allIcons = mode.loadImage(fileName); + int cols = allIcons.getWidth(null) / ICON_SIZE; + int rows = allIcons.getHeight(null) / ICON_SIZE; + ImageIcon[][] iconImages = new ImageIcon[cols][rows]; + + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + //Image image = createImage(ICON_SIZE, ICON_SIZE); + Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null); + iconImages[i][j] = new ImageIcon(image); + } + } + return iconImages; + } + + protected Icon getIcon(int type, int state) { + if (type < 0 || type > icons.length - 1) { + return null; + } + return icons[type][state]; + } + + protected VariableNode toVariableNode(Object o) { + if (o instanceof VariableNode) { + return (VariableNode) o; + } else { + return null; + } + } + + protected Icon toGray(Icon icon) { + if (icon instanceof ImageIcon) { + Image grayImage = GrayFilter.createDisabledImage(((ImageIcon) icon).getImage()); + return new ImageIcon(grayImage); + } + // Cannot convert + return icon; + } + + @Override + public String getDisplayName(Object o) { + return o.toString(); // VariableNode.toString() returns name; (for sorting) +// VariableNode var = toVariableNode(o); +// if (var != null) { +// return var.getName(); +// } else { +// return o.toString(); +// } + } + + @Override + public boolean isHtmlDisplayName(Object o) { + return false; + } + + @Override + public Color getBackground(Object o) { + return null; + } + + @Override + public Color getForeground(Object o) { + if (tree.isEnabled()) { + return null; // default + } else { + return Color.GRAY; + } + } + + @Override + public String getTooltipText(Object o) { + VariableNode var = toVariableNode(o); + if (var != null) { + return var.getDescription(); + } else { + return ""; + } + } + + @Override + public Icon getIcon(Object o) { + VariableNode var = toVariableNode(o); + if (var != null) { + if (tree.isEnabled()) { + return getIcon(var.getType(), 0); + } else { + return getIcon(var.getType(), 1); + } + } else { + if (o instanceof TreeNode) { + TreeNode node = (TreeNode) o; + AbstractLayoutCache layout = tree.getLayoutCache(); + UIDefaults defaults = UIManager.getDefaults(); + + boolean isLeaf = model.isLeaf(o); + Icon icon; + if (isLeaf) { + icon = defaults.getIcon("Tree.leafIcon"); + } else { + icon = defaults.getIcon("Tree.closedIcon"); + } + + if (!tree.isEnabled()) { + return toGray(icon); + } + return icon; + } + } + return null; // use standard icon + //UIManager.getIcon(o); + } + } + + // TODO: could probably extend the simpler javax.swing.table.DefaultTableCellRenderer here + /** + * Renderer for the value column. Uses an italic font for null values and + * Object values ("instance of ..."). Uses a gray color when tree is not + * enabled. + */ + protected class ValueCellRenderer extends DefaultOutlineCellRenderer { + + public ValueCellRenderer() { + super(); + } + + protected void setItalic(boolean on) { + if (on) { + setFont(new Font(getFont().getName(), Font.ITALIC, getFont().getSize())); + } else { + setFont(new Font(getFont().getName(), Font.PLAIN, getFont().getSize())); + } + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if (!tree.isEnabled()) { + setForeground(Color.GRAY); + } else { + setForeground(Color.BLACK); + } + + if (value instanceof VariableNode) { + VariableNode var = (VariableNode) value; + + if (var.getValue() == null || var.getType() == VariableNode.TYPE_OBJECT) { + setItalic(true); + } else { + setItalic(false); + } + value = var.getStringValue(); + } + + setValue(value); + return c; + } + } + + /** + * Editor for the value column. Will show an empty string when editing + * String values that are null. + */ + protected class ValueCellEditor extends DefaultCellEditor { + + public ValueCellEditor() { + super(new JTextField()); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + if (!(value instanceof VariableNode)) { + return super.getTableCellEditorComponent(table, value, isSelected, row, column); + } + VariableNode var = (VariableNode) value; + if (var.getType() == VariableNode.TYPE_STRING && var.getValue() == null) { + return super.getTableCellEditorComponent(table, "", isSelected, row, column); + } else { + return super.getTableCellEditorComponent(table, var.getStringValue(), isSelected, row, column); + } + } + } + + /** + * Handler for expanding and collapsing tree nodes. Implements lazy loading + * of tree data (on expand). + */ + protected class ExpansionHandler implements ExtTreeWillExpandListener, TreeExpansionListener { + + @Override + public void treeWillExpand(TreeExpansionEvent tee) throws ExpandVetoException { + //System.out.println("will expand"); + Object last = tee.getPath().getLastPathComponent(); + if (!(last instanceof VariableNode)) { + return; + } + VariableNode var = (VariableNode) last; + // load children +// if (!dbg.isPaused()) { +// System.out.println("throwing veto"); +// //throw new ExpandVetoException(tee, "Debugger busy"); +// } else { + var.removeAllChildren(); // TODO: should we only load it once? + // TODO: don't filter in advanced mode + //System.out.println("loading children for: " + var); + // true means include inherited + var.addChildren(filterNodes(dbg.getFields(var.getValue(), 0, true), new ThisFilter())); +// } + } + + @Override + public void treeWillCollapse(TreeExpansionEvent tee) throws ExpandVetoException { + //throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void treeExpanded(TreeExpansionEvent tee) { + //System.out.println("expanded: " + tee.getPath()); + if (!expandedNodes.contains(tee.getPath())) { + expandedNodes.add(tee.getPath()); + } + +// TreePath newPath = tee.getPath(); +// if (expandedLast != null) { +// // test each node of the path for equality +// for (int i = 0; i < expandedLast.getPathCount(); i++) { +// if (i < newPath.getPathCount()) { +// Object last = expandedLast.getPathComponent(i); +// Object cur = newPath.getPathComponent(i); +// System.out.println(last + " =? " + cur + ": " + last.equals(cur) + "/" + (last.hashCode() == cur.hashCode())); +// } +// } +// } +// System.out.println("path equality: " + newPath.equals(expandedLast)); +// expandedLast = newPath; + } + + @Override + public void treeCollapsed(TreeExpansionEvent tee) { + //System.out.println("collapsed: " + tee.getPath()); + + // first remove all children of collapsed path + // this makes sure children do not appear before parents in the list. + // (children can't be expanded before their parents) + List removalList = new ArrayList(); + for (TreePath path : expandedNodes) { + if (path.getParentPath().equals(tee.getPath())) { + removalList.add(path); + } + } + for (TreePath path : removalList) { + expandedNodes.remove(path); + } + // remove collapsed path + expandedNodes.remove(tee.getPath()); + } + + @Override + public void treeExpansionVetoed(TreeExpansionEvent tee, ExpandVetoException eve) { + //System.out.println("expansion vetoed"); + // nop + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + scrollPane = new javax.swing.JScrollPane(); + tree = new org.netbeans.swing.outline.Outline(); + + scrollPane.setViewportView(tree); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + +// /** +// * @param args the command line arguments +// */ +// public static void main(String args[]) { +// /* +// * Set the Nimbus look and feel +// */ +// // +// /* +// * If Nimbus (introduced in Java SE 6) is not available, stay with the +// * default look and feel. For details see +// * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html +// */ +// try { +// javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); +// } catch (ClassNotFoundException ex) { +// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); +// } catch (InstantiationException ex) { +// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); +// } catch (IllegalAccessException ex) { +// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); +// } catch (javax.swing.UnsupportedLookAndFeelException ex) { +// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); +// } +// // +// +// /* +// * Create and display the form +// */ +// run(new VariableInspector()); +// } + protected static void run(final VariableInspector vi) { + /* + * Create and display the form + */ + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + vi.setVisible(true); + } + }); + } + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane scrollPane; + protected org.netbeans.swing.outline.Outline tree; + // End of variables declaration//GEN-END:variables + + /** + * Access the root node of the tree. + * + * @return the root node + */ + public DefaultMutableTreeNode getRootNode() { + return rootNode; + } + + /** + * Unlock the inspector window. Rebuild after this to avoid ... dots in the + * trees labels + */ + public void unlock() { + tree.setEnabled(true); + } + + /** + * Lock the inspector window. Cancels open edits. + */ + public void lock() { + if (tree.getCellEditor() != null) { + //tree.getCellEditor().stopCellEditing(); // force quit open edit + tree.getCellEditor().cancelCellEditing(); // cancel an open edit + } + tree.setEnabled(false); + } + + /** + * Reset the inspector windows data. Rebuild after this to make changes + * visible. + */ + public void reset() { + rootNode.removeAllChildren(); + // clear local data for good measure (in case someone rebuilds) + callStack.clear(); + locals.clear(); + thisFields.clear(); + declaredThisFields.clear(); + expandedNodes.clear(); + // update + treeModel.nodeStructureChanged(rootNode); + } + +// public void setAdvancedMode() { +// p5mode = false; +// } +// +// public void setP5Mode() { +// p5mode = true; +// } +// +// public void toggleMode() { +// if (p5mode) { +// setAdvancedMode(); +// } else { +// setP5Mode(); +// } +// } + /** + * Update call stack data. + * + * @param nodes a list of nodes that represent the call stack. + * @param title the title to be used when labeling or otherwise grouping + * call stack data. + */ + public void updateCallStack(List nodes, String title) { + callStack = nodes; + } + + /** + * Update locals data. + * + * @param nodes a list of {@link VariableNode} to be shown as local + * variables in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * locals data. + */ + public void updateLocals(List nodes, String title) { + locals = nodes; + } + + /** + * Update this-fields data. + * + * @param nodes a list of {@link VariableNode}s to be shown as this-fields + * in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * this-fields data. + */ + public void updateThisFields(List nodes, String title) { + thisFields = nodes; + } + + /** + * Update declared (non-inherited) this-fields data. + * + * @param nodes a list of {@link VariableNode}s to be shown as declared + * this-fields in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * declared this-fields data. + */ + public void updateDeclaredThisFields(List nodes, String title) { + declaredThisFields = nodes; + } + + /** + * Rebuild the outline tree from current data. Uses the data provided by + * {@link #updateCallStack}, {@link #updateLocals}, {@link #updateThisFields} + * and {@link #updateDeclaredThisFields} + */ + public void rebuild() { + rootNode.removeAllChildren(); + if (p5mode) { + // add all locals to root + addAllNodes(rootNode, locals); + + // add non-inherited this fields + addAllNodes(rootNode, filterNodes(declaredThisFields, new LocalHidesThisFilter(locals, LocalHidesThisFilter.MODE_PREFIX))); + + // add p5 builtins in a new folder + builtins.removeAllChildren(); + addAllNodes(builtins, filterNodes(thisFields, new P5BuiltinsFilter())); + if (builtins.getChildCount() > 0) { // skip builtins in certain situations e.g. in pure java tabs. + rootNode.add(builtins); + } + + // notify tree (using model) changed a node and its children + // http://stackoverflow.com/questions/2730851/how-to-update-jtree-elements + // needs to be done before expanding paths! + treeModel.nodeStructureChanged(rootNode); + + // handle node expansions + for (TreePath path : expandedNodes) { + //System.out.println("re-expanding: " + path); + path = synthesizePath(path); + if (path != null) { + tree.expandPath(path); + } else { + //System.out.println("couldn't synthesize path"); + } + } + + // this expansion causes problems when sorted and stepping + //tree.expandPath(new TreePath(new Object[]{rootNode, builtins})); + + } else { + // TODO: implement advanced mode here + } + } + + /** + * Re-build a {@link TreePath} from a previous path using equals-checks + * starting at the root node. This is used to use paths from previous trees + * for use on the current tree. + * + * @param path the path to synthesize. + * @return the rebuilt path, usable on the current tree. + */ + protected TreePath synthesizePath(TreePath path) { + //System.out.println("synthesizing: " + path); + if (path.getPathCount() == 0 || !rootNode.equals(path.getPathComponent(0))) { + return null; + } + Object[] newPath = new Object[path.getPathCount()]; + newPath[0] = rootNode; + TreeNode currentNode = rootNode; + for (int i = 0; i < path.getPathCount() - 1; i++) { + // get next node + for (int j = 0; j < currentNode.getChildCount(); j++) { + TreeNode nextNode = currentNode.getChildAt(j); + if (nextNode.equals(path.getPathComponent(i + 1))) { + currentNode = nextNode; + newPath[i + 1] = nextNode; + //System.out.println("found node " + (i+1) + ": " + nextNode); + break; + } + } + if (newPath[i + 1] == null) { + //System.out.println("didn't find node"); + return null; + } + } + return new TreePath(newPath); + } + + /** + * Filter a list of nodes using a {@link VariableNodeFilter}. + * + * @param nodes the list of nodes to filter. + * @param filter the filter to be used. + * @return the filtered list. + */ + protected List filterNodes(List nodes, VariableNodeFilter filter) { + List filtered = new ArrayList(); + for (VariableNode node : nodes) { + if (filter.accept(node)) { + filtered.add(node); + } + } + return filtered; + } + + /** + * Add all nodes in a list to a root node. + * + * @param root the root node to add to. + * @param nodes the list of nodes to add. + */ + protected void addAllNodes(DefaultMutableTreeNode root, List nodes) { + for (MutableTreeNode node : nodes) { + root.add(node); + } + } + + /** + * A filter for {@link VariableNode}s. + */ + public interface VariableNodeFilter { + + /** + * Check whether the filter accepts a {@link VariableNode}. + * + * @param var the input node + * @return true when the filter accepts the input node otherwise false. + */ + public boolean accept(VariableNode var); + } + + /** + * A {@link VariableNodeFilter} that accepts Processing built-in variable + * names. + */ + public class P5BuiltinsFilter implements VariableNodeFilter { + + protected String[] p5Builtins = { + "focused", + "frameCount", + "frameRate", + "height", + "online", + "screen", + "width", + "mouseX", + "mouseY", + "pmouseX", + "pmouseY", + "key", + "keyCode", + "keyPressed" + }; + + @Override + public boolean accept(VariableNode var) { + return Arrays.asList(p5Builtins).contains(var.getName()); + } + } + + /** + * A {@link VariableNodeFilter} that rejects implicit this references. + * (Names starting with "this$") + */ + public class ThisFilter implements VariableNodeFilter { + + @Override + public boolean accept(VariableNode var) { + return !var.getName().startsWith("this$"); + } + } + + /** + * A {@link VariableNodeFilter} that either rejects this-fields if hidden by + * a local, or prefixes its name with "this." + */ + public class LocalHidesThisFilter implements VariableNodeFilter { + + /** + * Reject a this-field if hidden by a local. + */ + public static final int MODE_HIDE = 0; // don't show hidden this fields + /** + * Prefix a this-fields name with "this." if hidden by a local. + */ + public static final int MODE_PREFIX = 1; // prefix hidden this fields with "this." + protected List locals; + protected int mode; + + /** + * Construct a {@link LocalHidesThisFilter}. + * + * @param locals a list of locals to check against + * @param mode either {@link #MODE_HIDE} or {@link #MODE_PREFIX} + */ + public LocalHidesThisFilter(List locals, int mode) { + this.locals = locals; + this.mode = mode; + } + + @Override + public boolean accept(VariableNode var) { + // check if the same name appears in the list of locals i.e. the local hides the field + for (VariableNode local : locals) { + if (var.getName().equals(local.getName())) { + switch (mode) { + case MODE_PREFIX: + var.setName("this." + var.getName()); + return true; + case MODE_HIDE: + return false; + } + } + } + return true; + } + } +} diff --git a/app/src/processing/mode/java2/VariableNode.java b/app/src/processing/mode/java2/VariableNode.java new file mode 100755 index 000000000..ce4813061 --- /dev/null +++ b/app/src/processing/mode/java2/VariableNode.java @@ -0,0 +1,358 @@ +/* + * 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.ObjectReference; +import com.sun.jdi.StringReference; +import com.sun.jdi.Value; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeNode; + +/** + * Model for a variable in the variable inspector. Has a type and name and + * optionally a value. Can have sub-variables (as is the case for objects, and + * arrays). + * + * @author Martin Leopold + */ +public class VariableNode implements MutableTreeNode { + + public static final int TYPE_UNKNOWN = -1; + public static final int TYPE_OBJECT = 0; + public static final int TYPE_ARRAY = 1; + public static final int TYPE_INTEGER = 2; + public static final int TYPE_FLOAT = 3; + public static final int TYPE_BOOLEAN = 4; + public static final int TYPE_CHAR = 5; + public static final int TYPE_STRING = 6; + public static final int TYPE_LONG = 7; + public static final int TYPE_DOUBLE = 8; + public static final int TYPE_BYTE = 9; + public static final int TYPE_SHORT = 10; + public static final int TYPE_VOID = 11; + protected String type; + protected String name; + protected Value value; + protected List children = new ArrayList(); + protected MutableTreeNode parent; + + /** + * Construct a {@link VariableNode}. + * @param name the name + * @param type the type + * @param value the value + */ + public VariableNode(String name, String type, Value value) { + this.name = name; + this.type = type; + this.value = value; + } + + public void setValue(Value value) { + this.value = value; + } + + public Value getValue() { + return value; + } + + /** + * Get a String representation of this variable nodes value. + * + * @return a String representing the value. + */ + public String getStringValue() { + String str; + if (value != null) { + if (getType() == TYPE_OBJECT) { + str = "instance of " + type; + } else if (getType() == TYPE_ARRAY) { + //instance of int[5] (id=998) --> instance of int[5] + str = value.toString().substring(0, value.toString().lastIndexOf(" ")); + } else if (getType() == TYPE_STRING) { + str = ((StringReference) value).value(); // use original string value (without quotes) + } else { + str = value.toString(); + } + } else { + str = "null"; + } + return str; + } + + public String getTypeName() { + return type; + } + + public int getType() { + if (type == null) { + return TYPE_UNKNOWN; + } + if (type.endsWith("[]")) { + return TYPE_ARRAY; + } + if (type.equals("int")) { + return TYPE_INTEGER; + } + if (type.equals("long")) { + return TYPE_LONG; + } + if (type.equals("byte")) { + return TYPE_BYTE; + } + if (type.equals("short")) { + return TYPE_SHORT; + } + if (type.equals("float")) { + return TYPE_FLOAT; + } + if (type.equals("double")) { + return TYPE_DOUBLE; + } + if (type.equals("char")) { + return TYPE_CHAR; + } + if (type.equals("java.lang.String")) { + return TYPE_STRING; + } + if (type.equals("boolean")) { + return TYPE_BOOLEAN; + } + if (type.equals("void")) { + return TYPE_VOID; //TODO: check if this is correct + } + return TYPE_OBJECT; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Add a {@link VariableNode} as child. + * + * @param c the {@link VariableNode} to add. + */ + public void addChild(VariableNode c) { + children.add(c); + c.setParent(this); + } + + /** + * Add multiple {@link VariableNode}s as children. + * + * @param children the list of {@link VariableNode}s to add. + */ + public void addChildren(List children) { + for (VariableNode child : children) { + addChild(child); + } + } + + @Override + public TreeNode getChildAt(int i) { + return children.get(i); + } + + @Override + public int getChildCount() { + return children.size(); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public int getIndex(TreeNode tn) { + return children.indexOf(tn); + } + + @Override + public boolean getAllowsChildren() { + if (value == null) { + return false; + } + + // handle strings + if (getType() == TYPE_STRING) { + return false; + } + + // handle arrays + if (getType() == TYPE_ARRAY) { + ArrayReference array = (ArrayReference) value; + return array.length() > 0; + } + // handle objects + if (getType() == TYPE_OBJECT) { // this also rules out null + // check if this object has any fields + ObjectReference obj = (ObjectReference) value; + return !obj.referenceType().visibleFields().isEmpty(); + } + + return false; + } + + /** + * This controls the default icon and disclosure triangle. + * + * @return true, will show "folder" icon and disclosure triangle. + */ + @Override + public boolean isLeaf() { + //return children.size() == 0; + return !getAllowsChildren(); + } + + @Override + public Enumeration children() { + return Collections.enumeration(children); + } + + /** + * Get a String representation of this {@link VariableNode}. + * + * @return the name of the variable (for sorting to work). + */ + @Override + public String toString() { + return getName(); // for sorting + } + + /** + * Get a String description of this {@link VariableNode}. Contains the type, + * name and value. + * + * @return the description + */ + public String getDescription() { + String str = ""; + if (type != null) { + str += type + " "; + } + str += name; + str += " = " + getStringValue(); + return str; + } + + @Override + public void insert(MutableTreeNode mtn, int i) { + children.add(i, this); + } + + @Override + public void remove(int i) { + MutableTreeNode mtn = children.remove(i); + if (mtn != null) { + mtn.setParent(null); + } + } + + @Override + public void remove(MutableTreeNode mtn) { + children.remove(mtn); + mtn.setParent(null); + } + + /** + * Remove all children from this {@link VariableNode}. + */ + public void removeAllChildren() { + for (MutableTreeNode mtn : children) { + mtn.setParent(null); + } + children.clear(); + } + + @Override + public void setUserObject(Object o) { + if (o instanceof Value) { + value = (Value) o; + } + } + + @Override + public void removeFromParent() { + parent.remove(this); + this.parent = null; + } + + @Override + public void setParent(MutableTreeNode mtn) { + parent = mtn; + } + + /** + * Test for equality. To be equal, two {@link VariableNode}s need to have + * equal type, name and value. + * + * @param obj the object to test for equality with this {@link VariableNode} + * @return true if the given object is equal to this {@link VariableNode} + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final VariableNode other = (VariableNode) obj; + if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) { + //System.out.println("type not equal"); + return false; + } + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + //System.out.println("name not equal"); + return false; + } + if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) { + //System.out.println("value not equal"); + return false; + } +// if (this.parent != other.parent && (this.parent == null || !this.parent.equals(other.parent))) { +// System.out.println("parent not equal: " + this.parent + "/" + other.parent); +// return false; +// } + return true; + } + + /** + * Returns a hash code based on type, name and value. + */ + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); + hash = 97 * hash + (this.value != null ? this.value.hashCode() : 0); +// hash = 97 * hash + (this.parent != null ? this.parent.hashCode() : 0); + return hash; + } +} diff --git a/build/build.xml b/build/build.xml index 679f28d54..bf1f20445 100644 --- a/build/build.xml +++ b/build/build.xml @@ -37,10 +37,10 @@ - + @@ -123,8 +123,8 @@ ---> +--> @@ -132,6 +132,11 @@ + + + + + - $JAVAROOT/lib/pde.jar:$JAVAROOT/core/library/core.jar:$JAVAROOT/lib/ant.jar:$JAVAROOT/lib/ant-launcher.jar:$JAVAROOT/lib/antlr.jar:$JAVAROOT/lib/jna.jar:$JAVAROOT/quaqua.jar + $JAVAROOT/lib/pde.jar:$JAVAROOT/core/library/core.jar:$JAVAROOT/lib/ant.jar:$JAVAROOT/lib/ant-launcher.jar:$JAVAROOT/lib/antlr.jar:$JAVAROOT/lib/jna.jar:$JAVAROOT/quaqua.jar:$JAVAROOT/lib/org-netbeans-swing-outline.jar Properties diff --git a/build/windows/launcher/config-cmd.xml b/build/windows/launcher/config-cmd.xml index dcf3d1dc0..30829c23a 100755 --- a/build/windows/launcher/config-cmd.xml +++ b/build/windows/launcher/config-cmd.xml @@ -21,6 +21,7 @@ lib/antlr.jar lib/ant.jar lib/ant-launcher.jar + lib/org-netbeans-swing-outline.jar %EXEDIR%/java/lib/tools.jar diff --git a/build/windows/launcher/config.xml b/build/windows/launcher/config.xml index 6ae678be7..18fd36acb 100755 --- a/build/windows/launcher/config.xml +++ b/build/windows/launcher/config.xml @@ -21,6 +21,7 @@ lib/antlr.jar lib/ant.jar lib/ant-launcher.jar + lib/org-netbeans-swing-outline.jar %EXEDIR%/java/lib/tools.jar diff --git a/core/api.txt b/core/api.txt index 4b2746f8b..d8fba2435 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1,3 +1,4 @@ +This document was last updated for Processing 1.0. public void setParent(PApplet parent) public void setPrimary(boolean primary) diff --git a/java2/keywords.txt b/java2/keywords.txt new file mode 100755 index 000000000..7c9229ed4 --- /dev/null +++ b/java2/keywords.txt @@ -0,0 +1,637 @@ +# KEY + +# LITERAL2 - constants +# KEYWORD1 - Java datatypes and keywords +# KEYWORD2 - functions +# KEYWORD3 - methods (functions inside a class) +# KEYWORD4 - fields (variables inside a class) +# KEYWORD5 - Processing variables (width, height, focused, etc.) + +# + +# LITERAL2 specifies constants + +ADD LITERAL2 blend_ +ALIGN_CENTER LITERAL2 +ALIGN_LEFT LITERAL2 +ALIGN_RIGHT LITERAL2 +ALPHA LITERAL2 +ALPHA_MASK LITERAL2 +ALT LITERAL2 +AMBIENT LITERAL2 +ARROW LITERAL2 cursor_ +ARGB LITERAL2 +BACKSPACE LITERAL2 keyCode +BASELINE LITERAL2 textAlign_ +BEVEL LITERAL2 strokeJoin_ +BLEND LITERAL2 blend_ +BLUE_MASK LITERAL2 +BLUR LITERAL2 filter_ +BOTTOM LITERAL2 textAlign_ +BURN LITERAL2 blend_ +CENTER LITERAL2 +CHATTER LITERAL2 +CLOSE LITERAL2 +CMYK LITERAL2 +CODED LITERAL2 key +COMPLAINT LITERAL2 +COMPOSITE LITERAL2 +COMPONENT LITERAL2 +CONCAVE_POLYGON LITERAL2 +CONTROL LITERAL2 +CONVEX_POLYGON LITERAL2 +CORNER LITERAL2 textAlign_ +CORNERS LITERAL2 +CROSS LITERAL2 cursor_ +CUSTOM LITERAL2 +DARKEST LITERAL2 blend_ +DEGREES LITERAL2 +DEG_TO_RAD LITERAL2 +DELETE LITERAL2 +DIAMETER LITERAL2 +DIFFERENCE LITERAL2 blend_ +DIFFUSE LITERAL2 +DILATE LITERAL2 filter_ +DIRECTIONAL LITERAL2 +DISABLE_ACCURATE_TEXTURES LITERAL2 +DISABLE_DEPTH_SORT LITERAL2 +DISABLE_DEPTH_TEST LITERAL2 +DISABLE_NATIVE_FONTS LITERAL2 +DISABLE_OPENGL_ERROR_REPORT LITERAL2 +DISABLE_OPENGL_2X_SMOOTH LITERAL2 +DISABLE_OPENGL_4X_SMOOTH LITERAL2 +DISABLE_TEXT_SMOOTH LITERAL2 +DISABLED LITERAL2 +DODGE LITERAL2 blend_ +DOWN LITERAL2 keyCode +DXF LITERAL2 size_ +ENABLE_ACCURATE_TEXTURES LITERAL2 +ENABLE_DEPTH_SORT LITERAL2 +ENABLE_DEPTH_TEST LITERAL2 +ENABLE_NATIVE_FONTS LITERAL2 +ENABLE_OPENGL_2X_SMOOTH LITERAL2 +ENABLE_OPENGL_4X_SMOOTH LITERAL2 +ENABLE_OPENGL_ERROR_REPORT LITERAL2 +ENTER LITERAL2 keyCode +EPSILON LITERAL2 +ERODE LITERAL2 filter_ +ESC LITERAL2 keyCode +EXCLUSION LITERAL2 blend_ +GIF LITERAL2 +GRAY LITERAL2 filter_ +GREEN_MASK LITERAL2 +GROUP LITERAL2 +HALF LITERAL2 +HALF_PI LITERAL2 HALF_PI +HAND LITERAL2 cursor_ +HARD_LIGHT LITERAL2 blend_ +HINT_COUNT LITERAL2 +HSB LITERAL2 colorMode_ +IMAGE LITERAL2 textureMode_ +INVERT LITERAL2 filter_ +JPEG LITERAL2 +LEFT LITERAL2 keyCode +LIGHTEST LITERAL2 blend_ +LINES LITERAL2 beginShape_ +LINUX LITERAL2 +MACOSX LITERAL2 +MAX_FLOAT LITERAL2 +MAX_INT LITERAL2 +MITER LITERAL2 stokeJoin_ +MODEL LITERAL2 +MOVE LITERAL2 cursor_ +MULTIPLY LITERAL2 blend_ +NORMAL LITERAL2 +NORMALIZED LITERAL2 textureMode_ +NO_DEPTH_TEST LITERAL2 +NTSC LITERAL2 +ONE LITERAL2 +OPAQUE LITERAL2 filter_ +OPEN LITERAL2 +ORTHOGRAPHIC LITERAL2 +OVERLAY LITERAL2 blend_ +PAL LITERAL2 +PDF LITERAL2 size_ +P2D LITERAL2 size_ +P3D LITERAL2 size_ +PERSPECTIVE LITERAL2 +PI LITERAL2 PI +PIXEL_CENTER LITERAL2 +POINT LITERAL2 +POINTS LITERAL2 +POSTERIZE LITERAL2 filter_ +PROBLEM LITERAL2 +PROJECT LITERAL2 strokeCap_ +QUAD_STRIP LITERAL2 beginShape_ +QUADS LITERAL2 beginShape_ +QUARTER_PI LITERAL2 QUARTER_PI +RAD_TO_DEG LITERAL2 +RADIUS LITERAL2 +RADIANS LITERAL2 +RED_MASK LITERAL2 +REPLACE LITERAL2 +RETURN LITERAL2 +RGB LITERAL2 colorMode_ +RIGHT LITERAL2 keyCode +ROUND LITERAL2 strokeCap_ +SCREEN LITERAL2 blend_ +SECAM LITERAL2 +SHIFT LITERAL2 +SPECULAR LITERAL2 +SOFT_LIGHT LITERAL2 blend_ +SQUARE LITERAL2 strokeCap_ +SUBTRACT LITERAL2 blend_ +SVIDEO LITERAL2 +TAB LITERAL2 keyCode +TARGA LITERAL2 +TEXT LITERAL2 cursor_ +TFF LITERAL2 +THIRD_PI LITERAL2 +THRESHOLD LITERAL2 filter_ +TIFF LITERAL2 +TOP LITERAL2 textAlign_ +TRIANGLE_FAN LITERAL2 beginShape_ +TRIANGLES LITERAL2 beginShape_ +TRIANGLE_STRIP LITERAL2 beginShape_ +TUNER LITERAL2 +TWO LITERAL2 +TWO_PI LITERAL2 TWO_PI +UP LITERAL2 keyCode +WAIT LITERAL2 cursor_ +WHITESPACE LITERAL2 + + +# KEYWORD1 specifies datatypes and keywords + +Array KEYWORD1 Array +ArrayList KEYWORD1 ArrayList +Boolean KEYWORD1 +Byte KEYWORD1 +BufferedReader KEYWORD1 BufferedReader +Character KEYWORD1 +Class KEYWORD1 class +Double KEYWORD1 +Float KEYWORD1 +Integer KEYWORD1 +HashMap KEYWORD1 HashMap +PrintWriter KEYWORD1 PrintWriter +String KEYWORD1 String +StringBuffer KEYWORD1 +Thread KEYWORD1 +abstract KEYWORD1 +assert KEYWORD1 +boolean KEYWORD1 boolean +break KEYWORD1 break +byte KEYWORD1 byte +case KEYWORD1 case +catch KEYWORD1 catch +char KEYWORD1 char +class KEYWORD1 class +continue KEYWORD1 continue +default KEYWORD1 default +do KEYWORD1 +double KEYWORD1 double +else KEYWORD1 else +enum KEYWORD1 +extends KEYWORD1 extends +false KEYWORD1 false +final KEYWORD1 final +finally KEYWORD1 +for KEYWORD1 for +float KEYWORD1 float +if KEYWORD1 if +implements KEYWORD1 implements +import KEYWORD1 import +instanceof KEYWORD1 +int KEYWORD1 int +interface KEYWORD1 +long KEYWORD1 long +native KEYWORD1 +new KEYWORD1 new +null KEYWORD1 null +package KEYWORD1 +private KEYWORD1 private +protected KEYWORD1 +public KEYWORD1 public +return KEYWORD1 return +short KEYWORD1 +static KEYWORD1 static +strictfp KEYWORD1 +super KEYWORD1 super +switch KEYWORD1 switch +synchronized KEYWORD1 +this KEYWORD1 this +throw KEYWORD1 +throws KEYWORD1 +transient KEYWORD1 +true KEYWORD1 true +try KEYWORD1 try +void KEYWORD1 void +volatile KEYWORD1 +while KEYWORD1 while + + +# Depricated API + +arraycopy KEYWORD2 arrayCopy_ +openStream KEYWORD2 openStream_ +OPENGL LITERAL2 size_ +JAVA2D LITERAL2 size_ + + +# KEYWORD2 specifies functions and KEYWORD3 specifies methods +# These items are a part of Processing but are not included in the reference + +boolean KEYWORD2 booleanconvert_ +byte KEYWORD2 byteconvert_ +cache KEYWORD2 +char KEYWORD2 charconvert_ +color KEYWORD1 color_datatype +start KEYWORD2 +stop KEYWORD2 +breakShape KEYWORD2 +createPath KEYWORD2 +float KEYWORD2 floatconvert_ +int KEYWORD2 intconvert_ +loadMatrix KEYWORD2 +noHint KEYWORD2 +parseBoolean KEYWORD2 +parseByte KEYWORD2 +parseChar KEYWORD2 +parseFloat KEYWORD2 +parseInt KEYWORD2 +saveFile KEYWORD2 +savePath KEYWORD2 +sketchFile KEYWORD2 +sketchPath KEYWORD2 +string KEYWORD2 strconvert_ + +#KEYWORD3 is an experimental designation for methods +readLine KEYWORD3 BufferedReader_readLine_ +close KEYWORD3 PrintWriter_close_ +flush KEYWORD3 PrintWriter_flush_ +print KEYWORD3 PrintWriter_print_ +println KEYWORD3 PrintWriter_println_ +charAt KEYWORD3 String_charAt_ +equals KEYWORD3 String_equals_ +indexOf KEYWORD3 String_indexOf_ +length KEYWORD3 String_length_ +substring KEYWORD3 String_substring_ +toLowerCase KEYWORD3 String_toLowerCase_ +toUpperCase KEYWORD3 String_toUpperCase_ + + +# Operators are without KEYWORDS + ++= addassign ++ addition +[] arrayaccess += assign +& bitwiseAND +| bitwiseOR +, comma +// comment +? conditional +{} curlybraces +-- decrement +/ divide +/= divideassign +/** doccomment +. dot +== equality +> greaterthan +>= greaterthanorequalto +++ increment +!= inequality +<< leftshift +< lessthan +<= lessthanorequalto +&& logicalAND +! logicalNOT +|| logicalOR +- minus +% modulo +/* multilinecomment +* multiply +*= multiplyassign +() parentheses +<< rightshift +; semicolon +-= subtractassign + + +# THE TEXT ABOVE IS HAND-WRITTEN AND FOUND IN THE FILE "keywords_base.txt" +# THE TEXT BELOW IS AUTO-GENERATED + + +abs KEYWORD2 abs_ +acos KEYWORD2 acos_ +alpha KEYWORD2 alpha_ +ambient KEYWORD2 ambient_ +ambientLight KEYWORD2 ambientLight_ +append KEYWORD2 append_ +applyMatrix KEYWORD2 applyMatrix_ +arc KEYWORD2 arc_ +arrayCopy KEYWORD2 arrayCopy_ +asin KEYWORD2 asin_ +atan KEYWORD2 atan_ +atan2 KEYWORD2 atan2_ +background KEYWORD2 background_ +beginCamera KEYWORD2 beginCamera_ +beginRaw KEYWORD2 beginRaw_ +beginRecord KEYWORD2 beginRecord_ +beginShape KEYWORD2 beginShape_ +bezier KEYWORD2 bezier_ +bezierDetail KEYWORD2 bezierDetail_ +bezierPoint KEYWORD2 bezierPoint_ +bezierTangent KEYWORD2 bezierTangent_ +bezierVertex KEYWORD2 bezierVertex_ +binary KEYWORD2 binary_ +blend KEYWORD2 blend_ +blendColor KEYWORD2 blendColor_ +blue KEYWORD2 blue_ +box KEYWORD2 box_ +brightness KEYWORD2 brightness_ +camera KEYWORD2 camera_ +ceil KEYWORD2 ceil_ +color KEYWORD2 color_ +colorMode KEYWORD2 colorMode_ +concat KEYWORD2 concat_ +constrain KEYWORD2 constrain_ +copy KEYWORD2 copy_ +cos KEYWORD2 cos_ +createFont KEYWORD2 createFont_ +createGraphics KEYWORD2 createGraphics_ +createImage KEYWORD2 createImage_ +createInput KEYWORD2 createInput_ +createOutput KEYWORD2 createOutput_ +createReader KEYWORD2 createReader_ +createWriter KEYWORD2 createWriter_ +cursor KEYWORD2 cursor_ +curve KEYWORD2 curve_ +curveDetail KEYWORD2 curveDetail_ +curvePoint KEYWORD2 curvePoint_ +curveTangent KEYWORD2 curveTangent_ +curveTightness KEYWORD2 curveTightness_ +curveVertex KEYWORD2 curveVertex_ +day KEYWORD2 day_ +degrees KEYWORD2 degrees_ +directionalLight KEYWORD2 directionalLight_ +dist KEYWORD2 dist_ +draw KEYWORD2 draw_ +ellipse KEYWORD2 ellipse_ +ellipseMode KEYWORD2 ellipseMode_ +emissive KEYWORD2 emissive_ +endCamera KEYWORD2 endCamera_ +endRaw KEYWORD2 endRaw_ +endRecord KEYWORD2 endRecord_ +endShape KEYWORD2 endShape_ +exit KEYWORD2 exit_ +exp KEYWORD2 exp_ +expand KEYWORD2 expand_ +fill KEYWORD2 fill_ +filter KEYWORD2 filter_ +floor KEYWORD2 floor_ +focused KEYWORD5 focused +frameCount KEYWORD5 frameCount +frameRate KEYWORD5 frameRate +frameRate KEYWORD2 frameRate_ +frustum KEYWORD2 frustum_ +get KEYWORD2 get_ +green KEYWORD2 green_ +HALF_PI LITERAL2 HALF_PI +height KEYWORD5 height +hex KEYWORD2 hex_ +hint KEYWORD2 hint_ +hour KEYWORD2 hour_ +hue KEYWORD2 hue_ +image KEYWORD2 image_ +imageMode KEYWORD2 imageMode_ +join KEYWORD2 join_ +key KEYWORD5 key +keyCode KEYWORD5 keyCode +keyPressed KEYWORD2 keyPressed_ +keyPressed KEYWORD5 keyPressed +keyReleased KEYWORD2 keyReleased_ +keyTyped KEYWORD2 keyTyped_ +lerp KEYWORD2 lerp_ +lerpColor KEYWORD2 lerpColor_ +lightFalloff KEYWORD2 lightFalloff_ +lights KEYWORD2 lights_ +lightSpecular KEYWORD2 lightSpecular_ +line KEYWORD2 line_ +link KEYWORD2 link_ +loadBytes KEYWORD2 loadBytes_ +loadFont KEYWORD2 loadFont_ +loadImage KEYWORD2 loadImage_ +loadPixels KEYWORD2 loadPixels_ +loadShape KEYWORD2 loadShape_ +loadStrings KEYWORD2 loadStrings_ +log KEYWORD2 log_ +loop KEYWORD2 loop_ +mag KEYWORD2 mag_ +map KEYWORD2 map_ +match KEYWORD2 match_ +matchAll KEYWORD2 matchAll_ +max KEYWORD2 max_ +millis KEYWORD2 millis_ +min KEYWORD2 min_ +minute KEYWORD2 minute_ +modelX KEYWORD2 modelX_ +modelY KEYWORD2 modelY_ +modelZ KEYWORD2 modelZ_ +month KEYWORD2 month_ +mouseButton KEYWORD5 mouseButton +mouseClicked KEYWORD2 mouseClicked_ +mouseDragged KEYWORD2 mouseDragged_ +mouseMoved KEYWORD2 mouseMoved_ +mousePressed KEYWORD2 mousePressed_ +mousePressed KEYWORD5 mousePressed +mouseReleased KEYWORD2 mouseReleased_ +mouseX KEYWORD5 mouseX +mouseY KEYWORD5 mouseY +nf KEYWORD2 nf_ +nfc KEYWORD2 nfc_ +nfp KEYWORD2 nfp_ +nfs KEYWORD2 nfs_ +noCursor KEYWORD2 noCursor_ +noFill KEYWORD2 noFill_ +noise KEYWORD2 noise_ +noiseDetail KEYWORD2 noiseDetail_ +noiseSeed KEYWORD2 noiseSeed_ +noLights KEYWORD2 noLights_ +noLoop KEYWORD2 noLoop_ +norm KEYWORD2 norm_ +normal KEYWORD2 normal_ +noSmooth KEYWORD2 noSmooth_ +noStroke KEYWORD2 noStroke_ +noTint KEYWORD2 noTint_ +online KEYWORD5 online +open KEYWORD2 open_ +ortho KEYWORD2 ortho_ +param KEYWORD2 param_ +perspective KEYWORD2 perspective_ +PFont KEYWORD1 PFont +list KEYWORD2 PFont_list_ +PGraphics KEYWORD2 PGraphics_ +beginDraw KEYWORD3 PGraphics_beginDraw_ +endDraw KEYWORD3 PGraphics_endDraw_ +PI LITERAL2 PI +PImage KEYWORD1 PImage +alpha KEYWORD3 PImage_alpha_ +blend KEYWORD3 PImage_blend_ +copy KEYWORD3 PImage_copy_ +filter KEYWORD3 PImage_filter_ +get KEYWORD3 PImage_get_ +height KEYWORD4 PImage_height +loadPixels KEYWORD3 PImage_loadPixels_ +mask KEYWORD3 PImage_mask_ +pixels KEYWORD4 PImage_pixels +resize KEYWORD3 PImage_resize_ +save KEYWORD3 PImage_save_ +set KEYWORD3 PImage_set_ +updatePixels KEYWORD3 PImage_updatePixels_ +width KEYWORD4 PImage_width +pixels KEYWORD5 pixels +pmouseX KEYWORD5 pmouseX +pmouseY KEYWORD5 pmouseY +point KEYWORD2 point_ +pointLight KEYWORD2 pointLight_ +popMatrix KEYWORD2 popMatrix_ +popStyle KEYWORD2 popStyle_ +pow KEYWORD2 pow_ +print KEYWORD2 print_ +printCamera KEYWORD2 printCamera_ +println KEYWORD2 println_ +printMatrix KEYWORD2 printMatrix_ +printProjection KEYWORD2 printProjection_ +PShape KEYWORD1 PShape +disableStyle KEYWORD3 PShape_disableStyle_ +enableStyle KEYWORD3 PShape_enableStyle_ +getChild KEYWORD3 PShape_getChild_ +height KEYWORD4 PShape_height +isVisible KEYWORD3 PShape_isVisible_ +resetMatrix KEYWORD3 PShape_resetMatrix_ +rotate KEYWORD3 PShape_rotate_ +rotateX KEYWORD3 PShape_rotateX_ +rotateY KEYWORD3 PShape_rotateY_ +rotateZ KEYWORD3 PShape_rotateZ_ +scale KEYWORD3 PShape_scale_ +setVisible KEYWORD3 PShape_setVisible_ +translate KEYWORD3 PShape_translate_ +width KEYWORD4 PShape_width +pushMatrix KEYWORD2 pushMatrix_ +pushStyle KEYWORD2 pushStyle_ +PVector KEYWORD1 PVector +add KEYWORD3 PVector_add_ +angleBetween KEYWORD3 PVector_angleBetween_ +array KEYWORD3 PVector_array_ +copy KEYWORD3 PVector_copy_ +cross KEYWORD3 PVector_cross_ +dist KEYWORD3 PVector_dist_ +div KEYWORD3 PVector_div_ +dot KEYWORD3 PVector_dot_ +get KEYWORD3 PVector_get_ +limit KEYWORD3 PVector_limit_ +mag KEYWORD3 PVector_mag_ +mult KEYWORD3 PVector_mult_ +normalize KEYWORD3 PVector_normalize_ +set KEYWORD3 PVector_set_ +setMag KEYWORD3 PVector_setMag_ +sub KEYWORD3 PVector_sub_ +x KEYWORD4 PVector_x +y KEYWORD4 PVector_y +z KEYWORD4 PVector_z +quad KEYWORD2 quad_ +quadraticVertex KEYWORD2 quadraticVertex_ +QUARTER_PI LITERAL2 QUARTER_PI +radians KEYWORD2 radians_ +random KEYWORD2 random_ +randomSeed KEYWORD2 randomSeed_ +rect KEYWORD2 rect_ +rectMode KEYWORD2 rectMode_ +red KEYWORD2 red_ +redraw KEYWORD2 redraw_ +requestImage KEYWORD2 requestImage_ +resetMatrix KEYWORD2 resetMatrix_ +reverse KEYWORD2 reverse_ +rotate KEYWORD2 rotate_ +rotateX KEYWORD2 rotateX_ +rotateY KEYWORD2 rotateY_ +rotateZ KEYWORD2 rotateZ_ +round KEYWORD2 round_ +saturation KEYWORD2 saturation_ +save KEYWORD2 save_ +saveBytes KEYWORD2 saveBytes_ +saveFrame KEYWORD2 saveFrame_ +saveStream KEYWORD2 saveStream_ +saveStrings KEYWORD2 saveStrings_ +scale KEYWORD2 scale_ +screenHeight KEYWORD5 screenHeight +screenWidth KEYWORD5 screenWidth +screenX KEYWORD2 screenX_ +screenY KEYWORD2 screenY_ +screenZ KEYWORD2 screenZ_ +second KEYWORD2 second_ +selectFolder KEYWORD2 selectFolder_ +selectInput KEYWORD2 selectInput_ +selectOutput KEYWORD2 selectOutput_ +set KEYWORD2 set_ +setup KEYWORD2 setup_ +shape KEYWORD2 shape_ +shapeMode KEYWORD2 shapeMode_ +shearX KEYWORD2 shearX_ +shearY KEYWORD2 shearY_ +shininess KEYWORD2 shininess_ +shorten KEYWORD2 shorten_ +sin KEYWORD2 sin_ +size KEYWORD2 size_ +smooth KEYWORD2 smooth_ +sort KEYWORD2 sort_ +specular KEYWORD2 specular_ +sphere KEYWORD2 sphere_ +sphereDetail KEYWORD2 sphereDetail_ +splice KEYWORD2 splice_ +split KEYWORD2 split_ +splitTokens KEYWORD2 splitTokens_ +spotLight KEYWORD2 spotLight_ +sq KEYWORD2 sq_ +sqrt KEYWORD2 sqrt_ +status KEYWORD2 status_ +stroke KEYWORD2 stroke_ +strokeCap KEYWORD2 strokeCap_ +strokeJoin KEYWORD2 strokeJoin_ +strokeWeight KEYWORD2 strokeWeight_ +subset KEYWORD2 subset_ +tan KEYWORD2 tan_ +text KEYWORD2 text_ +textAlign KEYWORD2 textAlign_ +textAscent KEYWORD2 textAscent_ +textDescent KEYWORD2 textDescent_ +textFont KEYWORD2 textFont_ +textLeading KEYWORD2 textLeading_ +textMode KEYWORD2 textMode_ +textSize KEYWORD2 textSize_ +texture KEYWORD2 texture_ +textureMode KEYWORD2 textureMode_ +textWidth KEYWORD2 textWidth_ +tint KEYWORD2 tint_ +translate KEYWORD2 translate_ +triangle KEYWORD2 triangle_ +trim KEYWORD2 trim_ +TWO_PI LITERAL2 TWO_PI +unbinary KEYWORD2 unbinary_ +unhex KEYWORD2 unhex_ +updatePixels KEYWORD2 updatePixels_ +vertex KEYWORD2 vertex_ +width KEYWORD5 width +XMLElement KEYWORD2 XMLElement_ +getChild KEYWORD3 XMLElement_getChild_ +getChildCount KEYWORD3 XMLElement_getChildCount_ +getChildren KEYWORD3 XMLElement_getChildren_ +getContent KEYWORD3 XMLElement_getContent_ +getFloat KEYWORD3 XMLElement_getFloat_ +getInt KEYWORD3 XMLElement_getInt_ +getName KEYWORD3 XMLElement_getName_ +getString KEYWORD3 XMLElement_getString_ +year KEYWORD2 year_ diff --git a/java2/theme/buttons.gif b/java2/theme/buttons.gif new file mode 100755 index 000000000..a16fa330f Binary files /dev/null and b/java2/theme/buttons.gif differ diff --git a/java2/theme/tab-sel-left.gif b/java2/theme/tab-sel-left.gif new file mode 100755 index 000000000..bdee43c25 Binary files /dev/null and b/java2/theme/tab-sel-left.gif differ diff --git a/java2/theme/tab-sel-menu.gif b/java2/theme/tab-sel-menu.gif new file mode 100755 index 000000000..d926650e7 Binary files /dev/null and b/java2/theme/tab-sel-menu.gif differ diff --git a/java2/theme/tab-sel-mid.gif b/java2/theme/tab-sel-mid.gif new file mode 100755 index 000000000..fa8ed45fc Binary files /dev/null and b/java2/theme/tab-sel-mid.gif differ diff --git a/java2/theme/tab-sel-right.gif b/java2/theme/tab-sel-right.gif new file mode 100755 index 000000000..d901fdba4 Binary files /dev/null and b/java2/theme/tab-sel-right.gif differ diff --git a/java2/theme/tab-unsel-left.gif b/java2/theme/tab-unsel-left.gif new file mode 100755 index 000000000..eea22d8d8 Binary files /dev/null and b/java2/theme/tab-unsel-left.gif differ diff --git a/java2/theme/tab-unsel-menu.gif b/java2/theme/tab-unsel-menu.gif new file mode 100755 index 000000000..a1720a589 Binary files /dev/null and b/java2/theme/tab-unsel-menu.gif differ diff --git a/java2/theme/tab-unsel-mid.gif b/java2/theme/tab-unsel-mid.gif new file mode 100755 index 000000000..a2c5497b8 Binary files /dev/null and b/java2/theme/tab-unsel-mid.gif differ diff --git a/java2/theme/tab-unsel-right.gif b/java2/theme/tab-unsel-right.gif new file mode 100755 index 000000000..91c03f5a9 Binary files /dev/null and b/java2/theme/tab-unsel-right.gif differ diff --git a/java2/theme/theme.txt b/java2/theme/theme.txt new file mode 100755 index 000000000..a5d65ec7c --- /dev/null +++ b/java2/theme/theme.txt @@ -0,0 +1,133 @@ +# GUI - STATUS +status.notice.fgcolor = #000000 +status.notice.bgcolor = #818b95 +status.error.fgcolor = #ffffff +status.error.bgcolor = #662000 +status.edit.fgcolor = #000000 +status.edit.bgcolor = #cc9900 +status.font = SansSerif,plain,12 +#status.font.macosx = Helvetica,plain,12 + +# GUI - TABS +# settings for the tabs at the top +# (tab images are stored in the lib/theme folder) +header.bgcolor = #818b95 +header.text.selected.color = #1a1a00 +header.text.unselected.color = #ffffff +header.text.font = SansSerif,plain,12 +#header.text.font.macosx = Helvetica,plain,12 + +# GUI - CONSOLE +# font is handled by preferences, since size/etc is modifiable +console.color = #000000 +console.output.color = #cccccc +console.error.color = #ff3000 + +# GUI - BUTTONS +buttons.bgcolor = #4a545e +buttons.status.font = SansSerif,plain,12 +#buttons.status.font.macosx = Helvetica,plain,12 +buttons.status.color = #ffffff + +# GUI - MODE +#mode.button.bgcolor = #9ca6b0 +mode.button.font = SansSerif,plain,9 +#mode.button.font.macosx = Helvetica,plain,9 +#mode.button.color = #4a545e +mode.button.color = #9ca6b0 + +# GUI - LINESTATUS +linestatus.color = #ffffff +linestatus.bgcolor = #29333d + +# EDITOR - DETAILS + +# foreground and background colors +editor.fgcolor = #000000 +editor.bgcolor = #ffffff + +# highlight for the current line +editor.linehighlight.color=#e2e2e2 +# highlight for the current line +editor.linehighlight=true + +# caret blinking and caret color +editor.caret.color = #333300 + +# color to be used for background when 'external editor' enabled +editor.external.bgcolor = #c8d2dc + +# selection color +editor.selection.color = #ffcc00 + +# area that's not in use by the text (replaced with tildes) +editor.invalid.style = #7e7e7e,bold + +# little pooties at the end of lines that show where they finish +editor.eolmarkers = false +editor.eolmarkers.color = #999999 + +# bracket/brace highlighting +editor.brackethighlight = true +editor.brackethighlight.color = #006699 + + +# TEXT - KEYWORDS + +# e.g abstract, final, private +editor.keyword1.style = #cc6600,plain + +# e.g. beginShape, point, line +editor.keyword2.style = #cc6600,plain + +# e.g. byte, char, short, color +editor.keyword3.style = #cc6600,bold + + +# TEXT - LITERALS + +# constants: e.g. null, true, this, RGB, TWO_PI +editor.literal1.style = #006699,plain + +# p5 built in variables: e.g. mouseX, width, pixels +editor.literal2.style = #006699,plain + +# e.g. + - = / +editor.operator.style = #000000,plain + +# ?? maybe this is for words followed by a colon +# like in case statements or goto +editor.label.style = #7e7e7e,bold + + +# TEXT - COMMENTS +editor.comment1.style = #7e7e7e,plain +editor.comment2.style = #7e7e7e,plain + + +# LINE STATUS - editor line number status bar at the bottom of the screen +linestatus.font = SansSerif,plain,10 +#linestatus.font.macosx = Helvetica,plain,10 +linestatus.height = 20 + + +# DEBUGGER + +# breakpointed line background color +breakpoint.bgcolor = #f0f0f0 +# marker for breakpointed lines in left hand gutter (2 ascii characters) +breakpoint.marker = <> +breakpoint.marker.color = #4a545e + +# current line background color +currentline.bgcolor = #ffff96 +# marker for the current line in left hand gutter (2 ascii characters) +currentline.marker = -> +currentline.marker.color = #e27500 + +# left hand gutter background color +gutter.bgcolor = #fcfcfc +# color of vertical separation line +gutter.linecolor = #e9e9e9 +# space (in px) added to left and right of gutter markers +gutter.padding = 3 \ No newline at end of file diff --git a/java2/theme/var-icons.gif b/java2/theme/var-icons.gif new file mode 100755 index 000000000..1d0086a38 Binary files /dev/null and b/java2/theme/var-icons.gif differ diff --git a/todo.txt b/todo.txt index 4767cf490..542487745 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,27 @@ 0215 pde +_ standard "Emacs" keybindings not implemented on OS X +_ http://code.google.com/p/processing/issues/detail?id=1354 +_ http://guides.macrumors.com/Keyboard_shortcuts§ion=10#Text_Shortcuts + +control-A move to start of current paragraph +control-B move left one character +control-D forwards delete +control-E move to end of current paragraph +control-F move right one character +control-H delete +control-K delete remainder of current paragraph +control-N move down one line +control-O insert new line after cursor +control-P move up one line +control-T transpose (swap) two surrounding character +control-V move to end, then left one character +control-Y paste text previously deleted with control-K + +http://download.eclipse.org/eclipse/downloads/ + +_ clientEvent() called even w/o data from server +_ http://code.google.com/p/processing/issues/detail?id=189 +_ check on adding ip() method _ Sketch that exported to Linux doesn't get the command line arguments @@ -21,6 +44,7 @@ _ is editable in use? _ what's electricScroll? _ excessive CPU usage of PDE after using library manager _ http://code.google.com/p/processing/issues/detail?id=1036 +_ confirmed to still be a problem with b5/6 _ remove PdeKeyListener, roll it into the Java InputHandler for JEditTextArea _ move Java-specific InputHandler to its own subclass _ problem changing sketchbook to new location (e.g. from ex to regular) @@ -743,13 +767,6 @@ _ need to unpack InvocationTargetException in xxxxxxEvent calls _ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=VideoCamera;action=display;num=1116850328#3 - -LIBRARIES / Net - -_ clientEvent() called even w/o data from server -_ http://code.google.com/p/processing/issues/detail?id=189 - - LIBRARIES / Serial _ need 64-bit version of RXTX library