Index: trunk/.classpath
===================================================================
--- trunk/.classpath	(revision 1077)
+++ trunk/.classpath	(revision 1078)
@@ -12,4 +12,5 @@
 	<classpathentry kind="lib" path="releases/jars/JFreeCharts.jar"/>
 	<classpathentry kind="lib" path="releases/jars/js.jar"/>
+	<classpathentry kind="lib" path="releases/jars/jython-standalone-2.7.0.jar"/>
 	<classpathentry kind="lib" path="releases/jars/lobo.jar"/>
 	<classpathentry kind="lib" path="releases/jars/lobo-pub.jar"/>
Index: trunk/build.xml
===================================================================
--- trunk/build.xml	(revision 1077)
+++ trunk/build.xml	(revision 1078)
@@ -35,4 +35,5 @@
 		<zipfileset excludes="META-INF/*.SF" src="releases/jars/JFreeCharts.jar"/>
 		<zipfileset excludes="META-INF/*.SF" src="releases/jars/js.jar"/>
+		<zipfileset excludes="META-INF/*.SF" src="releases/jars/jython-standalone-2.7.0.jar"/>
 		<zipfileset excludes="META-INF/*.SF" src="releases/jars/lobo.jar"/>
 		<zipfileset excludes="META-INF/*.SF" src="releases/jars/lobo-pub.jar"/>
Index: trunk/src/org/expeditee/actions/Javascript2.java
===================================================================
--- trunk/src/org/expeditee/actions/Javascript2.java	(revision 1077)
+++ trunk/src/org/expeditee/actions/Javascript2.java	(revision 1078)
@@ -48,238 +48,67 @@
  * @author jts21
  */
