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 = ""; } + else { + // Not to produce window for eval from different lines, + // strip line numbers, i.e. replace all #[0-9]+\(eval\) by (eval) + // Option: similar teatment for Function? + char evalSeparator = '#'; + StringBuffer sb = null; + int urlLength = url.length(); + int cursor = 0; + for (;;) { + int searchStart = url.indexOf(evalSeparator, cursor); + if (searchStart < 0) { + break; + } + String replace = null; + int i = searchStart + 1; + boolean hasDigits = false; + while (i != urlLength) { + int c = url.charAt(i); + if (!('0' <= c && c <= '9')) { + break; + } + ++i; + } + if (i != searchStart + 1) { + // i points after #[0-9]+ + if ("(eval)".regionMatches(0, url, i, 6)) { + cursor = i + 6; + replace = "(eval)"; + } + } + if (replace == null) { + break; + } + if (sb == null) { + sb = new StringBuffer(); + sb.append(url.substring(0, searchStart)); + } + sb.append(replace); + } + if (sb != null) { + if (cursor != urlLength) { + sb.append(url.substring(cursor)); + } + url = sb.toString(); + } + } + // System.err.println(fnOrScript.getSourceName() + " -> " + url); + return url; + } + + private static InputStream openSource(String sourceUrl) + throws IOException + { + int hash = sourceUrl.indexOf('#'); + if (hash >= 0) { + sourceUrl = sourceUrl.substring(0, hash); + } + if (sourceUrl.indexOf(':') < 0) { + // Can be a file name + try { + if (sourceUrl.startsWith("~/")) { + String home = System.getProperty("user.home"); + if (home != null) { + String pathFromHome = sourceUrl.substring(2); + File f = new File(new File(home), pathFromHome); + if (f.exists()) { + return new FileInputStream(f); + } + } + } + File f = new File(sourceUrl); + if (f.exists()) { + return new FileInputStream(f); + } + } catch (SecurityException ex) { } + // No existing file, assume missed http:// + if (sourceUrl.startsWith("//")) { + sourceUrl = "http:" + sourceUrl; + } else if (sourceUrl.startsWith("/")) { + sourceUrl = "http://127.0.0.1" + sourceUrl; + } else { + sourceUrl = "http://" + sourceUrl; + } + } + + return (new java.net.URL(sourceUrl)).openStream(); + } + + private static String readSource(InputStream is) throws IOException { + byte[] buffer = new byte[4096]; + int offset = 0; + for (;;) { + int n = is.read(buffer, 0, buffer.length - offset); + if (n < 0) { break; } + offset += n; + if (offset == buffer.length) { + byte[] tmp = new byte[buffer.length * 2]; + System.arraycopy(buffer, 0, tmp, 0, offset); + buffer = tmp; + } + } + return new String(buffer, 0, offset); + } + + private SourceInfo registerSource(String sourceUrl, String source) { + SourceInfo si; + synchronized (sourceNames) { + si = (SourceInfo)sourceNames.get(sourceUrl); + if (si == null) { + si = new SourceInfo(sourceUrl, source); + sourceNames.put(sourceUrl, si); + } else { + si.setSource(source); + } + } + return si; + } + + private ScriptItem registerScript(SourceInfo si, + DebuggableScript fnOrScript) + { + ScriptItem item = new ScriptItem(fnOrScript, si); + si.updateLineInfo(item); + scriptItems.put(fnOrScript, item); + + String name = fnOrScript.getFunctionName(); + if (name != null && name.length() > 0 && !name.equals("anonymous")) { + functionNames.put(name, item); + } + + // loadedFile(si); + return item; + } + + void handleBreakpointHit(Context cx) { + breakFlag = false; + interrupted(cx); + } + + private static String exceptionString(Throwable ex) { + if (ex instanceof JavaScriptException) { + JavaScriptException jse = (JavaScriptException)ex; + return ScriptRuntime.toString(jse.getValue()); + } else if (ex instanceof EcmaError) { + return ex.toString(); + } else if (ex instanceof WrappedException) { + Throwable wrapped = ((WrappedException)ex).getWrappedException(); + if (wrapped != null) { + ex = wrapped; + } + } + String msg = ex.toString(); + if (msg == null || msg.length() == 0) { + msg = ex.getClass().toString(); + } + return msg; + } + + void handleExceptionThrown(Context cx, Throwable ex, FrameHelper frame) { + if (breakOnExceptions) { + String url = frame.getUrl(); + int lineNumber = frame.getLineNumber(); + FileWindow w = getFileWindow(url); + String msg = exceptionString(ex); + msg += " (" + url + ", line " + lineNumber + ")"; + if (w != null) { + swingInvoke(new SetFilePosition(this, w, lineNumber)); + } + MessageDialogWrapper.showMessageDialog(this, + msg, + "Exception in Script", + JOptionPane.ERROR_MESSAGE); + //if (w != null) { + //swingInvoke(new SetFilePosition(this, w, -1)); + //} + interrupted(cx); + } + } + + public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) { + return new FrameHelper(cx, this, fnOrScript); + } + + /* end Debugger interface */ + + JDesktopPane desk; + ContextWindow context; + Menubar menubar; + JToolBar toolBar; + JSInternalConsole console; + EvalWindow evalWindow; + JSplitPane split1; + JLabel statusBar; + + void init() { + setJMenuBar(menubar = new Menubar(this)); + toolBar = new JToolBar(); + JButton button; + JButton breakButton, goButton, stepIntoButton, + stepOverButton, stepOutButton; + String [] toolTips = {"Break (Pause)", + "Go (F5)", + "Step Into (F11)", + "Step Over (F7)", + "Step Out (F8)"}; + int count = 0; + button = breakButton = new JButton("Break"); + JButton focusButton = button; + button.setToolTipText("Break"); + button.setActionCommand("Break"); + button.addActionListener(menubar); + button.setEnabled(true); + button.setToolTipText(toolTips[count++]); + + button = goButton = new JButton("Go"); + button.setToolTipText("Go"); + button.setActionCommand("Go"); + button.addActionListener(menubar); + button.setEnabled(false); + button.setToolTipText(toolTips[count++]); + + button = stepIntoButton = new JButton("Step Into"); + button.setToolTipText("Step Into"); + button.setActionCommand("Step Into"); + button.addActionListener(menubar); + button.setEnabled(false); + button.setToolTipText(toolTips[count++]); + + button = stepOverButton = new JButton("Step Over"); + button.setToolTipText("Step Over"); + button.setActionCommand("Step Over"); + button.setEnabled(false); + button.addActionListener(menubar); + button.setToolTipText(toolTips[count++]); + + button = stepOutButton = new JButton("Step Out"); + button.setToolTipText("Step Out"); + button.setActionCommand("Step Out"); + button.setEnabled(false); + button.addActionListener(menubar); + button.setToolTipText(toolTips[count++]); + + Dimension dim = stepOverButton.getPreferredSize(); + breakButton.setPreferredSize(dim); + breakButton.setMinimumSize(dim); + breakButton.setMaximumSize(dim); + breakButton.setSize(dim); + goButton.setPreferredSize(dim); + goButton.setMinimumSize(dim); + goButton.setMaximumSize(dim); + stepIntoButton.setPreferredSize(dim); + stepIntoButton.setMinimumSize(dim); + stepIntoButton.setMaximumSize(dim); + stepOverButton.setPreferredSize(dim); + stepOverButton.setMinimumSize(dim); + stepOverButton.setMaximumSize(dim); + stepOutButton.setPreferredSize(dim); + stepOutButton.setMinimumSize(dim); + stepOutButton.setMaximumSize(dim); + toolBar.add(breakButton); + toolBar.add(goButton); + toolBar.add(stepIntoButton); + toolBar.add(stepOverButton); + toolBar.add(stepOutButton); + + JPanel contentPane = new JPanel(); + contentPane.setLayout(new BorderLayout()); + getContentPane().add(toolBar, BorderLayout.NORTH); + getContentPane().add(contentPane, BorderLayout.CENTER); + desk = new JDesktopPane(); + desk.setPreferredSize(new Dimension(600, 300)); + desk.setMinimumSize(new Dimension(150, 50)); + desk.add(console = new JSInternalConsole("JavaScript Console")); + context = new ContextWindow(this); + context.setPreferredSize(new Dimension(600, 120)); + context.setMinimumSize(new Dimension(50, 50)); + + split1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, desk, + context); + split1.setOneTouchExpandable(true); + Main.setResizeWeight(split1, 0.66); + contentPane.add(split1, BorderLayout.CENTER); + statusBar = new JLabel(); + statusBar.setText("Thread: "); + contentPane.add(statusBar, BorderLayout.SOUTH); + dlg = new JFileChooser(); + + javax.swing.filechooser.FileFilter filter = + new javax.swing.filechooser.FileFilter() { + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + String n = f.getName(); + int i = n.lastIndexOf('.'); + if (i > 0 && i < n.length() -1) { + String ext = n.substring(i + 1).toLowerCase(); + if (ext.equals("js")) { + return true; + } + } + return false; + } + + public String getDescription() { + return "JavaScript Files (*.js)"; + } + }; + dlg.addChoosableFileFilter(filter); + final Main self = this; + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + self.Exit(); + } + }); + } + + + ScopeProvider scopeProvider; + Runnable exitAction; + + + Scriptable getScope() { + return (scopeProvider != null) ? scopeProvider.getScope() : null; + } + + FileWindow getFileWindow(String url) { + if (url == null || url.equals("")) { + return null; + } + return (FileWindow)fileWindows.get(url); + } + + void loadedFile(SourceInfo si) { + String fileName = si.getUrl(); + FileWindow w = getFileWindow(fileName); + if (w != null) { + swingInvoke(UpdateFileText.action(w)); + w.show(); + } else if (!fileName.equals("")) { + swingInvoke(CreateFileWindow.action(this, si, -1)); + } + } + + static void swingInvoke(Runnable f) { + if (SwingUtilities.isEventDispatchThread()) { + f.run(); + return; + } + try { + SwingUtilities.invokeAndWait(f); + } catch (Exception exc) { + exc.printStackTrace(); + } + } + + static void swingInvokeLater(Runnable f) { + try { + SwingUtilities.invokeLater(f); + } catch (RuntimeException exc) { + exc.printStackTrace(); + } + } + + int frameIndex = -1; + + void contextSwitch (int frameIndex) { + Context cx = getCurrentContext(); + ContextData contextData = ContextData.get(cx); + ContextHelper helper = new ContextHelper(); + helper.attach(cx); + if (cx != null) { + int frameCount = contextData.getFrameCount(); + if (frameIndex < 0 || frameIndex >= frameCount) { + helper.reset(); + return; + } + this.frameIndex = frameIndex; + FrameHelper frame = contextData.getFrame(frameIndex); + String sourceName = frame.getUrl(); + if (sourceName == null || sourceName.equals("")) { + console.show(); + helper.reset(); + return; + } + int lineNumber = frame.getLineNumber(); + this.frameIndex = frameIndex; + FileWindow w = getFileWindow(sourceName); + if (w != null) { + SetFilePosition action = + new SetFilePosition(this, w, lineNumber); + action.run(); + } else { + SourceInfo si = frame.getSourceInfo(); + CreateFileWindow.action(this, si, lineNumber).run(); + } + helper.reset(); + } + } + + boolean isInterrupted = false; + boolean nonDispatcherWaiting = false; + int dispatcherIsWaiting = 0; + Context currentContext = null; + + Context getCurrentContext() { + return currentContext; + } + + void interrupted(Context cx) { + synchronized (swingMonitor) { + if (SwingUtilities.isEventDispatchThread()) { + dispatcherIsWaiting++; + if (nonDispatcherWaiting) { + // Another thread is stopped in the debugger + // process events until it resumes and we + // can enter + java.awt.EventQueue eventQ = + java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue(); + while (nonDispatcherWaiting) { + try { + AWTEvent event = eventQ.getNextEvent(); + if (event instanceof ActiveEvent) { + ((ActiveEvent)event).dispatch(); + } else { + Object source = event.getSource(); + if (source instanceof Component) { + Component comp = (Component)source; + // Suppress Window/InputEvent's that aren't + // directed to the Debugger + // if (!(event instanceof InputEvent || + //event instanceof WindowEvent)|| + //shouldDispatchTo(comp)) { + comp.dispatchEvent(event); + //} + } else if (source instanceof MenuComponent) { + ((MenuComponent)source).dispatchEvent(event); + } + } + if (this.returnValue == EXIT) { + return; + } + swingMonitor.wait(1); + } catch (InterruptedException exc) { + return; + } + } + } + } else { + while (isInterrupted || dispatcherIsWaiting > 0) { + try { + swingMonitor.wait(); + } catch (InterruptedException exc) { + return; + } + } + nonDispatcherWaiting = true; + } + isInterrupted = true; + } + do { + currentContext = cx; + ContextData contextData = ContextData.get(cx); + Thread thread = Thread.currentThread(); + statusBar.setText("Thread: " + thread.toString()); + ThreadState state = (ThreadState)threadState.get(thread); + int stopAtFrameDepth = -1; + if (state != null) { + stopAtFrameDepth = state.stopAtFrameDepth; + } + if (runToCursorFile != null && thread == runToCursorThread) { + int frameCount = contextData.getFrameCount(); + if (frameCount > 0) { + FrameHelper frame = contextData.getFrame(0); + String url = frame.getUrl(); + if (url != null) { + if (url.equals(runToCursorFile)) { + int lineNumber = frame.getLineNumber(); + if (lineNumber == runToCursorLine) { + stopAtFrameDepth = -1; + runToCursorFile = null; + } else { + FileWindow w = getFileWindow(url); + if (w == null || + !w.isBreakPoint(lineNumber)) { + return; + } else { + runToCursorFile = null; + } + } + } + } + } else { + } + } + if (stopAtFrameDepth > 0) { + if (contextData.getFrameCount() > stopAtFrameDepth) { + break; + } + } + if (state != null) { + state.stopAtFrameDepth = -1; + } + threadState.remove(thread); + int frameCount = contextData.getFrameCount(); + this.frameIndex = frameCount -1; + int line = 0; + if (frameCount == 0) { + break; + } + FrameHelper frame = contextData.getFrame(0); + String url = frame.getUrl(); + contextData.breakNextLine = false; + line = frame.getLineNumber(); + int enterCount = 0; + boolean isDispatchThread = + SwingUtilities.isEventDispatchThread(); + + if (!isDispatchThread) { + // detach cx from its thread so the debugger (in the awt + // dispatcher thread) can enter it if necessary + cx.exit(); + while (Context.getCurrentContext() != null) { + Context.exit(); + enterCount++; + } + } + if (url != null && !url.equals("")) { + FileWindow w = (FileWindow)getFileWindow(url); + if (w != null) { + SetFilePosition action = + new SetFilePosition(this, w, line); + swingInvoke(action); + } else { + SourceInfo si = frame.getSourceInfo(); + swingInvoke(CreateFileWindow.action(this, si, line)); + } + } else { + if (console.isVisible()) { + final JSInternalConsole finalConsole = console; + swingInvoke(new Runnable() { + public void run() { + finalConsole.show(); + } + }); + } + } + swingInvoke(new EnterInterrupt(this, cx)); + swingInvoke(new UpdateContext(this, cx)); + int returnValue; + if (!isDispatchThread) { + synchronized (monitor) { + this.returnValue = -1; + try { + while (this.returnValue == -1) { + monitor.wait(); + } + returnValue = this.returnValue; + } catch (InterruptedException exc) { + break; + } + } + } else { + java.awt.EventQueue eventQ = + java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue(); + this.returnValue = -1; + while (this.returnValue == -1) { + try { + AWTEvent event = eventQ.getNextEvent(); + if (event instanceof ActiveEvent) { + ((ActiveEvent)event).dispatch(); + } else { + Object source = event.getSource(); + if (source instanceof Component) { + Component comp = (Component)source; + // Suppress Window/InputEvent's that aren't + // directed to the Debugger + // if (!(event instanceof InputEvent || + //event instanceof WindowEvent)|| + // shouldDispatchTo(comp)) { + //comp.dispatchEvent(event); + //} + comp.dispatchEvent(event); + } else if (source instanceof MenuComponent) { + ((MenuComponent)source).dispatchEvent(event); + } + } + } catch (InterruptedException exc) { + } + } + returnValue = this.returnValue; + } + swingInvoke(new ExitInterrupt(this)); + if (!isDispatchThread) { + // reattach cx to its thread + Context current; + if ((current = Context.enter(cx)) != cx) { + System.out.println("debugger error: cx = " + cx + " current = " + current); + + } + while (enterCount > 0) { + Context.enter(); + enterCount--; + } + } + switch (returnValue) { + case STEP_OVER: + contextData.breakNextLine = true; + stopAtFrameDepth = contextData.getFrameCount(); + if (state == null) { + state = new ThreadState(); + } + state.stopAtFrameDepth = stopAtFrameDepth; + threadState.put(thread, state); + break; + case STEP_INTO: + contextData.breakNextLine = true; + if (state != null) { + state.stopAtFrameDepth = -1; + } + break; + case STEP_OUT: + stopAtFrameDepth = contextData.getFrameCount() -1; + if (stopAtFrameDepth > 0) { + contextData.breakNextLine = true; + if (state == null) { + state = new ThreadState(); + } + state.stopAtFrameDepth = stopAtFrameDepth; + threadState.put(thread, state); + } + break; + case RUN_TO_CURSOR: + contextData.breakNextLine = true; + if (state != null) { + state.stopAtFrameDepth = -1; + } + break; + } + } while (false); + synchronized (swingMonitor) { + isInterrupted = false; + if (SwingUtilities.isEventDispatchThread()) { + dispatcherIsWaiting--; + } else { + nonDispatcherWaiting = false; + } + swingMonitor.notifyAll(); + } + } + + JFileChooser dlg; + + String chooseFile(String title) { + dlg.setDialogTitle(title); + File CWD = null; + String dir = System.getProperty("user.dir"); + if (dir != null) { + CWD = new File(dir); + } + if (CWD != null) { + dlg.setCurrentDirectory(CWD); + } + int returnVal = dlg.showOpenDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + try { + String result = dlg.getSelectedFile().getCanonicalPath(); + CWD = dlg.getSelectedFile().getParentFile(); + Properties props = System.getProperties(); + props.put("user.dir", CWD.getPath()); + System.setProperties(props); + return result; + }catch (IOException ignored) { + }catch (SecurityException ignored) { + } + } + return null; + } + + JInternalFrame getSelectedFrame() { + JInternalFrame[] frames = desk.getAllFrames(); + for (int i = 0; i < frames.length; i++) { + if (frames[i].isShowing()) { + return frames[i]; + } + } + return frames[frames.length - 1]; + } + + void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + int returnValue = -1; + if (cmd.equals("Cut") || cmd.equals("Copy") || cmd.equals("Paste")) { + JInternalFrame f = getSelectedFrame(); + if (f != null && f instanceof ActionListener) { + ((ActionListener)f).actionPerformed(e); + } + } else if (cmd.equals("Step Over")) { + returnValue = STEP_OVER; + } else if (cmd.equals("Step Into")) { + returnValue = STEP_INTO; + } else if (cmd.equals("Step Out")) { + returnValue = STEP_OUT; + } else if (cmd.equals("Go")) { + returnValue = GO; + } else if (cmd.equals("Break")) { + doBreak(); + } else if (cmd.equals("Run to Cursor")) { + returnValue = RUN_TO_CURSOR; + } else if (cmd.equals("Exit")) { + Exit(); + } else if (cmd.equals("Open")) { + String fileName = chooseFile("Select a file to compile"); + if (fileName != null) { + new Thread(new OpenFile(this, fileName)).start(); + } + } else if (cmd.equals("Load")) { + Scriptable scope = getScope(); + if (scope == null) { + MessageDialogWrapper.showMessageDialog(this, "Can't run scripts: no scope available", "Run", JOptionPane.ERROR_MESSAGE); + } else { + String fileName = chooseFile("Select a file to execute"); + if (fileName != null) { + new Thread(new LoadFile(this, scope, + fileName)).start(); + } + } + } else if (cmd.equals("More Windows...")) { + MoreWindows dlg = new MoreWindows(this, fileWindows, + "Window", "Files"); + dlg.showDialog(this); + } else if (cmd.equals("Console")) { + if (console.isIcon()) { + desk.getDesktopManager().deiconifyFrame(console); + } + console.show(); + desk.getDesktopManager().activateFrame(console); + console.consoleTextArea.requestFocus(); + } else if (cmd.equals("Cut")) { + } else if (cmd.equals("Copy")) { + } else if (cmd.equals("Paste")) { + } else if (cmd.equals("Go to function...")) { + FindFunction dlg = new FindFunction(this, functionNames, + "Go to function", + "Function"); + dlg.showDialog(this); + } else if (cmd.equals("Tile")) { + JInternalFrame[] frames = desk.getAllFrames(); + int count = frames.length; + int rows, cols; + rows = cols = (int)Math.sqrt(count); + if (rows*cols < count) { + cols++; + if (rows * cols < count) { + rows++; + } + } + Dimension size = desk.getSize(); + int w = size.width/cols; + int h = size.height/rows; + int x = 0; + int y = 0; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + int index = (i*cols) + j; + if (index >= frames.length) { + break; + } + JInternalFrame f = frames[index]; + try { + f.setIcon(false); + f.setMaximum(false); + } catch (Exception exc) { + } + desk.getDesktopManager().setBoundsForFrame(f, x, y, + w, h); + x += w; + } + y += h; + x = 0; + } + } else if (cmd.equals("Cascade")) { + JInternalFrame[] frames = desk.getAllFrames(); + int count = frames.length; + int x, y, w, h; + x = y = 0; + h = desk.getHeight(); + int d = h / count; + if (d > 30) d = 30; + for (int i = count -1; i >= 0; i--, x += d, y += d) { + JInternalFrame f = frames[i]; + try { + f.setIcon(false); + f.setMaximum(false); + } catch (Exception exc) { + } + Dimension dimen = f.getPreferredSize(); + w = dimen.width; + h = dimen.height; + desk.getDesktopManager().setBoundsForFrame(f, x, y, w, h); + } + } else { + Object obj = getFileWindow(cmd); + if (obj != null) { + FileWindow w = (FileWindow)obj; + try { + if (w.isIcon()) { + w.setIcon(false); + } + w.setVisible(true); + w.moveToFront(); + w.setSelected(true); + } catch (Exception exc) { + } + } + } + if (returnValue != -1) { + if (currentWindow != null) currentWindow.setPosition(-1); + synchronized (monitor) { + this.returnValue = returnValue; + monitor.notify(); + } + } + } + + void runToCursor(String fileName, + int lineNumber, + ActionEvent evt) { + SourceInfo si = (SourceInfo)sourceNames.get(fileName); + if (si == null) { + System.out.println("debugger error: Couldn't find source: " + fileName); + } + if (si.breakableLine(lineNumber)) { + runToCursorFile = fileName; + runToCursorLine = lineNumber; + actionPerformed(evt); + } + } + + JMenu getWindowMenu() { + return menubar.getMenu(3); + } + + void removeWindow(FileWindow w) { + fileWindows.remove(w.getUrl()); + JMenu windowMenu = getWindowMenu(); + int count = windowMenu.getItemCount(); + JMenuItem lastItem = windowMenu.getItem(count -1); + String name = SourceInfo.getShortName(w.getUrl()); + for (int i = 5; i < count; i++) { + JMenuItem item = windowMenu.getItem(i); + if (item == null) continue; // separator + String text = item.getText(); + //1 D:\foo.js + //2 D:\bar.js + int pos = text.indexOf(' '); + if (text.substring(pos + 1).equals(name)) { + windowMenu.remove(item); + // Cascade [0] + // Tile [1] + // ------- [2] + // Console [3] + // ------- [4] + if (count == 6) { + // remove the final separator + windowMenu.remove(4); + } else { + int j = i - 4; + for (;i < count -1; i++) { + JMenuItem thisItem = windowMenu.getItem(i); + if (thisItem != null) { + //1 D:\foo.js + //2 D:\bar.js + text = thisItem.getText(); + if (text.equals("More Windows...")) { + break; + } else { + pos = text.indexOf(' '); + thisItem.setText((char)('0' + j) + " " + + text.substring(pos + 1)); + thisItem.setMnemonic('0' + j); + j++; + } + } + } + if (count - 6 == 0 && lastItem != item) { + if (lastItem.getText().equals("More Windows...")) { + windowMenu.remove(lastItem); + } + } + } + break; + } + } + windowMenu.revalidate(); + } + + boolean stringIsCompilableUnit(String expr) { + Context cx = Context.enter(); + boolean result = cx.stringIsCompilableUnit(expr); + cx.exit(); + return result; + } + + String eval(String expr) { + Context cx = getCurrentContext(); + if (cx == null) return "undefined"; + ContextData contextData = ContextData.get(cx); + ContextHelper helper = new ContextHelper(); + helper.attach(cx); + if (frameIndex >= contextData.getFrameCount()) { + helper.reset(); + return "undefined"; + } + String resultString; + cx.setDebugger(null, null); + cx.setGeneratingDebug(false); + cx.setOptimizationLevel(-1); + boolean savedBreakNextLine = contextData.breakNextLine; + contextData.breakNextLine = false; + try { + Script script; + int savedLevel = cx.getOptimizationLevel(); + try { + cx.setOptimizationLevel(-1); + script = cx.compileString(expr, "", 0, null); + } finally { + cx.setOptimizationLevel(savedLevel); + } + FrameHelper frame = contextData.getFrame(frameIndex); + Scriptable scope = frame.getVariableObject(); + Object result; + if (scope instanceof NativeCall + && script instanceof Function) + { + NativeCall call = (NativeCall)scope; + Function f = (Function)script; + result = f.call(cx, scope, call.getThisObj(), + ScriptRuntime.emptyArgs); + } else { + result = script.exec(cx, scope); + } + if (result == Undefined.instance) { + result = ""; + } + try { + resultString = ScriptRuntime.toString(result); + } catch (RuntimeException exc) { + resultString = result.toString(); + } + } catch (Exception exc) { + resultString = exc.getMessage(); + } + if (resultString == null) { + resultString = "null"; + } + cx.setDebugger(this, contextData); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + contextData.breakNextLine = savedBreakNextLine; + helper.reset(); + return resultString; + } + + void Exit() { + // stop handling events + this.returnValue = EXIT; + // call the exit handler if any + if (exitAction != null) { + swingInvokeLater(exitAction); + } + } + + java.util.Hashtable fileWindows = new java.util.Hashtable(); + FileWindow currentWindow; + Object monitor = new Object(); + Object swingMonitor = new Object(); + int returnValue = -1; + boolean breakOnExceptions; + boolean breakOnEnter; + boolean breakOnReturn; + + static void setResizeWeight(JSplitPane pane, double weight) { + // call through reflection for portability + // pre-1.3 JDK JSplitPane doesn't have this method + try { + Method m = JSplitPane.class.getMethod("setResizeWeight", + new Class[]{double.class}); + m.invoke(pane, new Object[]{new Double(weight)}); + } catch (NoSuchMethodException exc) { + } catch (IllegalAccessException exc) { + } catch (java.lang.reflect.InvocationTargetException exc) { + } + } + + java.util.Hashtable toplevels = new java.util.Hashtable(); + + boolean shouldDispatchTo(Component source) { + Component root = SwingUtilities.getRoot(source); + if (root == this) { + return true; + } + Enumeration e = toplevels.keys(); + while (e.hasMoreElements()) { + Object key = e.nextElement(); + JFrame frame = (JFrame)toplevels.get(key); + if (root == frame) { + return true; + } + } + return false; + } + + void addTopLevel(String key, JFrame frame) { + if (frame != this) { + toplevels.put(key, frame); + } + } + + // + // public interface + // + + public Main(String title) { + super(title); + init(); + } + + /** + * Toggle Break-on-Exception behavior + */ + public void setBreakOnExceptions(boolean value) { + this.breakOnExceptions = value; + } + + /** + * Toggle Break-on-Enter behavior + */ + public void setBreakOnEnter(boolean value) { + this.breakOnEnter = value; + } + + /** + * Toggle Break-on-Return behavior + */ + public void setBreakOnReturn(boolean value) { + this.breakOnReturn = value; + } + + /** + * + * Remove all breakpoints + */ + public void clearAllBreakpoints() { + Enumeration e = sourceNames.elements(); + while (e.hasMoreElements()) { + SourceInfo si = (SourceInfo)e.nextElement(); + si.removeAllBreakpoints(); + } + } + + /** + * Resume Execution + */ + public void go() + { + returnValue = GO; + synchronized (monitor) { + this.returnValue = returnValue; + monitor.notifyAll(); + } + } + + /** + * Assign an object that can provide a Scriptable that will + * be used as the scope for loading scripts from files + * when the user selects "Open..." or "Run..." + */ + public void setScopeProvider(ScopeProvider p) { + scopeProvider = p; + } + + /** + * Assign a Runnable object that will be invoked when the user + * selects "Exit..." or closes the Debugger main window + */ + public void setExitAction(Runnable r) { + exitAction = r; + } + + /** + * Get an input stream to the Debugger's internal Console window + */ + + public InputStream getIn() { + return console.getIn(); + } + + /** + * Get an output stream to the Debugger's internal Console window + */ + + public PrintStream getOut() { + return console.getOut(); + } + + /** + * Get an error stream to the Debugger's internal Console window + */ + + public PrintStream getErr() { + return console.getErr(); + } + + public static void main(String[] args) { + try { + mainThread = Thread.currentThread(); + final Main sdb = new Main("Rhino JavaScript Debugger"); + swingInvoke(new Runnable() { + public void run() { + sdb.pack(); + sdb.setSize(600, 460); + sdb.setVisible(true); + } + }); + sdb.setExitAction(new Runnable() { + public void run() { + System.exit(0); + } + }); + System.setIn(sdb.getIn()); + System.setOut(sdb.getOut()); + System.setErr(sdb.getErr()); + Context.addContextListener(sdb); + sdb.setScopeProvider(new ScopeProvider() { + public Scriptable getScope() { + return org.mozilla.javascript.tools.shell.Main.getScope(); + } + }); + org.mozilla.javascript.tools.shell.Main.exec(args); + } catch (Exception exc) { + exc.printStackTrace(); + } + } + +} + diff --git a/src/helma/scripting/rhino/debug/ScopeProvider.java b/src/helma/scripting/rhino/debug/ScopeProvider.java new file mode 100644 index 00000000..1266c111 --- /dev/null +++ b/src/helma/scripting/rhino/debug/ScopeProvider.java @@ -0,0 +1,43 @@ +/* -*- 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 See Beyond Corporation. + + * Portions created by See Beyond are + * Copyright (C) 2000 See Beyond Communications 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.Scriptable; + +public interface ScopeProvider { + public Scriptable getScope(); +}; diff --git a/src/helma/scripting/rhino/debug/TreeTableModel.java b/src/helma/scripting/rhino/debug/TreeTableModel.java new file mode 100644 index 00000000..445afc6d --- /dev/null +++ b/src/helma/scripting/rhino/debug/TreeTableModel.java @@ -0,0 +1,71 @@ +/* + * TreeTableModel.java + * + * Copyright (c) 1998 Sun Microsystems, Inc. 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. + * + * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE + * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES + * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING + * THIS SOFTWARE OR ITS DERIVATIVES. + * + */ + + + package helma.scripting.rhino.debug; + import javax.swing.tree.TreeModel; + + +/** + * TreeTableModel is the model used by a JTreeTable. It extends TreeModel + * to add methods for getting inforamtion about the set of columns each + * node in the TreeTableModel may have. Each column, like a column in + * a TableModel, has a name and a type associated with it. Each node in + * the TreeTableModel can return a value for each of the columns and + * set that value if isCellEditable() returns true. + * + * @author Philip Milne + * @author Scott Violet + */ +public interface TreeTableModel extends TreeModel +{ + /** + * Returns the number ofs availible column. + */ + public int getColumnCount(); + + /** + * Returns the name for column number 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; + } +} +