org.mozilla.javascript.tools.debugger.SwingGui Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino Show documentation
Show all versions of rhino Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript.tools.debugger;
import java.awt.AWTEvent;
import java.awt.ActiveEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.MenuComponent;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.TreeModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Segment;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import org.mozilla.javascript.Kit;
import org.mozilla.javascript.SecurityUtilities;
import org.mozilla.javascript.tools.debugger.treetable.JTreeTable;
import org.mozilla.javascript.tools.debugger.treetable.TreeTableModel;
import org.mozilla.javascript.tools.debugger.treetable.TreeTableModelAdapter;
import org.mozilla.javascript.tools.shell.ConsoleTextArea;
/** 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 final Map toplevels =
Collections.synchronizedMap(new HashMap());
/** Hash table of script URLs to their internal frames. */
private final Map fileWindows =
Collections.synchronizedMap(new TreeMap());
/** 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. */
@Override
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() {
@Override
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;
}
@Override
public String getDescription() {
return "JavaScript Files (*.js)";
}
};
dlg.addChoosableFileFilter(filter);
addWindowListener(
new WindowAdapter() {
@Override
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 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;
}
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;
if (sourceUrl != null) {
w = getFileWindow(sourceUrl);
} else {
JInternalFrame f = getSelectedFrame();
if (f != null && f instanceof FileWindow) {
w = (FileWindow) f;
} else {
w = currentWindow;
}
}
if (w == null && sourceUrl != null) {
Dim.SourceInfo si = dim.sourceInfo(sourceUrl);
createFileWindow(si, -1);
w = getFileWindow(sourceUrl);
}
if (w == null) {
return;
}
if (lineNumber > -1) {
int start = w.getPosition(lineNumber - 1);
int end = w.getPosition(lineNumber) - 1;
if (start <= 0) {
return;
}
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;
List 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.clear();
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.add(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.setEnabled(true);
} else {
if (currentWindow != null) currentWindow.setPosition(-1);
context.setEnabled(false);
}
}
/**
* 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[] {Double.valueOf(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 {
try (Reader r = new FileReader(fileName)) {
text = Kit.readReader(r);
}
} 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. */
@Override
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. */
@Override
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. */
@Override
public boolean isGuiEventThread() {
return SwingUtilities.isEventDispatchThread();
}
/** Processes the next GUI event. */
@Override
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("Go to line...")) {
final String s =
(String)
JOptionPane.showInputDialog(
this,
"Line number",
"Go to line...",
JOptionPane.QUESTION_MESSAGE,
null,
null,
null);
if (s == null || s.trim().length() == 0) {
return;
}
try {
final int line = Integer.parseInt(s);
showFileWindow(null, line);
} catch (final NumberFormatException nfe) {
// ignore
}
} 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) {
StringBuilder buf = new StringBuilder();
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 List 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 = Collections.synchronizedList(new ArrayList());
Document doc = getDocument();
doc.addDocumentListener(this);
addKeyListener(this);
setLineWrap(true);
setFont(new Font("Monospaced", 0, Math.max(12, UIManager.getFont("Label.font").getSize())));
append("% ");
outputMark = doc.getLength();
}
/** Selects a subrange of the text. */
@Override
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.add(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. */
@Override
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 = history.get(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 = history.get(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. */
@Override
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. */
@Override
public synchronized void keyReleased(KeyEvent e) {}
// DocumentListener
/** Called when text was inserted into the text area. */
@Override
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. */
@Override
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. */
@Override
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. */
@Override
public void setEnabled(boolean b) {
super.setEnabled(b);
evalTextArea.setEnabled(b);
}
// ActionListener
/** Performs an action on the text area. */
@Override
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() {
@Override
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. */
@Override
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, Math.max(12, UIManager.getFont("Label.font").getSize())));
}
/** 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. */
@Override
public void mousePressed(MouseEvent e) {
checkPopup(e);
}
/** Called when the mouse is clicked. */
@Override
public void mouseClicked(MouseEvent e) {
checkPopup(e);
requestFocus();
getCaret().setVisible(true);
}
/** Called when the mouse enters the component. */
@Override
public void mouseEntered(MouseEvent e) {}
/** Called when the mouse exits the component. */
@Override
public void mouseExited(MouseEvent e) {}
/** Called when a mouse button is released. */
@Override
public void mouseReleased(MouseEvent e) {
checkPopup(e);
}
// PopupMenuListener
/** Called before the popup menu will become visible. */
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
/** Called before the popup menu will become invisible. */
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
/** Called when the popup menu is cancelled. */
@Override
public void popupMenuCanceled(PopupMenuEvent e) {}
// ActionListener
/** Performs an action. */
@Override
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. */
@Override
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. */
@Override
public void keyTyped(KeyEvent e) {
e.consume();
}
/** Called when a key is released. */
@Override
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, Map 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);
for (String data : fileWindows.keySet()) {
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() {
@Override
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. */
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("Cancel")) {
setVisible(false);
value = null;
} else if (cmd.equals("Select")) {
value = list.getSelectedValue();
setVisible(false);
swingGui.showFileWindow(value, -1);
}
}
/** MouseListener implementation for {@link #list}. */
private class MouseHandler extends MouseAdapter {
@Override
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() {
@Override
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. */
@Override
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 = 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 {
@Override
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. */
@Override
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. */
@Override
public void mouseEntered(MouseEvent e) {}
/** Called when a mouse button is pressed. */
@Override
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. */
@Override
public void mouseClicked(MouseEvent e) {}
/** Called when the mouse exits the component. */
@Override
public void mouseExited(MouseEvent e) {}
/** Called when a mouse button is released. */
@Override
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. */
@Override
public void dispose() {
debugGui.removeWindow(this);
super.dispose();
}
// ActionListener
/** Performs an action. */
@Override
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;
/** List of watched expressions. */
private List expressions;
/** List of values from evaluated from {@link #expressions}. */
private List values;
/** Creates a new MyTableModel. */
public MyTableModel(SwingGui debugGui) {
this.debugGui = debugGui;
expressions = Collections.synchronizedList(new ArrayList());
values = Collections.synchronizedList(new ArrayList());
expressions.add("");
values.add("");
}
/** Returns the number of columns in the table (2). */
@Override
public int getColumnCount() {
return 2;
}
/** Returns the number of rows in the table. */
@Override
public int getRowCount() {
return expressions.size();
}
/** Returns the name of the given column. */
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "Expression";
case 1:
return "Value";
}
return null;
}
/** Returns whether the given cell is editable. */
@Override
public boolean isCellEditable(int row, int column) {
return true;
}
/** Returns the value in the given cell. */
@Override
public Object getValueAt(int row, int column) {
switch (column) {
case 0:
return expressions.get(row);
case 1:
return values.get(row);
}
return "";
}
/** Sets the value in the given cell. */
@Override
public void setValueAt(Object value, int row, int column) {
switch (column) {
case 0:
String expr = value.toString();
expressions.set(row, expr);
String result = "";
if (expr.length() > 0) {
result = debugGui.dim.eval(expr);
if (result == null) result = "";
}
values.set(row, result);
updateModel();
if (row + 1 == expressions.size()) {
expressions.add("");
values.add("");
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) {
String expr = expressions.get(i);
String result = "";
if (expr.length() > 0) {
result = debugGui.dim.eval(expr);
if (result == null) result = "";
} else {
result = "";
}
result = result.replace('\n', ' ');
values.set(i, result);
}
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. */
@Override
public Object getRoot() {
if (debugger == null) {
return null;
}
return root;
}
/** Returns the number of children of the given node. */
@Override
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. */
@Override
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. */
@Override
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. */
@Override
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. */
@Override
public boolean isCellEditable(Object node, int column) {
return column == 0;
}
/** Sets the value at the given cell. */
@Override
public void setValueAt(Object value, Object node, int column) {}
/** Adds a TreeModelListener to this tree. */
@Override
public void addTreeModelListener(TreeModelListener l) {}
/** Removes a TreeModelListener from this tree. */
@Override
public void removeTreeModelListener(TreeModelListener l) {}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {}
// TreeTableNode
/** Returns the number of columns. */
@Override
public int getColumnCount() {
return cNames.length;
}
/** Returns the name of the given column. */
@Override
public String getColumnName(int column) {
return cNames[column];
}
/** Returns the type of value stored in the given column. */
@Override
public Class> getColumnClass(int column) {
return cTypes[column];
}
/** Returns the value at the given cell. */
@Override
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();
if (result == null) {
result = exc.toString();
}
}
StringBuilder buf = new StringBuilder();
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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy