<div dir="ltr">Good catch Johannes! I miss you and the rest of the gang too. Remember I'll always be around until you've closed all the tickets I ever filed. :) Happy holidays and best wishes to the ImageJ2 team.</div>
<div class="gmail_extra"><br><br><div class="gmail_quote">On Fri, Dec 13, 2013 at 11:12 PM, Johannes Schindelin <span dir="ltr"><<a href="mailto:Johannes.Schindelin@gmx.de" target="_blank">Johannes.Schindelin@gmx.de</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi all,<br>
<br>
I dedicate this mail to Barry because he reported the issue first, and<br>
besides, I miss him ;-)<br>
<br>
The issue Barry pointed out earlier, on Ubuntu, was that ImageJ2 started<br>
behaving strangely with regards to the menu: as soon as the mouse was<br>
hovering over the tool bar, the menu would go away.<br>
<br>
Back then, I could not reproduce the issue, but today I could, so I went<br>
on a hunt, and here is the write-up for everybody to enjoy:<br>
<br>
First of all, I figured that I should find out whether some strange AWT<br>
events were processed, so I fired up Eclipse -- because I wanted to use<br>
Eclipse's debugger to investigate the issue -- and added this code to the<br>
end of AbstractSwingUI#createUI():<br>
<br>
-- snip --<br>
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {<br>
<br>
        @Override<br>
        public void eventDispatched(AWTEvent event) {<br>
                System.err.println("event: " + event);<br>
        }<br>
}, -1);<br>
-- snap --<br>
<br>
Basically, this code asks Java to output all the UI events as they<br>
happen.<br>
<br>
Then, I set a breakpoint on the first line and started ImageJ2 in debug<br>
mode. A little-known gem is that Eclipse's Console window not only shows<br>
the standard output and standard error stream of the debugged program,<br>
but also accepts the standard input. I used this fact by adding some<br>
empty lines (simply focusing the Console window and hitting the Return<br>
key a couple of times) as soon as the main window was visible because<br>
the events that had been shown so far could not be responsible (or so I<br>
thought).<br>
<br>
But after moving the mouse over the toolbar (and the menu bar<br>
vanishing), really only the obvious events were shown: mouse enter,<br>
mouse moves (and then mouse leave).<br>
<br>
So I imagined that another way to debug the issue might be to look<br>
whether there actually *was* a menu bar still when the tool bar was<br>
painted after the mouse entered the main window.<br>
<br>
To this end, I patched in a paintComponent method into the SwingToolBar<br>
class:<br>
<br>
-- snip --<br>
        @Override<br>
        public void paintComponent(final Graphics g) {<br>
                Container c = getParent();<br>
                c = c.getParent();<br>
                c = c.getParent();<br>
                c = c.getParent();<br>
                System.err.println(c);<br>
                System.err.println(((JFrame)c).getJMenuBar());<br>
                super.paintComponent(g);<br>
        }<br>
-- snap --<br>
<br>
Of course, that was not the initial version. The initial version assumed<br>
that already the direct parent container of the tool bar would be the<br>
JFrame, and in hindsight, I'd better have written something like:<br>
<br>
                Container c = getParent();<br>
                while (c != null && !(c instanceof JFrame)) {<br>
                        c = c.getParent();<br>
                }<br>
<br>
but hindsight is 20/20 and I did not have the luxury of that yet.<br>
However, I had the luxury of incremental compilation (as performed by<br>
Eclipse, one of the few things I really like about Eclipse): I could set<br>
a breakpoint on the "System.err.println(c);" call, launch ImageJ2 in debug<br>
mode, and then insert the "c = c.getParent();" lines one by one, saving<br>
them, which would trigger Eclipse's incremental compiler and move<br>
execution back to the first line of the method, until I had the right<br>
amount.<br>
<br>
And sure enough, the first time round, there was a menu bar, but after<br>
moving the mouse over the main window the menu bar was null!<br>
<br>
Therefore the next step was to find out when it was set to null. To find<br>
out, I edited the SwingApplicationFrame class (which I found out from<br>
inspecting the "c" variable in the SwingToolBar#paintComponent(Graphics)<br>
method above) like so:<br>
<br>
-- snip --<br>
        @Override<br>
        public void setJMenuBar(final JMenuBar bar) {<br>
                super.setJMenuBar(bar);<br>
        }<br>
-- snap --<br>
<br>
and -- you probably guessed it by now -- added a breakpoint on the<br>
"super.setJMenuBar(bar);" line.<br>
<br>
It turns out that it is called the first time from<br>
AbstractSwingUI#createUI(), as expected, but then -- unexpectedly --<br>
from MacOSXPlatform#onEvent(WinActivatedEvent)! On Linux, no less...<br>
<br>
There we have the culprit. As I found out by inserting<br>
<br>
-- snip --<br>
if (c.getName().endsWith("WinActivatedEvent")) {<br>
        System.err.println();<br>
}<br>
-- snap --<br>
<br>
into the DefaultEventService#subscribe(Class, EventSubscriber) method<br>
and -- you probably guessed that again -- setting a breakpoint on the<br>
"System.err.println();" line (after importing scijava-common into the<br>
Eclipse workspace and adjusting ImageJ2's pom.xml to set the<br>
scijava-common.version property to point to the checked out version),<br>
MacOSXPlatform is registered as an event listener.<br>
<br>
This is the consequence of some subtle, recent change that at the same<br>
time a Context is injected, the class is also automatically registered<br>
as event listener if it has @EventHandler-annotated methods).<br>
<br>
Now, MacOSXPlatform's onEvent() method does not verify that the current<br>
platform is actually MacOSX, nor does the EventService "forget" the<br>
MacOSXPlatform as one would expect when the MacOSXPlatform is<br>
garbage-collected after it has been determined that it does not match<br>
the current platform because the PlatformService is now a<br>
SingletonService that does not let go of its instances...<br>
<br>
So we actually need two changes to fix this:<br>
<br>
1) MacOSXPlatform#onEvent(WinActivatedEvent) needs to be safe, and<br>
<br>
2) the DefaultPlatformService must not keep references to Platform<br>
instances that are not applicable to the currently-running platform.<br>
<br>
<a href="https://github.com/imagej/imagej/commit/c814f4b0" target="_blank">https://github.com/imagej/imagej/commit/c814f4b0</a> addresses 1) while<br>
<a href="https://github.com/scijava/scijava-common/commit/46f64db2" target="_blank">https://github.com/scijava/scijava-common/commit/46f64db2</a> and<br>
<a href="https://github.com/scijava/scijava-common/commit/522f524" target="_blank">https://github.com/scijava/scijava-common/commit/522f524</a> address 2)<br>
<br>
Ciao,<br>
Dscho<br>
</blockquote></div><br></div>