package net.cakenet.jsaton.ui.tools.script;
import net.cakenet.jsaton.script.Script;
import net.cakenet.jsaton.script.debug.BreakInformation;
import net.cakenet.jsaton.script.debug.DebugBreakListener;
import net.cakenet.jsaton.script.debug.DebugFrame;
import net.cakenet.jsaton.script.debug.DebugObject;
import net.cakenet.jsaton.ui.tools.ToolWindow;
import net.cakenet.jsaton.util.ImageUtil;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class DebugWindow extends ToolWindow implements PropertyChangeListener, DebugBreakListener, ListSelectionListener, TreeSelectionListener {
private JList frameList;
private JPanel panel1;
private JTree variableTree;
private ScriptEditor editor;
public DebugWindow() {
super("Debug");
frameList.addListSelectionListener(this);
frameList.setCellRenderer(new FrameListCellRenderer());
variableTree.setRootVisible(false);
variableTree.setShowsRootHandles(true);
DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
renderer.setLeafIcon(null);
renderer.setOpenIcon(null);
renderer.setClosedIcon(null);
variableTree.setCellRenderer(renderer);
variableTree.addTreeSelectionListener(this);
}
public Component getContents() {
return panel1;
}
public Icon getIcon() {
return ImageUtil.loadIcon("icons/bug.png");
}
public void setEditor(ScriptEditor editor) {
if (this.editor != null) {
this.editor.script.removePropertyChangeListener(this);
this.editor.script.removeDebugListener(this);
}
this.editor = editor;
if (editor != null) {
editor.script.addPropertyChangeListener(this);
editor.script.addDebugListener(this);
}
updateComponents();
}
private void updateComponents() {
if (editor != null) {
setTitle("Debug - " + editor.script.getName());
Script s = editor.script;
if (!s.isDebug()) {
updateWithMessage("Not currently debugging", true);
} else {
switch (s.getState()) {
case INITIALISING:
case STOPPED:
updateWithMessage("Not running", true);
break;
case RUNNING:
updateWithMessage("Running", true);
break;
case SUSPENDED:
if (s.getBreakInfo() == null)
updateWithMessage("Not available during suspend", true);
break;
}
}
} else {
setTitle("Debug");
updateWithMessage("No script open", true);
}
}
private void updateWithMessage(String message, boolean removeFrameModel) {
if(removeFrameModel)
frameList.setModel(new DefaultListModel());
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root", true);
root.add(new DefaultMutableTreeNode(message, false));
variableTree.setModel(new DefaultTreeModel(root));
}
public void onBreak(Script source, BreakInformation info) {
frameList.setModel(new FrameListModel(info));
frameList.setSelectedIndex(0);
}
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals("state") || name.equals("name"))
updateComponents();
}
public void valueChanged(ListSelectionEvent e) {
Object selected = frameList.getSelectedValue();
if (selected == null || !(selected instanceof DebugFrame))
return;
DebugFrame frame = (DebugFrame) selected;
if (frame.source.equals(editor.script.getName())) {
variableTree.setModel(new FrameTreeModel(frame));
editor.highlightBreakpoint(frame);
} else {
editor.unhighlightLastHighlightedBreakpoint();
updateWithMessage("Out of scope", false);
}
}
public void valueChanged(TreeSelectionEvent e) {
TreePath selected = e.getPath();
if (selected.getLastPathComponent() == FrameTreeModel.EXPAND_OBJ) {
int sel = variableTree.getMaxSelectionRow();
variableTree.setSelectionRow(-1);
FrameTreeModel ftm = (FrameTreeModel) variableTree.getModel();
ftm.loadMore(selected.getParentPath());
variableTree.treeDidChange();
variableTree.setSelectionRow(sel);
}
}
private static class FrameListModel implements ListModel<DebugFrame> {
private BreakInformation info;
public FrameListModel(BreakInformation info) {
this.info = info;
}
public int getSize() {
return info.frames.size();
}
public DebugFrame getElementAt(int index) {
return info.frames.get(index);
}
public void addListDataListener(ListDataListener l) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void removeListDataListener(ListDataListener l) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
private static class FrameListCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof DebugFrame) {
DebugFrame frame = (DebugFrame) value;
JLabel label = new JLabel(frame.name + ":" + frame.line);
label.setOpaque(true);
if (isSelected) {
label.setBackground(list.getSelectionBackground());
label.setForeground(list.getSelectionForeground());
} else {
label.setBackground(list.getBackground());
label.setForeground(list.getForeground());
}
return label;
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); //To change body of overridden methods use File | Settings | File Templates.
}
}
private static class FrameTreeModel implements TreeModel {
private LinkedList<TreeModelListener> listeners = new LinkedList<>();
public static final Object EXPAND_OBJ = "Load more...";
private Map<Object, Integer> max_index = new HashMap<>();
private DebugFrame frame;
public FrameTreeModel(DebugFrame frame) {
this.frame = frame;
}
public Object getRoot() {
return frame;
}
private int getMax(Object parent) {
if (parent == EXPAND_OBJ)
return 0;
if (!max_index.containsKey(parent))
max_index.put(parent, 100); // Start off at 100...
return max_index.get(parent);
}
public void loadMore(TreePath path) {
Object parent = path.getLastPathComponent();
int max = getMax(parent);
max += 100;
max = Math.min(max, getActualChildCount(parent));
max_index.put(parent, max);
if (listeners.isEmpty())
return;
// CBF'D, just reload the entire node...
TreeModelEvent ev = new TreeModelEvent(this, path);
for (int i = 0; i < listeners.size(); i++) {
TreeModelListener l = listeners.get(i);
l.treeStructureChanged(ev);
}
}
private int getActualChildCount(Object parent) {
int count = 0;
if (parent instanceof DebugFrame) {
DebugFrame frame = (DebugFrame) parent;
count = frame.variables.size();
if (frame.self != null)
count++;
} else if (parent instanceof DebugObject) {
count = ((DebugObject) parent).size();
}
return count;
}
public Object getChild(Object parent, int index) {
int max = getMax(parent);
if (index >= max)
return EXPAND_OBJ;
if (parent instanceof DebugFrame) {
DebugFrame frame = (DebugFrame) parent;
if (frame.self != null) {
if (index == 0)
return frame.self;
return frame.variables.get(index - 1);
}
return frame.variables.get(index);
} else if (parent instanceof DebugObject) {
return ((DebugObject) parent).get(index);
}
return null;
}
public int getChildCount(Object parent) {
int count = getActualChildCount(parent);
return Math.min(getMax(parent) + 1, count);
}
public boolean isLeaf(Object node) {
return getChildCount(node) == 0;
}
public void valueForPathChanged(TreePath path, Object newValue) {
}
public int getIndexOfChild(Object parent, Object child) {
if (parent instanceof DebugFrame) {
DebugFrame frame = (DebugFrame) parent;
if (frame.self != null) {
if (child == frame.self)
return 0;
return frame.variables.indexOf(child) + 1;
}
return frame.variables.indexOf(child);
} else if (parent instanceof DebugObject) {
return ((DebugObject) parent).indexOf(child);
}
return 0;
}
public void addTreeModelListener(TreeModelListener l) {
listeners.add(l);
}
public void removeTreeModelListener(TreeModelListener l) {
listeners.remove(l);
}
}
}