Tuesday, May 02, 2006

A debugging glass pane

I've started on a largish Java project (4000+ classes) with a complex Swing UI front-end and have had difficulty working out just which class is in play for a given visual component.

Glass pane to the rescue.

To help me out, I've written a small debugging glass pane based on a universal right-click handler found on Google similar to the example provided in the Swing documentation.

Use is simple:

JFrame frame = ...;
DebugGlassPane.setDebugGlassPaneOn(frame);

Now when you hover the mouse over a component in the content pane, up pops a tooltip identifying the class and instance name. If you want to change the displayed information, edit createTipText(Component).

The complete class:

public class DebugGlassPane
        extends JComponent
        implements MouseListener, MouseMotionListener {
    private final JLayeredPane layered;

    public static void setDebugGlassPaneOn(final JFrame frame) {
        final Component glass = new DebugGlassPane(frame);
        // Must be in this order
        frame.setGlassPane(glass);
        glass.setVisible(true);
    }

    public DebugGlassPane(final JFrame frame) {
        layered = frame.getLayeredPane();

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    public void mouseMoved(final MouseEvent e) {
        setToolTipText(createTipText(getChildUnderMouse(e)));

        redispatchMouseEvent(e);
    }

    public void mouseDragged(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    public void mouseClicked(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    public void mouseEntered(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    public void mouseExited(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    public void mousePressed(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    public void mouseReleased(final MouseEvent e) {
        redispatchMouseEvent(e);
    }

    private String createTipText(final Component c) {
        return "<html>Class: <b>" + c.getClass().getName()
                + "</b><br>Name: <i>" + c.getName();
    }

    private void redispatchMouseEvent(final MouseEvent e) {
        final Component component = getChildUnderMouse(e);

        // E.g., popup menus
        if (component == null) return;

        // redispatch the event
        component.dispatchEvent(
                SwingUtilities.convertMouseEvent(this, e, component));
    }

    private Component getChildUnderMouse(final MouseEvent e) {
        // get the mouse click point relative to the content pane
        final Point containerPoint = SwingUtilities.convertPoint(this,
                e.getPoint(), layered);

        return SwingUtilities.getDeepestComponentAt(layered,
                containerPoint.x, containerPoint.y);
    }
}

Surprisingly, I had a difficult time finding such a class already coded up somewhere. It is quite handy for exploring the UI.

UPDATE: An important fix (already incorporated in the code, above). Say you have a menu bar. The current code throws a NullPointerException. The problem is that all the work is relative to the content pane, but a JFrame can have visual space which recieves mouse events but is not part of the content.

The fix is simple: change getChildUnderMouse(MouseEvent) to work with the component from frame.getLayeredPane() instead of frame.getContentPane(). Sun has a good explanatory diagram and description making this point clear.

No comments: