diff options
Diffstat (limited to 'tool/src/org/antlr/v4/gui/TreeViewer.java')
-rw-r--r-- | tool/src/org/antlr/v4/gui/TreeViewer.java | 764 |
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. */ + } + } +} |