Closed jpasski closed 8 years ago
Travis built this successfully, closing #35 and will open a new one.
Learned about Eclipse add-ons. You know, just adding a Java class to a plug-in
doesn't magically make it work in RCP, even with @Inject
annotations :)
Researching, add-ons seemed like the right way to go. They come with problems
though. They're added way early, before the application model is formed, so
they can influence the application model. That's cool. But it also means
things like the ECP MPart
won't exist yet, so trying to search for it will
fail.
For example, I called the ESelectionService
from the add-on via an annotated
@PostConstruct
method and saw this NPE:
Caused by: java.lang.NullPointerException
at org.eclipse.e4.ui.internal.workbench.SelectionServiceImpl.getServiceAggregator(SelectionServiceImpl.java:129)
at org.eclipse.e4.ui.internal.workbench.SelectionServiceImpl.addSelectionListener(SelectionServiceImpl.java:75)
at io.opensemantics.semiotics.extension.e4.addon.ModelFocusAddOn.postConstruct(ModelFocusAddOn.java:46)
SelectionServiceImpl.getServiceAggregator
:
private SelectionAggregator getServiceAggregator() {
SelectionAggregator aggregator = context.get(SelectionAggregator.class);
if (aggregator != null)
return aggregator;
MApplication app = context.get(MApplication.class);
if (app == null)
return null;
MWindow selectedWindow = app.getSelectedElement(); // this can return null
IEclipseContext windowContext = selectedWindow.getContext(); // line 129: NPE on `selectedWindow`
if (windowContext == null)
return null;
return windowContext.get(SelectionAggregator.class);
}
This kinda makes sense. Add-ons occur before the model is rendered. (No items
will be selected.) So there's no SelectionAggregator
available when an
add-on is called.
The vogella book has a couple pages on add-ons. The example directly addressed what I observed. I probably even read it beforehand but failed to realize it :) Ended up with the following:
// vogella to the rescue
@Inject
@Optional
private void addSelectionListener(
@UIEventTopic(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE) final MApplication application) {
I also noticed that subscribing to event topics that seem to receive a bunch of
notifications within an add-on greatly slows down the start time. Instead of
APP_STARTUP_COMPLETE
I had some like ACTIVATE
or whatever UILifeCycle
has, injecting an MPart
. Lol, Eclipse didn't even start because of all of
the overhead. Dunno if this is add-on specific or a general issue.
vogella released some cool e4 spy tools that aid in identifying component IDs and events going on. During my testing I wanted to verify some information using these tools. Getting them working was a bit more work than anticipated.
The e4 tools have somewhat been integrated into the standard Eclipse release.
The spies have not. That means the spies have their own site :) The best site
I found was this one:
http://download.eclipse.org/e4/snapshots/org.eclipse.e4.tools/latest
. I added
that to the target platform, added all the spy
features to the e4.extension
run configuration, ran it, and saw the below error:
org.osgi.framework.BundleException: Could not resolve module: org.eclipse.e4.tools.model.spy [83]
Unresolved requirement: Require-Bundle: org.eclipse.e4.tools.emf.ui; bundle-version="0.9.0"
I stopped using the run configuration plugin verification because it has dependencies on a bunch of other platforms. So verification always fails since my platform cannot fulfill the dependencies... (I'm probably ignorant of something here. Anywho.)
After edited the e4.extension
configuration and noting the plug-in
org.eclipse.e4.tools.emf.ui
wasn't available, I went searching for its
feature. The main platform site had the Eclipse e4 Tools
feature, which I
added to target platform. The e4.extension
run configuration was updated to
include the org.eclipse.e4.core.tools.feature
, which contains the plug-in
org.eclipse.e4.tools.emf.ui
. Lather, rinse, repeat:
org.osgi.framework.BundleException: Could not resolve module: org.eclipse.e4.tools [79]
Unresolved requirement: Require-Bundle: org.eclipse.pde.core; bundle-version="3.9.0"
Alas... Tracked down to the Eclipse Plug-in Development Environment
(org.eclipse.pde.feature.group
) to target platform and noted the plug-in appears.
Then added the org.eclipse.pde
feature to the run configuration.
Ran it and verified it worked, whoop.
The below are notes captured prior to hitting a dead end. The dead end I think
I hit was with somehow changing the ECP Model Explorer tree viewer selection.
The goal was to synchronize the visual selection in the tree viewer whenever
some component updated the model focus service. My assumption was calling
ESelectionService.setSelection
would somehow update the tree viewer
selection in the ECP MPart
. I ended up successfully calling
ESelectionService.setSelection
to influence the current selection as seen by
the ECP part. (Note: injecting it at the MApplication
context didn't seem
to set the correct active selection; only by obtaining an ESelectionService
from the MPart
itself, via a selection change listener parameter. Anywho.)
Setting the selection worked in that the selection service really thought something else was the current active selection. However, the ECP tree viewer selection didn't change visually; it was still highlighted. If I clicked on the currently visually highlighted item, it would fire a change selection event, since the selection service was previously changed.
It seems setting the selection service is one-way only, meaning a tree viewer
(or anything really) can set it, but setting it doesn't mean a tree viewer's
selection will change. At least this seems true for the compatibility MPart
and Eclipse 3.x views. That sucks.
Also, because of my ignorance, I dunno if I ever should have had this expectation :) Maybe I was assuming more magic than there is. So it seems the only way forward with synchronizing the tree viewer is by managing the viewer directly. That implies using the e4 plugin, which uses the newer product, etc.
The below captures getting the EPartService
and adding a part listener onto
the ECP MPart
. I got familiar with what events fired at what times. My
consolation prize is learning something :) That's never a bad thing.
I want the following loop:
The UI get is the hard part. Since the UI isn't guaranteed to be active,
I don't think I can use the ESelectionService.setSelection
. If there was
a way to set an inactive part selection to some value, then cool. I wasn't
able to find such a way that was obvious to me:
Looking around I found this response to an Eclipse forum question of "Correct way to listen to a part activation/visibility change?". So if there's no way to change the inactive selection (unknown), then I could at least change the selection once the part in question is active. Then I could check to see if the current UI selection equals the current focus object. If not, set selection with the current focus object.
Reading that forum post shows all the ways a part can be visible. I think visibility is what I care about. Can the user see the part?
Here's the cases I see in the forum post:
So this concept of visibility is more nuanced than I thought. It's like there's N ways something can be visible to the user.
The OP keeps on having issues with item 4. It seems the
@UIEventTopic(UIEvents.UILifeCycle.BRINGTOTOP)
event doesn't solve it. The
response indicates adding a part listener (IPartListener
) via the part's
EPartService
(gotten via the part's context) should do the trick. There's
a handful of listener states.
So the idea here is to add a listener to the ECP MPart (getting it somehow). OK, seems legit. The next part is to get it somehow.
One forum search recommends injecting MApplication
and
EModelService
, then searching in EModelService
for the MPart
. (I'm
assuming MApplication
is injected to get its context...). Looking at the
API docs for EModelService
, I see
EModelService.find(String, MUIElement)
. Since I have the ID of the part
and MApplication
to search upon (maybe this is why it was injected :),
I should be able to return the part. If it's not null (it could be) then
I should have the MPart
. With the MPart
I can register a listener
on it.
To wrap this up, I'll add to the existing Add-On this functionality.
Hmm, the above find
though returns an MUIElement
, which does not have
a context (not a sub-interface of MContext
). I can use the model service
to then getContainingContext
and return something for that part. From there,
part service. No, no, no. That method gets the containing context, not the
context of the element if it has one (which an MPart
will).
At this point, an instanceof
makes sense. The other finds are useful if the
part has a tag, whatever that is :)
Tried this:
@Inject
@Optional
private void addPartListener(
@UIEventTopic(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE) final MApplication application,
final EModelService modelService,
final ModelFocal modelFocusService) {
MUIElement mElement = modelService.find(ECP_E3_VIEW_ID, application);
...
The element returned is null. I think this is because MApplication
is returning
a freshly-minted application (right at the end of start up). No parts are added
to it. I want to find when the part I care about is added.
OK, screw the model service :)
Going back to the forum post I see the part accessible via an event. So I can use the event to get the part, which will be called a lot, and then do further checks.
@Inject
@Optional
private void addPartListener(
@UIEventTopic(UIEvents.UILifeCycle.BRINGTOTOP) final Event event,
final EModelService modelService,
final ModelFocal modelFocusService) {
This greatly increases start-up time. I had to kill it.
OK, smart idea! Add the part listener when adding the selection listener :) Man, much quicker. Filtering on the element ID also reduced noise.
Noted behavior:
OK, I get it now.
The events I care about are visibility. It only loses visibility if the part is in no way viewable by the user (me) in its container, regardless if the container itself is visible or not.
Active doesn't imply visibility: a part can be visible but inactive.
Hmm, on second thought, I care about activated. The part can be activated or inactivated within a visible container.
I'll go with this.
ISelectionListener implementor
ESelectionService
and register aISelectionListener
on the ECP view (which is an Eclipse 3.x view).ModelFocal
service (issue #34)ISelectionListener.selectionChanged
is called, callModelFocal.setFocus
ModelFocalUpdateHandler implementor
ESelectionService
ModelFocalUpdateHandler
serviceModelFocalUpdateHandler.onUpdate
is called:EContextService.activateContext
and add the ECP view IDESelectionService.setSelection
and set it to the passed inEObject
Helpful URLs: