From 89bda83e0570ab87c6e449f5955613d5385e90b3 Mon Sep 17 00:00:00 2001 From: "alexanders@b2ef00c0-3703-41da-baef-cfe82387ac0c" Date: Wed, 3 Feb 2010 00:50:41 +0000 Subject: removed obsolete svn folder from hg tree --HG-- extra : convert_revision : svn%3Ab2ef00c0-3703-41da-baef-cfe82387ac0c/trunk%408 --- .../javascript/tools/debugger/SwingGui.java | 3547 ++++++++++++++++++++ 1 file changed, 3547 insertions(+) create mode 100644 infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java (limited to 'infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java') diff --git a/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java b/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java new file mode 100644 index 0000000..61dc065 --- /dev/null +++ b/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java @@ -0,0 +1,3547 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla 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/MPL/ + * + * 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 the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Matt Gould + * Cameron McCormack + * Christopher Oliver + * Hannes Wallnoefer + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (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 + * MPL, indicate your decision by deleting the provisions above and replacing + * 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 MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.debugger; + +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.*; +import java.io.*; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreePath; +import java.lang.reflect.Method; + +import org.mozilla.javascript.Kit; +import org.mozilla.javascript.SecurityUtilities; + +import org.mozilla.javascript.tools.shell.ConsoleTextArea; + +import org.mozilla.javascript.tools.debugger.downloaded.JTreeTable; +import org.mozilla.javascript.tools.debugger.downloaded.TreeTableModel; +import org.mozilla.javascript.tools.debugger.downloaded.TreeTableModelAdapter; + +/** + * GUI for the Rhino debugger. + */ +public class SwingGui extends JFrame implements GuiCallback { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -8217029773456711621L; + + /** + * The debugger. + */ + Dim dim; + + /** + * The action to run when the 'Exit' menu item is chosen or the + * frame is closed. + */ + private Runnable exitAction; + + /** + * The {@link JDesktopPane} that holds the script windows. + */ + private JDesktopPane desk; + + /** + * The {@link JPanel} that shows information about the context. + */ + private ContextWindow context; + + /** + * The menu bar. + */ + private Menubar menubar; + + /** + * The tool bar. + */ + private JToolBar toolBar; + + /** + * The console that displays I/O from the script. + */ + private JSInternalConsole console; + + /** + * The {@link JSplitPane} that separates {@link #desk} from + * {@link org.mozilla.javascript.Context}. + */ + private JSplitPane split1; + + /** + * The status bar. + */ + private JLabel statusBar; + + /** + * Hash table of internal frame names to the internal frames themselves. + */ + private Hashtable toplevels = new Hashtable(); + + /** + * Hash table of script URLs to their internal frames. + */ + private Hashtable fileWindows = new Hashtable(); + + /** + * The {@link FileWindow} that last had the focus. + */ + private FileWindow currentWindow; + + /** + * File choose dialog for loading a script. + */ + JFileChooser dlg; + + /** + * The AWT EventQueue. Used for manually pumping AWT events from + * {@link #dispatchNextGuiEvent()}. + */ + private EventQueue awtEventQueue; + + /** + * Creates a new SwingGui. + */ + public SwingGui(Dim dim, String title) { + super(title); + this.dim = dim; + init(); + dim.setGuiCallback(this); + } + + /** + * Returns the Menubar of this debugger frame. + */ + public Menubar getMenubar() { + return menubar; + } + + /** + * Sets the {@link Runnable} that will be run when the "Exit" menu + * item is chosen. + */ + public void setExitAction(Runnable r) { + exitAction = r; + } + + /** + * Returns the debugger console component. + */ + public JSInternalConsole getConsole() { + return console; + } + + /** + * Sets the visibility of the debugger GUI. + */ + 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) { + } + } + } + + /** + * Records a new internal frame. + */ + void addTopLevel(String key, JFrame frame) { + if (frame != this) { + toplevels.put(key, frame); + } + } + + /** + * Constructs the debugger GUI. + */ + private void init() { + menubar = new Menubar(this); + setJMenuBar(menubar); + 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"); + 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); + SwingGui.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); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + exit(); + } + }); + } + + /** + * Runs the {@link #exitAction}. + */ + private void exit() { + if (exitAction != null) { + SwingUtilities.invokeLater(exitAction); + } + dim.setReturnValue(Dim.EXIT); + } + + /** + * Returns the {@link FileWindow} for the given URL. + */ + FileWindow getFileWindow(String url) { + if (url == null || url.equals("")) { + return null; + } + return (FileWindow)fileWindows.get(url); + } + + /** + * Returns a short version of the given URL. + */ + 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; + } + + /** + * Closes the given {@link FileWindow}. + */ + void removeWindow(FileWindow w) { + fileWindows.remove(w.getUrl()); + JMenu windowMenu = getWindowMenu(); + int count = windowMenu.getItemCount(); + JMenuItem lastItem = windowMenu.getItem(count -1); + String name = 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(); + } + + /** + * Shows the line at which execution in the given stack frame just stopped. + */ + void showStopLine(Dim.StackFrame frame) { + String sourceName = frame.getUrl(); + if (sourceName == null || sourceName.equals("")) { + if (console.isVisible()) { + console.show(); + } + } else { + showFileWindow(sourceName, -1); + int lineNumber = frame.getLineNumber(); + FileWindow w = getFileWindow(sourceName); + if (w != null) { + setFilePosition(w, lineNumber); + } + } + } + + /** + * Shows a {@link FileWindow} for the given source, creating it + * if it doesn't exist yet. if lineNumber is greater + * than -1, it indicates the line number to select and display. + * @param sourceUrl the source URL + * @param lineNumber the line number to select, or -1 + */ + protected void showFileWindow(String sourceUrl, int lineNumber) { + FileWindow w = getFileWindow(sourceUrl); + if (w == null) { + Dim.SourceInfo si = dim.sourceInfo(sourceUrl); + createFileWindow(si, -1); + w = getFileWindow(sourceUrl); + } + if (lineNumber > -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 { + if (w.isIcon()) { + w.setIcon(false); + } + w.setVisible(true); + w.moveToFront(); + w.setSelected(true); + requestFocus(); + w.requestFocus(); + w.textArea.requestFocus(); + } catch (Exception exc) { + } + } + + /** + * Creates and shows a new {@link FileWindow} for the given source. + */ + protected void createFileWindow(Dim.SourceInfo sourceInfo, int line) { + boolean activate = true; + + String url = sourceInfo.url(); + FileWindow w = new FileWindow(this, sourceInfo); + fileWindows.put(url, w); + if (line != -1) { + if (currentWindow != null) { + 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); + } + } + } + desk.add(w); + if (line != -1) { + currentWindow = w; + } + menubar.addFile(url); + w.setVisible(true); + + if (activate) { + try { + w.setMaximum(true); + w.setSelected(true); + w.moveToFront(); + } catch (Exception exc) { + } + } + } + + /** + * Update the source text for sourceInfo. This returns true + * if a {@link FileWindow} for the given source exists and could be updated. + * Otherwise, this does nothing and returns false. + * @param sourceInfo the source info + * @return true if a {@link FileWindow} for the given source exists + * and could be updated, false otherwise. + */ + protected boolean updateFileWindow(Dim.SourceInfo sourceInfo) { + String fileName = sourceInfo.url(); + FileWindow w = getFileWindow(fileName); + if (w != null) { + w.updateText(sourceInfo); + w.show(); + return true; + } + return false; + } + + /** + * Moves the current position in the given {@link FileWindow} to the + * given line. + */ + private void setFilePosition(FileWindow w, int line) { + boolean activate = true; + JTextArea ta = w.textArea; + try { + if (line == -1) { + w.setPosition(-1); + if (currentWindow == w) { + currentWindow = null; + } + } else { + int loc = ta.getLineStartOffset(line-1); + if (currentWindow != null && currentWindow != w) { + currentWindow.setPosition(-1); + } + w.setPosition(loc); + currentWindow = w; + } + } catch (BadLocationException exc) { + // fix me + } + if (activate) { + if (w.isIcon()) { + desk.getDesktopManager().deiconifyFrame(w); + } + desk.getDesktopManager().activateFrame(w); + try { + w.show(); + w.toFront(); // required for correct frame layering (JDK 1.4.1) + w.setSelected(true); + } catch (Exception exc) { + } + } + } + + /** + * Handles script interruption. + */ + void enterInterruptImpl(Dim.StackFrame lastFrame, + String threadTitle, String alertMessage) { + statusBar.setText("Thread: " + threadTitle); + + showStopLine(lastFrame); + + if (alertMessage != null) { + MessageDialogWrapper.showMessageDialog(this, + alertMessage, + "Exception in Script", + JOptionPane.ERROR_MESSAGE); + } + + updateEnabled(true); + + Dim.ContextData contextData = lastFrame.contextData(); + + JComboBox ctx = context.context; + Vector toolTips = context.toolTips; + context.disableUpdate(); + int frameCount = contextData.frameCount(); + 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++) { + Dim.StackFrame 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); + } + context.enableUpdate(); + ctx.setSelectedIndex(0); + ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height)); + } + + /** + * Returns the 'Window' menu. + */ + private JMenu getWindowMenu() { + return menubar.getMenu(3); + } + + /** + * Displays a {@link JFileChooser} and returns the selected filename. + */ + private String chooseFile(String title) { + dlg.setDialogTitle(title); + File CWD = null; + String dir = SecurityUtilities.getSystemProperty("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; + } + + /** + * Returns the current selected internal frame. + */ + private 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]; + } + + /** + * Enables or disables the menu and tool bars with respect to the + * state of script execution. + */ + private void updateEnabled(boolean interrupted) { + ((Menubar)getJMenuBar()).updateEnabled(interrupted); + for (int ci = 0, cc = toolBar.getComponentCount(); ci < cc; ci++) { + boolean enableButton; + if (ci == 0) { + // Break + enableButton = !interrupted; + } else { + enableButton = interrupted; + } + toolBar.getComponent(ci).setEnabled(enableButton); + } + if (interrupted) { + toolBar.setEnabled(true); + // raise the debugger window + int state = getExtendedState(); + if (state == Frame.ICONIFIED) { + setExtendedState(Frame.NORMAL); + } + toFront(); + context.enable(); + } else { + if (currentWindow != null) currentWindow.setPosition(-1); + context.disable(); + } + } + + /** + * Calls {@link JSplitPane#setResizeWeight} via reflection. + * For compatibility, since JDK < 1.3 does not have this method. + */ + static void setResizeWeight(JSplitPane pane, double weight) { + 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) { + } + } + + /** + * Reads the file with the given name and returns its contents as a String. + */ + private String readFile(String fileName) { + String text; + try { + Reader r = new FileReader(fileName); + try { + text = Kit.readReader(r); + } finally { + r.close(); + } + } catch (IOException ex) { + MessageDialogWrapper.showMessageDialog(this, + ex.getMessage(), + "Error reading "+fileName, + JOptionPane.ERROR_MESSAGE); + text = null; + } + return text; + } + + // GuiCallback + + /** + * Called when the source text for a script has been updated. + */ + public void updateSourceText(Dim.SourceInfo sourceInfo) { + RunProxy proxy = new RunProxy(this, RunProxy.UPDATE_SOURCE_TEXT); + proxy.sourceInfo = sourceInfo; + SwingUtilities.invokeLater(proxy); + } + + /** + * Called when the interrupt loop has been entered. + */ + public void enterInterrupt(Dim.StackFrame lastFrame, + String threadTitle, + String alertMessage) { + if (SwingUtilities.isEventDispatchThread()) { + enterInterruptImpl(lastFrame, threadTitle, alertMessage); + } else { + RunProxy proxy = new RunProxy(this, RunProxy.ENTER_INTERRUPT); + proxy.lastFrame = lastFrame; + proxy.threadTitle = threadTitle; + proxy.alertMessage = alertMessage; + SwingUtilities.invokeLater(proxy); + } + } + + /** + * Returns whether the current thread is the GUI event thread. + */ + public boolean isGuiEventThread() { + return SwingUtilities.isEventDispatchThread(); + } + + /** + * Processes the next GUI event. + */ + public void dispatchNextGuiEvent() throws InterruptedException { + EventQueue queue = awtEventQueue; + if (queue == null) { + queue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + awtEventQueue = queue; + } + AWTEvent event = queue.getNextEvent(); + if (event instanceof ActiveEvent) { + ((ActiveEvent)event).dispatch(); + } else { + Object source = event.getSource(); + if (source instanceof Component) { + Component comp = (Component)source; + comp.dispatchEvent(event); + } else if (source instanceof MenuComponent) { + ((MenuComponent)source).dispatchEvent(event); + } + } + } + + // ActionListener + + /** + * Performs an action from the menu or toolbar. + */ + public 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 = Dim.STEP_OVER; + } else if (cmd.equals("Step Into")) { + returnValue = Dim.STEP_INTO; + } else if (cmd.equals("Step Out")) { + returnValue = Dim.STEP_OUT; + } else if (cmd.equals("Go")) { + returnValue = Dim.GO; + } else if (cmd.equals("Break")) { + dim.setBreak(); + } else if (cmd.equals("Exit")) { + exit(); + } else if (cmd.equals("Open")) { + String fileName = chooseFile("Select a file to compile"); + if (fileName != null) { + String text = readFile(fileName); + if (text != null) { + RunProxy proxy = new RunProxy(this, RunProxy.OPEN_FILE); + proxy.fileName = fileName; + proxy.text = text; + new Thread(proxy).start(); + } + } + } else if (cmd.equals("Load")) { + String fileName = chooseFile("Select a file to execute"); + if (fileName != null) { + String text = readFile(fileName); + if (text != null) { + RunProxy proxy = new RunProxy(this, RunProxy.LOAD_FILE); + proxy.fileName = fileName; + proxy.text = text; + new Thread(proxy).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, "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) { + updateEnabled(false); + dim.setReturnValue(returnValue); + } + } +} + +/** + * Helper class for showing a message dialog. + */ +class MessageDialogWrapper { + + /** + * Shows a message dialog, wrapping the msg at 60 + * columns. + */ + public 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 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); + } +} + +/** + * Extension of JTextArea for script evaluation input. + */ +class EvalTextArea + extends JTextArea + implements KeyListener, DocumentListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -3918033649601064194L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * History of expressions that have been evaluated + */ + private Vector history; + + /** + * Index of the selected history item. + */ + private int historyIndex = -1; + + /** + * Position in the display where output should go. + */ + private int outputMark; + + /** + * Creates a new EvalTextArea. + */ + public EvalTextArea(SwingGui debugGui) { + this.debugGui = debugGui; + 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(); + } + + /** + * Selects a subrange of the text. + */ + public void select(int start, int end) { + //requestFocus(); + super.select(start, end); + } + + /** + * Called when Enter is pressed. + */ + private 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 (debugGui.dim.stringIsCompilableUnit(text)) { + if (text.trim().length() > 0) { + history.addElement(text); + historyIndex = history.size(); + } + append("\n"); + String result = debugGui.dim.eval(text); + if (result.length() > 0) { + append(result); + append("\n"); + } + append("% "); + outputMark = doc.getLength(); + } else { + append("\n"); + } + } + + /** + * Writes output into the text area. + */ + public synchronized void write(String str) { + insert(str, outputMark); + int len = str.length(); + outputMark += len; + select(outputMark, outputMark); + } + + // KeyListener + + /** + * Called when a key is pressed. + */ + 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(); + } + } + + /** + * Called when a key is typed. + */ + 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); + } + } + + /** + * Called when a key is released. + */ + public synchronized void keyReleased(KeyEvent e) { + } + + // DocumentListener + + /** + * Called when text was inserted into the text area. + */ + public synchronized void insertUpdate(DocumentEvent e) { + int len = e.getLength(); + int off = e.getOffset(); + if (outputMark > off) { + outputMark += len; + } + } + + /** + * Called when text was removed from the text area. + */ + 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; + } + } + } + + /** + * Attempts to clean up the damage done by {@link #updateUI()}. + */ + public synchronized void postUpdateUI() { + //requestFocus(); + setCaret(getCaret()); + select(outputMark, outputMark); + } + + /** + * Called when text has changed in the text area. + */ + public synchronized void changedUpdate(DocumentEvent e) { + } +} + +/** + * An internal frame for evaluating script. + */ +class EvalWindow extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -2860585845212160176L; + + /** + * The text area into which expressions can be typed. + */ + private EvalTextArea evalTextArea; + + /** + * Creates a new EvalWindow. + */ + public EvalWindow(String name, SwingGui debugGui) { + super(name, true, false, true, true); + evalTextArea = new EvalTextArea(debugGui); + evalTextArea.setRows(24); + evalTextArea.setColumns(80); + JScrollPane scroller = new JScrollPane(evalTextArea); + setContentPane(scroller); + //scroller.setPreferredSize(new Dimension(600, 400)); + pack(); + setVisible(true); + } + + /** + * Sets whether the text area is enabled. + */ + public void setEnabled(boolean b) { + super.setEnabled(b); + evalTextArea.setEnabled(b); + } + + // ActionListener + + /** + * Performs an action on the text area. + */ + 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(); + } + } +} + +/** + * Internal frame for the console. + */ +class JSInternalConsole extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -5523468828771087292L; + + /** + * Creates a new JSInternalConsole. + */ + 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); + } + } + }); + } + + /** + * The console text area. + */ + ConsoleTextArea consoleTextArea; + + /** + * Returns the input stream of the console text area. + */ + public InputStream getIn() { + return consoleTextArea.getIn(); + } + + /** + * Returns the output stream of the console text area. + */ + public PrintStream getOut() { + return consoleTextArea.getOut(); + } + + /** + * Returns the error stream of the console text area. + */ + public PrintStream getErr() { + return consoleTextArea.getErr(); + } + + // ActionListener + + /** + * Performs an action on the text area. + */ + 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(); + } + } +} + +/** + * Popup menu class for right-clicking on {@link FileTextArea}s. + */ +class FilePopupMenu extends JPopupMenu { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3589525009546013565L; + + /** + * The popup x position. + */ + int x; + + /** + * The popup y position. + */ + int y; + + /** + * Creates a new FilePopupMenu. + */ + public FilePopupMenu(FileTextArea 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")); + item.addActionListener(w); + } + + /** + * Displays the menu at the given coordinates. + */ + public void show(JComponent comp, int x, int y) { + this.x = x; + this.y = y; + super.show(comp, x, y); + } +} + +/** + * Text area to display script source. + */ +class FileTextArea + extends JTextArea + implements ActionListener, PopupMenuListener, KeyListener, MouseListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -25032065448563720L; + + /** + * The owning {@link FileWindow}. + */ + private FileWindow w; + + /** + * The popup menu. + */ + private FilePopupMenu popup; + + /** + * Creates a new FileTextArea. + */ + public FileTextArea(FileWindow w) { + this.w = w; + popup = new FilePopupMenu(this); + popup.addPopupMenuListener(this); + addMouseListener(this); + addKeyListener(this); + setFont(new Font("Monospaced", 0, 12)); + } + + /** + * Moves the selection to the given offset. + */ + public 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(); + } + } + } + + /** + * Checks if the popup menu should be shown. + */ + private void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popup.show(this, e.getX(), e.getY()); + } + } + + // MouseListener + + /** + * Called when a mouse button is pressed. + */ + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + + /** + * Called when the mouse is clicked. + */ + public void mouseClicked(MouseEvent e) { + checkPopup(e); + requestFocus(); + getCaret().setVisible(true); + } + + /** + * Called when the mouse enters the component. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Called when the mouse exits the component. + */ + public void mouseExited(MouseEvent e) { + } + + /** + * Called when a mouse button is released. + */ + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + // PopupMenuListener + + /** + * Called before the popup menu will become visible. + */ + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + + /** + * Called before the popup menu will become invisible. + */ + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + /** + * Called when the popup menu is cancelled. + */ + public void popupMenuCanceled(PopupMenuEvent e) { + } + + // ActionListener + + /** + * Performs an action. + */ + 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")) { + w.load(); + } + } + + // KeyListener + + /** + * Called when a key is pressed. + */ + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_BACK_SPACE: + case KeyEvent.VK_ENTER: + case KeyEvent.VK_DELETE: + case KeyEvent.VK_TAB: + e.consume(); + break; + } + } + + /** + * Called when a key is typed. + */ + public void keyTyped(KeyEvent e) { + e.consume(); + } + + /** + * Called when a key is released. + */ + public void keyReleased(KeyEvent e) { + e.consume(); + } +} + +/** + * Dialog to list the available windows. + */ +class MoreWindows extends JDialog implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 5177066296457377546L; + + /** + * Last selected value. + */ + private String value; + + /** + * The list component. + */ + private JList list; + + /** + * Our parent frame. + */ + private SwingGui swingGui; + + /** + * The "Select" button. + */ + private JButton setButton; + + /** + * The "Cancel" button. + */ + private JButton cancelButton; + + /** + * Creates a new MoreWindows. + */ + MoreWindows(SwingGui frame, Hashtable fileWindows, String title, + String labelText) { + super(frame, title, true); + this.swingGui = frame; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //dim 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); + } + } + }); + } + + /** + * Shows the dialog. + */ + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + // ActionListener + + /** + * Performs an action. + */ + 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); + swingGui.showFileWindow(value, -1); + } + } + + /** + * MouseListener implementation for {@link #list}. + */ + private class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + } +} + +/** + * Find function dialog. + */ +class FindFunction extends JDialog implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 559491015232880916L; + + /** + * Last selected function. + */ + private String value; + + /** + * List of functions. + */ + private JList list; + + /** + * The debug GUI frame. + */ + private SwingGui debugGui; + + /** + * The "Select" button. + */ + private JButton setButton; + + /** + * The "Cancel" button. + */ + private JButton cancelButton; + + /** + * Creates a new FindFunction. + */ + public FindFunction(SwingGui debugGui, String title, String labelText) { + super(debugGui, title, true); + this.debugGui = debugGui; + + 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(); + + String[] a = debugGui.dim.functionNames(); + java.util.Arrays.sort(a); + for (int 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); + } + } + }); + } + + /** + * Shows the dialog. + */ + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + // ActionListener + + /** + * Performs an action. + */ + 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); + Dim.FunctionSource item = debugGui.dim.functionSourceByName(value); + if (item != null) { + Dim.SourceInfo si = item.sourceInfo(); + String url = si.url(); + int lineNumber = item.firstLine(); + debugGui.showFileWindow(url, lineNumber); + } + } + } + + /** + * MouseListener implementation for {@link #list}. + */ + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + } +} + +/** + * Gutter for FileWindows. + */ +class FileHeader extends JPanel implements MouseListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -2858905404778259127L; + + /** + * The line that the mouse was pressed on. + */ + private int pressLine = -1; + + /** + * The owning FileWindow. + */ + private FileWindow fileWindow; + + /** + * Creates a new FileHeader. + */ + public FileHeader(FileWindow fileWindow) { + this.fileWindow = fileWindow; + addMouseListener(this); + update(); + } + + /** + * Updates the gutter. + */ + public 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); + } + + /** + * Paints the component. + */ + 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 ascent = metrics.getMaxAscent(); + int h = metrics.getHeight(); + int lineCount = textArea.getLineCount() + 1; + String dummy = Integer.toString(lineCount); + if (dummy.length() < 2) { + dummy = "99"; + } + 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 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); + } + } + } + + // MouseListener + + /** + * Called when the mouse enters the component. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Called when a mouse button is pressed. + */ + public void mousePressed(MouseEvent e) { + Font font = fileWindow.textArea.getFont(); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + pressLine = e.getY() / h; + } + + /** + * Called when the mouse is clicked. + */ + public void mouseClicked(MouseEvent e) { + } + + /** + * Called when the mouse exits the component. + */ + public void mouseExited(MouseEvent e) { + } + + /** + * Called when a mouse button is released. + */ + public void mouseReleased(MouseEvent e) { + if (e.getComponent() == this + && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { + 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; + } + } + } +} + +/** + * An internal frame for script files. + */ +class FileWindow extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -6212382604952082370L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The SourceInfo object that describes the file. + */ + private Dim.SourceInfo sourceInfo; + + /** + * The FileTextArea that displays the file. + */ + FileTextArea textArea; + + /** + * The FileHeader that is the gutter for {@link #textArea}. + */ + private FileHeader fileHeader; + + /** + * Scroll pane for containing {@link #textArea}. + */ + private JScrollPane p; + + /** + * The current offset position. + */ + int currentPos; + + /** + * Loads the file. + */ + void load() { + String url = getUrl(); + if (url != null) { + RunProxy proxy = new RunProxy(debugGui, RunProxy.LOAD_FILE); + proxy.fileName = url; + proxy.text = sourceInfo.source(); + new Thread(proxy).start(); + } + } + + /** + * Returns the offset position for the given line. + */ + public int getPosition(int line) { + int result = -1; + try { + result = textArea.getLineStartOffset(line); + } catch (javax.swing.text.BadLocationException exc) { + } + return result; + } + + /** + * Returns whether the given line has a breakpoint. + */ + public boolean isBreakPoint(int line) { + return sourceInfo.breakableLine(line) && sourceInfo.breakpoint(line); + } + + /** + * Toggles the breakpoint on the given line. + */ + public void toggleBreakPoint(int line) { + if (!isBreakPoint(line)) { + setBreakPoint(line); + } else { + clearBreakPoint(line); + } + } + + /** + * Sets a breakpoint on the given line. + */ + public void setBreakPoint(int line) { + if (sourceInfo.breakableLine(line)) { + boolean changed = sourceInfo.breakpoint(line, true); + if (changed) { + fileHeader.repaint(); + } + } + } + + /** + * Clears a breakpoint from the given line. + */ + public void clearBreakPoint(int line) { + if (sourceInfo.breakableLine(line)) { + boolean changed = sourceInfo.breakpoint(line, false); + if (changed) { + fileHeader.repaint(); + } + } + } + + /** + * Creates a new FileWindow. + */ + public FileWindow(SwingGui debugGui, Dim.SourceInfo sourceInfo) { + super(SwingGui.getShortName(sourceInfo.url()), + true, true, true, true); + this.debugGui = debugGui; + 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(sourceInfo); + textArea.select(0); + } + + /** + * Updates the tool tip contents. + */ + private void updateToolTip() { + // Try to set tool tip on frame. On Mac OS X 10.5, + // the number of components is different, so try to be safe. + int n = getComponentCount() - 1; + if (n > 1) { + n = 1; + } else if (n < 0) { + return; + } + Component c = getComponent(n); + // this will work at least for Metal L&F + if (c != null && c instanceof JComponent) { + ((JComponent)c).setToolTipText(getUrl()); + } + } + + /** + * Returns the URL of the source. + */ + public String getUrl() { + return sourceInfo.url(); + } + + /** + * Called when the text of the script has changed. + */ + public void updateText(Dim.SourceInfo sourceInfo) { + this.sourceInfo = sourceInfo; + String newText = sourceInfo.source(); + if (!textArea.getText().equals(newText)) { + textArea.setText(newText); + int pos = 0; + if (currentPos != -1) { + pos = currentPos; + } + textArea.select(pos); + } + fileHeader.update(); + fileHeader.repaint(); + } + + /** + * Sets the cursor position. + */ + public void setPosition(int pos) { + textArea.select(pos); + currentPos = pos; + fileHeader.repaint(); + } + + /** + * Selects a range of characters. + */ + public void select(int start, int end) { + int docEnd = textArea.getDocument().getLength(); + textArea.select(docEnd, docEnd); + textArea.select(start, end); + } + + /** + * Disposes this FileWindow. + */ + public void dispose() { + debugGui.removeWindow(this); + super.dispose(); + } + + // ActionListener + + /** + * Performs an action. + */ + 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(); + } + } +} + +/** + * Table model class for watched expressions. + */ +class MyTableModel extends AbstractTableModel { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 2971618907207577000L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * Vector of watched expressions. + */ + private Vector expressions; + + /** + * Vector of values from evaluated from {@link #expressions}. + */ + private Vector values; + + /** + * Creates a new MyTableModel. + */ + public MyTableModel(SwingGui debugGui) { + this.debugGui = debugGui; + expressions = new Vector(); + values = new Vector(); + expressions.addElement(""); + values.addElement(""); + } + + /** + * Returns the number of columns in the table (2). + */ + public int getColumnCount() { + return 2; + } + + /** + * Returns the number of rows in the table. + */ + public int getRowCount() { + return expressions.size(); + } + + /** + * Returns the name of the given column. + */ + public String getColumnName(int column) { + switch (column) { + case 0: + return "Expression"; + case 1: + return "Value"; + } + return null; + } + + /** + * Returns whether the given cell is editable. + */ + public boolean isCellEditable(int row, int column) { + return true; + } + + /** + * Returns the value in the given cell. + */ + public Object getValueAt(int row, int column) { + switch (column) { + case 0: + return expressions.elementAt(row); + case 1: + return values.elementAt(row); + } + return ""; + } + + /** + * Sets the value in the given cell. + */ + 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 = debugGui.dim.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(); + } + } + + /** + * Re-evaluates the expressions in the table. + */ + 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 = debugGui.dim.eval(expr); + if (result == null) result = ""; + } else { + result = ""; + } + result = result.replace('\n', ' '); + values.setElementAt(result, i); + } + fireTableDataChanged(); + } +} + +/** + * A table for evaluated expressions. + */ +class Evaluator extends JTable { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 8133672432982594256L; + + /** + * The {@link TableModel} for this table. + */ + MyTableModel tableModel; + + /** + * Creates a new Evaluator. + */ + public Evaluator(SwingGui debugGui) { + super(new MyTableModel(debugGui)); + tableModel = (MyTableModel)getModel(); + } +} + +/** + * Tree model for script object inspection. + */ +class VariableModel implements TreeTableModel { + + /** + * Serializable magic number. + */ + private static final String[] cNames = { " Name", " Value" }; + + /** + * Tree column types. + */ + private static final Class[] cTypes = + { TreeTableModel.class, String.class }; + + /** + * Empty {@link VariableNode} array. + */ + private static final VariableNode[] CHILDLESS = new VariableNode[0]; + + /** + * The debugger. + */ + private Dim debugger; + + /** + * The root node. + */ + private VariableNode root; + + /** + * Creates a new VariableModel. + */ + public VariableModel() { + } + + /** + * Creates a new VariableModel. + */ + public VariableModel(Dim debugger, Object scope) { + this.debugger = debugger; + this.root = new VariableNode(scope, "this"); + } + + // TreeTableModel + + /** + * Returns the root node of the tree. + */ + public Object getRoot() { + if (debugger == null) { + return null; + } + return root; + } + + /** + * Returns the number of children of the given node. + */ + public int getChildCount(Object nodeObj) { + if (debugger == null) { + return 0; + } + VariableNode node = (VariableNode) nodeObj; + return children(node).length; + } + + /** + * Returns a child of the given node. + */ + public Object getChild(Object nodeObj, int i) { + if (debugger == null) { + return null; + } + VariableNode node = (VariableNode) nodeObj; + return children(node)[i]; + } + + /** + * Returns whether the given node is a leaf node. + */ + public boolean isLeaf(Object nodeObj) { + if (debugger == null) { + return true; + } + VariableNode node = (VariableNode) nodeObj; + return children(node).length == 0; + } + + /** + * Returns the index of a node under its parent. + */ + public int getIndexOfChild(Object parentObj, Object childObj) { + if (debugger == null) { + return -1; + } + VariableNode parent = (VariableNode) parentObj; + VariableNode child = (VariableNode) childObj; + VariableNode[] children = children(parent); + for (int i = 0; i != children.length; i++) { + if (children[i] == child) { + return i; + } + } + return -1; + } + + /** + * Returns whether the given cell is editable. + */ + public boolean isCellEditable(Object node, int column) { + return column == 0; + } + + /** + * Sets the value at the given cell. + */ + public void setValueAt(Object value, Object node, int column) { } + + /** + * Adds a TreeModelListener to this tree. + */ + public void addTreeModelListener(TreeModelListener l) { } + + /** + * Removes a TreeModelListener from this tree. + */ + public void removeTreeModelListener(TreeModelListener l) { } + + public void valueForPathChanged(TreePath path, Object newValue) { } + + // TreeTableNode + + /** + * Returns the number of columns. + */ + public int getColumnCount() { + return cNames.length; + } + + /** + * Returns the name of the given column. + */ + public String getColumnName(int column) { + return cNames[column]; + } + + /** + * Returns the type of value stored in the given column. + */ + public Class getColumnClass(int column) { + return cTypes[column]; + } + + /** + * Returns the value at the given cell. + */ + public Object getValueAt(Object nodeObj, int column) { + if (debugger == null) { return null; } + VariableNode node = (VariableNode)nodeObj; + switch (column) { + case 0: // Name + return node.toString(); + case 1: // Value + String result; + try { + result = debugger.objectToString(getValue(node)); + } 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(); + } + return null; + } + + /** + * Returns an array of the children of the given node. + */ + private VariableNode[] children(VariableNode node) { + if (node.children != null) { + return node.children; + } + + VariableNode[] children; + + Object value = getValue(node); + Object[] ids = debugger.getObjectIds(value); + if (ids == null || ids.length == 0) { + children = CHILDLESS; + } else { + Arrays.sort(ids, new Comparator() { + public int compare(Object l, Object r) + { + if (l instanceof String) { + if (r instanceof Integer) { + return -1; + } + return ((String)l).compareToIgnoreCase((String)r); + } else { + if (r instanceof String) { + return 1; + } + int lint = ((Integer)l).intValue(); + int rint = ((Integer)r).intValue(); + return lint - rint; + } + } + }); + children = new VariableNode[ids.length]; + for (int i = 0; i != ids.length; ++i) { + children[i] = new VariableNode(value, ids[i]); + } + } + node.children = children; + return children; + } + + /** + * Returns the value of the given node. + */ + public Object getValue(VariableNode node) { + try { + return debugger.getObjectProperty(node.object, node.id); + } catch (Exception exc) { + return "undefined"; + } + } + + /** + * A variable node in the tree. + */ + private static class VariableNode { + + /** + * The script object. + */ + private Object object; + + /** + * The object name. Either a String or an Integer. + */ + private Object id; + + /** + * Array of child nodes. This is filled with the properties of + * the object. + */ + private VariableNode[] children; + + /** + * Creates a new VariableNode. + */ + public VariableNode(Object object, Object id) { + this.object = object; + this.id = id; + } + + /** + * Returns a string representation of this node. + */ + public String toString() { + return id instanceof String + ? (String) id : "[" + ((Integer) id).intValue() + "]"; + } + } +} + +/** + * A tree table for browsing script objects. + */ +class MyTreeTable extends JTreeTable { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3457265548184453049L; + + /** + * Creates a new MyTreeTable. + */ + public MyTreeTable(VariableModel model) { + super(model); + } + + /** + * Initializes a tree for this tree table. + */ + 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; + } + + /** + * Returns whether the cell under the coordinates of the mouse + * in the {@link EventObject} is editable. + */ + 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; + } +} + +/** + * Panel that shows information about the context. + */ +class ContextWindow extends JPanel implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 2306040975490228051L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The combo box that holds the stack frames. + */ + JComboBox context; + + /** + * Tool tips for the stack frames. + */ + Vector toolTips; + + /** + * Tabbed pane for "this" and "locals". + */ + private JTabbedPane tabs; + + /** + * Tabbed pane for "watch" and "evaluate". + */ + private JTabbedPane tabs2; + + /** + * The table showing the "this" object. + */ + private MyTreeTable thisTable; + + /** + * The table showing the stack local variables. + */ + private MyTreeTable localsTable; + + /** + * The {@link #evaluator}'s table model. + */ + private MyTableModel tableModel; + + /** + * The script evaluator table. + */ + private Evaluator evaluator; + + /** + * The script evaluation text area. + */ + private EvalTextArea cmdLine; + + /** + * The split pane. + */ + JSplitPane split; + + /** + * Whether the ContextWindow is enabled. + */ + private boolean enabled; + + /** + * Creates a new ContextWindow. + */ + public ContextWindow(final SwingGui debugGui) { + this.debugGui = debugGui; + 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 VariableModel()); + JScrollPane jsp = new JScrollPane(thisTable); + jsp.getViewport().setViewSize(new Dimension(5,2)); + tabs.add("this", jsp); + localsTable = new MyTreeTable(new VariableModel()); + 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(debugGui); + cmdLine = new EvalTextArea(debugGui); + //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); + SwingGui.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; + + ComponentListener clistener = new ComponentListener() { + 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; + debugGui.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; + debugGui.addTopLevel("Evaluate", frame); + frame.setResizable(true); + rightDocked = false; + } else { + rightDocked = true; + } + } + if (leftDocked && t2Docked && rightDocked && t2Docked) { + // no change + return; + } + 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(); + } + + /** + * Disables the component. + */ + public void disable() { + context.setEnabled(false); + thisTable.setEnabled(false); + localsTable.setEnabled(false); + evaluator.setEnabled(false); + cmdLine.setEnabled(false); + } + + /** + * Enables the component. + */ + public void enable() { + context.setEnabled(true); + thisTable.setEnabled(true); + localsTable.setEnabled(true); + evaluator.setEnabled(true); + cmdLine.setEnabled(true); + } + + /** + * Disables updating of the component. + */ + public void disableUpdate() { + enabled = false; + } + + /** + * Enables updating of the component. + */ + public void enableUpdate() { + enabled = true; + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + if (!enabled) return; + if (e.getActionCommand().equals("ContextSwitch")) { + Dim.ContextData contextData = debugGui.dim.currentContextData(); + if (contextData == null) { return; } + int frameIndex = context.getSelectedIndex(); + context.setToolTipText(toolTips.elementAt(frameIndex).toString()); + int frameCount = contextData.frameCount(); + if (frameIndex >= frameCount) { + return; + } + Dim.StackFrame frame = contextData.getFrame(frameIndex); + Object scope = frame.scope(); + Object thisObj = frame.thisObj(); + thisTable.resetTree(new VariableModel(debugGui.dim, thisObj)); + VariableModel scopeModel; + if (scope != thisObj) { + scopeModel = new VariableModel(debugGui.dim, scope); + } else { + scopeModel = new VariableModel(); + } + localsTable.resetTree(scopeModel); + debugGui.dim.contextSwitch(frameIndex); + debugGui.showStopLine(frame); + tableModel.updateModel(); + } + } +} + +/** + * The debugger frame menu bar. + */ +class Menubar extends JMenuBar implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3217170497245911461L; + + /** + * Items that are enabled only when interrupted. + */ + private Vector interruptOnlyItems = new Vector(); + + /** + * Items that are enabled only when running. + */ + private Vector runOnlyItems = new Vector(); + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The menu listing the internal frames. + */ + private JMenu windowMenu; + + /** + * The "Break on exceptions" menu item. + */ + private JCheckBoxMenuItem breakOnExceptions; + + /** + * The "Break on enter" menu item. + */ + private JCheckBoxMenuItem breakOnEnter; + + /** + * The "Break on return" menu item. + */ + private JCheckBoxMenuItem breakOnReturn; + + /** + * Creates a new Menubar. + */ + Menubar(SwingGui debugGui) { + super(); + this.debugGui = debugGui; + 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) { + interruptOnlyItems.add(item); + } else { + runOnlyItems.add(item); + } + 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); + + updateEnabled(false); + } + + /** + * Returns the "Break on exceptions" menu item. + */ + public JCheckBoxMenuItem getBreakOnExceptions() { + return breakOnExceptions; + } + + /** + * Returns the "Break on enter" menu item. + */ + public JCheckBoxMenuItem getBreakOnEnter() { + return breakOnEnter; + } + + /** + * Returns the "Break on return" menu item. + */ + public JCheckBoxMenuItem getBreakOnReturn() { + return breakOnReturn; + } + + /** + * Returns the "Debug" menu. + */ + public JMenu getDebugMenu() { + return getMenu(2); + } + + // ActionListener + + /** + * Performs an action. + */ + 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) { + debugGui.dim.setBreakOnExceptions(breakOnExceptions.isSelected()); + } else if (source == breakOnEnter) { + debugGui.dim.setBreakOnEnter(breakOnEnter.isSelected()); + } else if (source == breakOnReturn) { + debugGui.dim.setBreakOnReturn(breakOnReturn.isSelected()); + } else { + debugGui.actionPerformed(e); + } + return; + } + try { + UIManager.setLookAndFeel(plaf_name); + SwingUtilities.updateComponentTreeUI(debugGui); + SwingUtilities.updateComponentTreeUI(debugGui.dlg); + } catch (Exception ignored) { + //ignored.printStackTrace(); + } + } + + /** + * Adds a file to the window menu. + */ + public 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 = SwingGui.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); + } + + /** + * Updates the enabledness of menu items. + */ + public void updateEnabled(boolean interrupted) { + for (int i = 0; i != interruptOnlyItems.size(); ++i) { + JMenuItem item = (JMenuItem)interruptOnlyItems.elementAt(i); + item.setEnabled(interrupted); + } + + for (int i = 0; i != runOnlyItems.size(); ++i) { + JMenuItem item = (JMenuItem)runOnlyItems.elementAt(i); + item.setEnabled(!interrupted); + } + } +} + +/** + * Class to consolidate all cases that require to implement Runnable + * to avoid class generation bloat. + */ +class RunProxy implements Runnable { + + // Constants for 'type'. + static final int OPEN_FILE = 1; + static final int LOAD_FILE = 2; + static final int UPDATE_SOURCE_TEXT = 3; + static final int ENTER_INTERRUPT = 4; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The type of Runnable this object is. Takes one of the constants + * defined in this class. + */ + private int type; + + /** + * The name of the file to open or load. + */ + String fileName; + + /** + * The source text to update. + */ + String text; + + /** + * The source for which to update the text. + */ + Dim.SourceInfo sourceInfo; + + /** + * The frame to interrupt in. + */ + Dim.StackFrame lastFrame; + + /** + * The name of the interrupted thread. + */ + String threadTitle; + + /** + * The message of the exception thrown that caused the thread + * interruption, if any. + */ + String alertMessage; + + /** + * Creates a new RunProxy. + */ + public RunProxy(SwingGui debugGui, int type) { + this.debugGui = debugGui; + this.type = type; + } + + /** + * Runs this Runnable. + */ + public void run() { + switch (type) { + case OPEN_FILE: + try { + debugGui.dim.compileScript(fileName, text); + } catch (RuntimeException ex) { + MessageDialogWrapper.showMessageDialog( + debugGui, ex.getMessage(), "Error Compiling "+fileName, + JOptionPane.ERROR_MESSAGE); + } + break; + + case LOAD_FILE: + try { + debugGui.dim.evalScript(fileName, text); + } catch (RuntimeException ex) { + MessageDialogWrapper.showMessageDialog( + debugGui, ex.getMessage(), "Run error for "+fileName, + JOptionPane.ERROR_MESSAGE); + } + break; + + case UPDATE_SOURCE_TEXT: + { + String fileName = sourceInfo.url(); + if (!debugGui.updateFileWindow(sourceInfo) && + !fileName.equals("")) { + debugGui.createFileWindow(sourceInfo, -1); + } + } + break; + + case ENTER_INTERRUPT: + debugGui.enterInterruptImpl(lastFrame, threadTitle, alertMessage); + break; + + default: + throw new IllegalArgumentException(String.valueOf(type)); + + } + } +} -- cgit v1.2.3