CoastalHacking / semiotics-main

Semiotics models and stand-alone product / UI
Apache License 2.0
0 stars 0 forks source link

e4 Extension: ECP Focus Services #35

Closed jpasski closed 8 years ago

jpasski commented 8 years ago

ISelectionListener implementor

Helpful URLs:

jpasski commented 8 years ago

Main

Travis built this successfully, closing #35 and will open a new one.

Model AddOns

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.

Model Spy to Target

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.

Dead End

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.

MPart Listener

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:

  1. Open the part (create a new one)
  2. On an existing part stack, selecting the part
  3. On a perspective switch, part has focus
  4. On a perspective switch, part does not have focus

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.

Get Named MPart

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.