summaryrefslogtreecommitdiff
path: root/tool/src/org/antlr/v4/gui/TreeViewer.java
diff options
context:
space:
mode:
Diffstat (limited to 'tool/src/org/antlr/v4/gui/TreeViewer.java')
-rw-r--r--tool/src/org/antlr/v4/gui/TreeViewer.java764
1 files changed, 764 insertions, 0 deletions
diff --git a/tool/src/org/antlr/v4/gui/TreeViewer.java b/tool/src/org/antlr/v4/gui/TreeViewer.java
new file mode 100644
index 0000000..0ca15f3
--- /dev/null
+++ b/tool/src/org/antlr/v4/gui/TreeViewer.java
@@ -0,0 +1,764 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.gui;
+
+import org.abego.treelayout.NodeExtentProvider;
+import org.abego.treelayout.TreeForTreeLayout;
+import org.abego.treelayout.TreeLayout;
+import org.abego.treelayout.util.DefaultConfiguration;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.misc.Utils;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.Tree;
+import org.antlr.v4.runtime.tree.Trees;
+
+import javax.imageio.ImageIO;
+import javax.print.PrintException;
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.prefs.Preferences;
+
+public class TreeViewer extends JComponent {
+ public static final Color LIGHT_RED = new Color(244, 213, 211);
+
+ public static class DefaultTreeTextProvider implements TreeTextProvider {
+ private final List<String> ruleNames;
+
+ public DefaultTreeTextProvider(List<String> ruleNames) {
+ this.ruleNames = ruleNames;
+ }
+
+ @Override
+ public String getText(Tree node) {
+ return String.valueOf(Trees.getNodeText(node, ruleNames));
+ }
+ }
+
+ public static class VariableExtentProvide implements NodeExtentProvider<Tree> {
+ TreeViewer viewer;
+ public VariableExtentProvide(TreeViewer viewer) {
+ this.viewer = viewer;
+ }
+ @Override
+ public double getWidth(Tree tree) {
+ FontMetrics fontMetrics = viewer.getFontMetrics(viewer.font);
+ String s = viewer.getText(tree);
+ int w = fontMetrics.stringWidth(s) + viewer.nodeWidthPadding*2;
+ return w;
+ }
+
+ @Override
+ public double getHeight(Tree tree) {
+ FontMetrics fontMetrics = viewer.getFontMetrics(viewer.font);
+ int h = fontMetrics.getHeight() + viewer.nodeHeightPadding*2;
+ String s = viewer.getText(tree);
+ String[] lines = s.split("\n");
+ return h * lines.length;
+ }
+ }
+
+ protected TreeTextProvider treeTextProvider;
+ protected TreeLayout<Tree> treeLayout;
+ protected java.util.List<Tree> highlightedNodes;
+
+ protected String fontName = "Helvetica"; //Font.SANS_SERIF;
+ protected int fontStyle = Font.PLAIN;
+ protected int fontSize = 11;
+ protected Font font = new Font(fontName, fontStyle, fontSize);
+
+ protected double gapBetweenLevels = 17;
+ protected double gapBetweenNodes = 7;
+ protected int nodeWidthPadding = 2; // added to left/right
+ protected int nodeHeightPadding = 0; // added above/below
+ protected int arcSize = 0; // make an arc in node outline?
+
+ protected double scale = 1.0;
+
+ protected Color boxColor = null; // set to a color to make it draw background
+
+ protected Color highlightedBoxColor = Color.lightGray;
+ protected Color borderColor = null;
+ protected Color textColor = Color.black;
+
+ public TreeViewer(List<String> ruleNames, Tree tree) {
+ setRuleNames(ruleNames);
+ if ( tree!=null ) {
+ setTree(tree);
+ }
+ setFont(font);
+ }
+
+ private void updatePreferredSize() {
+ setPreferredSize(getScaledTreeSize());
+ invalidate();
+ if (getParent() != null) {
+ getParent().validate();
+ }
+ repaint();
+ }
+
+ // ---------------- PAINT -----------------------------------------------
+
+ private boolean useCurvedEdges = false;
+
+ public boolean getUseCurvedEdges() {
+ return useCurvedEdges;
+ }
+
+ public void setUseCurvedEdges(boolean useCurvedEdges) {
+ this.useCurvedEdges = useCurvedEdges;
+ }
+
+ protected void paintEdges(Graphics g, Tree parent) {
+ if (!getTree().isLeaf(parent)) {
+ BasicStroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND);
+ ((Graphics2D)g).setStroke(stroke);
+
+ Rectangle2D.Double parentBounds = getBoundsOfNode(parent);
+ double x1 = parentBounds.getCenterX();
+ double y1 = parentBounds.getMaxY();
+ for (Tree child : getTree().getChildren(parent)) {
+ Rectangle2D.Double childBounds = getBoundsOfNode(child);
+ double x2 = childBounds.getCenterX();
+ double y2 = childBounds.getMinY();
+ if (getUseCurvedEdges()) {
+ CubicCurve2D c = new CubicCurve2D.Double();
+ double ctrlx1 = x1;
+ double ctrly1 = (y1+y2)/2;
+ double ctrlx2 = x2;
+ double ctrly2 = y1;
+ c.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
+ ((Graphics2D) g).draw(c);
+ } else {
+ g.drawLine((int) x1, (int) y1,
+ (int) x2, (int) y2);
+ }
+ paintEdges(g, child);
+ }
+ }
+ }
+
+ protected void paintBox(Graphics g, Tree tree) {
+ Rectangle2D.Double box = getBoundsOfNode(tree);
+ // draw the box in the background
+ boolean ruleFailedAndMatchedNothing = false;
+ if ( tree instanceof ParserRuleContext ) {
+ ParserRuleContext ctx = (ParserRuleContext) tree;
+ ruleFailedAndMatchedNothing = ctx.exception != null &&
+ ctx.stop != null && ctx.stop.getTokenIndex() < ctx.start.getTokenIndex();
+ }
+ if ( isHighlighted(tree) || boxColor!=null ||
+ tree instanceof ErrorNode ||
+ ruleFailedAndMatchedNothing)
+ {
+ if ( isHighlighted(tree) ) g.setColor(highlightedBoxColor);
+ else if ( tree instanceof ErrorNode || ruleFailedAndMatchedNothing ) g.setColor(LIGHT_RED);
+ else g.setColor(boxColor);
+ g.fillRoundRect((int) box.x, (int) box.y, (int) box.width - 1,
+ (int) box.height - 1, arcSize, arcSize);
+ }
+ if ( borderColor!=null ) {
+ g.setColor(borderColor);
+ g.drawRoundRect((int) box.x, (int) box.y, (int) box.width - 1,
+ (int) box.height - 1, arcSize, arcSize);
+ }
+
+ // draw the text on top of the box (possibly multiple lines)
+ g.setColor(textColor);
+ String s = getText(tree);
+ String[] lines = s.split("\n");
+ FontMetrics m = getFontMetrics(font);
+ int x = (int) box.x + arcSize / 2 + nodeWidthPadding;
+ int y = (int) box.y + m.getAscent() + m.getLeading() + 1 + nodeHeightPadding;
+ for (int i = 0; i < lines.length; i++) {
+ text(g, lines[i], x, y);
+ y += m.getHeight();
+ }
+ }
+
+ public void text(Graphics g, String s, int x, int y) {
+// System.out.println("drawing '"+s+"' @ "+x+","+y);
+ s = Utils.escapeWhitespace(s, true);
+ g.drawString(s, x, y);
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+
+ if ( treeLayout==null ) {
+ return;
+ }
+
+ Graphics2D g2 = (Graphics2D)g;
+ // anti-alias the lines
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // Anti-alias the text
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+// AffineTransform at = g2.getTransform();
+// g2.scale(
+// (double) this.getWidth() / 400,
+// (double) this.getHeight() / 400);
+//
+// g2.setTransform(at);
+
+ paintEdges(g, getTree().getRoot());
+
+ // paint the boxes
+ for (Tree Tree : treeLayout.getNodeBounds().keySet()) {
+ paintBox(g, Tree);
+ }
+ }
+
+ @Override
+ protected Graphics getComponentGraphics(Graphics g) {
+ Graphics2D g2d=(Graphics2D)g;
+ g2d.scale(scale, scale);
+ return super.getComponentGraphics(g2d);
+ }
+
+ // ----------------------------------------------------------------------
+
+
+ private static final String DIALOG_WIDTH_PREFS_KEY = "dialog_width";
+ private static final String DIALOG_HEIGHT_PREFS_KEY = "dialog_height";
+ private static final String DIALOG_X_PREFS_KEY = "dialog_x";
+ private static final String DIALOG_Y_PREFS_KEY = "dialog_y";
+ private static final String DIALOG_DIVIDER_LOC_PREFS_KEY = "dialog_divider_location";
+ private static final String DIALOG_VIEWER_SCALE_PREFS_KEY = "dialog_viewer_scale";
+
+ protected static JDialog showInDialog(final TreeViewer viewer) {
+ final JDialog dialog = new JDialog();
+ dialog.setTitle("Parse Tree Inspector");
+
+ final Preferences prefs = Preferences.userNodeForPackage(TreeViewer.class);
+
+ // Make new content panes
+ final Container mainPane = new JPanel(new BorderLayout(5,5));
+ final Container contentPane = new JPanel(new BorderLayout(0,0));
+ contentPane.setBackground(Color.white);
+
+ // Wrap viewer in scroll pane
+ JScrollPane scrollPane = new JScrollPane(viewer);
+ // Make the scrollpane (containing the viewer) the center component
+ contentPane.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel wrapper = new JPanel(new FlowLayout());
+
+ // Add button to bottom
+ JPanel bottomPanel = new JPanel(new BorderLayout(0,0));
+ contentPane.add(bottomPanel, BorderLayout.SOUTH);
+
+ JButton ok = new JButton("OK");
+ ok.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));
+ }
+ }
+ );
+ wrapper.add(ok);
+
+ // Add an export-to-png button right of the "OK" button
+ JButton png = new JButton("Export as PNG");
+ png.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ generatePNGFile(viewer, dialog);
+ }
+ }
+ );
+ wrapper.add(png);
+
+ bottomPanel.add(wrapper, BorderLayout.SOUTH);
+
+ // Add scale slider
+ double lastKnownViewerScale = prefs.getDouble(DIALOG_VIEWER_SCALE_PREFS_KEY, viewer.getScale());
+ viewer.setScale(lastKnownViewerScale);
+
+ int sliderValue = (int) ((lastKnownViewerScale - 1.0) * 1000);
+ final JSlider scaleSlider = new JSlider(JSlider.HORIZONTAL, -999, 1000, sliderValue);
+
+ scaleSlider.addChangeListener(
+ new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ int v = scaleSlider.getValue();
+ viewer.setScale(v / 1000.0 + 1.0);
+ }
+ }
+ );
+ bottomPanel.add(scaleSlider, BorderLayout.CENTER);
+
+ // Add a JTree representing the parser tree of the input.
+ JPanel treePanel = new JPanel(new BorderLayout(5, 5));
+
+ // An "empty" icon that will be used for the JTree's nodes.
+ Icon empty = new EmptyIcon();
+
+ UIManager.put("Tree.closedIcon", empty);
+ UIManager.put("Tree.openIcon", empty);
+ UIManager.put("Tree.leafIcon", empty);
+
+ Tree parseTreeRoot = viewer.getTree().getRoot();
+ TreeNodeWrapper nodeRoot = new TreeNodeWrapper(parseTreeRoot, viewer);
+ fillTree(nodeRoot, parseTreeRoot, viewer);
+ final JTree tree = new JTree(nodeRoot);
+ tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+ tree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+
+ JTree selectedTree = (JTree) e.getSource();
+ TreePath path = selectedTree.getSelectionPath();
+ TreeNodeWrapper treeNode = (TreeNodeWrapper) path.getLastPathComponent();
+
+ // Set the clicked AST.
+ viewer.setTree((Tree) treeNode.getUserObject());
+ }
+ });
+
+ treePanel.add(new JScrollPane(tree));
+
+ // Create the pane for both the JTree and the AST
+ final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+ treePanel, contentPane);
+
+ mainPane.add(splitPane, BorderLayout.CENTER);
+
+ dialog.setContentPane(mainPane);
+
+ // make viz
+ WindowListener exitListener = new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ prefs.putInt(DIALOG_WIDTH_PREFS_KEY, (int) dialog.getSize().getWidth());
+ prefs.putInt(DIALOG_HEIGHT_PREFS_KEY, (int) dialog.getSize().getHeight());
+ prefs.putDouble(DIALOG_X_PREFS_KEY, dialog.getLocationOnScreen().getX());
+ prefs.putDouble(DIALOG_Y_PREFS_KEY, dialog.getLocationOnScreen().getY());
+ prefs.putInt(DIALOG_DIVIDER_LOC_PREFS_KEY, splitPane.getDividerLocation());
+ prefs.putDouble(DIALOG_VIEWER_SCALE_PREFS_KEY, viewer.getScale());
+
+ dialog.setVisible(false);
+ dialog.dispose();
+ }
+ };
+ dialog.addWindowListener(exitListener);
+ dialog.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ int width = prefs.getInt(DIALOG_WIDTH_PREFS_KEY, 600);
+ int height = prefs.getInt(DIALOG_HEIGHT_PREFS_KEY, 500);
+ dialog.setPreferredSize(new Dimension(width, height));
+ dialog.pack();
+
+ // After pack(): set the divider at 1/3 (200/600) of the frame.
+ int dividerLocation = prefs.getInt(DIALOG_DIVIDER_LOC_PREFS_KEY, 200);
+ splitPane.setDividerLocation(dividerLocation);
+
+ if (prefs.getDouble(DIALOG_X_PREFS_KEY, -1) != -1) {
+ dialog.setLocation(
+ (int)prefs.getDouble(DIALOG_X_PREFS_KEY, 100),
+ (int)prefs.getDouble(DIALOG_Y_PREFS_KEY, 100)
+ );
+ }
+ else {
+ dialog.setLocationRelativeTo(null);
+ }
+
+ dialog.setVisible(true);
+ return dialog;
+ }
+
+ private static void generatePNGFile(TreeViewer viewer, JDialog dialog) {
+ BufferedImage bi = new BufferedImage(viewer.getSize().width,
+ viewer.getSize().height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.createGraphics();
+ viewer.paint(g);
+ g.dispose();
+
+ try {
+ File suggestedFile = generateNonExistingPngFile();
+ JFileChooser fileChooser = new JFileChooserConfirmOverwrite();
+ fileChooser.setCurrentDirectory(suggestedFile.getParentFile());
+ fileChooser.setSelectedFile(suggestedFile);
+ FileFilter pngFilter = new FileFilter() {
+
+ @Override
+ public boolean accept(File pathname) {
+ if (pathname.isFile()) {
+ return pathname.getName().toLowerCase().endsWith(".png");
+ }
+
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return "PNG Files (*.png)";
+ }
+ };
+
+ fileChooser.addChoosableFileFilter(pngFilter);
+ fileChooser.setFileFilter(pngFilter);
+
+ int returnValue = fileChooser.showSaveDialog(dialog);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File pngFile = fileChooser.getSelectedFile();
+ ImageIO.write(bi, "png", pngFile);
+
+ try {
+ // Try to open the parent folder using the OS' native file manager.
+ Desktop.getDesktop().open(pngFile.getParentFile());
+ }
+ catch (Exception ex) {
+ // We could not launch the file manager: just show a popup that we
+ // succeeded in saving the PNG file.
+ JOptionPane.showMessageDialog(dialog, "Saved PNG to: " +
+ pngFile.getAbsolutePath());
+ ex.printStackTrace();
+ }
+ }
+ }
+ catch (Exception ex) {
+ JOptionPane.showMessageDialog(dialog,
+ "Could not export to PNG: " + ex.getMessage(),
+ "Error",
+ JOptionPane.ERROR_MESSAGE);
+ ex.printStackTrace();
+ }
+ }
+
+ private static File generateNonExistingPngFile() {
+
+ final String parent = ".";
+ final String name = "antlr4_parse_tree";
+ final String extension = ".png";
+
+ File pngFile = new File(parent, name + extension);
+
+ int counter = 1;
+
+ // Keep looping until we create a File that does not yet exist.
+ while (pngFile.exists()) {
+ pngFile = new File(parent, name + "_"+ counter + extension);
+ counter++;
+ }
+
+ return pngFile;
+ }
+
+ private static void fillTree(TreeNodeWrapper node, Tree tree, TreeViewer viewer) {
+
+ if (tree == null) {
+ return;
+ }
+
+ for (int i = 0; i < tree.getChildCount(); i++) {
+
+ Tree childTree = tree.getChild(i);
+ TreeNodeWrapper childNode = new TreeNodeWrapper(childTree, viewer);
+
+ node.add(childNode);
+
+ fillTree(childNode, childTree, viewer);
+ }
+ }
+
+ private Dimension getScaledTreeSize() {
+ Dimension scaledTreeSize =
+ treeLayout.getBounds().getBounds().getSize();
+ scaledTreeSize = new Dimension((int)(scaledTreeSize.width*scale),
+ (int)(scaledTreeSize.height*scale));
+ return scaledTreeSize;
+ }
+
+
+ public Future<JDialog> open() {
+ final TreeViewer viewer = this;
+ viewer.setScale(1.5);
+ Callable<JDialog> callable = new Callable<JDialog>() {
+ JDialog result;
+
+ @Override
+ public JDialog call() throws Exception {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ result = showInDialog(viewer);
+ }
+ });
+
+ return result;
+ }
+ };
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ try {
+ return executor.submit(callable);
+ }
+ finally {
+ executor.shutdown();
+ }
+ }
+
+ public void save(String fileName) throws IOException, PrintException {
+ JDialog dialog = new JDialog();
+ Container contentPane = dialog.getContentPane();
+ ((JComponent) contentPane).setBorder(BorderFactory.createEmptyBorder(
+ 10, 10, 10, 10));
+ contentPane.add(this);
+ contentPane.setBackground(Color.white);
+ dialog.pack();
+ dialog.setLocationRelativeTo(null);
+ dialog.dispose();
+ GraphicsSupport.saveImage(this, fileName);
+ }
+
+ // ---------------------------------------------------
+
+ protected Rectangle2D.Double getBoundsOfNode(Tree node) {
+ return treeLayout.getNodeBounds().get(node);
+ }
+
+ protected String getText(Tree tree) {
+ String s = treeTextProvider.getText(tree);
+ s = Utils.escapeWhitespace(s, true);
+ return s;
+ }
+
+ public TreeTextProvider getTreeTextProvider() {
+ return treeTextProvider;
+ }
+
+ public void setTreeTextProvider(TreeTextProvider treeTextProvider) {
+ this.treeTextProvider = treeTextProvider;
+ }
+
+ public void setFontSize(int sz) {
+ fontSize = sz;
+ font = new Font(fontName, fontStyle, fontSize);
+ }
+
+ public void setFontName(String name) {
+ fontName = name;
+ font = new Font(fontName, fontStyle, fontSize);
+ }
+
+ /** Slow for big lists of highlighted nodes */
+ public void addHighlightedNodes(Collection<Tree> nodes) {
+ highlightedNodes = new ArrayList<Tree>();
+ highlightedNodes.addAll(nodes);
+ }
+
+ public void removeHighlightedNodes(Collection<Tree> nodes) {
+ if ( highlightedNodes!=null ) {
+ // only remove exact objects defined by ==, not equals()
+ for (Tree t : nodes) {
+ int i = getHighlightedNodeIndex(t);
+ if ( i>=0 ) highlightedNodes.remove(i);
+ }
+ }
+ }
+
+ protected boolean isHighlighted(Tree node) {
+ return getHighlightedNodeIndex(node) >= 0;
+ }
+
+ protected int getHighlightedNodeIndex(Tree node) {
+ if ( highlightedNodes==null ) return -1;
+ for (int i = 0; i < highlightedNodes.size(); i++) {
+ Tree t = highlightedNodes.get(i);
+ if ( t == node ) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public Font getFont() {
+ return font;
+ }
+
+ @Override
+ public void setFont(Font font) {
+ this.font = font;
+ }
+
+ public int getArcSize() {
+ return arcSize;
+ }
+
+ public void setArcSize(int arcSize) {
+ this.arcSize = arcSize;
+ }
+
+ public Color getBoxColor() {
+ return boxColor;
+ }
+
+ public void setBoxColor(Color boxColor) {
+ this.boxColor = boxColor;
+ }
+
+ public Color getHighlightedBoxColor() {
+ return highlightedBoxColor;
+ }
+
+ public void setHighlightedBoxColor(Color highlightedBoxColor) {
+ this.highlightedBoxColor = highlightedBoxColor;
+ }
+
+ public Color getBorderColor() {
+ return borderColor;
+ }
+
+ public void setBorderColor(Color borderColor) {
+ this.borderColor = borderColor;
+ }
+
+ public Color getTextColor() {
+ return textColor;
+ }
+
+ public void setTextColor(Color textColor) {
+ this.textColor = textColor;
+ }
+
+ protected TreeForTreeLayout<Tree> getTree() {
+ return treeLayout.getTree();
+ }
+
+ public void setTree(Tree root) {
+ if ( root!=null ) {
+ boolean useIdentity = true; // compare node identity
+ this.treeLayout =
+ new TreeLayout<Tree>(getTreeLayoutAdaptor(root),
+ new TreeViewer.VariableExtentProvide(this),
+ new DefaultConfiguration<Tree>(gapBetweenLevels,
+ gapBetweenNodes),
+ useIdentity);
+ // Let the UI display this new AST.
+ updatePreferredSize();
+ }
+ else {
+ this.treeLayout = null;
+ repaint();
+ }
+ }
+
+ /** Get an adaptor for root that indicates how to walk ANTLR trees.
+ * Override to change the adapter from the default of {@link TreeLayoutAdaptor} */
+ public TreeForTreeLayout<Tree> getTreeLayoutAdaptor(Tree root) {
+ return new TreeLayoutAdaptor(root);
+ }
+
+ public double getScale() {
+ return scale;
+ }
+
+ public void setScale(double scale) {
+ if(scale <= 0) {
+ scale = 1;
+ }
+ this.scale = scale;
+ updatePreferredSize();
+ }
+
+ public void setRuleNames(List<String> ruleNames) {
+ setTreeTextProvider(new DefaultTreeTextProvider(ruleNames));
+ }
+
+ private static class TreeNodeWrapper extends DefaultMutableTreeNode {
+
+ final TreeViewer viewer;
+
+ TreeNodeWrapper(Tree tree, TreeViewer viewer) {
+ super(tree);
+ this.viewer = viewer;
+ }
+
+ @Override
+ public String toString() {
+ return viewer.getText((Tree) this.getUserObject());
+ }
+ }
+
+ private static class EmptyIcon implements Icon {
+
+ @Override
+ public int getIconWidth() {
+ return 0;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return 0;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ /* Do nothing. */
+ }
+ }
+}