diff --git a/src/helma/scripting/rhino/debug/AbstractCellEditor.java b/src/helma/scripting/rhino/debug/AbstractCellEditor.java new file mode 100644 index 00000000..0a7317d0 --- /dev/null +++ b/src/helma/scripting/rhino/debug/AbstractCellEditor.java @@ -0,0 +1,63 @@ + + package helma.scripting.rhino.debug; + import java.awt.Component; + +import java.awt.event.*; +import java.awt.AWTEvent; +import javax.swing.*; +import javax.swing.event.*; +import java.util.EventObject; +import java.io.Serializable; + +public class AbstractCellEditor implements CellEditor { + + protected EventListenerList listenerList = new EventListenerList(); + + public Object getCellEditorValue() { return null; } + public boolean isCellEditable(EventObject e) { return true; } + public boolean shouldSelectCell(EventObject anEvent) { return false; } + public boolean stopCellEditing() { return true; } + public void cancelCellEditing() {} + + public void addCellEditorListener(CellEditorListener l) { + listenerList.add(CellEditorListener.class, l); + } + + public void removeCellEditorListener(CellEditorListener l) { + listenerList.remove(CellEditorListener.class, l); + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. + * @see EventListenerList + */ + protected void fireEditingStopped() { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + ((CellEditorListener)listeners[i+1]).editingStopped(new ChangeEvent(this)); + } + } + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. + * @see EventListenerList + */ + protected void fireEditingCanceled() { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + ((CellEditorListener)listeners[i+1]).editingCanceled(new ChangeEvent(this)); + } + } + } +} diff --git a/src/helma/scripting/rhino/debug/AbstractTreeTableModel.java b/src/helma/scripting/rhino/debug/AbstractTreeTableModel.java new file mode 100644 index 00000000..31b91495 --- /dev/null +++ b/src/helma/scripting/rhino/debug/AbstractTreeTableModel.java @@ -0,0 +1,198 @@ +/* + * @(#)AbstractTreeTableModel.java 1.2 98/10/27 + * + * Copyright 1997, 1998 by Sun Microsystems, Inc., + * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. + * All rights reserved. + * + * This software is the confidential and proprietary information + * of Sun Microsystems, Inc. ("Confidential Information"). You + * shall not disclose such Confidential Information and shall use + * it only in accordance with the terms of the license agreement + * you entered into with Sun. + */ + + + package helma.scripting.rhino.debug; + import javax.swing.tree.*; + +import javax.swing.event.*; + +/** + * @version 1.2 10/27/98 + * An abstract implementation of the TreeTableModel interface, handling the list + * of listeners. + * @author Philip Milne + */ + +public abstract class AbstractTreeTableModel implements TreeTableModel { + protected Object root; + protected EventListenerList listenerList = new EventListenerList(); + + public AbstractTreeTableModel(Object root) { + this.root = root; + } + + // + // Default implmentations for methods in the TreeModel interface. + // + + public Object getRoot() { + return root; + } + + public boolean isLeaf(Object node) { + return getChildCount(node) == 0; + } + + public void valueForPathChanged(TreePath path, Object newValue) {} + + // This is not called in the JTree's default mode: use a naive implementation. + public int getIndexOfChild(Object parent, Object child) { + for (int i = 0; i < getChildCount(parent); i++) { + if (getChild(parent, i).equals(child)) { + return i; + } + } + return -1; + } + + public void addTreeModelListener(TreeModelListener l) { + listenerList.add(TreeModelListener.class, l); + } + + public void removeTreeModelListener(TreeModelListener l) { + listenerList.remove(TreeModelListener.class, l); + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. The event instance + * is lazily created using the parameters passed into + * the fire method. + * @see EventListenerList + */ + protected void fireTreeNodesChanged(Object source, Object[] path, + int[] childIndices, + Object[] children) { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + TreeModelEvent e = null; + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // Lazily create the event: + if (e == null) + e = new TreeModelEvent(source, path, + childIndices, children); + ((TreeModelListener)listeners[i+1]).treeNodesChanged(e); + } + } + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. The event instance + * is lazily created using the parameters passed into + * the fire method. + * @see EventListenerList + */ + protected void fireTreeNodesInserted(Object source, Object[] path, + int[] childIndices, + Object[] children) { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + TreeModelEvent e = null; + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // Lazily create the event: + if (e == null) + e = new TreeModelEvent(source, path, + childIndices, children); + ((TreeModelListener)listeners[i+1]).treeNodesInserted(e); + } + } + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. The event instance + * is lazily created using the parameters passed into + * the fire method. + * @see EventListenerList + */ + protected void fireTreeNodesRemoved(Object source, Object[] path, + int[] childIndices, + Object[] children) { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + TreeModelEvent e = null; + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // Lazily create the event: + if (e == null) + e = new TreeModelEvent(source, path, + childIndices, children); + ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e); + } + } + } + + /* + * Notify all listeners that have registered interest for + * notification on this event type. The event instance + * is lazily created using the parameters passed into + * the fire method. + * @see EventListenerList + */ + protected void fireTreeStructureChanged(Object source, Object[] path, + int[] childIndices, + Object[] children) { + // Guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + TreeModelEvent e = null; + // Process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // Lazily create the event: + if (e == null) + e = new TreeModelEvent(source, path, + childIndices, children); + ((TreeModelListener)listeners[i+1]).treeStructureChanged(e); + } + } + } + + // + // Default impelmentations for methods in the TreeTableModel interface. + // + + public Class getColumnClass(int column) { return Object.class; } + + /** By default, make the column with the Tree in it the only editable one. + * Making this column editable causes the JTable to forward mouse + * and keyboard events in the Tree column to the underlying JTree. + */ + public boolean isCellEditable(Object node, int column) { + return getColumnClass(column) == TreeTableModel.class; + } + + public void setValueAt(Object aValue, Object node, int column) {} + + + // Left to be implemented in the subclass: + + /* + * public Object getChild(Object parent, int index) + * public int getChildCount(Object parent) + * public int getColumnCount() + * public String getColumnName(Object node, int column) + * public Object getValueAt(Object node, int column) + */ +} diff --git a/src/helma/scripting/rhino/debug/JTreeTable.java b/src/helma/scripting/rhino/debug/JTreeTable.java new file mode 100644 index 00000000..f235086b --- /dev/null +++ b/src/helma/scripting/rhino/debug/JTreeTable.java @@ -0,0 +1,356 @@ +/* + * @(#)JTreeTable.java 1.2 98/10/27 + * + * Copyright 1997, 1998 by Sun Microsystems, Inc., + * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. + * All rights reserved. + * + * This software is the confidential and proprietary information + * of Sun Microsystems, Inc. ("Confidential Information"). You + * shall not disclose such Confidential Information and shall use + * it only in accordance with the terms of the license agreement + * you entered into with Sun. + */ + + + package helma.scripting.rhino.debug; + import javax.swing.*; + +import javax.swing.event.*; +import javax.swing.tree.*; +import javax.swing.table.*; + +import java.awt.Dimension; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; + +import java.awt.event.MouseEvent; + +import java.util.EventObject; + +/** + * This example shows how to create a simple JTreeTable component, + * by using a JTree as a renderer (and editor) for the cells in a + * particular column in the JTable. + * + * @version 1.2 10/27/98 + * + * @author Philip Milne + * @author Scott Violet + */ +public class JTreeTable extends JTable { + /** A subclass of JTree. */ + protected TreeTableCellRenderer tree; + + public JTreeTable(TreeTableModel treeTableModel) { + super(); + + // Create the tree. It will be used as a renderer and editor. + tree = new TreeTableCellRenderer(treeTableModel); + + // Install a tableModel representing the visible rows in the tree. + super.setModel(new TreeTableModelAdapter(treeTableModel, tree)); + + // Force the JTable and JTree to share their row selection models. + ListToTreeSelectionModelWrapper selectionWrapper = new + ListToTreeSelectionModelWrapper(); + tree.setSelectionModel(selectionWrapper); + setSelectionModel(selectionWrapper.getListSelectionModel()); + + // Install the tree editor renderer and editor. + setDefaultRenderer(TreeTableModel.class, tree); + setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); + + // No grid. + setShowGrid(false); + + // No intercell spacing + setIntercellSpacing(new Dimension(0, 0)); + + // And update the height of the trees row to match that of + // the table. + if (tree.getRowHeight() < 1) { + // Metal looks better like this. + setRowHeight(18); + } + } + + /** + * Overridden to message super and forward the method to the tree. + * Since the tree is not actually in the component hieachy it will + * never receive this unless we forward it in this manner. + */ + public void updateUI() { + super.updateUI(); + if(tree != null) { + tree.updateUI(); + } + // Use the tree's default foreground and background colors in the + // table. + LookAndFeel.installColorsAndFont(this, "Tree.background", + "Tree.foreground", "Tree.font"); + } + + /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to + * paint the editor. The UI currently uses different techniques to + * paint the renderers and editors and overriding setBounds() below + * is not the right thing to do for an editor. Returning -1 for the + * editing row in this case, ensures the editor is never painted. + */ + public int getEditingRow() { + return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : + editingRow; + } + + /** + * Overridden to pass the new rowHeight to the tree. + */ + public void setRowHeight(int rowHeight) { + super.setRowHeight(rowHeight); + if (tree != null && tree.getRowHeight() != rowHeight) { + tree.setRowHeight(getRowHeight()); + } + } + + /** + * Returns the tree that is being shared between the model. + */ + public JTree getTree() { + return tree; + } + + /** + * A TreeCellRenderer that displays a JTree. + */ + public class TreeTableCellRenderer extends JTree implements + TableCellRenderer { + /** Last table/tree row asked to renderer. */ + protected int visibleRow; + + public TreeTableCellRenderer(TreeModel model) { + super(model); + } + + /** + * updateUI is overridden to set the colors of the Tree's renderer + * to match that of the table. + */ + public void updateUI() { + super.updateUI(); + // Make the tree's cell renderer use the table's cell selection + // colors. + TreeCellRenderer tcr = getCellRenderer(); + if (tcr instanceof DefaultTreeCellRenderer) { + DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); + // For 1.1 uncomment this, 1.2 has a bug that will cause an + // exception to be thrown if the border selection color is + // null. + // dtcr.setBorderSelectionColor(null); + dtcr.setTextSelectionColor(UIManager.getColor + ("Table.selectionForeground")); + dtcr.setBackgroundSelectionColor(UIManager.getColor + ("Table.selectionBackground")); + } + } + + /** + * Sets the row height of the tree, and forwards the row height to + * the table. + */ + public void setRowHeight(int rowHeight) { + if (rowHeight > 0) { + super.setRowHeight(rowHeight); + if (JTreeTable.this != null && + JTreeTable.this.getRowHeight() != rowHeight) { + JTreeTable.this.setRowHeight(getRowHeight()); + } + } + } + + /** + * This is overridden to set the height to match that of the JTable. + */ + public void setBounds(int x, int y, int w, int h) { + super.setBounds(x, 0, w, JTreeTable.this.getHeight()); + } + + /** + * Sublcassed to translate the graphics such that the last visible + * row will be drawn at 0,0. + */ + public void paint(Graphics g) { + g.translate(0, -visibleRow * getRowHeight()); + super.paint(g); + } + + /** + * TreeCellRenderer method. Overridden to update the visible row. + */ + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, int column) { + if(isSelected) + setBackground(table.getSelectionBackground()); + else + setBackground(table.getBackground()); + + visibleRow = row; + return this; + } + } + + + /** + * TreeTableCellEditor implementation. Component returned is the + * JTree. + */ + public class TreeTableCellEditor extends AbstractCellEditor implements + TableCellEditor { + public Component getTableCellEditorComponent(JTable table, + Object value, + boolean isSelected, + int r, int c) { + return tree; + } + + /** + * Overridden to return false, and if the event is a mouse event + * it is forwarded to the tree.
+ * The behavior for this is debatable, and should really be offered + * as a property. By returning false, all keyboard actions are + * implemented in terms of the table. By returning true, the + * tree would get a chance to do something with the keyboard + * events. For the most part this is ok. But for certain keys, + * such as left/right, the tree will expand/collapse where as + * the table focus should really move to a different column. Page + * up/down should also be implemented in terms of the table. + * By returning false this also has the added benefit that clicking + * outside of the bounds of the tree node, but still in the tree + * column will select the row, whereas if this returned true + * that wouldn't be the case. + *
By returning false we are also enforcing the policy that
+ * the tree will never be editable (at least by a key sequence).
+ */
+ public boolean isCellEditable(EventObject e) {
+ if (e instanceof MouseEvent) {
+ for (int counter = getColumnCount() - 1; counter >= 0;
+ counter--) {
+ if (getColumnClass(counter) == TreeTableModel.class) {
+ MouseEvent me = (MouseEvent)e;
+ MouseEvent newME = new MouseEvent(tree, me.getID(),
+ me.getWhen(), me.getModifiers(),
+ me.getX() - getCellRect(0, counter, true).x,
+ me.getY(), me.getClickCount(),
+ me.isPopupTrigger());
+ tree.dispatchEvent(newME);
+ break;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+
+ /**
+ * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
+ * to listen for changes in the ListSelectionModel it maintains. Once
+ * a change in the ListSelectionModel happens, the paths are updated
+ * in the DefaultTreeSelectionModel.
+ */
+ class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
+ /** Set to true when we are updating the ListSelectionModel. */
+ protected boolean updatingListSelectionModel;
+
+ public ListToTreeSelectionModelWrapper() {
+ super();
+ getListSelectionModel().addListSelectionListener
+ (createListSelectionListener());
+ }
+
+ /**
+ * Returns the list selection model. ListToTreeSelectionModelWrapper
+ * listens for changes to this model and updates the selected paths
+ * accordingly.
+ */
+ ListSelectionModel getListSelectionModel() {
+ return listSelectionModel;
+ }
+
+ /**
+ * This is overridden to set updatingListSelectionModel
+ * and message super. This is the only place DefaultTreeSelectionModel
+ * alters the ListSelectionModel.
+ */
+ public void resetRowSelection() {
+ if(!updatingListSelectionModel) {
+ updatingListSelectionModel = true;
+ try {
+ super.resetRowSelection();
+ }
+ finally {
+ updatingListSelectionModel = false;
+ }
+ }
+ // Notice how we don't message super if
+ // updatingListSelectionModel is true. If
+ // updatingListSelectionModel is true, it implies the
+ // ListSelectionModel has already been updated and the
+ // paths are the only thing that needs to be updated.
+ }
+
+ /**
+ * Creates and returns an instance of ListSelectionHandler.
+ */
+ protected ListSelectionListener createListSelectionListener() {
+ return new ListSelectionHandler();
+ }
+
+ /**
+ * If updatingListSelectionModel
is false, this will
+ * reset the selected paths from the selected rows in the list
+ * selection model.
+ */
+ protected void updateSelectedPathsFromSelectedRows() {
+ if(!updatingListSelectionModel) {
+ updatingListSelectionModel = true;
+ try {
+ // This is way expensive, ListSelectionModel needs an
+ // enumerator for iterating.
+ int min = listSelectionModel.getMinSelectionIndex();
+ int max = listSelectionModel.getMaxSelectionIndex();
+
+ clearSelection();
+ if(min != -1 && max != -1) {
+ for(int counter = min; counter <= max; counter++) {
+ if(listSelectionModel.isSelectedIndex(counter)) {
+ TreePath selPath = tree.getPathForRow
+ (counter);
+
+ if(selPath != null) {
+ addSelectionPath(selPath);
+ }
+ }
+ }
+ }
+ }
+ finally {
+ updatingListSelectionModel = false;
+ }
+ }
+ }
+
+ /**
+ * Class responsible for calling updateSelectedPathsFromSelectedRows
+ * when the selection of the list changse.
+ */
+ class ListSelectionHandler implements ListSelectionListener {
+ public void valueChanged(ListSelectionEvent e) {
+ updateSelectedPathsFromSelectedRows();
+ }
+ }
+ }
+}
diff --git a/src/helma/scripting/rhino/debug/Main.java b/src/helma/scripting/rhino/debug/Main.java
new file mode 100644
index 00000000..06ad12b7
--- /dev/null
+++ b/src/helma/scripting/rhino/debug/Main.java
@@ -0,0 +1,3640 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino JavaScript Debugger code, released
+ * November 21, 2000.
+ *
+ * The Initial Developer of the Original Code is SeeBeyond Corporation.
+
+ * Portions created by SeeBeyond are
+ * Copyright (C) 2000 SeeBeyond Technology Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ * Christopher Oliver
+ * Matt Gould
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package helma.scripting.rhino.debug;
+
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.StringTokenizer;
+
+import org.mozilla.javascript.*;
+import org.mozilla.javascript.debug.*;
+import org.mozilla.javascript.tools.shell.ConsoleTextArea;
+import java.util.*;
+import java.io.*;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import java.lang.reflect.Method;
+import java.net.URL;
+
+class MessageDialogWrapper {
+
+ static void showMessageDialog(Component parent, String msg,
+ String title, int flags) {
+ if (msg.length() > 60) {
+ StringBuffer buf = new StringBuffer();
+ int len = msg.length();
+ int j = 0;
+ int i;
+ for (i = 0; i < len; i++, j++) {
+ char c = msg.charAt(i);
+ buf.append(c);
+ if (Character.isWhitespace(c)) {
+ int remainder = len - i;
+ int k;
+ for (k = i + 1; k < len; k++) {
+ if (Character.isWhitespace(msg.charAt(k))) {
+ break;
+ }
+ }
+ if (k < len) {
+ int nextWordLen = k - i;
+ if (j + nextWordLen > 60) {
+ buf.append('\n');
+ j = 0;
+ }
+ }
+ }
+ }
+ msg = buf.toString();
+ }
+ JOptionPane.showMessageDialog(parent, msg, title, flags);
+ }
+};
+
+class EvalTextArea extends JTextArea implements KeyListener,
+DocumentListener {
+ Main db;
+ private java.util.Vector history;
+ private int historyIndex = -1;
+ private int outputMark = 0;
+
+ public void select(int start, int end) {
+ //requestFocus();
+ super.select(start, end);
+ }
+
+ public EvalTextArea(Main db) {
+ super();
+ this.db = db;
+ history = new java.util.Vector();
+ Document doc = getDocument();
+ doc.addDocumentListener(this);
+ addKeyListener(this);
+ setLineWrap(true);
+ setFont(new Font("Monospaced", 0, 12));
+ append("% ");
+ outputMark = doc.getLength();
+ }
+
+ synchronized void returnPressed() {
+ Document doc = getDocument();
+ int len = doc.getLength();
+ Segment segment = new Segment();
+ try {
+ doc.getText(outputMark, len - outputMark, segment);
+ } catch (javax.swing.text.BadLocationException ignored) {
+ ignored.printStackTrace();
+ }
+ String text = segment.toString();
+ if (db.stringIsCompilableUnit(text)) {
+ if (text.trim().length() > 0) {
+ history.addElement(text);
+ historyIndex = history.size();
+ }
+ append("\n");
+ String result = db.eval(text);
+ if (result.length() > 0) {
+ append(result);
+ append("\n");
+ }
+ append("% ");
+ outputMark = doc.getLength();
+ } else {
+ append("\n");
+ }
+ }
+
+ public void keyPressed(KeyEvent e) {
+ int code = e.getKeyCode();
+ if (code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_LEFT) {
+ if (outputMark == getCaretPosition()) {
+ e.consume();
+ }
+ } else if (code == KeyEvent.VK_HOME) {
+ int caretPos = getCaretPosition();
+ if (caretPos == outputMark) {
+ e.consume();
+ } else if (caretPos > outputMark) {
+ if (!e.isControlDown()) {
+ if (e.isShiftDown()) {
+ moveCaretPosition(outputMark);
+ } else {
+ setCaretPosition(outputMark);
+ }
+ e.consume();
+ }
+ }
+ } else if (code == KeyEvent.VK_ENTER) {
+ returnPressed();
+ e.consume();
+ } else if (code == KeyEvent.VK_UP) {
+ historyIndex--;
+ if (historyIndex >= 0) {
+ if (historyIndex >= history.size()) {
+ historyIndex = history.size() -1;
+ }
+ if (historyIndex >= 0) {
+ String str = (String)history.elementAt(historyIndex);
+ int len = getDocument().getLength();
+ replaceRange(str, outputMark, len);
+ int caretPos = outputMark + str.length();
+ select(caretPos, caretPos);
+ } else {
+ historyIndex++;
+ }
+ } else {
+ historyIndex++;
+ }
+ e.consume();
+ } else if (code == KeyEvent.VK_DOWN) {
+ int caretPos = outputMark;
+ if (history.size() > 0) {
+ historyIndex++;
+ if (historyIndex < 0) {historyIndex = 0;}
+ int len = getDocument().getLength();
+ if (historyIndex < history.size()) {
+ String str = (String)history.elementAt(historyIndex);
+ replaceRange(str, outputMark, len);
+ caretPos = outputMark + str.length();
+ } else {
+ historyIndex = history.size();
+ replaceRange("", outputMark, len);
+ }
+ }
+ select(caretPos, caretPos);
+ e.consume();
+ }
+ }
+
+ public void keyTyped(KeyEvent e) {
+ int keyChar = e.getKeyChar();
+ if (keyChar == 0x8 /* KeyEvent.VK_BACK_SPACE */) {
+ if (outputMark == getCaretPosition()) {
+ e.consume();
+ }
+ } else if (getCaretPosition() < outputMark) {
+ setCaretPosition(outputMark);
+ }
+ }
+
+ public synchronized void keyReleased(KeyEvent e) {
+ }
+
+ public synchronized void write(String str) {
+ insert(str, outputMark);
+ int len = str.length();
+ outputMark += len;
+ select(outputMark, outputMark);
+ }
+
+ public synchronized void insertUpdate(DocumentEvent e) {
+ int len = e.getLength();
+ int off = e.getOffset();
+ if (outputMark > off) {
+ outputMark += len;
+ }
+ }
+
+ public synchronized void removeUpdate(DocumentEvent e) {
+ int len = e.getLength();
+ int off = e.getOffset();
+ if (outputMark > off) {
+ if (outputMark >= off + len) {
+ outputMark -= len;
+ } else {
+ outputMark = off;
+ }
+ }
+ }
+
+ public synchronized void postUpdateUI() {
+ // this attempts to cleanup the damage done by updateComponentTreeUI
+ //requestFocus();
+ setCaret(getCaret());
+ select(outputMark, outputMark);
+ }
+
+ public synchronized void changedUpdate(DocumentEvent e) {
+ }
+};
+
+class EvalWindow extends JInternalFrame
+implements ActionListener {
+
+ EvalTextArea evalTextArea;
+
+ public void setEnabled(boolean b) {
+ super.setEnabled(b);
+ evalTextArea.setEnabled(b);
+ }
+
+ public EvalWindow(String name, Main db) {
+ super(name, true, false, true, true);
+ evalTextArea = new EvalTextArea(db);
+ evalTextArea.setRows(24);
+ evalTextArea.setColumns(80);
+ JScrollPane scroller = new JScrollPane(evalTextArea);
+ setContentPane(scroller);
+ //scroller.setPreferredSize(new Dimension(600, 400));
+ pack();
+ setVisible(true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Cut")) {
+ evalTextArea.cut();
+ } else if (cmd.equals("Copy")) {
+ evalTextArea.copy();
+ } else if (cmd.equals("Paste")) {
+ evalTextArea.paste();
+ }
+ }
+};
+
+class JSInternalConsole extends JInternalFrame
+ implements ActionListener {
+
+ ConsoleTextArea consoleTextArea;
+
+ public InputStream getIn() {
+ return consoleTextArea.getIn();
+ }
+
+ public PrintStream getOut() {
+ return consoleTextArea.getOut();
+ }
+
+ public PrintStream getErr() {
+ return consoleTextArea.getErr();
+ }
+
+ public JSInternalConsole(String name) {
+ super(name, true, false, true, true);
+ consoleTextArea = new ConsoleTextArea(null);
+ consoleTextArea.setRows(24);
+ consoleTextArea.setColumns(80);
+ JScrollPane scroller = new JScrollPane(consoleTextArea);
+ setContentPane(scroller);
+ pack();
+ addInternalFrameListener(new InternalFrameAdapter() {
+ public void internalFrameActivated(InternalFrameEvent e) {
+ // hack
+ if (consoleTextArea.hasFocus()) {
+ consoleTextArea.getCaret().setVisible(false);
+ consoleTextArea.getCaret().setVisible(true);
+ }
+ }
+ });
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Cut")) {
+ consoleTextArea.cut();
+ } else if (cmd.equals("Copy")) {
+ consoleTextArea.copy();
+ } else if (cmd.equals("Paste")) {
+ consoleTextArea.paste();
+ }
+ }
+
+
+};
+
+class FilePopupMenu extends JPopupMenu {
+ FileTextArea w;
+ int x, y;
+
+ FilePopupMenu(FileTextArea w) {
+ super();
+ this.w = w;
+ JMenuItem item;
+ add(item = new JMenuItem("Set Breakpoint"));
+ item.addActionListener(w);
+ add(item = new JMenuItem("Clear Breakpoint"));
+ item.addActionListener(w);
+ //add(item = new JMenuItem("Run to Cursor"));
+ //item.addActionListener(w);
+ add(item = new JMenuItem("Run"));
+ item.addActionListener(w);
+ }
+
+ void show(JComponent comp, int x, int y) {
+ this.x = x;
+ this.y = y;
+ super.show(comp, x, y);
+ }
+};
+
+class FileTextArea extends JTextArea implements ActionListener,
+ PopupMenuListener,
+ KeyListener,
+ MouseListener {
+ FileWindow w;
+ FilePopupMenu popup;
+
+ FileTextArea(FileWindow w) {
+ this.w = w;
+ popup = new FilePopupMenu(this);
+ popup.addPopupMenuListener(this);
+ addMouseListener(this);
+ addKeyListener(this);
+ setFont(new Font("Monospaced", 0, 12));
+ }
+
+ void select(int pos) {
+ if (pos >= 0) {
+ try {
+ int line = getLineOfOffset(pos);
+ Rectangle rect = modelToView(pos);
+ if (rect == null) {
+ select(pos, pos);
+ } else {
+ try {
+ Rectangle nrect =
+ modelToView(getLineStartOffset(line + 1));
+ if (nrect != null) {
+ rect = nrect;
+ }
+ } catch (Exception exc) {
+ }
+ JViewport vp = (JViewport)getParent();
+ Rectangle viewRect = vp.getViewRect();
+ if (viewRect.y + viewRect.height > rect.y) {
+ // need to scroll up
+ select(pos, pos);
+ } else {
+ // need to scroll down
+ rect.y += (viewRect.height - rect.height)/2;
+ scrollRectToVisible(rect);
+ select(pos, pos);
+ }
+ }
+ } catch (BadLocationException exc) {
+ select(pos, pos);
+ //exc.printStackTrace();
+ }
+ }
+ }
+
+
+ public void mousePressed(MouseEvent e) {
+ checkPopup(e);
+ }
+ public void mouseClicked(MouseEvent e) {
+ checkPopup(e);
+ requestFocus();
+ getCaret().setVisible(true);
+ }
+ public void mouseEntered(MouseEvent e) {
+ }
+ public void mouseExited(MouseEvent e) {
+ }
+ public void mouseReleased(MouseEvent e) {
+ checkPopup(e);
+ }
+
+ private void checkPopup(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ popup.show(this, e.getX(), e.getY());
+ }
+ }
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ }
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ }
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ }
+ public void actionPerformed(ActionEvent e) {
+ int pos = viewToModel(new Point(popup.x, popup.y));
+ popup.setVisible(false);
+ String cmd = e.getActionCommand();
+ int line = -1;
+ try {
+ line = getLineOfOffset(pos);
+ } catch (Exception exc) {
+ }
+ if (cmd.equals("Set Breakpoint")) {
+ w.setBreakPoint(line + 1);
+ } else if (cmd.equals("Clear Breakpoint")) {
+ w.clearBreakPoint(line + 1);
+ } else if (cmd.equals("Run to Cursor")) {
+ w.runToCursor(e);
+ } else if (cmd.equals("Run")) {
+ w.load();
+ }
+ }
+ public void keyPressed(KeyEvent e) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_BACK_SPACE:
+ case KeyEvent.VK_ENTER:
+ case KeyEvent.VK_DELETE:
+ e.consume();
+ break;
+ }
+ }
+ public void keyTyped(KeyEvent e) {
+ e.consume();
+ }
+ public void keyReleased(KeyEvent e) {
+ e.consume();
+ }
+}
+
+class MoreWindows extends JDialog implements ActionListener {
+
+ private String value = null;
+ private JList list;
+ Hashtable fileWindows;
+ JButton setButton;
+ JButton refreshButton;
+ JButton cancelButton;
+
+
+ public String showDialog(Component comp) {
+ value = null;
+ setLocationRelativeTo(comp);
+ setVisible(true);
+ return value;
+ }
+
+ private void setValue(String newValue) {
+ value = newValue;
+ list.setSelectedValue(value, true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Cancel")) {
+ setVisible(false);
+ value = null;
+ } else if (cmd.equals("Select")) {
+ value = (String)list.getSelectedValue();
+ setVisible(false);
+ JInternalFrame w = (JInternalFrame)fileWindows.get(value);
+ if (w != null) {
+ try {
+ w.show();
+ w.setSelected(true);
+ } catch (Exception exc) {
+ }
+ }
+ }
+ }
+
+ class MouseHandler extends MouseAdapter {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ setButton.doClick();
+ }
+ }
+ };
+
+ MoreWindows(JFrame frame, Hashtable fileWindows,
+ String title,
+ String labelText) {
+ super(frame, title, true);
+ this.fileWindows = fileWindows;
+ //buttons
+ cancelButton = new JButton("Cancel");
+ setButton = new JButton("Select");
+ cancelButton.addActionListener(this);
+ setButton.addActionListener(this);
+ getRootPane().setDefaultButton(setButton);
+
+ //main part of the dialog
+ list = new JList(new DefaultListModel());
+ DefaultListModel model = (DefaultListModel)list.getModel();
+ model.clear();
+ //model.fireIntervalRemoved(model, 0, size);
+ Enumeration e = fileWindows.keys();
+ while (e.hasMoreElements()) {
+ String data = e.nextElement().toString();
+ model.addElement(data);
+ }
+ list.setSelectedIndex(0);
+ //model.fireIntervalAdded(model, 0, data.length);
+ setButton.setEnabled(true);
+ list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+ list.addMouseListener(new MouseHandler());
+ JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setPreferredSize(new Dimension(320, 240));
+ //XXX: Must do the following, too, or else the scroller thinks
+ //XXX: it's taller than it is:
+ listScroller.setMinimumSize(new Dimension(250, 80));
+ listScroller.setAlignmentX(LEFT_ALIGNMENT);
+
+ //Create a container so that we can add a title around
+ //the scroll pane. Can't add a title directly to the
+ //scroll pane because its background would be white.
+ //Lay out the label and scroll pane from top to button.
+ JPanel listPane = new JPanel();
+ listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS));
+ JLabel label = new JLabel(labelText);
+ label.setLabelFor (list);
+ listPane.add(label);
+ listPane.add(Box.createRigidArea(new Dimension(0,5)));
+ listPane.add(listScroller);
+ listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+ //Lay out the buttons from left to right.
+ JPanel buttonPane = new JPanel();
+ buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
+ buttonPane.add(Box.createHorizontalGlue());
+ buttonPane.add(cancelButton);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(setButton);
+
+ //Put everything together, using the content pane's BorderLayout.
+ Container contentPane = getContentPane();
+ contentPane.add(listPane, BorderLayout.CENTER);
+ contentPane.add(buttonPane, BorderLayout.SOUTH);
+ pack();
+ addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent ke) {
+ int code = ke.getKeyCode();
+ if (code == KeyEvent.VK_ESCAPE) {
+ ke.consume();
+ value = null;
+ setVisible(false);
+ }
+ }
+ });
+ }
+};
+
+class FindFunction extends JDialog implements ActionListener {
+ private String value = null;
+ private JList list;
+ Hashtable functionNames;
+ Main db;
+ JButton setButton;
+ JButton refreshButton;
+ JButton cancelButton;
+
+ public String showDialog(Component comp) {
+ value = null;
+ setLocationRelativeTo(comp);
+ setVisible(true);
+ return value;
+ }
+
+ private void setValue(String newValue) {
+ value = newValue;
+ list.setSelectedValue(value, true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Cancel")) {
+ setVisible(false);
+ value = null;
+ } else if (cmd.equals("Select")) {
+ if (list.getSelectedIndex() < 0) {
+ return;
+ }
+ try {
+ value = (String)list.getSelectedValue();
+ } catch (ArrayIndexOutOfBoundsException exc) {
+ return;
+ }
+ setVisible(false);
+ ScriptItem item = (ScriptItem)functionNames.get(value);
+ if (item != null) {
+ SourceInfo si = item.getSourceInfo();
+ String url = si.getUrl();
+ int lineNumber = item.getFirstLine();
+ FileWindow w = db.getFileWindow(url);
+ if (w == null) {
+ CreateFileWindow.action(db, si, lineNumber).run();
+ w = db.getFileWindow(url);
+ w.setPosition(-1);
+ }
+ int start = w.getPosition(lineNumber-1);
+ int end = w.getPosition(lineNumber)-1;
+ w.textArea.select(start);
+ w.textArea.setCaretPosition(start);
+ w.textArea.moveCaretPosition(end);
+ try {
+ w.show();
+ db.requestFocus();
+ w.requestFocus();
+ w.textArea.requestFocus();
+ } catch (Exception exc) {
+ }
+ }
+ }
+ }
+
+ class MouseHandler extends MouseAdapter {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ setButton.doClick();
+ }
+ }
+ };
+
+ FindFunction(Main db, Hashtable functionNames,
+ String title,
+ String labelText) {
+ super(db, title, true);
+ this.functionNames = functionNames;
+ this.db = db;
+
+ cancelButton = new JButton("Cancel");
+ setButton = new JButton("Select");
+ cancelButton.addActionListener(this);
+ setButton.addActionListener(this);
+ getRootPane().setDefaultButton(setButton);
+
+ list = new JList(new DefaultListModel());
+ DefaultListModel model = (DefaultListModel)list.getModel();
+ model.clear();
+
+ Enumeration e = functionNames.keys();
+ String[] a = new String[functionNames.size()];
+ int i = 0;
+ while (e.hasMoreElements()) {
+ a[i++] = e.nextElement().toString();
+ }
+ java.util.Arrays.sort(a);
+ for (i = 0; i < a.length; i++) {
+ model.addElement(a[i]);
+ }
+ list.setSelectedIndex(0);
+
+ setButton.setEnabled(a.length > 0);
+ list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+ list.addMouseListener(new MouseHandler());
+ JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setPreferredSize(new Dimension(320, 240));
+ listScroller.setMinimumSize(new Dimension(250, 80));
+ listScroller.setAlignmentX(LEFT_ALIGNMENT);
+
+ //Create a container so that we can add a title around
+ //the scroll pane. Can't add a title directly to the
+ //scroll pane because its background would be white.
+ //Lay out the label and scroll pane from top to button.
+ JPanel listPane = new JPanel();
+ listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS));
+ JLabel label = new JLabel(labelText);
+ label.setLabelFor (list);
+ listPane.add(label);
+ listPane.add(Box.createRigidArea(new Dimension(0,5)));
+ listPane.add(listScroller);
+ listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+ //Lay out the buttons from left to right.
+ JPanel buttonPane = new JPanel();
+ buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
+ buttonPane.add(Box.createHorizontalGlue());
+ buttonPane.add(cancelButton);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(setButton);
+
+ //Put everything together, using the content pane's BorderLayout.
+ Container contentPane = getContentPane();
+ contentPane.add(listPane, BorderLayout.CENTER);
+ contentPane.add(buttonPane, BorderLayout.SOUTH);
+ pack();
+ addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent ke) {
+ int code = ke.getKeyCode();
+ if (code == KeyEvent.VK_ESCAPE) {
+ ke.consume();
+ value = null;
+ setVisible(false);
+ }
+ }
+ });
+ }
+};
+
+class FileHeader extends JPanel implements MouseListener {
+ private int pressLine = -1;
+ FileWindow fileWindow;
+
+ public void mouseEntered(MouseEvent e) {
+ }
+ public void mousePressed(MouseEvent e) {
+ Font font = fileWindow.textArea.getFont();
+ FontMetrics metrics = getFontMetrics(font);
+ int h = metrics.getHeight();
+ pressLine = e.getY() / h;
+ }
+ public void mouseClicked(MouseEvent e) {
+ }
+ public void mouseExited(MouseEvent e) {
+ }
+ public void mouseReleased(MouseEvent e) {
+ if (e.getComponent() == this &&
+ (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
+ {
+ int x = e.getX();
+ int y = e.getY();
+ Font font = fileWindow.textArea.getFont();
+ FontMetrics metrics = getFontMetrics(font);
+ int h = metrics.getHeight();
+ int line = y/h;
+ if (line == pressLine) {
+ fileWindow.toggleBreakPoint(line + 1);
+ }
+ else {
+ pressLine = -1;
+ }
+ }
+ }
+
+ FileHeader(FileWindow fileWindow) {
+ this.fileWindow = fileWindow;
+ addMouseListener(this);
+ update();
+ }
+
+ void update() {
+ FileTextArea textArea = fileWindow.textArea;
+ Font font = textArea.getFont();
+ setFont(font);
+ FontMetrics metrics = getFontMetrics(font);
+ int h = metrics.getHeight();
+ int lineCount = textArea.getLineCount() + 1;
+ String dummy = Integer.toString(lineCount);
+ if (dummy.length() < 2) {
+ dummy = "99";
+ }
+ Dimension d = new Dimension();
+ d.width = metrics.stringWidth(dummy) + 16;
+ d.height = lineCount * h + 100;
+ setPreferredSize(d);
+ setSize(d);
+ }
+
+ public void paint(Graphics g) {
+ super.paint(g);
+ FileTextArea textArea = fileWindow.textArea;
+ Font font = textArea.getFont();
+ g.setFont(font);
+ FontMetrics metrics = getFontMetrics(font);
+ Rectangle clip = g.getClipBounds();
+ g.setColor(getBackground());
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+ int left = getX();
+ int ascent = metrics.getMaxAscent();
+ int h = metrics.getHeight();
+ int lineCount = textArea.getLineCount() + 1;
+ String dummy = Integer.toString(lineCount);
+ if (dummy.length() < 2) {
+ dummy = "99";
+ }
+ int maxWidth = metrics.stringWidth(dummy);
+ int startLine = clip.y / h;
+ int endLine = (clip.y + clip.height) / h + 1;
+ int width = getWidth();
+ if (endLine > lineCount) endLine = lineCount;
+ for (int i = startLine; i < endLine; i++) {
+ String text;
+ int pos = -2;
+ try {
+ pos = textArea.getLineStartOffset(i);
+ } catch (BadLocationException ignored) {
+ }
+ boolean isBreakPoint = fileWindow.isBreakPoint(i + 1);
+ text = Integer.toString(i + 1) + " ";
+ int w = metrics.stringWidth(text);
+ int y = i * h;
+ g.setColor(Color.blue);
+ g.drawString(text, 0, y + ascent);
+ int x = width - ascent;
+ if (isBreakPoint) {
+ g.setColor(new Color(0x80, 0x00, 0x00));
+ int dy = y + ascent - 9;
+ g.fillOval(x, dy, 9, 9);
+ g.drawOval(x, dy, 8, 8);
+ g.drawOval(x, dy, 9, 9);
+ }
+ if (pos == fileWindow.currentPos) {
+ Polygon arrow = new Polygon();
+ int dx = x;
+ y += ascent - 10;
+ int dy = y;
+ arrow.addPoint(dx, dy + 3);
+ arrow.addPoint(dx + 5, dy + 3);
+ for (x = dx + 5; x <= dx + 10; x++, y++) {
+ arrow.addPoint(x, y);
+ }
+ for (x = dx + 9; x >= dx + 5; x--, y++) {
+ arrow.addPoint(x, y);
+ }
+ arrow.addPoint(dx + 5, dy + 7);
+ arrow.addPoint(dx, dy + 7);
+ g.setColor(Color.yellow);
+ g.fillPolygon(arrow);
+ g.setColor(Color.black);
+ g.drawPolygon(arrow);
+ }
+ }
+ }
+};
+
+class FileWindow extends JInternalFrame implements ActionListener {
+
+ Main db;
+ SourceInfo sourceInfo;
+ FileTextArea textArea;
+ FileHeader fileHeader;
+ JScrollPane p;
+ int currentPos;
+ JLabel statusBar;
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Cut")) {
+ // textArea.cut();
+ } else if (cmd.equals("Copy")) {
+ textArea.copy();
+ } else if (cmd.equals("Paste")) {
+ // textArea.paste();
+ }
+ }
+
+ void runToCursor(ActionEvent e) {
+ try {
+ db.runToCursor(getUrl(),
+ textArea.getLineOfOffset(textArea.getCaretPosition()) + 1,
+ e);
+ } catch (BadLocationException exc) {
+ }
+ }
+
+ void load() {
+ Scriptable scope = db.getScope();
+ if (scope == null) {
+ MessageDialogWrapper.showMessageDialog(db, "Can't load scripts: no scope available", "Run", JOptionPane.ERROR_MESSAGE);
+ } else {
+ String url = getUrl();
+ if (url != null) {
+ new Thread(new LoadFile(db,scope,url)).start();
+ }
+ }
+ }
+
+ public int getPosition(int line) {
+ int result = -1;
+ try {
+ result = textArea.getLineStartOffset(line);
+ } catch (javax.swing.text.BadLocationException exc) {
+ }
+ return result;
+ }
+
+ boolean isBreakPoint(int line) {
+ return sourceInfo.hasBreakpoint(line);
+ }
+
+ void toggleBreakPoint(int line) {
+ if (!isBreakPoint(line)) {
+ setBreakPoint(line);
+ } else {
+ clearBreakPoint(line);
+ }
+ }
+
+ void setBreakPoint(int line) {
+ if (sourceInfo.placeBreakpoint(line)) {
+ fileHeader.repaint();
+ }
+ }
+
+ void clearBreakPoint(int line) {
+ if (sourceInfo.removeBreakpoint(line)) {
+ fileHeader.repaint();
+ }
+ }
+
+ FileWindow(Main db, SourceInfo sourceInfo) {
+ super(SourceInfo.getShortName(sourceInfo.getUrl()),
+ true, true, true, true);
+ this.db = db;
+ this.sourceInfo = sourceInfo;
+ updateToolTip();
+ currentPos = -1;
+ textArea = new FileTextArea(this);
+ textArea.setRows(24);
+ textArea.setColumns(80);
+ p = new JScrollPane();
+ fileHeader = new FileHeader(this);
+ p.setViewportView(textArea);
+ p.setRowHeaderView(fileHeader);
+ setContentPane(p);
+ pack();
+ updateText();
+ textArea.select(0);
+ }
+
+ private void updateToolTip() {
+ // in case fileName is very long, try to set tool tip on frame
+ Component c = getComponent(1);
+ // this will work at least for Metal L&F
+ if (c != null && c instanceof JComponent) {
+ ((JComponent)c).setToolTipText(getUrl());
+ }
+ }
+
+ public String getUrl() {
+ return sourceInfo.getUrl();
+ }
+
+ void updateText() {
+ String newText = sourceInfo.getSource();
+ if (!textArea.getText().equals(newText)) {
+ textArea.setText(newText);
+ int pos = 0;
+ if (currentPos != -1) {
+ pos = currentPos;
+ }
+ textArea.select(pos);
+ }
+ fileHeader.update();
+ fileHeader.repaint();
+ }
+
+ void setPosition(int pos) {
+ textArea.select(pos);
+ currentPos = pos;
+ fileHeader.repaint();
+ }
+
+ void select(int start, int end) {
+ int docEnd = textArea.getDocument().getLength();
+ textArea.select(docEnd, docEnd);
+ textArea.select(start, end);
+ }
+
+ public void dispose() {
+ db.removeWindow(this);
+ super.dispose();
+ }
+
+};
+
+class MyTableModel extends AbstractTableModel {
+ Main db;
+ Vector expressions;
+ Vector values;
+ MyTableModel(Main db) {
+ this.db = db;
+ expressions = new Vector();
+ values = new Vector();
+ expressions.addElement("");
+ values.addElement("");
+ }
+
+ public int getColumnCount() {
+ return 2;
+ }
+
+ public int getRowCount() {
+ return expressions.size();
+ }
+
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return "Expression";
+ case 1:
+ return "Value";
+ }
+ return null;
+ }
+
+ public boolean isCellEditable(int row, int column) {
+ return true;
+ }
+
+ public Object getValueAt(int row, int column) {
+ switch (column) {
+ case 0:
+ return expressions.elementAt(row);
+ case 1:
+ return values.elementAt(row);
+ }
+ return "";
+ }
+
+ public void setValueAt(Object value, int row, int column) {
+ switch (column) {
+ case 0:
+ String expr = value.toString();
+ expressions.setElementAt(expr, row);
+ String result = "";
+ if (expr.length() > 0) {
+ result = db.eval(expr);
+ if (result == null) result = "";
+ }
+ values.setElementAt(result, row);
+ updateModel();
+ if (row + 1 == expressions.size()) {
+ expressions.addElement("");
+ values.addElement("");
+ fireTableRowsInserted(row + 1, row + 1);
+ }
+ break;
+ case 1:
+ // just reset column 2; ignore edits
+ fireTableDataChanged();
+ }
+ }
+
+ void updateModel() {
+ for (int i = 0; i < expressions.size(); ++i) {
+ Object value = expressions.elementAt(i);
+ String expr = value.toString();
+ String result = "";
+ if (expr.length() > 0) {
+ result = db.eval(expr);
+ if (result == null) result = "";
+ } else {
+ result = "";
+ }
+ result = result.replace('\n', ' ');
+ values.setElementAt(result, i);
+ }
+ fireTableDataChanged();
+ }
+};
+
+class Evaluator extends JTable {
+ MyTableModel tableModel;
+ Evaluator(Main db) {
+ super(new MyTableModel(db));
+ tableModel = (MyTableModel)getModel();
+ }
+}
+
+class MyTreeTable extends JTreeTable {
+
+ public MyTreeTable(TreeTableModel model) {
+ super(model);
+ }
+
+ public JTree resetTree(TreeTableModel treeTableModel) {
+ tree = new TreeTableCellRenderer(treeTableModel);
+
+ // Install a tableModel representing the visible rows in the tree.
+ super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
+
+ // Force the JTable and JTree to share their row selection models.
+ ListToTreeSelectionModelWrapper selectionWrapper = new
+ ListToTreeSelectionModelWrapper();
+ tree.setSelectionModel(selectionWrapper);
+ setSelectionModel(selectionWrapper.getListSelectionModel());
+
+ // Make the tree and table row heights the same.
+ if (tree.getRowHeight() < 1) {
+ // Metal looks better like this.
+ setRowHeight(18);
+ }
+
+ // Install the tree editor renderer and editor.
+ setDefaultRenderer(TreeTableModel.class, tree);
+ setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
+ setShowGrid(true);
+ setIntercellSpacing(new Dimension(1,1));
+ tree.setRootVisible(false);
+ tree.setShowsRootHandles(true);
+ DefaultTreeCellRenderer r = (DefaultTreeCellRenderer)tree.getCellRenderer();
+ r.setOpenIcon(null);
+ r.setClosedIcon(null);
+ r.setLeafIcon(null);
+ return tree;
+ }
+
+ public boolean isCellEditable(EventObject e) {
+ if (e instanceof MouseEvent) {
+ MouseEvent me = (MouseEvent)e;
+ // If the modifiers are not 0 (or the left mouse button),
+ // tree may try and toggle the selection, and table
+ // will then try and toggle, resulting in the
+ // selection remaining the same. To avoid this, we
+ // only dispatch when the modifiers are 0 (or the left mouse
+ // button).
+ if (me.getModifiers() == 0 ||
+ ((me.getModifiers() & (InputEvent.BUTTON1_MASK|1024)) != 0 &&
+ (me.getModifiers() &
+ (InputEvent.SHIFT_MASK |
+ InputEvent.CTRL_MASK |
+ InputEvent.ALT_MASK |
+ InputEvent.BUTTON2_MASK |
+ InputEvent.BUTTON3_MASK |
+ 64 | //SHIFT_DOWN_MASK
+ 128 | //CTRL_DOWN_MASK
+ 512 | // ALT_DOWN_MASK
+ 2048 | //BUTTON2_DOWN_MASK
+ 4096 //BUTTON3_DOWN_MASK
+ )) == 0)) {
+ int row = rowAtPoint(me.getPoint());
+ for (int counter = getColumnCount() - 1; counter >= 0;
+ counter--) {
+ if (TreeTableModel.class == getColumnClass(counter)) {
+ MouseEvent newME = new MouseEvent
+ (MyTreeTable.this.tree, me.getID(),
+ me.getWhen(), me.getModifiers(),
+ me.getX() - getCellRect(row, counter, true).x,
+ me.getY(), me.getClickCount(),
+ me.isPopupTrigger());
+ MyTreeTable.this.tree.dispatchEvent(newME);
+ break;
+ }
+ }
+ }
+ if (me.getClickCount() >= 3) {
+ return true;
+ }
+ return false;
+ }
+ if (e == null) {
+ return true;
+ }
+ return false;
+ }
+};
+
+class ContextWindow extends JPanel implements ActionListener {
+ JComboBox context;
+ Vector toolTips;
+ JTabbedPane tabs;
+ JTabbedPane tabs2;
+ MyTreeTable thisTable;
+ MyTreeTable localsTable;
+ VariableModel model;
+ MyTableModel tableModel;
+ Evaluator evaluator;
+ EvalTextArea cmdLine;
+ JSplitPane split;
+ Main db;
+ boolean enabled;
+ ContextWindow(Main db) {
+ super();
+ this.db = db;
+ enabled = false;
+ JPanel left = new JPanel();
+ JToolBar t1 = new JToolBar();
+ t1.setName("Variables");
+ t1.setLayout(new GridLayout());
+ t1.add(left);
+ JPanel p1 = new JPanel();
+ p1.setLayout(new GridLayout());
+ JPanel p2 = new JPanel();
+ p2.setLayout(new GridLayout());
+ p1.add(t1);
+ JLabel label = new JLabel("Context:");
+ context = new JComboBox();
+ context.setLightWeightPopupEnabled(false);
+ toolTips = new java.util.Vector();
+ label.setBorder(context.getBorder());
+ context.addActionListener(this);
+ context.setActionCommand("ContextSwitch");
+ GridBagLayout layout = new GridBagLayout();
+ left.setLayout(layout);
+ GridBagConstraints lc = new GridBagConstraints();
+ lc.insets.left = 5;
+ lc.anchor = GridBagConstraints.WEST;
+ lc.ipadx = 5;
+ layout.setConstraints(label, lc);
+ left.add(label);
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(context, c);
+ left.add(context);
+ tabs = new JTabbedPane(SwingConstants.BOTTOM);
+ tabs.setPreferredSize(new Dimension(500,300));
+ thisTable = new MyTreeTable(new AbstractTreeTableModel(new DefaultMutableTreeNode()) {
+ public Object getChild(Object parent, int index) {
+ return null;
+ }
+ public int getChildCount(Object parent) {
+ return 0;
+ }
+ public int getColumnCount() {
+ //return 3;
+ return 2;
+ }
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return " Name";
+ case 1:
+ //return "Type";
+ //case 2:
+ return " Value";
+ }
+ return null;
+ }
+ public Object getValueAt(Object node, int column) {
+ return null;
+ }
+ });
+ JScrollPane jsp = new JScrollPane(thisTable);
+ jsp.getViewport().setViewSize(new Dimension(5,2));
+ tabs.add("this", jsp);
+ localsTable = new MyTreeTable(new AbstractTreeTableModel(new DefaultMutableTreeNode()) {
+ public Object getChild(Object parent, int index) {
+ return null;
+ }
+ public int getChildCount(Object parent) {
+ return 0;
+ }
+ public int getColumnCount() {
+ return 2;
+ }
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return " Name";
+ case 1:
+ return " Value";
+ }
+ return null;
+ }
+ public Object getValueAt(Object node, int column) {
+ return null;
+ }
+ });
+ localsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+ localsTable.setPreferredSize(null);
+ jsp = new JScrollPane(localsTable);
+ tabs.add("Locals", jsp);
+ c.weightx = c.weighty = 1;
+ c.gridheight = GridBagConstraints.REMAINDER;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ layout.setConstraints(tabs, c);
+ left.add(tabs);
+ evaluator = new Evaluator(db);
+ cmdLine = new EvalTextArea(db);
+ //cmdLine.requestFocus();
+ tableModel = evaluator.tableModel;
+ jsp = new JScrollPane(evaluator);
+ JToolBar t2 = new JToolBar();
+ t2.setName("Evaluate");
+ tabs2 = new JTabbedPane(SwingConstants.BOTTOM);
+ tabs2.add("Watch", jsp);
+ tabs2.add("Evaluate", new JScrollPane(cmdLine));
+ tabs2.setPreferredSize(new Dimension(500,300));
+ t2.setLayout(new GridLayout());
+ t2.add(tabs2);
+ p2.add(t2);
+ evaluator.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+ split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+ p1, p2);
+ split.setOneTouchExpandable(true);
+ Main.setResizeWeight(split, 0.5);
+ setLayout(new BorderLayout());
+ add(split, BorderLayout.CENTER);
+
+ final JToolBar finalT1 = t1;
+ final JToolBar finalT2 = t2;
+ final JPanel finalP1 = p1;
+ final JPanel finalP2 = p2;
+ final JSplitPane finalSplit = split;
+ final JPanel finalThis = this;
+ final Main finalDb = db;
+
+ ComponentListener clistener = new ComponentListener() {
+ boolean t1Docked = true;
+ boolean t2Docked = true;
+ void check(Component comp) {
+ Component thisParent = finalThis.getParent();
+ if (thisParent == null) {
+ return;
+ }
+ Component parent = finalT1.getParent();
+ boolean leftDocked = true;
+ boolean rightDocked = true;
+ boolean adjustVerticalSplit = false;
+ if (parent != null) {
+ if (parent != finalP1) {
+ while (!(parent instanceof JFrame)) {
+ parent = parent.getParent();
+ }
+ JFrame frame = (JFrame)parent;
+ finalDb.addTopLevel("Variables", frame);
+
+ // We need the following hacks because:
+ // - We want an undocked toolbar to be
+ // resizable.
+ // - We are using JToolbar as a container of a
+ // JComboBox. Without this JComboBox's popup
+ // can get left floating when the toolbar is
+ // re-docked.
+ //
+ // We make the frame resizable and then
+ // remove JToolbar's window listener
+ // and insert one of our own that first ensures
+ // the JComboBox's popup window is closed
+ // and then calls JToolbar's window listener.
+ if (!frame.isResizable()) {
+ frame.setResizable(true);
+ frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ final EventListener[] l =
+ frame.getListeners(WindowListener.class);
+ frame.removeWindowListener((WindowListener)l[0]);
+ frame.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ context.hidePopup();
+ ((WindowListener)l[0]).windowClosing(e);
+ }
+ });
+ //adjustVerticalSplit = true;
+ }
+ leftDocked = false;
+ } else {
+ leftDocked = true;
+ }
+ }
+ parent = finalT2.getParent();
+ if (parent != null) {
+ if (parent != finalP2) {
+ while (!(parent instanceof JFrame)) {
+ parent = parent.getParent();
+ }
+ JFrame frame = (JFrame)parent;
+ finalDb.addTopLevel("Evaluate", frame);
+ frame.setResizable(true);
+ rightDocked = false;
+ } else {
+ rightDocked = true;
+ }
+ }
+ if (leftDocked && t2Docked && rightDocked && t2Docked) {
+ // no change
+ return;
+ }
+ t1Docked = leftDocked;
+ t2Docked = rightDocked;
+ JSplitPane split = (JSplitPane)thisParent;
+ if (leftDocked) {
+ if (rightDocked) {
+ finalSplit.setDividerLocation(0.5);
+ } else {
+ finalSplit.setDividerLocation(1.0);
+ }
+ if (adjustVerticalSplit) {
+ split.setDividerLocation(0.66);
+ }
+
+ } else if (rightDocked) {
+ finalSplit.setDividerLocation(0.0);
+ split.setDividerLocation(0.66);
+ } else {
+ // both undocked
+ split.setDividerLocation(1.0);
+ }
+ }
+ public void componentHidden(ComponentEvent e) {
+ check(e.getComponent());
+ }
+ public void componentMoved(ComponentEvent e) {
+ check(e.getComponent());
+ }
+ public void componentResized(ComponentEvent e) {
+ check(e.getComponent());
+ }
+ public void componentShown(ComponentEvent e) {
+ check(e.getComponent());
+ }
+ };
+ p1.addContainerListener(new ContainerListener() {
+ public void componentAdded(ContainerEvent e) {
+ Component thisParent = finalThis.getParent();
+ JSplitPane split = (JSplitPane)thisParent;
+ if (e.getChild() == finalT1) {
+ if (finalT2.getParent() == finalP2) {
+ // both docked
+ finalSplit.setDividerLocation(0.5);
+ } else {
+ // left docked only
+ finalSplit.setDividerLocation(1.0);
+ }
+ split.setDividerLocation(0.66);
+ }
+ }
+ public void componentRemoved(ContainerEvent e) {
+ Component thisParent = finalThis.getParent();
+ JSplitPane split = (JSplitPane)thisParent;
+ if (e.getChild() == finalT1) {
+ if (finalT2.getParent() == finalP2) {
+ // right docked only
+ finalSplit.setDividerLocation(0.0);
+ split.setDividerLocation(0.66);
+ } else {
+ // both undocked
+ split.setDividerLocation(1.0);
+ }
+ }
+ }
+ });
+ t1.addComponentListener(clistener);
+ t2.addComponentListener(clistener);
+ disable();
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (!enabled) return;
+ if (e.getActionCommand().equals("ContextSwitch")) {
+ ContextHelper helper = new ContextHelper();
+ Context cx = db.getCurrentContext();
+ ContextData contextData = ContextData.get(cx);
+ helper.attach(cx);
+ int frameIndex = context.getSelectedIndex();
+ context.setToolTipText(toolTips.elementAt(frameIndex).toString());
+ Scriptable obj;
+ int frameCount = contextData.getFrameCount();
+ if (frameIndex < frameCount) {
+ FrameHelper frame = contextData.getFrame(frameIndex);
+ obj = frame.getVariableObject();
+ } else {
+ helper.reset();
+ return;
+ }
+ NativeCall call = null;
+ if (obj instanceof NativeCall) {
+ call = (NativeCall)obj;
+ obj = call.getThisObj();
+ }
+ JTree tree = thisTable.resetTree(model = new VariableModel(obj));
+
+ if (call == null) {
+ tree = localsTable.resetTree(new AbstractTreeTableModel(new DefaultMutableTreeNode()) {
+ public Object getChild(Object parent, int index) {
+ return null;
+ }
+ public int getChildCount(Object parent) {
+ return 0;
+ }
+ public int getColumnCount() {
+ return 2;
+ }
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return " Name";
+ case 1:
+ return " Value";
+ }
+ return null;
+ }
+ public Object getValueAt(Object node, int column) {
+ return null;
+ }
+ });
+ } else {
+ tree = localsTable.resetTree(model = new VariableModel(call));
+ }
+ helper.reset();
+ db.contextSwitch (frameIndex);
+ tableModel.updateModel();
+ }
+ }
+
+ public void disable() {
+ context.setEnabled(false);
+ thisTable.setEnabled(false);
+ localsTable.setEnabled(false);
+ evaluator.setEnabled(false);
+ cmdLine.setEnabled(false);
+ }
+
+ public void enable() {
+ context.setEnabled(true);
+ thisTable.setEnabled(true);
+ localsTable.setEnabled(true);
+ evaluator.setEnabled(true);
+ cmdLine.setEnabled(true);
+ }
+
+ public void disableUpdate() {
+ enabled = false;
+ }
+
+ public void enableUpdate() {
+ enabled = true;
+ }
+};
+
+class CreateFileWindow implements Runnable {
+
+ Main db;
+ SourceInfo sourceInfo;
+ int line;
+ boolean activate;
+
+ private CreateFileWindow() { }
+
+ static Runnable action(Main db, SourceInfo sourceInfo, int line) {
+ CreateFileWindow obj = new CreateFileWindow();
+ obj.db = db;
+ obj.sourceInfo = sourceInfo;
+ obj.line = line;
+ obj.activate = true;
+ return obj;
+ }
+
+ static Runnable action(Main db,
+ SourceInfo sourceInfo, int line, boolean activate)
+ {
+ CreateFileWindow obj = new CreateFileWindow();
+ obj.db = db;
+ obj.sourceInfo = sourceInfo;
+ obj.line = line;
+ obj.activate = activate;
+ return obj;
+ }
+
+ public void run() {
+ String url = sourceInfo.getUrl();
+ FileWindow w = new FileWindow(db, sourceInfo);
+ db.fileWindows.put(url, w);
+ if (line != -1) {
+ if (db.currentWindow != null) {
+ db.currentWindow.setPosition(-1);
+ }
+ try {
+ w.setPosition(w.textArea.getLineStartOffset(line-1));
+ } catch (BadLocationException exc) {
+ try {
+ w.setPosition(w.textArea.getLineStartOffset(0));
+ } catch (BadLocationException ee) {
+ w.setPosition(-1);
+ }
+ }
+ }
+ db.desk.add(w);
+ if (line != -1) {
+ db.currentWindow = w;
+ }
+ db.menubar.addFile(url);
+ w.setVisible(true);
+ if (activate) {
+ try {
+ w.setMaximum(true);
+ w.setSelected(true);
+ w.moveToFront();
+ } catch (Exception exc) {
+ }
+ }
+ }
+}
+
+class SetFilePosition implements Runnable {
+
+ Main db;
+ FileWindow w;
+ int line;
+ boolean activate;
+
+ SetFilePosition(Main db, FileWindow w, int line) {
+ this.db = db;
+ this.w = w;
+ this.line = line;
+ activate = true;
+ }
+
+ SetFilePosition(Main db, FileWindow w, int line, boolean activate) {
+ this.db = db;
+ this.w = w;
+ this.line = line;
+ this.activate = activate;
+ }
+
+ public void run() {
+ JTextArea ta = w.textArea;
+ try {
+ if (line == -1) {
+ w.setPosition(-1);
+ if (db.currentWindow == w) {
+ db.currentWindow = null;
+ }
+ } else {
+ int loc = ta.getLineStartOffset(line-1);
+ if (db.currentWindow != null && db.currentWindow != w) {
+ db.currentWindow.setPosition(-1);
+ }
+ w.setPosition(loc);
+ db.currentWindow = w;
+ }
+ } catch (BadLocationException exc) {
+ // fix me
+ }
+ if (activate) {
+ if (w.isIcon()) {
+ db.desk.getDesktopManager().deiconifyFrame(w);
+ }
+ db.desk.getDesktopManager().activateFrame(w);
+ try {
+ w.show();
+ w.toFront(); // required for correct frame layering (JDK 1.4.1)
+ w.setSelected(true);
+ } catch (Exception exc) {
+ }
+ }
+ }
+}
+
+class UpdateFileText implements Runnable {
+
+ private FileWindow w;
+
+ private UpdateFileText() {}
+
+ static Runnable action(FileWindow w) {
+ UpdateFileText obj = new UpdateFileText();
+ obj.w = w;
+ return obj;
+ }
+
+ public void run() {
+ w.updateText();
+ }
+}
+
+class UpdateContext implements Runnable {
+ Main db;
+ ContextData contextData;
+ UpdateContext(Main db, Context cx) {
+ this.db = db;
+ this.contextData = ContextData.get(cx);
+ }
+
+ public void run() {
+ db.context.enable();
+ JComboBox ctx = db.context.context;
+ Vector toolTips = db.context.toolTips;
+ db.context.disableUpdate();
+ int frameCount = contextData.getFrameCount();
+ ctx.removeAllItems();
+ // workaround for JDK 1.4 bug that caches selected value even after
+ // removeAllItems() is called
+ ctx.setSelectedItem(null);
+ toolTips.removeAllElements();
+ for (int i = 0; i < frameCount; i++) {
+ FrameHelper frame = contextData.getFrame(i);
+ String url = frame.getUrl();
+ int lineNumber = frame.getLineNumber();
+ String shortName = url;
+ if (url.length() > 20) {
+ shortName = "..." + url.substring(url.length() - 17);
+ }
+ String location = "\"" + shortName + "\", line " + lineNumber;
+ ctx.insertItemAt(location, i);
+ location = "\"" + url + "\", line " + lineNumber;
+ toolTips.addElement(location);
+ }
+ db.context.enableUpdate();
+ if (frameCount != 0) {
+ ctx.setSelectedIndex(0);
+ }
+ ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height));
+ }
+};
+
+class Menubar extends JMenuBar implements ActionListener {
+
+ JMenu getDebugMenu() {
+ return getMenu(2);
+ }
+
+ Menubar(Main db) {
+ super();
+ this.db = db;
+ String[] fileItems = {"Open...", "Run...", "", "Exit"};
+ String[] fileCmds = {"Open", "Load", "", "Exit"};
+ char[] fileShortCuts = {'0', 'N', '\0', 'X'};
+ int[] fileAccelerators = {KeyEvent.VK_O,
+ KeyEvent.VK_N,
+ 0,
+ KeyEvent.VK_Q};
+ String[] editItems = {"Cut", "Copy", "Paste", "Go to function..."};
+ char[] editShortCuts = {'T', 'C', 'P', 'F'};
+ String[] debugItems = {"Break", "Go", "Step Into", "Step Over", "Step Out"};
+ char[] debugShortCuts = {'B', 'G', 'I', 'O', 'T'};
+ String[] plafItems = {"Metal", "Windows", "Motif"};
+ char [] plafShortCuts = {'M', 'W', 'F'};
+ int[] debugAccelerators = {KeyEvent.VK_PAUSE,
+ KeyEvent.VK_F5,
+ KeyEvent.VK_F11,
+ KeyEvent.VK_F7,
+ KeyEvent.VK_F8,
+ 0, 0};
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic('F');
+ JMenu editMenu = new JMenu("Edit");
+ editMenu.setMnemonic('E');
+ JMenu plafMenu = new JMenu("Platform");
+ plafMenu.setMnemonic('P');
+ JMenu debugMenu = new JMenu("Debug");
+ debugMenu.setMnemonic('D');
+ windowMenu = new JMenu("Window");
+ windowMenu.setMnemonic('W');
+ for (int i = 0; i < fileItems.length; ++i) {
+ if (fileItems[i].length() == 0) {
+ fileMenu.addSeparator();
+ } else {
+ JMenuItem item = new JMenuItem(fileItems[i],
+ fileShortCuts[i]);
+ item.setActionCommand(fileCmds[i]);
+ item.addActionListener(this);
+ fileMenu.add(item);
+ if (fileAccelerators[i] != 0) {
+ KeyStroke k = KeyStroke.getKeyStroke(fileAccelerators[i], Event.CTRL_MASK);
+ item.setAccelerator(k);
+ }
+ }
+ }
+ for (int i = 0; i < editItems.length; ++i) {
+ JMenuItem item = new JMenuItem(editItems[i],
+ editShortCuts[i]);
+ item.addActionListener(this);
+ editMenu.add(item);
+ }
+ for (int i = 0; i < plafItems.length; ++i) {
+ JMenuItem item = new JMenuItem(plafItems[i],
+ plafShortCuts[i]);
+ item.addActionListener(this);
+ plafMenu.add(item);
+ }
+ for (int i = 0; i < debugItems.length; ++i) {
+ JMenuItem item = new JMenuItem(debugItems[i],
+ debugShortCuts[i]);
+ item.addActionListener(this);
+ if (debugAccelerators[i] != 0) {
+ KeyStroke k = KeyStroke.getKeyStroke(debugAccelerators[i], 0);
+ item.setAccelerator(k);
+ }
+ if (i != 0) {
+ item.setEnabled(false);
+ }
+ debugMenu.add(item);
+ }
+ breakOnExceptions = new JCheckBoxMenuItem("Break on Exceptions");
+ breakOnExceptions.setMnemonic('X');
+ breakOnExceptions.addActionListener(this);
+ breakOnExceptions.setSelected(false);
+ debugMenu.add(breakOnExceptions);
+
+ breakOnEnter = new JCheckBoxMenuItem("Break on Function Enter");
+ breakOnEnter.setMnemonic('E');
+ breakOnEnter.addActionListener(this);
+ breakOnEnter.setSelected(false);
+ debugMenu.add(breakOnEnter);
+
+ breakOnReturn = new JCheckBoxMenuItem("Break on Function Return");
+ breakOnReturn.setMnemonic('R');
+ breakOnReturn.addActionListener(this);
+ breakOnReturn.setSelected(false);
+ debugMenu.add(breakOnReturn);
+
+ add(fileMenu);
+ add(editMenu);
+ //add(plafMenu);
+ add(debugMenu);
+ JMenuItem item;
+ windowMenu.add(item = new JMenuItem("Cascade", 'A'));
+ item.addActionListener(this);
+ windowMenu.add(item = new JMenuItem("Tile", 'T'));
+ item.addActionListener(this);
+ windowMenu.addSeparator();
+ windowMenu.add(item = new JMenuItem("Console", 'C'));
+ item.addActionListener(this);
+ add(windowMenu);
+
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ String plaf_name = null;
+ if (cmd.equals("Metal")) {
+ plaf_name = "javax.swing.plaf.metal.MetalLookAndFeel";
+ } else if (cmd.equals("Windows")) {
+ plaf_name = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
+ } else if (cmd.equals("Motif")) {
+ plaf_name = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
+ } else {
+ Object source = e.getSource();
+ if (source == breakOnExceptions) {
+ db.setBreakOnExceptions(breakOnExceptions.isSelected());
+ }else if (source == breakOnEnter) {
+ db.setBreakOnEnter(breakOnEnter.isSelected());
+ }else if (source == breakOnReturn) {
+ db.setBreakOnReturn(breakOnReturn.isSelected());
+ }else {
+ db.actionPerformed(e);
+ }
+ return;
+ }
+ try {
+ UIManager.setLookAndFeel(plaf_name);
+ SwingUtilities.updateComponentTreeUI(db);
+ SwingUtilities.updateComponentTreeUI(db.dlg);
+ } catch (Exception ignored) {
+ //ignored.printStackTrace();
+ }
+ }
+
+ void addFile(String url) {
+ int count = windowMenu.getItemCount();
+ JMenuItem item;
+ if (count == 4) {
+ windowMenu.addSeparator();
+ count++;
+ }
+ JMenuItem lastItem = windowMenu.getItem(count -1);
+ boolean hasMoreWin = false;
+ int maxWin = 5;
+ if (lastItem != null &&
+ lastItem.getText().equals("More Windows...")) {
+ hasMoreWin = true;
+ maxWin++;
+ }
+ if (!hasMoreWin && count - 4 == 5) {
+ windowMenu.add(item = new JMenuItem("More Windows...", 'M'));
+ item.setActionCommand("More Windows...");
+ item.addActionListener(this);
+ return;
+ } else if (count - 4 <= maxWin) {
+ if (hasMoreWin) {
+ count--;
+ windowMenu.remove(lastItem);
+ }
+ String shortName = SourceInfo.getShortName(url);
+
+ windowMenu.add(item = new JMenuItem((char)('0' + (count-4)) + " " + shortName, '0' + (count - 4)));
+ if (hasMoreWin) {
+ windowMenu.add(lastItem);
+ }
+ } else {
+ return;
+ }
+ item.setActionCommand(url);
+ item.addActionListener(this);
+ }
+
+ Main db;
+ JMenu windowMenu;
+ JCheckBoxMenuItem breakOnExceptions;
+ JCheckBoxMenuItem breakOnEnter;
+ JCheckBoxMenuItem breakOnReturn;
+};
+
+class EnterInterrupt implements Runnable {
+ Main db;
+ Context cx;
+ EnterInterrupt(Main db, Context cx) {
+ this.db = db;
+ this.cx = cx;
+ }
+ public void run() {
+ JMenu menu = db.getJMenuBar().getMenu(0);
+ //menu.getItem(0).setEnabled(false); // File->Load
+ menu = db.getJMenuBar().getMenu(2);
+ menu.getItem(0).setEnabled(false); // Debug->Break
+ int count = menu.getItemCount();
+ for (int i = 1; i < count; ++i) {
+ menu.getItem(i).setEnabled(true);
+ }
+ boolean b = false;
+ for (int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) {
+ db.toolBar.getComponent(ci).setEnabled(b);
+ b = true;
+ }
+ db.toolBar.setEnabled(true);
+ // raise the debugger window
+ db.toFront();
+ }
+};
+
+class ExitInterrupt implements Runnable {
+ Main db;
+ ExitInterrupt(Main db) {
+ this.db = db;
+ }
+ public void run() {
+ JMenu menu = db.getJMenuBar().getMenu(0);
+ menu.getItem(0).setEnabled(true); // File->Load
+ menu = db.getJMenuBar().getMenu(2);
+ menu.getItem(0).setEnabled(true); // Debug->Break
+ int count = menu.getItemCount() - 1;
+ int i = 1;
+ for (; i < count; ++i) {
+ menu.getItem(i).setEnabled(false);
+ }
+ db.context.disable();
+ boolean b = true;
+ for (int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) {
+ db.toolBar.getComponent(ci).setEnabled(b);
+ b = false;
+ }
+ //db.console.consoleTextArea.requestFocus();
+ }
+};
+
+class OpenFile implements Runnable
+{
+ String fileName;
+ Main db;
+ OpenFile(Main db, String fileName)
+ {
+ this.fileName = fileName;
+ this.db = db;
+ }
+ public void run() {
+ Context cx = Context.enter();
+ ContextData contextData = ContextData.get(cx);
+ contextData.breakNextLine = true;
+ try {
+ cx.compileReader(new FileReader(fileName), fileName, 1, null);
+ } catch (Exception exc) {
+ String msg = exc.getMessage();
+ if (exc instanceof EcmaError) {
+ EcmaError err = (EcmaError)exc;
+ msg = err.getSourceName() + ", line " + err.getLineNumber() + ": " + msg;
+ }
+ MessageDialogWrapper.showMessageDialog(db,
+ msg,
+ "Error Compiling File",
+ JOptionPane.ERROR_MESSAGE);
+ } finally {
+ cx.exit();
+ }
+ }
+}
+
+class LoadFile implements Runnable {
+ Scriptable scope;
+ String fileName;
+ Main db;
+ LoadFile(Main db, Scriptable scope, String fileName) {
+ this.scope = scope;
+ this.fileName = fileName;
+ this.db = db;
+ }
+ public void run() {
+ Context cx = Context.enter();
+ ContextData contextData = ContextData.get(cx);
+ contextData.breakNextLine = true;
+ try {
+ cx.evaluateReader(scope, new FileReader(fileName),
+ fileName, 1, null);
+ } catch (Exception exc) {
+ String msg = exc.getMessage();
+ if (exc instanceof EcmaError) {
+ EcmaError err = (EcmaError)exc;
+ msg = err.getSourceName() + ", line " + err.getLineNumber() + ": " + msg;
+ }
+ MessageDialogWrapper.showMessageDialog(db,
+ msg,
+ "Run",
+ JOptionPane.ERROR_MESSAGE);
+ } finally {
+ cx.exit();
+ }
+ }
+}
+
+
+class ContextHelper {
+ Context old;
+ int enterCount;
+ Context New;
+ public void attach(Context cx) {
+ old = Context.getCurrentContext();
+ enterCount = 0;
+ if (old != null) {
+ old.exit();
+ while (Context.getCurrentContext() != null) {
+ enterCount++;
+ old.exit();
+ }
+ }
+ Context.enter(cx);
+ New = cx;
+ }
+ void reset() {
+ New.exit();
+ if (old != null) {
+ if (Context.enter(old) != old) {
+ throw new RuntimeException("debugger error: failed to reset context");
+ }
+ while (enterCount > 0) {
+ Context.enter();
+ enterCount--;
+ }
+ }
+ }
+}
+
+class ContextData {
+ static ContextData get(Context cx) {
+ return (ContextData)cx.getDebuggerContextData();
+ }
+
+ int getFrameCount() {
+ return frameStack.size();
+ }
+
+ FrameHelper getFrame(int frameNumber) {
+ return (FrameHelper) frameStack.get(frameStack.size() - frameNumber - 1);
+ }
+
+ void pushFrame(FrameHelper frame) {
+ frameStack.push(frame);
+ }
+
+ void popFrame() {
+ frameStack.pop();
+ }
+
+ ObjArray frameStack = new ObjArray();
+ boolean breakNextLine;
+}
+
+class FrameHelper implements DebugFrame {
+
+ FrameHelper(Context cx, Main db, DebuggableScript fnOrScript)
+ {
+ this.db = db;
+ this.contextData = ContextData.get(cx);
+ this.fnOrScript = fnOrScript;
+ ScriptItem item = db.getScriptItem(fnOrScript);
+ if (item != null) {
+ this.sourceInfo = item.getSourceInfo();
+ this.lineNumber = item.getFirstLine();
+ }
+ contextData.pushFrame(this);
+ }
+
+ public void onEnter(Context cx, Scriptable activation,
+ Scriptable thisObj, Object[] args)
+ {
+ this.activation = activation;
+ if (db.breakOnEnter) {
+ db.handleBreakpointHit(cx);
+ }
+ }
+
+ public void onLineChange(Context cx, int lineno) {
+ this.lineNumber = lineno;
+ if (contextData.breakNextLine
+ || (sourceInfo != null && sourceInfo.hasBreakpoint(lineno)))
+ {
+ db.handleBreakpointHit(cx);
+ }
+ }
+
+ public void onExceptionThrown(Context cx, Throwable exception) {
+ db.handleExceptionThrown(cx, exception, this);
+ }
+
+ public void onExit(Context cx, boolean byThrow, Object resultOrException) {
+ if (db.breakOnReturn && !byThrow) {
+ db.handleBreakpointHit(cx);
+ }
+ contextData.popFrame();
+ }
+
+ SourceInfo getSourceInfo() {
+ return sourceInfo;
+ }
+
+ Scriptable getVariableObject() {
+ return activation;
+ }
+
+ String getUrl() {
+ if (sourceInfo != null) {
+ return sourceInfo.getUrl();
+ }
+ return db.getNormilizedUrl(fnOrScript);
+ }
+
+ int getLineNumber() {
+ return lineNumber;
+ }
+
+ DebuggableScript getScript() {
+ return fnOrScript;
+ }
+
+ private Main db;
+ private ContextData contextData;
+ private Scriptable activation;
+ private DebuggableScript fnOrScript;
+ private SourceInfo sourceInfo;
+ private int lineNumber;
+}
+
+class ScriptItem {
+
+ ScriptItem(DebuggableScript fnOrScript, SourceInfo sourceInfo) {
+ this.fnOrScript = fnOrScript;
+ this.sourceInfo = sourceInfo;
+ }
+
+ DebuggableScript getScript() { return fnOrScript; }
+
+ SourceInfo getSourceInfo() { return sourceInfo; }
+
+ int getFirstLine() {
+ return (firstLine == 0) ? 1 : firstLine;
+ }
+
+ void setFirstLine(int firstLine) {
+ if (firstLine <= 0) { throw new IllegalArgumentException(); }
+ if (this.firstLine != 0) { throw new IllegalStateException(); }
+ this.firstLine = firstLine;
+ }
+
+ private DebuggableScript fnOrScript;
+ private SourceInfo sourceInfo;
+ private int firstLine;
+}
+
+class SourceInfo {
+
+ static String getShortName(String url) {
+ int lastSlash = url.lastIndexOf('/');
+ if (lastSlash < 0) {
+ lastSlash = url.lastIndexOf('\\');
+ }
+ String shortName = url;
+ if (lastSlash >= 0 && lastSlash + 1 < url.length()) {
+ shortName = url.substring(lastSlash + 1);
+ }
+ return shortName;
+ }
+
+ SourceInfo(String sourceUrl, String source) {
+ this.sourceUrl = sourceUrl;
+ this.source = source;
+ }
+
+ String getUrl() {
+ return sourceUrl;
+ }
+
+ String getSource() {
+ return source;
+ }
+
+ synchronized void setSource(String source) {
+ if (!this.source.equals(source)) {
+ this.source = source;
+ endLine = 0;
+ breakableLines = null;
+
+ if (breakpoints != null) {
+ for (int i = breakpoints.length - 1; i >= 0; --i) {
+ if (breakpoints[i] == BREAK_FLAG) {
+ breakpoints[i] = OLD_BREAK_FLAG;
+ }
+ }
+ }
+ }
+ }
+
+ synchronized void updateLineInfo(ScriptItem item) {
+
+ int[] lines = item.getScript().getLineNumbers();
+ if (lines.length == 0) {
+ return;
+ }
+
+ int fnFirstLine = lines[0];
+ int fnEndLine = fnFirstLine + 1;
+ for (int i = 1; i != lines.length; ++i) {
+ int line = lines[i];
+ if (line < fnFirstLine) {
+ fnFirstLine = line;
+ }else if (line >= fnEndLine) {
+ fnEndLine = line + 1;
+ }
+ }
+ item.setFirstLine(fnFirstLine);
+
+ if (endLine < fnEndLine) {
+ endLine = fnEndLine;
+ }
+ if (breakableLines == null) {
+ int newLength = 20;
+ if (newLength < endLine) { newLength = endLine; }
+ breakableLines = new boolean[newLength];
+ }else if (breakableLines.length < endLine) {
+ int newLength = breakableLines.length * 2;
+ if (newLength < endLine) { newLength = endLine; }
+ boolean[] tmp = new boolean[newLength];
+ System.arraycopy(breakableLines, 0, tmp, 0, breakableLines.length);
+ breakableLines = tmp;
+ }
+ int breakpointsEnd = (breakpoints == null) ? 0 : breakpoints.length;
+ for (int i = 0; i != lines.length; ++i) {
+ int line = lines[i];
+ breakableLines[line] = true;
+ if (line < breakpointsEnd) {
+ if (breakpoints[line] == OLD_BREAK_FLAG) {
+ breakpoints[line] = BREAK_FLAG;
+ }
+ }
+ }
+ }
+
+ boolean breakableLine(int line) {
+ boolean[] breakableLines = this.breakableLines;
+ if (breakableLines != null && line < breakableLines.length) {
+ return breakableLines[line];
+ }
+ return false;
+ }
+
+ boolean hasBreakpoint(int line) {
+ byte[] breakpoints = this.breakpoints;
+ if (breakpoints != null && line < breakpoints.length) {
+ return breakpoints[line] == BREAK_FLAG;
+ }
+ return false;
+ }
+
+ synchronized boolean placeBreakpoint(int line) {
+ if (breakableLine(line)) {
+ if (breakpoints == null) {
+ breakpoints = new byte[endLine];
+ }else if (line >= breakpoints.length) {
+ byte[] tmp = new byte[endLine];
+ System.arraycopy(breakpoints, 0, tmp, 0, breakpoints.length);
+ breakpoints = tmp;
+ }
+ breakpoints[line] = BREAK_FLAG;
+ return true;
+ }
+ return false;
+ }
+
+ synchronized boolean removeBreakpoint(int line) {
+ boolean wasBreakpoint = false;
+ if (breakpoints != null && line < breakpoints.length) {
+ wasBreakpoint = (breakpoints[line] == BREAK_FLAG);
+ breakpoints[line] = 0;
+ }
+ return wasBreakpoint;
+ }
+
+ synchronized void removeAllBreakpoints() {
+ breakpoints = null;
+ }
+
+ private String sourceUrl;
+ private String source;
+
+ private int endLine;
+ private boolean[] breakableLines;
+
+ private static final byte BREAK_FLAG = 1;
+ private static final byte OLD_BREAK_FLAG = 2;
+ private byte[] breakpoints;
+
+}
+
+public class Main extends JFrame implements Debugger, ContextListener {
+
+ /* ContextListener interface */
+
+ ObjToIntMap contexts = new ObjToIntMap();
+
+ static Thread mainThread; // thread used to run the shell
+
+ public void contextCreated(Context cx) {
+
+ synchronized (contexts) {
+ ContextData contextData = new ContextData();
+ cx.setDebugger(this, contextData);
+ cx.setGeneratingDebug(true);
+ cx.setOptimizationLevel(-1);
+ // if the user pressed "Break" or if this thread is the shell's
+ // Main then set the break flag so that when the debugger is run
+ // with a file argument on the command line it will
+ // break at the start of the file
+ if (breakFlag || Thread.currentThread() == mainThread) {
+ contextData.breakNextLine = true;
+ }
+ }
+ }
+
+ public void contextEntered(Context cx) {
+ // If the debugger is attached to cx
+ // keep a reference to it even if it was detached
+ // from its thread (we cause that to happen below
+ // in interrupted)
+ synchronized (contexts) {
+ if (!contexts.has(cx)) {
+ if (cx.getDebugger() == this) {
+ contexts.put(cx, 1);
+ }
+ }
+ }
+ }
+
+ public void contextExited(Context cx) {
+ }
+
+ public void contextReleased(Context cx) {
+ synchronized (contexts) {
+ contexts.remove(cx);
+ }
+ }
+
+ /* end ContextListener interface */
+
+ boolean breakFlag = false;
+
+ public void doBreak() {
+ breakFlag = true;
+ synchronized (contexts) {
+ ObjToIntMap.Iterator iter = contexts.newIterator();
+ for (iter.start(); !iter.done(); iter.next()) {
+ Context cx = (Context)iter.getKey();
+ ContextData.get(cx).breakNextLine = true;
+ }
+ }
+ }
+
+ public void setVisible(boolean b) {
+ super.setVisible(b);
+ if (b) {
+ // this needs to be done after the window is visible
+ console.consoleTextArea.requestFocus();
+ context.split.setDividerLocation(0.5);
+ try {
+ console.setMaximum(true);
+ console.setSelected(true);
+ console.show();
+ console.consoleTextArea.requestFocus();
+ } catch (Exception exc) {
+ }
+ }
+ }
+
+ static final int STEP_OVER = 0;
+ static final int STEP_INTO = 1;
+ static final int STEP_OUT = 2;
+ static final int GO = 3;
+ static final int BREAK = 4;
+ static final int RUN_TO_CURSOR = 5;
+ static final int EXIT = 6;
+
+ class ThreadState {
+ private int stopAtFrameDepth = -1;
+ };
+
+ private Hashtable threadState = new Hashtable();
+ private Thread runToCursorThread;
+ private int runToCursorLine;
+ private String runToCursorFile;
+ private Hashtable scriptItems = new Hashtable();
+ Hashtable sourceNames = new Hashtable();
+
+ Hashtable functionNames = new Hashtable();
+
+ ScriptItem getScriptItem(DebuggableScript fnOrScript) {
+ ScriptItem item = (ScriptItem)scriptItems.get(fnOrScript);
+ if (item == null) {
+ String url = getNormilizedUrl(fnOrScript);
+ SourceInfo si = (SourceInfo)sourceNames.get(url);
+ if (si == null) {
+ if (!fnOrScript.isGeneratedScript()) {
+ // Not eval or Function, try to load it from URL
+ String source = null;
+ try {
+ InputStream is = openSource(url);
+ try { source = readSource(is); }
+ finally { is.close(); }
+ } catch (IOException ex) {
+ System.err.println
+ ("Failed to load source from "+url+": "+ ex);
+ }
+ if (source != null) {
+ si = registerSource(url, source);
+ }
+ }
+ }
+ if (si != null) {
+ item = registerScript(si, fnOrScript);
+ }
+ }
+ return item;
+ }
+
+ /* Debugger Interface */
+
+ public void handleCompilationDone(Context cx, DebuggableScript fnOrScript,
+ String source)
+ {
+ String sourceUrl = getNormilizedUrl(fnOrScript);
+ SourceInfo si = registerSource(sourceUrl, source);
+ registerScript(si, fnOrScript);
+ }
+
+ String getNormilizedUrl(DebuggableScript fnOrScript) {
+ String url = fnOrScript.getSourceName();
+ if (url == null) { url = "column
.
+ */
+ public String getColumnName(int column);
+
+ /**
+ * Returns the type for column number column
.
+ */
+ public Class getColumnClass(int column);
+
+ /**
+ * Returns the value to be displayed for node node
,
+ * at column number column
.
+ */
+ public Object getValueAt(Object node, int column);
+
+ /**
+ * Indicates whether the the value for node node
,
+ * at column number column
is editable.
+ */
+ public boolean isCellEditable(Object node, int column);
+
+ /**
+ * Sets the value for node node
,
+ * at column number column
.
+ */
+ public void setValueAt(Object aValue, Object node, int column);
+}
diff --git a/src/helma/scripting/rhino/debug/TreeTableModelAdapter.java b/src/helma/scripting/rhino/debug/TreeTableModelAdapter.java
new file mode 100644
index 00000000..c82250ae
--- /dev/null
+++ b/src/helma/scripting/rhino/debug/TreeTableModelAdapter.java
@@ -0,0 +1,128 @@
+/*
+ * @(#)TreeTableModelAdapter.java 1.2 98/10/27
+ *
+ * Copyright 1997, 1998 by Sun Microsystems, Inc.,
+ * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information
+ * of Sun Microsystems, Inc. ("Confidential Information"). You
+ * shall not disclose such Confidential Information and shall use
+ * it only in accordance with the terms of the license agreement
+ * you entered into with Sun.
+ */
+
+
+ package helma.scripting.rhino.debug;
+ import javax.swing.JTree;
+
+import javax.swing.SwingUtilities;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.tree.TreePath;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+
+/**
+ * This is a wrapper class takes a TreeTableModel and implements
+ * the table model interface. The implementation is trivial, with
+ * all of the event dispatching support provided by the superclass:
+ * the AbstractTableModel.
+ *
+ * @version 1.2 10/27/98
+ *
+ * @author Philip Milne
+ * @author Scott Violet
+ */
+public class TreeTableModelAdapter extends AbstractTableModel
+{
+ JTree tree;
+ TreeTableModel treeTableModel;
+
+ public TreeTableModelAdapter(TreeTableModel treeTableModel, JTree tree) {
+ this.tree = tree;
+ this.treeTableModel = treeTableModel;
+
+ tree.addTreeExpansionListener(new TreeExpansionListener() {
+ // Don't use fireTableRowsInserted() here; the selection model
+ // would get updated twice.
+ public void treeExpanded(TreeExpansionEvent event) {
+ fireTableDataChanged();
+ }
+ public void treeCollapsed(TreeExpansionEvent event) {
+ fireTableDataChanged();
+ }
+ });
+
+ // Install a TreeModelListener that can update the table when
+ // tree changes. We use delayedFireTableDataChanged as we can
+ // not be guaranteed the tree will have finished processing
+ // the event before us.
+ treeTableModel.addTreeModelListener(new TreeModelListener() {
+ public void treeNodesChanged(TreeModelEvent e) {
+ delayedFireTableDataChanged();
+ }
+
+ public void treeNodesInserted(TreeModelEvent e) {
+ delayedFireTableDataChanged();
+ }
+
+ public void treeNodesRemoved(TreeModelEvent e) {
+ delayedFireTableDataChanged();
+ }
+
+ public void treeStructureChanged(TreeModelEvent e) {
+ delayedFireTableDataChanged();
+ }
+ });
+ }
+
+ // Wrappers, implementing TableModel interface.
+
+ public int getColumnCount() {
+ return treeTableModel.getColumnCount();
+ }
+
+ public String getColumnName(int column) {
+ return treeTableModel.getColumnName(column);
+ }
+
+ public Class getColumnClass(int column) {
+ return treeTableModel.getColumnClass(column);
+ }
+
+ public int getRowCount() {
+ return tree.getRowCount();
+ }
+
+ protected Object nodeForRow(int row) {
+ TreePath treePath = tree.getPathForRow(row);
+ return treePath.getLastPathComponent();
+ }
+
+ public Object getValueAt(int row, int column) {
+ return treeTableModel.getValueAt(nodeForRow(row), column);
+ }
+
+ public boolean isCellEditable(int row, int column) {
+ return treeTableModel.isCellEditable(nodeForRow(row), column);
+ }
+
+ public void setValueAt(Object value, int row, int column) {
+ treeTableModel.setValueAt(value, nodeForRow(row), column);
+ }
+
+ /**
+ * Invokes fireTableDataChanged after all the pending events have been
+ * processed. SwingUtilities.invokeLater is used to handle this.
+ */
+ protected void delayedFireTableDataChanged() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ fireTableDataChanged();
+ }
+ });
+ }
+}
+
diff --git a/src/helma/scripting/rhino/debug/VariableModel.java b/src/helma/scripting/rhino/debug/VariableModel.java
new file mode 100644
index 00000000..052324f4
--- /dev/null
+++ b/src/helma/scripting/rhino/debug/VariableModel.java
@@ -0,0 +1,339 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino JavaScript Debugger code, released
+ * November 21, 2000.
+ *
+ * The Initial Developer of the Original Code is SeeBeyond Corporation.
+
+ * Portions created by SeeBeyond are
+ * Copyright (C) 2000 SeeBeyond Technology Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ * Christopher Oliver
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package helma.scripting.rhino.debug;
+import org.mozilla.javascript.*;
+import javax.swing.event.TableModelEvent;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+public class VariableModel extends AbstractTreeTableModel
+ implements TreeTableModel {
+
+ // Names of the columns.
+
+ static protected String[] cNames = { " Name", " Value"};
+
+ // Types of the columns.
+
+ static protected Class[] cTypes = {TreeTableModel.class, String.class};
+
+
+ public VariableModel(Scriptable scope) {
+ super(scope == null ? null : new VariableNode(scope, "this"));
+ }
+
+ //
+ // Some convenience methods.
+ //
+
+ protected Object getObject(Object node) {
+ VariableNode varNode = ((VariableNode)node);
+ if (varNode == null) return null;
+ return varNode.getObject();
+ }
+
+ protected Object[] getChildren(Object node) {
+ VariableNode varNode = ((VariableNode)node);
+ return varNode.getChildren();
+ }
+
+ //
+ // The TreeModel interface
+ //
+
+ public int getChildCount(Object node) {
+ Object[] children = getChildren(node);
+ return (children == null) ? 0 : children.length;
+ }
+
+ public Object getChild(Object node, int i) {
+ return getChildren(node)[i];
+ }
+
+ // The superclass's implementation would work, but this is more efficient.
+ public boolean isLeaf(Object node) {
+ if (node == null) return true;
+ VariableNode varNode = (VariableNode)node;
+ Object[] children = varNode.getChildren();
+ if (children != null && children.length > 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isCellEditable(Object node, int column) {
+ return column == 0;
+ }
+
+ //
+ // The TreeTableNode interface.
+ //
+
+ public int getColumnCount() {
+ return cNames.length;
+ }
+
+ public String getColumnName(int column) {
+ return cNames[column];
+ }
+
+ public Class getColumnClass(int column) {
+ return cTypes[column];
+ }
+
+ public Object getValueAt(Object node, int column) {
+ Context cx = Context.enter();
+ try {
+ Object value = getObject(node);
+ switch (column) {
+ case 0: // Name
+ VariableNode varNode = (VariableNode)node;
+ String name = "";
+ if (varNode.name != null) {
+ return name + varNode.name;
+ }
+ return name + "[" + varNode.index + "]";
+ case 1: // Value
+ if (value == Undefined.instance ||
+ value == ScriptableObject.NOT_FOUND) {
+ return "undefined";
+ }
+ if (value == null) {
+ return "null";
+ }
+ if (value instanceof NativeCall) {
+ return "[object Call]";
+ }
+ String result;
+ try {
+ result = Context.toString(value);
+ } catch (RuntimeException exc) {
+ result = exc.getMessage();
+ }
+ StringBuffer buf = new StringBuffer();
+ int len = result.length();
+ for (int i = 0; i < len; i++) {
+ char ch = result.charAt(i);
+ if (Character.isISOControl(ch)) {
+ ch = ' ';
+ }
+ buf.append(ch);
+ }
+ return buf.toString();
+ }
+ } catch (Exception exc) {
+ //exc.printStackTrace();
+ } finally {
+ cx.exit();
+ }
+ return null;
+ }
+
+ public void setScope(Scriptable scope) {
+ VariableNode rootVar = (VariableNode)root;
+ rootVar.scope = scope;
+ fireTreeNodesChanged(this,
+ new Object[]{root},
+ null, new Object[]{root});
+ }
+
+}
+
+
+class VariableNode {
+ Scriptable scope;
+ String name;
+ int index;
+
+ public VariableNode(Scriptable scope, String name) {
+ this.scope = scope;
+ this.name = name;
+ }
+
+ public VariableNode(Scriptable scope, int index) {
+ this.scope = scope;
+ this.name = null;
+ this.index = index;
+ }
+
+ /**
+ * Returns the the string to be used to display this leaf in the JTree.
+ */
+ public String toString() {
+ return (name != null ? name : "[" + index + "]");
+ }
+
+ public Object getObject() {
+ try {
+ if (scope == null) return null;
+ if (name != null) {
+ if (name.equals("this")) {
+ return scope;
+ }
+ Object result = ScriptableObject.NOT_FOUND;
+ if (name.equals("__proto__")) {
+ result = scope.getPrototype();
+ } else if (name.equals("__parent__")) {
+ result = scope.getParentScope();
+ } else {
+ try {
+ result = ScriptableObject.getProperty(scope, name);
+ } catch (RuntimeException e) {
+ result = e.getMessage();
+ }
+ }
+ if (result == ScriptableObject.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+ return result;
+ }
+ Object result = ScriptableObject.getProperty(scope, index);
+ if (result == ScriptableObject.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+ return result;
+ } catch (Exception exc) {
+ return "undefined";
+ }
+ }
+
+ Object[] children;
+
+ static final Object[] empty = new Object[0];
+
+ protected Object[] getChildren() {
+ if (children != null) return children;
+ Context cx = Context.enter();
+ try {
+ Object value = getObject();
+ if (value == null) return children = empty;
+ if (value == ScriptableObject.NOT_FOUND ||
+ value == Undefined.instance) {
+ return children = empty;
+ }
+ if (value instanceof Scriptable) {
+ Scriptable scrip = (Scriptable)value;
+ Scriptable proto = scrip.getPrototype();
+ Scriptable parent = scrip.getParentScope();
+ if (scrip.has(0, scrip)) {
+ int len = 0;
+ try {
+ Scriptable start = scrip;
+ Scriptable obj = start;
+ Object result = Undefined.instance;
+ do {
+ if (obj.has("length", start)) {
+ result = obj.get("length", start);
+ if (result != Scriptable.NOT_FOUND)
+ break;
+ }
+ obj = obj.getPrototype();
+ } while (obj != null);
+ if (result instanceof Number) {
+ len = ((Number)result).intValue();
+ }
+ } catch (Exception exc) {
+ }
+ if (parent != null) {
+ len++;
+ }
+ if (proto != null) {
+ len++;
+ }
+ children = new VariableNode[len];
+ int i = 0;
+ int j = 0;
+ if (parent != null) {
+ children[i++] = new VariableNode(scrip, "__parent__");
+ j++;
+ }
+ if (proto != null) {
+ children[i++] = new VariableNode(scrip, "__proto__");
+ j++;
+ }
+ for (; i < len; i++) {
+ children[i] = new VariableNode(scrip, i-j);
+ }
+ } else {
+ int len = 0;
+ Hashtable t = new Hashtable();
+ Object[] ids;
+ if (scrip instanceof ScriptableObject) {
+ ids = ((ScriptableObject)scrip).getAllIds();
+ } else {
+ ids = scrip.getIds();
+ }
+ if (ids == null) ids = empty;
+ if (proto != null) t.put("__proto__", "__proto__");
+ if (parent != null) t.put("__parent__", "__parent__");
+ if (ids.length > 0) {
+ for (int j = 0; j < ids.length; j++) {
+ t.put(ids[j], ids[j]);
+ }
+ }
+ ids = new Object[t.size()];
+ Enumeration e = t.keys();
+ int j = 0;
+ while (e.hasMoreElements()) {
+ ids[j++] = e.nextElement().toString();
+ }
+ if (ids != null && ids.length > 0) {
+ java.util.Arrays.sort(ids, new java.util.Comparator() {
+ public int compare(Object l, Object r) {
+ return l.toString().compareToIgnoreCase(r.toString());
+
+ }
+ });
+ len = ids.length;
+ }
+ children = new VariableNode[len];
+ for (int i = 0; i < len; i++) {
+ Object id = ids[i];
+ children[i] = new
+ VariableNode(scrip, id.toString());
+ }
+ }
+ }
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ } finally {
+ cx.exit();
+ }
+ return children;
+ }
+}
+