From 5c87a14ebaef8a2faa8128801d5a28e8d078116f Mon Sep 17 00:00:00 2001 From: Ben Fry Date: Sat, 24 Jan 2015 20:41:10 -0500 Subject: [PATCH] link the inspector to the window --- .../mode/java/debug/VariableInspector.java | 1482 ++++++++--------- 1 file changed, 732 insertions(+), 750 deletions(-) diff --git a/java/src/processing/mode/java/debug/VariableInspector.java b/java/src/processing/mode/java/debug/VariableInspector.java index 1558f9f0a..06fe52d08 100644 --- a/java/src/processing/mode/java/debug/VariableInspector.java +++ b/java/src/processing/mode/java/debug/VariableInspector.java @@ -1,30 +1,33 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* -Part of the Processing project - http://processing.org -Copyright (c) 2012-15 The Processing Foundation + Part of the Processing project - http://processing.org + Copyright (c) 2012-15 The Processing Foundation -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License version 2 -as published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software Foundation, Inc. -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package processing.mode.java.debug; import java.awt.Color; import java.awt.Component; +import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; @@ -38,6 +41,7 @@ import javax.swing.GrayFilter; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; +import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.UIDefaults; @@ -55,6 +59,7 @@ import javax.swing.tree.TreePath; import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.ExtTreeWillExpandListener; +import org.netbeans.swing.outline.Outline; import org.netbeans.swing.outline.OutlineModel; import org.netbeans.swing.outline.RenderDataProvider; import org.netbeans.swing.outline.RowModel; @@ -68,330 +73,383 @@ import processing.mode.java.JavaMode; /** * Variable Inspector window. - * - * @author Martin Leopold */ public class VariableInspector extends JFrame { + /// the root node (invisible) + protected DefaultMutableTreeNode rootNode; + + /// node for Processing built-in variables + protected DefaultMutableTreeNode builtins; + + /// data model for the tree column + protected DefaultTreeModel treeModel; + + /// data model for the whole Outline (tree and other columns) + protected OutlineModel model; + + protected List callStack; + + /// current local variables + protected List locals; + + /// all fields of the current this-object + protected List thisFields; + + /// declared i.e. non-inherited fields of this + protected List declaredThisFields; + + protected JavaEditor editor; + protected Debugger dbg; + + /// list of expanded tree paths. (using list to maintain the order of expansion) + protected List expandedNodes = new ArrayList(); + + /// processing / "advanced" mode flag (currently not used) + protected boolean p5mode = true; - protected DefaultMutableTreeNode rootNode; // the root node (invisible) - protected DefaultMutableTreeNode builtins; // node for Processing built-in variables - protected DefaultTreeModel treeModel; // data model for the tree column - protected OutlineModel model; // data model for the whole Outline (tree and other columns) - protected List callStack; // the call stack - protected List locals; // current local variables - protected List thisFields; // all fields of the current this-object - protected List declaredThisFields; // declared i.e. non-inherited fields of this - protected JavaEditor editor; // the editor - protected Debugger dbg; // the debugger - protected List expandedNodes = new ArrayList(); // list of expanded tree paths. (using list to maintain the order of expansion) - protected boolean p5mode = true; // processing / "advanced" mode flag (currently not used + final int VERTICAL_OFFSET = 64; + + + public VariableInspector(JavaEditor je) { + editor = je; + dbg = editor.dbg(); + + setUndecorated(true); - /** - * Creates new form VariableInspector - */ - public VariableInspector(JavaEditor editor) { - this.editor = editor; - this.dbg = editor.dbg(); + editor.addComponentListener(new ComponentListener() { + + @Override + public void componentShown(ComponentEvent e) { } + + @Override + public void componentResized(ComponentEvent e) { + updateBounds(e); + } + + @Override + public void componentMoved(ComponentEvent e) { + updateBounds(e); + } + + @Override + public void componentHidden(ComponentEvent e) { } + + private void updateBounds(ComponentEvent e) { +// System.out.println(e); + setBounds(editor.getX() + editor.getWidth(), + editor.getY() + VERTICAL_OFFSET, + getPreferredSize().width, + editor.getHeight() - VERTICAL_OFFSET*2); + } + }); + + initComponents(); - initComponents(); + // setup Outline + rootNode = new DefaultMutableTreeNode("root"); + builtins = new DefaultMutableTreeNode("Processing"); + treeModel = new DefaultTreeModel(rootNode); // model for the tree column + model = DefaultOutlineModel.createOutlineModel(treeModel, new VariableRowModel(), true, "Name"); // model for all columns - // setup Outline - rootNode = new DefaultMutableTreeNode("root"); - builtins = new DefaultMutableTreeNode("Processing"); - treeModel = new DefaultTreeModel(rootNode); // model for the tree column - model = DefaultOutlineModel.createOutlineModel(treeModel, new VariableRowModel(), true, "Name"); // model for all columns + ExpansionHandler expansionHandler = new ExpansionHandler(); + model.getTreePathSupport().addTreeWillExpandListener(expansionHandler); + model.getTreePathSupport().addTreeExpansionListener(expansionHandler); + tree.setModel(model); + tree.setRootVisible(false); + tree.setRenderDataProvider(new OutlineRenderer()); + tree.setColumnHidingAllowed(false); // disable visible columns button (shows by default when right scroll bar is visible) + tree.setAutoscrolls(false); - ExpansionHandler expansionHandler = new ExpansionHandler(); - model.getTreePathSupport().addTreeWillExpandListener(expansionHandler); - model.getTreePathSupport().addTreeExpansionListener(expansionHandler); - tree.setModel(model); - tree.setRootVisible(false); - tree.setRenderDataProvider(new OutlineRenderer()); - tree.setColumnHidingAllowed(false); // disable visible columns button (shows by default when right scroll bar is visible) - tree.setAutoscrolls(false); + // set custom renderer and editor for value column, since we are using a custom class for values (VariableNode) + TableColumn valueColumn = tree.getColumnModel().getColumn(1); + valueColumn.setCellRenderer(new ValueCellRenderer()); + valueColumn.setCellEditor(new ValueCellEditor()); - // set custom renderer and editor for value column, since we are using a custom class for values (VariableNode) - TableColumn valueColumn = tree.getColumnModel().getColumn(1); - valueColumn.setCellRenderer(new ValueCellRenderer()); - valueColumn.setCellEditor(new ValueCellEditor()); + //System.out.println("renderer: " + tree.getDefaultRenderer(String.class).getClass()); + //System.out.println("editor: " + tree.getDefaultEditor(String.class).getClass()); - //System.out.println("renderer: " + tree.getDefaultRenderer(String.class).getClass()); - //System.out.println("editor: " + tree.getDefaultEditor(String.class).getClass()); + callStack = new ArrayList(); + locals = new ArrayList(); + thisFields = new ArrayList(); + declaredThisFields = new ArrayList(); - callStack = new ArrayList(); - locals = new ArrayList(); - thisFields = new ArrayList(); - declaredThisFields = new ArrayList(); +// this.setTitle(editor.getSketch().getName()); + } - this.setTitle(editor.getSketch().getName()); + +// @Override +// public void setTitle(String title) { +// super.setTitle(title + " | Variable Inspector"); +// } -// for (Entry entry : UIManager.getDefaults().entrySet()) { -// System.out.println(entry.getKey()); -// } + + /** + * Model for a Outline Row (excluding the tree column). Column 0 is "Value". + * Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a + * TableCellRenderer instead of this to also have a different icon based on + * expanded state. See: + * http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm + */ + protected class VariableRowModel implements RowModel { + protected String[] columnNames = {"Value", "Type"}; + protected int[] editableTypes = {VariableNode.TYPE_BOOLEAN, VariableNode.TYPE_FLOAT, VariableNode.TYPE_INTEGER, VariableNode.TYPE_STRING, VariableNode.TYPE_FLOAT, VariableNode.TYPE_DOUBLE, VariableNode.TYPE_LONG, VariableNode.TYPE_SHORT, VariableNode.TYPE_CHAR}; + + @Override + public int getColumnCount() { + if (p5mode) { + return 1; // only show value in p5 mode + } else { + return 2; + } } @Override - public void setTitle(String title) { - super.setTitle(title + " | Variable Inspector"); + public Object getValueFor(Object o, int i) { + if (o instanceof VariableNode) { + VariableNode var = (VariableNode) o; + switch (i) { + case 0: + return var; // will be converted to an appropriate String by ValueCellRenderer + case 1: + return var.getTypeName(); + default: + return ""; + } + } else { + return ""; + } + } + + @Override + public Class getColumnClass(int i) { + if (i == 0) { + return VariableNode.class; + } + return String.class; + } + + @Override + public boolean isCellEditable(Object o, int i) { + if (i == 0 && o instanceof VariableNode) { + VariableNode var = (VariableNode) o; + //System.out.println("type: " + var.getTypeName()); + for (int type : editableTypes) { + if (var.getType() == type) { + return true; + } + } + } + return false; + } + + @Override + public void setValueFor(Object o, int i, Object o1) { + VariableNode var = (VariableNode) o; + String stringValue = (String) o1; + + Value value = null; + try { + switch (var.getType()) { + case VariableNode.TYPE_INTEGER: + value = dbg.vm().mirrorOf(Integer.parseInt(stringValue)); + break; + case VariableNode.TYPE_BOOLEAN: + value = dbg.vm().mirrorOf(Boolean.parseBoolean(stringValue)); + break; + case VariableNode.TYPE_FLOAT: + value = dbg.vm().mirrorOf(Float.parseFloat(stringValue)); + break; + case VariableNode.TYPE_STRING: + value = dbg.vm().mirrorOf(stringValue); + break; + case VariableNode.TYPE_LONG: + value = dbg.vm().mirrorOf(Long.parseLong(stringValue)); + break; + case VariableNode.TYPE_BYTE: + value = dbg.vm().mirrorOf(Byte.parseByte(stringValue)); + break; + case VariableNode.TYPE_DOUBLE: + value = dbg.vm().mirrorOf(Double.parseDouble(stringValue)); + break; + case VariableNode.TYPE_SHORT: + value = dbg.vm().mirrorOf(Short.parseShort(stringValue)); + break; + case VariableNode.TYPE_CHAR: + // TODO: better char support + if (stringValue.length() > 0) { + value = dbg.vm().mirrorOf(stringValue.charAt(0)); + } + break; + } + } catch (NumberFormatException ex) { + Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "invalid value entered for {0}: {1}", new Object[]{var.getName(), stringValue}); + } + if (value != null) { + var.setValue(value); + Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "new value set: {0}", var.getStringValue()); + } + } + + @Override + public String getColumnName(int i) { + return columnNames[i]; + } + } + + + /** + * Renderer for the tree portion of the outline component. + * Handles icons, text color and tool tips. + */ + protected class OutlineRenderer implements RenderDataProvider { + + protected Icon[][] icons; + protected static final int ICON_SIZE = 16; // icon size (square, size=width=height) + + public OutlineRenderer() { + // load icons + icons = loadIcons("theme/var-icons.gif"); } /** - * Model for a Outline Row (excluding the tree column). Column 0 is "Value". - * Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a - * TableCellRenderer instead of this to also have a different icon based on - * expanded state. See: - * http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm + * Load multiple icons (horizotal) with multiple states (vertical) from + * a single file. + * + * @param fileName file path in the mode folder. + * @return a nested array (first index: icon, second index: state) or + * null if the file wasn't found. */ - protected class VariableRowModel implements RowModel { + protected ImageIcon[][] loadIcons(String fileName) { + JavaMode mode = editor.mode(); + File file = mode.getContentFile(fileName); + if (!file.exists()) { + Logger.getLogger(OutlineRenderer.class.getName()).log(Level.SEVERE, "icon file not found: {0}", file.getAbsolutePath()); + return null; + } + Image allIcons = mode.loadImage(fileName); + int cols = allIcons.getWidth(null) / ICON_SIZE; + int rows = allIcons.getHeight(null) / ICON_SIZE; + ImageIcon[][] iconImages = new ImageIcon[cols][rows]; - protected String[] columnNames = {"Value", "Type"}; - protected int[] editableTypes = {VariableNode.TYPE_BOOLEAN, VariableNode.TYPE_FLOAT, VariableNode.TYPE_INTEGER, VariableNode.TYPE_STRING, VariableNode.TYPE_FLOAT, VariableNode.TYPE_DOUBLE, VariableNode.TYPE_LONG, VariableNode.TYPE_SHORT, VariableNode.TYPE_CHAR}; - - @Override - public int getColumnCount() { - if (p5mode) { - return 1; // only show value in p5 mode - } else { - return 2; - } - } - - @Override - public Object getValueFor(Object o, int i) { - if (o instanceof VariableNode) { - VariableNode var = (VariableNode) o; - switch (i) { - case 0: - return var; // will be converted to an appropriate String by ValueCellRenderer - case 1: - return var.getTypeName(); - default: - return ""; - } - } else { - return ""; - } - } - - @Override - public Class getColumnClass(int i) { - if (i == 0) { - return VariableNode.class; - } - return String.class; - } - - @Override - public boolean isCellEditable(Object o, int i) { - if (i == 0 && o instanceof VariableNode) { - VariableNode var = (VariableNode) o; - //System.out.println("type: " + var.getTypeName()); - for (int type : editableTypes) { - if (var.getType() == type) { - return true; - } - } - } - return false; - } - - @Override - public void setValueFor(Object o, int i, Object o1) { - VariableNode var = (VariableNode) o; - String stringValue = (String) o1; - - Value value = null; - try { - switch (var.getType()) { - case VariableNode.TYPE_INTEGER: - value = dbg.vm().mirrorOf(Integer.parseInt(stringValue)); - break; - case VariableNode.TYPE_BOOLEAN: - value = dbg.vm().mirrorOf(Boolean.parseBoolean(stringValue)); - break; - case VariableNode.TYPE_FLOAT: - value = dbg.vm().mirrorOf(Float.parseFloat(stringValue)); - break; - case VariableNode.TYPE_STRING: - value = dbg.vm().mirrorOf(stringValue); - break; - case VariableNode.TYPE_LONG: - value = dbg.vm().mirrorOf(Long.parseLong(stringValue)); - break; - case VariableNode.TYPE_BYTE: - value = dbg.vm().mirrorOf(Byte.parseByte(stringValue)); - break; - case VariableNode.TYPE_DOUBLE: - value = dbg.vm().mirrorOf(Double.parseDouble(stringValue)); - break; - case VariableNode.TYPE_SHORT: - value = dbg.vm().mirrorOf(Short.parseShort(stringValue)); - break; - case VariableNode.TYPE_CHAR: - // TODO: better char support - if (stringValue.length() > 0) { - value = dbg.vm().mirrorOf(stringValue.charAt(0)); - } - break; - } - } catch (NumberFormatException ex) { - Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "invalid value entered for {0}: {1}", new Object[]{var.getName(), stringValue}); - } - if (value != null) { - var.setValue(value); - Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "new value set: {0}", var.getStringValue()); - } - } - - @Override - public String getColumnName(int i) { - return columnNames[i]; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + //Image image = createImage(ICON_SIZE, ICON_SIZE); + Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null); + iconImages[i][j] = new ImageIcon(image); } + } + return iconImages; } - /** - * Renderer for the tree portion of the outline component. Handles icons, - * text color and tool tips. - */ - protected class OutlineRenderer implements RenderDataProvider { - protected Icon[][] icons; - protected static final int ICON_SIZE = 16; // icon size (square, size=width=height) + protected Icon getIcon(int type, int state) { + if (type < 0 || type > icons.length - 1) { + return null; + } + return icons[type][state]; + } - public OutlineRenderer() { - // load icons - icons = loadIcons("theme/var-icons.gif"); - } - /** - * Load multiple icons (horizotal) with multiple states (vertical) from - * a single file. - * - * @param fileName file path in the mode folder. - * @return a nested array (first index: icon, second index: state) or - * null if the file wasn't found. - */ - protected ImageIcon[][] loadIcons(String fileName) { - JavaMode mode = editor.mode(); - File file = mode.getContentFile(fileName); - if (!file.exists()) { - Logger.getLogger(OutlineRenderer.class.getName()).log(Level.SEVERE, "icon file not found: {0}", file.getAbsolutePath()); - return null; - } - Image allIcons = mode.loadImage(fileName); - int cols = allIcons.getWidth(null) / ICON_SIZE; - int rows = allIcons.getHeight(null) / ICON_SIZE; - ImageIcon[][] iconImages = new ImageIcon[cols][rows]; + protected VariableNode toVariableNode(Object o) { + if (o instanceof VariableNode) { + return (VariableNode) o; + } else { + return null; + } + } - for (int i = 0; i < cols; i++) { - for (int j = 0; j < rows; j++) { - //Image image = createImage(ICON_SIZE, ICON_SIZE); - Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); - Graphics g = image.getGraphics(); - g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null); - iconImages[i][j] = new ImageIcon(image); - } - } - return iconImages; - } - protected Icon getIcon(int type, int state) { - if (type < 0 || type > icons.length - 1) { - return null; - } - return icons[type][state]; - } + protected Icon toGray(Icon icon) { + if (icon instanceof ImageIcon) { + Image grayImage = GrayFilter.createDisabledImage(((ImageIcon) icon).getImage()); + return new ImageIcon(grayImage); + } + // Cannot convert + return icon; + } - protected VariableNode toVariableNode(Object o) { - if (o instanceof VariableNode) { - return (VariableNode) o; - } else { - return null; - } - } - protected Icon toGray(Icon icon) { - if (icon instanceof ImageIcon) { - Image grayImage = GrayFilter.createDisabledImage(((ImageIcon) icon).getImage()); - return new ImageIcon(grayImage); - } - // Cannot convert - return icon; - } - - @Override - public String getDisplayName(Object o) { - return o.toString(); // VariableNode.toString() returns name; (for sorting) + @Override + public String getDisplayName(Object o) { + return o.toString(); // VariableNode.toString() returns name; (for sorting) // VariableNode var = toVariableNode(o); // if (var != null) { // return var.getName(); // } else { // return o.toString(); // } - } + } - @Override - public boolean isHtmlDisplayName(Object o) { - return false; - } + + @Override + public boolean isHtmlDisplayName(Object o) { + return false; + } - @Override - public Color getBackground(Object o) { - return null; - } - @Override - public Color getForeground(Object o) { - if (tree.isEnabled()) { - return null; // default - } else { - return Color.GRAY; - } - } + @Override + public Color getBackground(Object o) { + return null; + } - @Override - public String getTooltipText(Object o) { - VariableNode var = toVariableNode(o); - if (var != null) { - return var.getDescription(); - } else { - return ""; - } - } - @Override - public Icon getIcon(Object o) { - VariableNode var = toVariableNode(o); - if (var != null) { - if (tree.isEnabled()) { - return getIcon(var.getType(), 0); - } else { - return getIcon(var.getType(), 1); - } - } else { - if (o instanceof TreeNode) { + @Override + public Color getForeground(Object o) { + if (tree.isEnabled()) { + return null; // default + } else { + return Color.GRAY; + } + } + + + @Override + public String getTooltipText(Object o) { + VariableNode var = toVariableNode(o); + if (var != null) { + return var.getDescription(); + } else { + return ""; + } + } + + + @Override + public Icon getIcon(Object o) { + VariableNode var = toVariableNode(o); + if (var != null) { + if (tree.isEnabled()) { + return getIcon(var.getType(), 0); + } else { + return getIcon(var.getType(), 1); + } + } else { + if (o instanceof TreeNode) { // TreeNode node = (TreeNode) o; // AbstractLayoutCache layout = tree.getLayoutCache(); - UIDefaults defaults = UIManager.getDefaults(); + UIDefaults defaults = UIManager.getDefaults(); - boolean isLeaf = model.isLeaf(o); - Icon icon; - if (isLeaf) { - icon = defaults.getIcon("Tree.leafIcon"); - } else { - icon = defaults.getIcon("Tree.closedIcon"); - } + boolean isLeaf = model.isLeaf(o); + Icon icon; + if (isLeaf) { + icon = defaults.getIcon("Tree.leafIcon"); + } else { + icon = defaults.getIcon("Tree.closedIcon"); + } - if (!tree.isEnabled()) { - return toGray(icon); - } - return icon; - } - } - return null; // use standard icon - //UIManager.getIcon(o); + if (!tree.isEnabled()) { + return toGray(icon); + } + return icon; } + } + return null; // use standard icon + //UIManager.getIcon(o); } + } + // TODO: could probably extend the simpler javax.swing.table.DefaultTableCellRenderer here /** @@ -399,151 +457,131 @@ public class VariableInspector extends JFrame { * Object values ("instance of ..."). Uses a gray color when tree is not * enabled. */ - protected class ValueCellRenderer extends DefaultOutlineCellRenderer { + protected class ValueCellRenderer extends DefaultOutlineCellRenderer { - public ValueCellRenderer() { - super(); - } - - protected void setItalic(boolean on) { - if (on) { - setFont(new Font(getFont().getName(), Font.ITALIC, getFont().getSize())); - } else { - setFont(new Font(getFont().getName(), Font.PLAIN, getFont().getSize())); - } - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - if (!tree.isEnabled()) { - setForeground(Color.GRAY); - } else { - setForeground(Color.BLACK); - } - - if (value instanceof VariableNode) { - VariableNode var = (VariableNode) value; - - if (var.getValue() == null || var.getType() == VariableNode.TYPE_OBJECT) { - setItalic(true); - } else { - setItalic(false); - } - value = var.getStringValue(); - } - - setValue(value); - return c; - } + public ValueCellRenderer() { + super(); } - /** - * Editor for the value column. Will show an empty string when editing - * String values that are null. - */ - protected class ValueCellEditor extends DefaultCellEditor { - - public ValueCellEditor() { - super(new JTextField()); - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - if (!(value instanceof VariableNode)) { - return super.getTableCellEditorComponent(table, value, isSelected, row, column); - } - VariableNode var = (VariableNode) value; - if (var.getType() == VariableNode.TYPE_STRING && var.getValue() == null) { - return super.getTableCellEditorComponent(table, "", isSelected, row, column); - } else { - return super.getTableCellEditorComponent(table, var.getStringValue(), isSelected, row, column); - } - } + protected void setItalic(boolean on) { + if (on) { + setFont(new Font(getFont().getName(), Font.ITALIC, getFont().getSize())); + } else { + setFont(new Font(getFont().getName(), Font.PLAIN, getFont().getSize())); + } } - /** - * Handler for expanding and collapsing tree nodes. Implements lazy loading - * of tree data (on expand). - */ - protected class ExpansionHandler implements ExtTreeWillExpandListener, TreeExpansionListener { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - @Override - public void treeWillExpand(TreeExpansionEvent tee) throws ExpandVetoException { - //System.out.println("will expand"); - Object last = tee.getPath().getLastPathComponent(); - if (!(last instanceof VariableNode)) { - return; - } - VariableNode var = (VariableNode) last; - // load children -// if (!dbg.isPaused()) { -// System.out.println("throwing veto"); -// //throw new ExpandVetoException(tee, "Debugger busy"); -// } else { - var.removeAllChildren(); // TODO: should we only load it once? - // TODO: don't filter in advanced mode - //System.out.println("loading children for: " + var); - // true means include inherited - var.addChildren(filterNodes(dbg.getFields(var.getValue(), 0, true), new ThisFilter())); -// } + if (!tree.isEnabled()) { + setForeground(Color.GRAY); + } else { + setForeground(Color.BLACK); + } + + if (value instanceof VariableNode) { + VariableNode var = (VariableNode) value; + + if (var.getValue() == null || var.getType() == VariableNode.TYPE_OBJECT) { + setItalic(true); + } else { + setItalic(false); } + value = var.getStringValue(); + } - @Override - public void treeWillCollapse(TreeExpansionEvent tee) throws ExpandVetoException { - //throw new UnsupportedOperationException("Not supported yet."); - } + setValue(value); + return c; + } + } - @Override - public void treeExpanded(TreeExpansionEvent tee) { - //System.out.println("expanded: " + tee.getPath()); - if (!expandedNodes.contains(tee.getPath())) { - expandedNodes.add(tee.getPath()); - } + + /** + * Editor for the value column. Will show an empty string when editing + * String values that are null. + */ + protected class ValueCellEditor extends DefaultCellEditor { -// TreePath newPath = tee.getPath(); -// if (expandedLast != null) { -// // test each node of the path for equality -// for (int i = 0; i < expandedLast.getPathCount(); i++) { -// if (i < newPath.getPathCount()) { -// Object last = expandedLast.getPathComponent(i); -// Object cur = newPath.getPathComponent(i); -// System.out.println(last + " =? " + cur + ": " + last.equals(cur) + "/" + (last.hashCode() == cur.hashCode())); -// } -// } -// } -// System.out.println("path equality: " + newPath.equals(expandedLast)); -// expandedLast = newPath; - } - - @Override - public void treeCollapsed(TreeExpansionEvent tee) { - //System.out.println("collapsed: " + tee.getPath()); - - // first remove all children of collapsed path - // this makes sure children do not appear before parents in the list. - // (children can't be expanded before their parents) - List removalList = new ArrayList(); - for (TreePath path : expandedNodes) { - if (path.getParentPath().equals(tee.getPath())) { - removalList.add(path); - } - } - for (TreePath path : removalList) { - expandedNodes.remove(path); - } - // remove collapsed path - expandedNodes.remove(tee.getPath()); - } - - @Override - public void treeExpansionVetoed(TreeExpansionEvent tee, ExpandVetoException eve) { - //System.out.println("expansion vetoed"); - // nop - } + public ValueCellEditor() { + super(new JTextField()); } + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + if (!(value instanceof VariableNode)) { + return super.getTableCellEditorComponent(table, value, isSelected, row, column); + } + VariableNode var = (VariableNode) value; + if (var.getType() == VariableNode.TYPE_STRING && var.getValue() == null) { + return super.getTableCellEditorComponent(table, "", isSelected, row, column); + } else { + return super.getTableCellEditorComponent(table, var.getStringValue(), isSelected, row, column); + } + } + } + + + /** + * Handler for expanding and collapsing tree nodes. + * Implements lazy loading of tree data (on expand). + */ + protected class ExpansionHandler implements ExtTreeWillExpandListener, TreeExpansionListener { + + @Override + public void treeWillExpand(TreeExpansionEvent tee) throws ExpandVetoException { + //System.out.println("will expand"); + Object last = tee.getPath().getLastPathComponent(); + if (!(last instanceof VariableNode)) { + return; + } + VariableNode var = (VariableNode) last; + var.removeAllChildren(); // TODO: should we only load it once? + var.addChildren(filterNodes(dbg.getFields(var.getValue(), 0, true), new ThisFilter())); + } + + @Override + public void treeWillCollapse(TreeExpansionEvent tee) throws ExpandVetoException { + //throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void treeExpanded(TreeExpansionEvent tee) { + //System.out.println("expanded: " + tee.getPath()); + if (!expandedNodes.contains(tee.getPath())) { + expandedNodes.add(tee.getPath()); + } + } + + @Override + public void treeCollapsed(TreeExpansionEvent tee) { + //System.out.println("collapsed: " + tee.getPath()); + + // first remove all children of collapsed path + // this makes sure children do not appear before parents in the list. + // (children can't be expanded before their parents) + List removalList = new ArrayList(); + for (TreePath path : expandedNodes) { + if (path.getParentPath().equals(tee.getPath())) { + removalList.add(path); + } + } + for (TreePath path : removalList) { + expandedNodes.remove(path); + } + // remove collapsed path + expandedNodes.remove(tee.getPath()); + } + + @Override + public void treeExpansionVetoed(TreeExpansionEvent tee, ExpandVetoException eve) { + //System.out.println("expansion vetoed"); + // nop + } + } + + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -551,9 +589,8 @@ public class VariableInspector extends JFrame { */ // //GEN-BEGIN:initComponents private void initComponents() { - - scrollPane = new javax.swing.JScrollPane(); - tree = new org.netbeans.swing.outline.Outline(); + scrollPane = new JScrollPane(); + tree = new Outline(); scrollPane.setViewportView(tree); @@ -575,366 +612,311 @@ public class VariableInspector extends JFrame { pack(); }// //GEN-END:initComponents -// /** -// * @param args the command line arguments -// */ -// public static void main(String args[]) { -// /* -// * Set the Nimbus look and feel -// */ -// // -// /* -// * If Nimbus (introduced in Java SE 6) is not available, stay with the -// * default look and feel. For details see -// * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html -// */ -// try { -// javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); -// } catch (ClassNotFoundException ex) { -// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); -// } catch (InstantiationException ex) { -// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); -// } catch (IllegalAccessException ex) { -// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); -// } catch (javax.swing.UnsupportedLookAndFeelException ex) { -// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); -// } -// // -// -// /* -// * Create and display the form -// */ -// run(new VariableInspector()); -// } - protected static void run(final VariableInspector vi) { - /* - * Create and display the form - */ - java.awt.EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - vi.setVisible(true); - } - }); + + protected static void run(final VariableInspector vi) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + vi.setVisible(true); + } + }); + } + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane scrollPane; + protected org.netbeans.swing.outline.Outline tree; + // End of variables declaration//GEN-END:variables + + + public DefaultMutableTreeNode getRootNode() { + return rootNode; + } + + + /** + * Unlock the inspector window. Rebuild after this to avoid ... dots in the + * trees labels + */ + public void unlock() { + tree.setEnabled(true); + } + + + /** + * Lock the inspector window. Cancels open edits. + */ + public void lock() { + if (tree.getCellEditor() != null) { + //tree.getCellEditor().stopCellEditing(); // force quit open edit + tree.getCellEditor().cancelCellEditing(); // cancel an open edit } - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane scrollPane; - protected org.netbeans.swing.outline.Outline tree; - // End of variables declaration//GEN-END:variables + tree.setEnabled(false); + } - /** - * Access the root node of the tree. - * - * @return the root node - */ - public DefaultMutableTreeNode getRootNode() { - return rootNode; - } + + /** + * Reset the inspector windows data. Rebuild after this to make changes + * visible. + */ + public void reset() { + rootNode.removeAllChildren(); + // clear local data for good measure (in case someone rebuilds) + callStack.clear(); + locals.clear(); + thisFields.clear(); + declaredThisFields.clear(); + expandedNodes.clear(); + // update + treeModel.nodeStructureChanged(rootNode); + } - /** - * Unlock the inspector window. Rebuild after this to avoid ... dots in the - * trees labels - */ - public void unlock() { - tree.setEnabled(true); - } - /** - * Lock the inspector window. Cancels open edits. - */ - public void lock() { - if (tree.getCellEditor() != null) { - //tree.getCellEditor().stopCellEditing(); // force quit open edit - tree.getCellEditor().cancelCellEditing(); // cancel an open edit - } - tree.setEnabled(false); - } + /** + * Update call stack data. + * + * @param nodes a list of nodes that represent the call stack. + * @param title the title to be used when labeling or otherwise grouping + * call stack data. + */ + public void updateCallStack(List nodes, String title) { + callStack = nodes; + } - /** - * Reset the inspector windows data. Rebuild after this to make changes - * visible. - */ - public void reset() { - rootNode.removeAllChildren(); - // clear local data for good measure (in case someone rebuilds) - callStack.clear(); - locals.clear(); - thisFields.clear(); - declaredThisFields.clear(); - expandedNodes.clear(); - // update - treeModel.nodeStructureChanged(rootNode); - } + + /** + * Update locals data. + * + * @param nodes a list of {@link VariableNode} to be shown as local + * variables in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * locals data. + */ + public void updateLocals(List nodes, String title) { + locals = nodes; + } + -// public void setAdvancedMode() { -// p5mode = false; -// } -// -// public void setP5Mode() { -// p5mode = true; -// } -// -// public void toggleMode() { -// if (p5mode) { -// setAdvancedMode(); -// } else { -// setP5Mode(); -// } -// } - /** - * Update call stack data. - * - * @param nodes a list of nodes that represent the call stack. - * @param title the title to be used when labeling or otherwise grouping - * call stack data. - */ - public void updateCallStack(List nodes, String title) { - callStack = nodes; - } + /** + * Update this-fields data. + * + * @param nodes a list of {@link VariableNode}s to be shown as this-fields + * in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * this-fields data. + */ + public void updateThisFields(List nodes, String title) { + thisFields = nodes; + } + - /** - * Update locals data. - * - * @param nodes a list of {@link VariableNode} to be shown as local - * variables in the inspector. - * @param title the title to be used when labeling or otherwise grouping - * locals data. - */ - public void updateLocals(List nodes, String title) { - locals = nodes; - } + /** + * Update declared (non-inherited) this-fields data. + * + * @param nodes a list of {@link VariableNode}s to be shown as declared + * this-fields in the inspector. + * @param title the title to be used when labeling or otherwise grouping + * declared this-fields data. + */ + public void updateDeclaredThisFields(List nodes, String title) { + declaredThisFields = nodes; + } - /** - * Update this-fields data. - * - * @param nodes a list of {@link VariableNode}s to be shown as this-fields - * in the inspector. - * @param title the title to be used when labeling or otherwise grouping - * this-fields data. - */ - public void updateThisFields(List nodes, String title) { - thisFields = nodes; - } + + /** + * Rebuild the outline tree from current data. Uses the data provided by + * {@link #updateCallStack}, {@link #updateLocals}, {@link #updateThisFields} + * and {@link #updateDeclaredThisFields} + */ + public void rebuild() { + rootNode.removeAllChildren(); + if (p5mode) { + // add all locals to root + addAllNodes(rootNode, locals); - /** - * Update declared (non-inherited) this-fields data. - * - * @param nodes a list of {@link VariableNode}s to be shown as declared - * this-fields in the inspector. - * @param title the title to be used when labeling or otherwise grouping - * declared this-fields data. - */ - public void updateDeclaredThisFields(List nodes, String title) { - declaredThisFields = nodes; - } + // add non-inherited this fields + addAllNodes(rootNode, filterNodes(declaredThisFields, new LocalHidesThisFilter(locals, LocalHidesThisFilter.MODE_PREFIX))); - /** - * Rebuild the outline tree from current data. Uses the data provided by - * {@link #updateCallStack}, {@link #updateLocals}, {@link #updateThisFields} - * and {@link #updateDeclaredThisFields} - */ - public void rebuild() { - rootNode.removeAllChildren(); - if (p5mode) { - // add all locals to root - addAllNodes(rootNode, locals); + // add p5 builtins in a new folder + builtins.removeAllChildren(); + addAllNodes(builtins, filterNodes(thisFields, new P5BuiltinsFilter())); + if (builtins.getChildCount() > 0) { // skip builtins in certain situations e.g. in pure java tabs. + rootNode.add(builtins); + } - // add non-inherited this fields - addAllNodes(rootNode, filterNodes(declaredThisFields, new LocalHidesThisFilter(locals, LocalHidesThisFilter.MODE_PREFIX))); - - // add p5 builtins in a new folder - builtins.removeAllChildren(); - addAllNodes(builtins, filterNodes(thisFields, new P5BuiltinsFilter())); - if (builtins.getChildCount() > 0) { // skip builtins in certain situations e.g. in pure java tabs. - rootNode.add(builtins); - } - - // notify tree (using model) changed a node and its children - // http://stackoverflow.com/questions/2730851/how-to-update-jtree-elements - // needs to be done before expanding paths! - treeModel.nodeStructureChanged(rootNode); - - // handle node expansions - for (TreePath path : expandedNodes) { - //System.out.println("re-expanding: " + path); - path = synthesizePath(path); - if (path != null) { - tree.expandPath(path); - } else { - //System.out.println("couldn't synthesize path"); - } - } - - // this expansion causes problems when sorted and stepping - //tree.expandPath(new TreePath(new Object[]{rootNode, builtins})); + // notify tree (using model) changed a node and its children + // http://stackoverflow.com/questions/2730851/how-to-update-jtree-elements + // needs to be done before expanding paths! + treeModel.nodeStructureChanged(rootNode); + // handle node expansions + for (TreePath path : expandedNodes) { + //System.out.println("re-expanding: " + path); + path = synthesizePath(path); + if (path != null) { + tree.expandPath(path); } else { - // TODO: implement advanced mode here + //System.out.println("couldn't synthesize path"); } + } + + // this expansion causes problems when sorted and stepping + //tree.expandPath(new TreePath(new Object[]{rootNode, builtins})); + } else { + // TODO: implement advanced mode here } + } - /** - * Re-build a {@link TreePath} from a previous path using equals-checks - * starting at the root node. This is used to use paths from previous trees - * for use on the current tree. - * - * @param path the path to synthesize. - * @return the rebuilt path, usable on the current tree. - */ - protected TreePath synthesizePath(TreePath path) { - //System.out.println("synthesizing: " + path); - if (path.getPathCount() == 0 || !rootNode.equals(path.getPathComponent(0))) { - return null; - } - Object[] newPath = new Object[path.getPathCount()]; - newPath[0] = rootNode; - TreeNode currentNode = rootNode; - for (int i = 0; i < path.getPathCount() - 1; i++) { - // get next node - for (int j = 0; j < currentNode.getChildCount(); j++) { - TreeNode nextNode = currentNode.getChildAt(j); - if (nextNode.equals(path.getPathComponent(i + 1))) { - currentNode = nextNode; - newPath[i + 1] = nextNode; - //System.out.println("found node " + (i+1) + ": " + nextNode); - break; - } - } - if (newPath[i + 1] == null) { - //System.out.println("didn't find node"); - return null; - } - } - return new TreePath(newPath); + + /** + * Re-build a {@link TreePath} from a previous path using equals-checks + * starting at the root node. This is used to use paths from previous trees + * for use on the current tree. + * @param path the path to synthesize. + * @return the rebuilt path, usable on the current tree. + */ + protected TreePath synthesizePath(TreePath path) { + //System.out.println("synthesizing: " + path); + if (path.getPathCount() == 0 || !rootNode.equals(path.getPathComponent(0))) { + return null; } - - /** - * Filter a list of nodes using a {@link VariableNodeFilter}. - * - * @param nodes the list of nodes to filter. - * @param filter the filter to be used. - * @return the filtered list. - */ - protected List filterNodes(List nodes, VariableNodeFilter filter) { - List filtered = new ArrayList(); - for (VariableNode node : nodes) { - if (filter.accept(node)) { - filtered.add(node); - } + Object[] newPath = new Object[path.getPathCount()]; + newPath[0] = rootNode; + TreeNode currentNode = rootNode; + for (int i = 0; i < path.getPathCount() - 1; i++) { + // get next node + for (int j = 0; j < currentNode.getChildCount(); j++) { + TreeNode nextNode = currentNode.getChildAt(j); + if (nextNode.equals(path.getPathComponent(i + 1))) { + currentNode = nextNode; + newPath[i + 1] = nextNode; + //System.out.println("found node " + (i+1) + ": " + nextNode); + break; } - return filtered; + } + if (newPath[i + 1] == null) { + //System.out.println("didn't find node"); + return null; + } } + return new TreePath(newPath); + } + - /** - * Add all nodes in a list to a root node. - * - * @param root the root node to add to. - * @param nodes the list of nodes to add. - */ - protected void addAllNodes(DefaultMutableTreeNode root, List nodes) { - for (MutableTreeNode node : nodes) { - root.add(node); - } + /** + * Filter a list of nodes using a {@link VariableNodeFilter}. + * @param nodes the list of nodes to filter. + * @param filter the filter to be used. + * @return the filtered list. + */ + protected List filterNodes(List nodes, VariableNodeFilter filter) { + List filtered = new ArrayList(); + for (VariableNode node : nodes) { + if (filter.accept(node)) { + filtered.add(node); + } } + return filtered; + } - /** - * A filter for {@link VariableNode}s. - */ - public interface VariableNodeFilter { - - /** - * Check whether the filter accepts a {@link VariableNode}. - * - * @param var the input node - * @return true when the filter accepts the input node otherwise false. - */ - public boolean accept(VariableNode var); + + protected void addAllNodes(DefaultMutableTreeNode root, List nodes) { + for (MutableTreeNode node : nodes) { + root.add(node); } + } - /** - * A {@link VariableNodeFilter} that accepts Processing built-in variable - * names. - */ - public class P5BuiltinsFilter implements VariableNodeFilter { + + public interface VariableNodeFilter { - protected String[] p5Builtins = { - "focused", - "frameCount", - "frameRate", - "height", - "online", - "screen", - "width", - "mouseX", - "mouseY", - "pmouseX", - "pmouseY", - "key", - "keyCode", - "keyPressed" - }; + /** Check whether the filter accepts a {@link VariableNode}. */ + public boolean accept(VariableNode var); + } - @Override - public boolean accept(VariableNode var) { - return Arrays.asList(p5Builtins).contains(var.getName()); - } + + /** + * A {@link VariableNodeFilter} that accepts Processing built-in variable + * names. + */ + public class P5BuiltinsFilter implements VariableNodeFilter { + + protected String[] p5Builtins = { + "focused", + "frameCount", + "frameRate", + "height", + "online", + "screen", + "width", + "mouseX", + "mouseY", + "pmouseX", + "pmouseY", + "key", + "keyCode", + "keyPressed" + }; + + @Override + public boolean accept(VariableNode var) { + return Arrays.asList(p5Builtins).contains(var.getName()); } + } + - /** - * A {@link VariableNodeFilter} that rejects implicit this references. - * (Names starting with "this$") - */ - public class ThisFilter implements VariableNodeFilter { + /** + * A {@link VariableNodeFilter} that rejects implicit this references. + * (Names starting with "this$") + */ + public class ThisFilter implements VariableNodeFilter { - @Override - public boolean accept(VariableNode var) { - return !var.getName().startsWith("this$"); - } + @Override + public boolean accept(VariableNode var) { + return !var.getName().startsWith("this$"); } + } + + /** + * A {@link VariableNodeFilter} that either rejects this-fields if hidden by + * a local, or prefixes its name with "this." + */ + public class LocalHidesThisFilter implements VariableNodeFilter { + + // Reject a this-field if hidden by a local. + public static final int MODE_HIDE = 0; // don't show hidden this fields + + // Prefix a this-fields name with "this." if hidden by a local. + public static final int MODE_PREFIX = 1; + + protected List locals; + protected int mode; + + /** - * A {@link VariableNodeFilter} that either rejects this-fields if hidden by - * a local, or prefixes its name with "this." + * Construct a {@link LocalHidesThisFilter}. + * @param locals a list of locals to check against + * @param mode either {@link #MODE_HIDE} or {@link #MODE_PREFIX} */ - public class LocalHidesThisFilter implements VariableNodeFilter { + public LocalHidesThisFilter(List locals, int mode) { + this.locals = locals; + this.mode = mode; + } + - /** - * Reject a this-field if hidden by a local. - */ - public static final int MODE_HIDE = 0; // don't show hidden this fields - /** - * Prefix a this-fields name with "this." if hidden by a local. - */ - public static final int MODE_PREFIX = 1; // prefix hidden this fields with "this." - protected List locals; - protected int mode; - - /** - * Construct a {@link LocalHidesThisFilter}. - * - * @param locals a list of locals to check against - * @param mode either {@link #MODE_HIDE} or {@link #MODE_PREFIX} - */ - public LocalHidesThisFilter(List locals, int mode) { - this.locals = locals; - this.mode = mode; - } - - @Override - public boolean accept(VariableNode var) { - // check if the same name appears in the list of locals i.e. the local hides the field - for (VariableNode local : locals) { - if (var.getName().equals(local.getName())) { - switch (mode) { - case MODE_PREFIX: - var.setName("this." + var.getName()); - return true; - case MODE_HIDE: - return false; - } - } - } + @Override + public boolean accept(VariableNode var) { + // check if the same name appears in the list of locals i.e. the local hides the field + for (VariableNode local : locals) { + if (var.getName().equals(local.getName())) { + switch (mode) { + case MODE_PREFIX: + var.setName("this." + var.getName()); return true; + case MODE_HIDE: + return false; + } } + } + return true; } + } }