-public class Javascript2 {
+public class Javascript2 extends ScriptBase {
 	
-	public static final String ERROR_FRAMESET = "JavascriptErrors";
+    protected void init() {
+	ERROR_FRAMESET = "JavascriptErrors";
+	scriptEngineManager = new ScriptEngineManager();
+	scriptEngine = scriptEngineManager.getEngineByMimeType("application/javascript");
+	scriptEngine.put("invocable", (Invocable) scriptEngine);
+    }
 	
-	public static final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
-	public static final ScriptEngine scriptEngine = scriptEngineManager.getEngineByMimeType("application/javascript");
-	static {
-		scriptEngine.put("invocable", (Invocable) scriptEngine);
+    public static void printJSFrame(Item item) {
+	if(item.getChild() == null) {
+	    // if the user clicked on the action without an item on their cursor
+	    if(item.hasAction()) {
+		boolean isThis = false;
+		for(String s : item.getAction()) {
+		    if(s.equalsIgnoreCase("printJSFrame")) {
+			isThis = true;
+			break;
+		    }
+		}
+		if(isThis) {
+		    System.out.println(new Javascript2(item.getParentOrCurrentFrame(), true));
+		    return;
+		}
+	    }
+	    MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
+	} else {
+	    System.out.println(new Javascript2(item.getChild(), true));
 	}
+    }
 	
-	public static void printJSFrame(Item item) {
-		if(item.getChild() == null) {
-			// if the user clicked on the action without an item on their cursor
-    		if(item.hasAction()) {
-    			boolean isThis = false;
-    			for(String s : item.getAction()) {
-    				if(s.equalsIgnoreCase("runJSFrame")) {
-    					isThis = true;
-    					break;
-    				}
-    			}
-    			if(isThis) {
-    				System.out.println(new Javascript2(item.getParentOrCurrentFrame(), true));
-    				return;
-    			}
-    		}
-    		MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
-		} else {
-			System.out.println(new Javascript2(item.getChild(), true));
+    public static void runJSFrame(Item item) throws Exception {
+	if(item.getChild() == null) {
+	    // if the user clicked on the action without an item on their cursor
+	    if(item.hasAction()) {
+		boolean isThis = false;
+		for(String s : item.getAction()) {
+		    if(s.equalsIgnoreCase("runJSFrame")) {
+			isThis = true;
+			break;
+		    }
 		}
+		if(isThis) {
+		    Javascript2.runFrameFollow(item.getParentOrCurrentFrame());
+		    return;
+		}
+	    }
+	    MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
+	} else {
+	    Javascript2.runFrameFollow(item.getChild());
 	}
+    }
 	
-	public static void runJSFrame(Item item) throws Exception {
-		if(item.getChild() == null) {
-			// if the user clicked on the action without an item on their cursor
-    		if(item.hasAction()) {
-    			boolean isThis = false;
-    			for(String s : item.getAction()) {
-    				if(s.equalsIgnoreCase("runJSFrame")) {
-    					isThis = true;
-    					break;
-    				}
-    			}
-    			if(isThis) {
-    				Javascript2.runFrame(item.getParentOrCurrentFrame(), true);
-    				return;
-    			}
-    		}
-    		MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
-		} else {
-			Javascript2.runFrame(item.getChild(), true);
-		}
-	}
 	
-	public static Object eval(String code) {
-		try {
-	        return scriptEngine.eval(code);
-        } catch (ScriptException e) {
-	        e.printStackTrace();
-        }
-		return null;
-	}
+    private static synchronized void runFrameFollow(Frame frame) throws Exception {
+	Javascript2 js = new Javascript2(frame, true);
+	js.runFrame(frame,true);	
+    }
 	
-	private static synchronized void runFrame(Frame frame, boolean followLinks) throws Exception {
-		Javascript2 js = new Javascript2(frame, followLinks);
-		try {
-    		try {
-                scriptEngine.eval(js.toString());
-            } catch (ScriptException e) {
-        		js.handleError(e.getMessage(), e.getLineNumber());
-            } catch (RuntimeException e) {
-            	// there doesn't seem to be a way to safely get the lineNumber on which the error occurred
-            	// so as a workaround we just parse the exception
-            	if(e.getCause() == null) {
-            		throw e;
-            	}
-            	String detail = e.getCause().getStackTrace()[1].toString();
-            	int lastColon = detail.lastIndexOf(':');
-            	int lastBracket = detail.lastIndexOf(')');
-            	int lineNumber;
-            	if(lastColon == -1 || lastBracket == -1) {
-            		lineNumber = -1;
-            	} else {
-            		lineNumber = Integer.parseInt(detail.substring(lastColon + 1, lastBracket));
-            	}
-            	js.handleError(e.getMessage(), lineNumber);
-            }
-		} catch(Exception e) {
-			js.handleError(null, -1);
-			System.out.println(js.toString());
-			e.printStackTrace();
-		}
-	}
-	
-	private static final class CodeLine {
-		public Text item;
-		public int line;
-		public String source;
-		
-		public CodeLine(Text item, int line, String source) {
-			this.item = item;
-			this.line = line;
-			this.source = source;
-		}
-		
-		public String toString() {
-			return line + ": " + source;
-		}
-	}
-	
-	private List<Frame> seen = new LinkedList<Frame>();
-	private List<CodeLine> lines = new LinkedList<CodeLine>();
-	private StringBuffer sb = new StringBuffer();
-	private Javascript2(Frame frame, boolean followLinks) {
-		this.parseFrame(frame, followLinks);
-	}
-	
-	private void parseFrame(Frame frame, boolean followLinks) {
-		if(frame == null) {
-			return;
-		}
-		
-		// make sure we don't get into an infinite loop
-		// TODO: find a smarter way to do this that allows reusing frames but still stops infinite loops?
-		seen.add(frame);
-		
-		// get all items on the frame
-		List<Item> y_ordered_items = (List<Item>)frame.getItems();
-		// remove the title item
-		y_ordered_items.remove(frame.getTitleItem());
-		
-		XGroupItem toplevel_xgroup = new XGroupItem(frame,y_ordered_items);
-		// ... following on from Steps 1 and 2 in the Constructor in XGroupItem ...
-		
-		// Step 3: Reposition any 'out-of-flow' XGroupItems
-		toplevel_xgroup.repositionOutOfFlowGroups(toplevel_xgroup);
-		
-		// Step 4: Now add in the remaining (nested) XGroupItems
-		List<XGroupItem> grouped_item_list = toplevel_xgroup.getGroupedItemList();
-		toplevel_xgroup.mapInXGroupItemsRecursive(grouped_item_list);
-	
-		// Finally, retrieve linear list of all Items, (ordered, Y by X, allowing for overlap, nested-boxing, and arrow flow)
-		List<Item> overlapping_y_ordered_items = toplevel_xgroup.getYXOverlappingItemList(true);	
-		
-		// Loop through the items looking for code and links to new frames
-		for(Item i : overlapping_y_ordered_items) {
-			if(followLinks && i.hasLink()) {
-				Frame child = i.getChild();
-				if(child != null && !seen.contains(child)) {
-					this.parseFrame(child, true);
-				}
-			}
-			if(i instanceof Text && !i.isAnnotation()) {
-				String text = ((Text)i).getText();
-				if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_START) {
-					text = "{";
-				}
-				else if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_END) {
-						text = "}";
-				}
-				
-				int lineNumber = 0;
-				for(String line : text.trim().split("[\\n\\r]+")) {
-					sb.append(line).append("\n");
-					lines.add(new CodeLine((Text)i, lineNumber++, line));
-				}
-			}
-		}
-	}
-	
-	private void handleError(String message, int lineNumber) throws Exception {
-		// negative line number bad
-		if(lineNumber < 0) {
-			MessageBay.errorMessage("Failed to determine the line on which the error occurred");
-			return;
-		}
-		// if for some reason the error is after the end of the code, assume it should be the last line
-		if(lineNumber > this.lines.size()) {
-			lineNumber = this.lines.size();
-		}
-		CodeLine cl = this.lines.get(lineNumber - 1);
-		Frame errorSourceFrame = cl.item.getParent();
-		if(errorSourceFrame == null) {
-			MessageBay.errorMessage("Failed to find frame on which the error occurred");
-			return;
-		}
-		Frame errorFrame;
-		String title = "Error parsing \"" + errorSourceFrame.getTitle() + "\" (" + errorSourceFrame.getName() + ")";
-		if(FrameIO.canAccessFrameset(ERROR_FRAMESET)) {
-			errorFrame = FrameIO.CreateFrame(ERROR_FRAMESET, title, null);
-		} else {
-			errorFrame = FrameIO.CreateFrameset(ERROR_FRAMESET, FrameIO.FRAME_PATH);
-			errorFrame.setTitle(title);
-		}
-		Collection<Item> toAdd = errorSourceFrame.getAllItems();
-		toAdd.remove(errorSourceFrame.getTitleItem());
-		toAdd.remove(cl.item);
-		errorFrame.addAllItems(ItemUtils.CopyItems(toAdd));
-		String errorItemText = cl.item.getText().trim();
-		String[] errorItemLines = errorItemText.split("[\\n\\r]+");
-		int errorLinePos = 0;
-		int x = cl.item.getX();
-		int y = cl.item.getY();
-		if(cl.line != 0) {
-    		for(int i = 0; i < cl.line; i++) {
-    			errorLinePos += errorItemLines[i].length();
-    		}
-    		Text beforeErrorItem = errorFrame.addText(x, y,
-    				errorItemText.substring(0, errorLinePos), null);
-    		y = beforeErrorItem.getY() + beforeErrorItem.getBoundsHeight();
-		}
-		Text errorItem;
-		errorItem = errorFrame.addText(x, y, errorItemLines[cl.line], null);
-		errorItem.setBackgroundColor(Color.RED);
-		for(String line : message.split("[\\n\\r]+")) {
-			errorItem.setTooltip("text: " + line);
-		}
-		errorItem.setTooltip("font: " + Text.MONOSPACED_FONT);
-		errorItem.setTooltip("width: " + 80 * 12);
-		errorLinePos += errorItemLines[cl.line].length();
-		if(++errorLinePos < errorItemText.length()) {
-			errorFrame.addText(cl.item.getX(), errorItem.getY() + errorItem.getBoundsHeight(),
-					errorItemText.substring(errorLinePos + 1), null);
-		}
-		errorFrame.change();
-		FrameIO.SaveFrame(errorFrame);
-		MessageBay.displayMessage("Script failed at line " + lineNumber +  " - `" + cl.source + "`",
-				errorFrame.getName(), MessageBay.ERROR_COLOR, true, null);
-		FrameUtils.DisplayFrame(errorFrame, true, true);
-	}
-	
-	public String toString() {
-		return this.sb.toString();
-	}
 
+    private Javascript2(Frame frame, boolean followLinks) {
+	super(frame,followLinks);
+    }
+    
 }
Index: trunk/src/org/expeditee/actions/Python.java
===================================================================
--- trunk/src/org/expeditee/actions/Python.java	(revision 1078)
+++ trunk/src/org/expeditee/actions/Python.java	(revision 1078)
@@ -0,0 +1,152 @@
+/**
+ *    Python.java
+ *    Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
+ *
+ *    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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.expeditee.actions;
+
+import java.awt.Color;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.script.Bindings;
+import javax.script.Invocable;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import javax.script.SimpleScriptContext;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.gui.MessageBay;
+import org.expeditee.io.flowlayout.XGroupItem;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+
+/**
+ * Javascript parser.
+ * Works differently to the other Javascript class in that it
+ * parses a frame as a whole rather than parsing individual text items as separate statements
+ * 
+ * @author jts21
+ * @author davidb
+ */
+public class Python extends ScriptBase {
+	
+    protected void init() {
+	ERROR_FRAMESET = "PythonErrors";
+	scriptEngineManager = new ScriptEngineManager();
+	scriptEngine = scriptEngineManager.getEngineByMimeType("application/python");
+	scriptEngine.put("invocable", (Invocable) scriptEngine);
+    }
+    
+    public static void printPythonFrame(Item item) {
+	if(item.getChild() == null) {
+	    // if the user clicked on the action without an item on their cursor
+	    if(item.hasAction()) {
+		boolean isThis = false;
+		for(String s : item.getAction()) {
+		    if(s.equalsIgnoreCase("printPythonFrame")) {
+			isThis = true;
+			break;
+		    }
+		}
+		if(isThis) {
+		    System.out.println(new Python(item.getParentOrCurrentFrame(), true));
+		    return;
+		}
+	    }
+	    MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
+	} else {
+	    System.out.println(new Python(item.getChild(), true));
+	}
+    }
+    
+    public static void runPythonFrame(Item item) throws Exception {
+	if(item.getChild() == null) {
+	    // if the user clicked on the action without an item on their cursor
+	    if(item.hasAction()) {
+		boolean isThis = false;
+		for(String s : item.getAction()) {
+		    if(s.equalsIgnoreCase("runPythonFrame")) {
+			isThis = true;
+			break;
+		    }
+		}
+		if(isThis) {
+		    Python.runFrameFollow(item.getParentOrCurrentFrame());
+		    return;
+		}
+	    }
+	    MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
+	} else {
+	    Python.runFrameFollow(item.getChild());
+	}
+    }
+    
+    
+    private static synchronized void runFrameFollow(Frame frame) throws Exception {
+	Python python = new Python(frame, true);
+	python.runFrame(frame,true);   
+    }
+    
+    private Python(Frame frame, boolean followLinks) {
+	super(frame,followLinks);
+    }
+    
+    
+    protected void processItems(List<Item> overlapping_y_ordered_items, boolean followLinks, int depth) {
+	
+	// Loop through the items looking for code and links to new frames
+	for(Item i : overlapping_y_ordered_items) {
+	    if(followLinks && i.hasLink()) {
+		Frame child = i.getChild();
+		if(child != null && !seen.contains(child)) {
+		    this.parseFrame(child, true, depth);
+		}
+	    }
+	    if(i instanceof Text && !i.isAnnotation()) {
+		String text = ((Text)i).getText();
+		if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_START) {
+		    text = null;
+		    depth++;
+		}
+		else if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_END) {
+		    text = null;
+		    depth--;
+		}
+
+		if (text != null) {
+		    int lineNumber = 0;
+		    for(String line : text.trim().split("[\\n\\r]+")) {
+			if (depth>0) {
+			    sb.append(String.format("%1$" + (4*depth) + "s", "")); // indent with 'depth' x 4 spaces 	
+			}
+			sb.append(line).append("\n");
+			lines.add(new CodeLine((Text)i, lineNumber++, line));
+		    }
+		}
+	    }
+	}
+
+	
+    }
+
+}
Index: trunk/src/org/expeditee/actions/ScriptBase.java
===================================================================
--- trunk/src/org/expeditee/actions/ScriptBase.java	(revision 1078)
+++ trunk/src/org/expeditee/actions/ScriptBase.java	(revision 1078)
@@ -0,0 +1,257 @@
+/**
+ *    ScriptBase.java
+ *    Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
+ *
+ *    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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.expeditee.actions;
+
+import java.awt.Color;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.script.Bindings;
+import javax.script.Invocable;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import javax.script.SimpleScriptContext;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameUtils;
+import org.expeditee.gui.MessageBay;
+import org.expeditee.io.flowlayout.XGroupItem;
+import org.expeditee.items.Item;
+import org.expeditee.items.ItemUtils;
+import org.expeditee.items.Text;
+
+/**
+ * Javascript parser.
+ * Works differently to the other Javascript class in that it
+ * parses a frame as a whole rather than parsing individual text items as separate statements
+ * 
+ * @author jts21
+ * @author davidb
+ */
+public abstract class ScriptBase {
+	
+    public String ERROR_FRAMESET;	
+    public ScriptEngineManager scriptEngineManager;
+    public ScriptEngine scriptEngine = null;
+	
+    protected abstract void init();
+    
+    public Object eval(String code) {
+	try {
+	    return scriptEngine.eval(code);
+        } catch (ScriptException e) {
+	    e.printStackTrace();
+        }
+	return null;
+    }
+	
+    protected synchronized void runFrame(Frame frame, boolean followLinks) throws Exception {
+	try {
+	    try {
+                scriptEngine.eval(this.toString());
+            } catch (ScriptException e) {
+		this.handleError(e.getMessage(), e.getLineNumber());
+            } catch (RuntimeException e) {
+            	// there doesn't seem to be a way to safely get the lineNumber on which the error occurred
+            	// so as a workaround we just parse the exception
+            	if(e.getCause() == null) {
+		    throw e;
+            	}
+            	String detail = e.getCause().getStackTrace()[1].toString();
+            	int lastColon = detail.lastIndexOf(':');
+            	int lastBracket = detail.lastIndexOf(')');
+            	int lineNumber;
+            	if(lastColon == -1 || lastBracket == -1) {
+		    lineNumber = -1;
+            	} else {
+		    lineNumber = Integer.parseInt(detail.substring(lastColon + 1, lastBracket));
+            	}
+            	this.handleError(e.getMessage(), lineNumber);
+            }
+	} catch(Exception e) {
+	    this.handleError(null, -1);
+	    System.out.println(this.toString());
+	    e.printStackTrace();
+	}
+    }
+	
+    protected static final class CodeLine {
+	public Text item;
+	public int line;
+	public String source;
+	
+	public CodeLine(Text item, int line, String source) {
+	    this.item = item;
+	    this.line = line;
+	    this.source = source;
+	}
+	
+	public String toString() {
+	    return line + ": " + source;
+	}
+    }
+	
+    protected List<Frame> seen = new LinkedList<Frame>();
+    protected List<CodeLine> lines = new LinkedList<CodeLine>();
+    protected StringBuffer sb = new StringBuffer();
+
+    protected ScriptBase(Frame frame, boolean followLinks) {
+	if (scriptEngine == null) {
+	    init();
+	}
+	this.parseFrame(frame, followLinks);
+    }
+	
+    protected void parseFrame(Frame frame, boolean followLinks, int depth) {
+	if(frame == null) {
+	    return;
+	}
+	
+	// make sure we don't get into an infinite loop
+	// TODO: find a smarter way to do this that allows reusing frames but still stops infinite loops?
+	seen.add(frame);
+	
+	// get all items on the frame
+	List<Item> y_ordered_items = (List<Item>)frame.getItems();
+	// remove the title item
+	y_ordered_items.remove(frame.getTitleItem());
+	
+	XGroupItem toplevel_xgroup = new XGroupItem(frame,y_ordered_items);
+	// ... following on from Steps 1 and 2 in the Constructor in XGroupItem ...
+	
+	// Step 3: Reposition any 'out-of-flow' XGroupItems
+	toplevel_xgroup.repositionOutOfFlowGroups(toplevel_xgroup);
+	
+	// Step 4: Now add in the remaining (nested) XGroupItems
+	List<XGroupItem> grouped_item_list = toplevel_xgroup.getGroupedItemList();
+	toplevel_xgroup.mapInXGroupItemsRecursive(grouped_item_list);
+	
+	// Finally, retrieve linear list of all Items, (ordered, Y by X, allowing for overlap, nested-boxing, and arrow flow)
+	List<Item> overlapping_y_ordered_items = toplevel_xgroup.getYXOverlappingItemList(true);	
+
+	processItems(overlapping_y_ordered_items, followLinks, depth);
+    }
+
+    protected void parseFrame(Frame frame, boolean followLinks) {
+    	parseFrame(frame,followLinks,0);
+    }
+
+    protected void processItems(List<Item> overlapping_y_ordered_items, boolean followLinks, int depth) {
+
+	// Base version suits syntax that uses curly braces { ... } for block structured code
+	// Override in cases where scriptable language does not do this
+	
+	// Loop through the items looking for code and links to new frames
+	for(Item i : overlapping_y_ordered_items) {
+	    if(followLinks && i.hasLink()) {
+		Frame child = i.getChild();
+		if(child != null && !seen.contains(child)) {
+		    this.parseFrame(child, true, depth);
+		}
+	    }
+	    if(i instanceof Text && !i.isAnnotation()) {
+		String text = ((Text)i).getText();
+		if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_START) {
+		    text = "{";
+		    depth++;
+		}
+		else if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_END) {
+		    text = "}";
+		    depth--;
+		}
+
+		int lineNumber = 0;
+		for(String line : text.trim().split("[\\n\\r]+")) {
+		    sb.append(line).append("\n");
+		    lines.add(new CodeLine((Text)i, lineNumber++, line));
+		}
+	    }
+	}	
+    }
+    
+    private void handleError(String message, int lineNumber) throws Exception {
+	// negative line number bad
+	if(lineNumber < 0) {
+	    MessageBay.errorMessage("Failed to determine the line on which the error occurred");
+	    return;
+	}
+	// if for some reason the error is after the end of the code, assume it should be the last line
+	if(lineNumber > this.lines.size()) {
+	    lineNumber = this.lines.size();
+	}
+	CodeLine cl = this.lines.get(lineNumber - 1);
+	Frame errorSourceFrame = cl.item.getParent();
+	if(errorSourceFrame == null) {
+	    MessageBay.errorMessage("Failed to find frame on which the error occurred");
+	    return;
+	}
+	Frame errorFrame;
+	String title = "Error parsing \"" + errorSourceFrame.getTitle() + "\" (" + errorSourceFrame.getName() + ")";
+	if(FrameIO.canAccessFrameset(ERROR_FRAMESET)) {
+	    errorFrame = FrameIO.CreateFrame(ERROR_FRAMESET, title, null);
+	} else {
+	    errorFrame = FrameIO.CreateFrameset(ERROR_FRAMESET, FrameIO.FRAME_PATH);
+	    errorFrame.setTitle(title);
+	}
+	Collection<Item> toAdd = errorSourceFrame.getAllItems();
+	toAdd.remove(errorSourceFrame.getTitleItem());
+	toAdd.remove(cl.item);
+	errorFrame.addAllItems(ItemUtils.CopyItems(toAdd));
+	String errorItemText = cl.item.getText().trim();
+	String[] errorItemLines = errorItemText.split("[\\n\\r]+");
+	int errorLinePos = 0;
+	int x = cl.item.getX();
+	int y = cl.item.getY();
+	if(cl.line != 0) {
+	    for(int i = 0; i < cl.line; i++) {
+		errorLinePos += errorItemLines[i].length();
+	    }
+	    Text beforeErrorItem = errorFrame.addText(x, y,
+						      errorItemText.substring(0, errorLinePos), null);
+	    y = beforeErrorItem.getY() + beforeErrorItem.getBoundsHeight();
+	}
+	Text errorItem;
+	errorItem = errorFrame.addText(x, y, errorItemLines[cl.line], null);
+	errorItem.setBackgroundColor(Color.RED);
+	for(String line : message.split("[\\n\\r]+")) {
+	    errorItem.setTooltip("text: " + line);
+	}
+	errorItem.setTooltip("font: " + Text.MONOSPACED_FONT);
+	errorItem.setTooltip("width: " + 80 * 12);
+	errorLinePos += errorItemLines[cl.line].length();
+	if(++errorLinePos < errorItemText.length()) {
+	    errorFrame.addText(cl.item.getX(), errorItem.getY() + errorItem.getBoundsHeight(),
+			       errorItemText.substring(errorLinePos + 1), null);
+	}
+	errorFrame.change();
+	FrameIO.SaveFrame(errorFrame);
+	MessageBay.displayMessage("Script failed at line " + lineNumber +  " - `" + cl.source + "`",
+				  errorFrame.getName(), MessageBay.ERROR_COLOR, true, null);
+	FrameUtils.DisplayFrame(errorFrame, true, true);
+    }
+    
+    public String toString() {
+	return this.sb.toString();
+    }
+
+}
Index: trunk/src/org/expeditee/items/widgets/JSWidget.java
===================================================================
--- trunk/src/org/expeditee/items/widgets/JSWidget.java	(revision 1077)
+++ trunk/src/org/expeditee/items/widgets/JSWidget.java	(revision 1078)
@@ -43,5 +43,5 @@
 public class JSWidget extends DataFrameWidget implements JSThreadable {
 	
-	private static final Object global = Javascript2.eval("new Object()");
+        private static Object global = null;
 	
 	// a method to run that will set up and return the root JComponent for this Widget
@@ -68,4 +68,7 @@
 	private JSWidget(Text source, int width, int height, String init, String save, String load) throws Exception {
 		super(source, new JPanel(new BorderLayout()), -1, width, -1, -1, height, -1);
+		if (global == null) {
+		    global = Javascript2.eval("new Object()");
+		}
 		this.init = init;
 		this.save = save;
