[ImageJ-devel] The mystery of the vanishing menu in ImageJ2

Johannes Schindelin Johannes.Schindelin at gmx.de
Fri Dec 13 23:12:01 CST 2013


Hi all,

I dedicate this mail to Barry because he reported the issue first, and
besides, I miss him ;-)

The issue Barry pointed out earlier, on Ubuntu, was that ImageJ2 started
behaving strangely with regards to the menu: as soon as the mouse was
hovering over the tool bar, the menu would go away.

Back then, I could not reproduce the issue, but today I could, so I went
on a hunt, and here is the write-up for everybody to enjoy:

First of all, I figured that I should find out whether some strange AWT
events were processed, so I fired up Eclipse -- because I wanted to use
Eclipse's debugger to investigate the issue -- and added this code to the
end of AbstractSwingUI#createUI():

-- snip --
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {

	@Override
	public void eventDispatched(AWTEvent event) {
		System.err.println("event: " + event);
	}
}, -1);
-- snap --

Basically, this code asks Java to output all the UI events as they
happen.

Then, I set a breakpoint on the first line and started ImageJ2 in debug
mode. A little-known gem is that Eclipse's Console window not only shows
the standard output and standard error stream of the debugged program,
but also accepts the standard input. I used this fact by adding some
empty lines (simply focusing the Console window and hitting the Return
key a couple of times) as soon as the main window was visible because
the events that had been shown so far could not be responsible (or so I
thought).

But after moving the mouse over the toolbar (and the menu bar
vanishing), really only the obvious events were shown: mouse enter,
mouse moves (and then mouse leave).

So I imagined that another way to debug the issue might be to look
whether there actually *was* a menu bar still when the tool bar was
painted after the mouse entered the main window.

To this end, I patched in a paintComponent method into the SwingToolBar
class:

-- snip --
	@Override
	public void paintComponent(final Graphics g) {
		Container c = getParent();
		c = c.getParent();
		c = c.getParent();
		c = c.getParent();
		System.err.println(c);
		System.err.println(((JFrame)c).getJMenuBar());
		super.paintComponent(g);
	}
-- snap --

Of course, that was not the initial version. The initial version assumed
that already the direct parent container of the tool bar would be the
JFrame, and in hindsight, I'd better have written something like:

		Container c = getParent();
		while (c != null && !(c instanceof JFrame)) {
			c = c.getParent();
		}

but hindsight is 20/20 and I did not have the luxury of that yet.
However, I had the luxury of incremental compilation (as performed by
Eclipse, one of the few things I really like about Eclipse): I could set
a breakpoint on the "System.err.println(c);" call, launch ImageJ2 in debug
mode, and then insert the "c = c.getParent();" lines one by one, saving
them, which would trigger Eclipse's incremental compiler and move
execution back to the first line of the method, until I had the right
amount.

And sure enough, the first time round, there was a menu bar, but after
moving the mouse over the main window the menu bar was null!

Therefore the next step was to find out when it was set to null. To find
out, I edited the SwingApplicationFrame class (which I found out from
inspecting the "c" variable in the SwingToolBar#paintComponent(Graphics)
method above) like so:

-- snip --
	@Override
	public void setJMenuBar(final JMenuBar bar) {
		super.setJMenuBar(bar);
	}
-- snap --

and -- you probably guessed it by now -- added a breakpoint on the
"super.setJMenuBar(bar);" line.

It turns out that it is called the first time from
AbstractSwingUI#createUI(), as expected, but then -- unexpectedly --
from MacOSXPlatform#onEvent(WinActivatedEvent)! On Linux, no less...

There we have the culprit. As I found out by inserting

-- snip --
if (c.getName().endsWith("WinActivatedEvent")) {
	System.err.println();
}
-- snap --

into the DefaultEventService#subscribe(Class, EventSubscriber) method
and -- you probably guessed that again -- setting a breakpoint on the
"System.err.println();" line (after importing scijava-common into the
Eclipse workspace and adjusting ImageJ2's pom.xml to set the
scijava-common.version property to point to the checked out version),
MacOSXPlatform is registered as an event listener.

This is the consequence of some subtle, recent change that at the same
time a Context is injected, the class is also automatically registered
as event listener if it has @EventHandler-annotated methods).

Now, MacOSXPlatform's onEvent() method does not verify that the current
platform is actually MacOSX, nor does the EventService "forget" the
MacOSXPlatform as one would expect when the MacOSXPlatform is
garbage-collected after it has been determined that it does not match
the current platform because the PlatformService is now a
SingletonService that does not let go of its instances...

So we actually need two changes to fix this:

1) MacOSXPlatform#onEvent(WinActivatedEvent) needs to be safe, and

2) the DefaultPlatformService must not keep references to Platform
instances that are not applicable to the currently-running platform.

https://github.com/imagej/imagej/commit/c814f4b0 addresses 1) while
https://github.com/scijava/scijava-common/commit/46f64db2 and
https://github.com/scijava/scijava-common/commit/522f524 address 2)

Ciao,
Dscho



More information about the ImageJ-devel mailing list