Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 295003

Summary: [UI] Design and implement a EPartService API
Product: [Eclipse Project] e4 Reporter: Remy Suen <remy.suen>
Component: UIAssignee: Paul Webster <pwebster>
Status: RESOLVED FIXED QA Contact:
Severity: enhancement    
Priority: P3 CC: bokowski, emoffatt, ob1.eclipse, pwebster, susan
Version: 1.0   
Target Milestone: 1.0 RC0   
Hardware: All   
OS: All   
Whiteboard:
Attachments:
Description Flags
EPartService patch v1
none
EPartService v02
none
EPartServiceTest test additions v1
none
EPartService v03
none
EPartService v04
none
EPartService activate bug
none
EPartService v05
none
EPartService v06
none
EPartService v07
none
EPartService v08 none

Description Remy Suen CLA 2009-11-12 13:20:06 EST
In 3.x, we have the activate(IWorkbenchPart) method in IWorkbenchPage for activating a method. We have no equivalent in 4.0. Invoking setActiveChild(T) is insufficient at the moment as the active context chain does not get updated to reflect this. setActiveChild(T) is more in line with bringToTop(IWorkbenchPart) (so maybe the 'activeChild' attribute should be renamed?).

To force a context switch between sash containers, I invoke the following code instead.

// set the window's active child to be the other container
window.setActiveChild(sashContainer);
// get the widget of the container's part stack
CTabFolder folder = (CTabFolder) partStack.getWidget();
// fake an SWT 'Activate' event
folder.notifyListeners(SWT.Activate, null);
Comment 1 Remy Suen CLA 2009-11-16 11:18:51 EST
Eric and I talked briefly about this and he suggested that a "part service" be created for these calls.
Comment 2 Eric Moffatt CLA 2009-11-16 15:18:08 EST
The PartService implementation also wrap the event publisher for any part related events.
Comment 3 Remy Suen CLA 2009-11-17 17:13:36 EST
Boris agrees. I will begin working on this tomorrow. It will be similar to the API for 3.x's IWorkbenchPage. We will call it EPartService for now though we will want to rename it before 4.0. :)
Comment 4 Remy Suen CLA 2009-11-18 08:11:18 EST
Before I forget (since I already forgot once and had to debug again), the NPEs in the setInput method is because the client object has been uninjected (and 'uiItem' has been reset to null). The reason this is _not_ observed when you actually run the photo demo and close it is because PRE's stop() method will simply dispose and unbind the widgets without disposing the context. I could easily just add a null check but it's not clear to me if this is the pattern I should be using. Seems strange from a client point of view but maybe that's just me?
Comment 5 Remy Suen CLA 2009-11-18 08:15:30 EST
(In reply to comment #4)
> Before I forget (since I already forgot once and had to debug again), the NPEs
> in the setInput method is because the client object has been uninjected (and
> 'uiItem' has been reset to null). The reason this is _not_ observed when you
> actually run the photo demo and close it is because PRE's stop() method will
> simply dispose and unbind the widgets without disposing the context. I could
> easily just add a null check but it's not clear to me if this is the pattern I
> should be using. Seems strange from a client point of view but maybe that's
> just me?

Damn, this should've been in bug 293500.
Comment 6 Remy Suen CLA 2009-11-18 09:00:29 EST
Using recursion to implement findPart(String) is straightforward and all but what happens if there are two parts with identical ids? In 3.x it is not possible for the client to alter the id programmatically but we have public methods for doing so in e4. Or is this one of those "just don't do it" case?
Comment 7 Remy Suen CLA 2009-11-18 11:37:35 EST
(In reply to comment #2)
> The PartService implementation also wrap the event publisher for any part
> related events.

Eric, can you clarify this? Right now I can attach an event handler to listen for an element container's active child changing though that doesn't provide me with sufficient information as the part might've just been "brought to the top". I could use a RAT but that'd mean climbing down the active child chain which I'm not particularly enthusiastic about. Are you suggesting that a client should be able to attach listeners to the part service similar to the methods that 3.x's IPartService provides?
Comment 8 Remy Suen CLA 2009-11-26 07:22:50 EST
Created attachment 153159 [details]
EPartService patch v1

Feel free to reimplement the interface completely. ;)
Comment 9 Eric Moffatt CLA 2009-11-26 16:02:56 EST
I think that the PartService should both subscribe to the UI model events (to handle the 'on top' cases by publishing a partVisible event) as well as having a RAT for the currently active part (to publish the partActive events).

This puts the 'listeners' in the correct domain; the UI model manages the widget hierarchy and the context manages part 'activity'.
Comment 10 Paul Webster CLA 2009-11-26 19:19:23 EST
(In reply to comment #9)
> I think that the PartService should both subscribe to the UI model events (to
> handle the 'on top' cases by publishing a partVisible event) as well as having
> a RAT for the currently active part (to publish the partActive events).

So we would subscribe to the UI model events so when a goes on top that we can publish the partVisible and partBroughtToTop?

PW
Comment 11 Paul Webster CLA 2009-11-27 15:15:40 EST
Created attachment 153273 [details]
EPartService v02

OK, I've updated your patch and the tests all run and pass.

Things I think we need to think about:

This uses the mediator pattern, an object per context that requests it, with very little state that's injected from the context (usually the context itself, but in this case the it's the closest MElementContainer).

This works when we get our EPartService from the window, but will fail when if we were to get it from our own part's context (since it would potentially see the stack model element, not the window).

I think we should revisit how we want this to work (and possibly some of the other services I have working as well).

PW
Comment 12 Paul Webster CLA 2009-11-30 13:57:45 EST
I've released a first cut at the find(*) API (specific MWindow implementation).  Now we should look at the activate and show set of methods.

PW
Comment 13 Eric Moffatt CLA 2009-11-30 14:47:59 EST
Beauty, I might want to scam the 'find' logic once I start implementing the 'ModelService' since 'findById' is useful for elements other than parts.

Once I get this in it might be worth changing the PartService's implementation to use it...
Comment 14 Paul Webster CLA 2009-12-01 09:16:52 EST
I've released support for activate as well.

Just need a few more tests.

PW
Comment 15 Remy Suen CLA 2009-12-01 14:08:48 EST
While Paul was implementing this, we've come to realize that the model needs to be "complete" when it's being rendered. That is, there needs to be a logical chain of "active child" from the application down to the part. If the model that's parsed and passed to the renderer does not abide by this contract then it is up to the renderer to compensate for this oversight. The renderer also needs to ensure that there is a valid context chain from top to bottom as many lookup functions (such as the ActivePartLookupFunction) relies on the context chain.
Comment 16 Paul Webster CLA 2009-12-01 14:34:46 EST
OK, I've updated the headless test to create a valid activeChild chain and to not use the UIScheduler

Setting the active part is a little trickier, as it has to be done through EPartService.activate(MPart) to ensure context and model chain is intact.

PW
Comment 17 Remy Suen CLA 2009-12-04 08:27:49 EST
Created attachment 153815 [details]
EPartServiceTest test additions v1

Paul, this test fails, I think it looks right, am I missing something?
Comment 18 Remy Suen CLA 2009-12-04 09:18:30 EST
(In reply to comment #17)
> Created an attachment (id=153815) [details]
> EPartServiceTest test additions v1
> 
> Paul, this test fails, I think it looks right, am I missing something?

We had a chat offline and the test itself doesn't actually perform all the necessary steps to move a part from one window to another though the second window should certainly have a null active part (first point of failure). Hence, there is something wrong with both the EPS implementation and the attached test.
Comment 19 Paul Webster CLA 2009-12-04 15:18:11 EST
(In reply to comment #18)
> We had a chat offline and the test itself doesn't actually perform all the
> necessary steps to move a part from one window to another though the second
> window should certainly have a null active part (first point of failure).
> Hence, there is something wrong with both the EPS implementation and the
> attached test.

I modified the test, ActivePartLookupFunction, and PartServiceImpl so they would work in this scenario (highlighting another wrinkle when specifying @Optional properties :-)

PW
Comment 20 Remy Suen CLA 2009-12-22 13:23:33 EST
In 3.x, IWorkbenchPage has a getWorkbenchWindow() method. Do we want a similar getElementContainer() method in the EPartService? MElementContainer may not be the best choice though. Another idea may be to introduce a new tag interface, MPartManager or whatever.
Comment 21 Paul Webster CLA 2010-01-06 10:43:41 EST
Created attachment 155410 [details]
EPartService v03

I've changed the "root" from being model based to context based  The root is added to MWindow contexts currently (since they all manage the parts).  Lookup for the root might need to be added to work with the application level context for global lookups, but I don't have that usecase yet.

There are 2 "pre-processing" steps that will need to be considered when we work through the lifecycle enhancements.  1) the root needs to be added to all window contexts that exist when the model is being initialized and 2) as any new window context is created the root needs to be added to them (this is currently done by registering an EventHandler on the Context/context topic).

PW
Comment 22 Paul Webster CLA 2010-01-06 10:44:47 EST
(In reply to comment #21)
> Created an attachment (id=155410) [details]
> EPartService v03

Released to HEAD
PW
Comment 23 Remy Suen CLA 2010-01-06 20:17:48 EST
(In reply to comment #21)
> Created an attachment (id=155410) [details]
> EPartService v03

We need to record an activation list to know what part to activate if the activated one is closed/hidden/invisible/whatever-the-term-is. Since perspectives have contexts, would it make sense to make them the EPS root?
Comment 24 Remy Suen CLA 2010-01-06 20:24:17 EST
(In reply to comment #23)
> We need to record an activation list to know what part to activate if the
> activated one is closed/hidden/invisible/whatever-the-term-is. Since
> perspectives have contexts, would it make sense to make them the EPS root?

And of course, the activation list is perspective-dependent and not window-dependent, hence I asked.

And of course, not all e4 applications want a perspective.
Comment 25 Remy Suen CLA 2010-01-07 09:22:43 EST
(In reply to comment #23)
> We need to record an activation list to know what part to activate if the
> activated one is closed/hidden/invisible/whatever-the-term-is.

See bug 299038.
Comment 26 Remy Suen CLA 2010-01-08 09:26:11 EST
Created attachment 155599 [details]
EPartService v04

I've copy/pasted Oleg's MPartDescriptor code from his ShowViewHandler over to the EPS so we now have a showView(String) method.
Comment 27 Remy Suen CLA 2010-01-08 09:26:50 EST
(In reply to comment #26)
> Created an attachment (id=155599) [details]
> EPartService v04

Released to HEAD.
Comment 28 Remy Suen CLA 2010-01-08 13:06:37 EST
showPart(String) was always creating a new part. This is obviously wrong. Fix released to HEAD.
Comment 29 Remy Suen CLA 2010-01-08 17:58:06 EST
Should EPS go in org.eclipse.e4.ui.services?
Comment 30 Paul Webster CLA 2010-01-08 18:58:53 EST
(In reply to comment #29)
> Should EPS go in org.eclipse.e4.ui.services?

It might be possible to move it into its own bundle in the long term ... but not yet.  It will have dependencies on the model service, which is our intersection of model operations and IEclipseContext operations (with possibly some more).

PW
Comment 31 Remy Suen CLA 2010-01-08 21:18:43 EST
The shown part does not become the active part. Will need to investigate.
Comment 32 Remy Suen CLA 2010-01-09 15:43:55 EST
(In reply to comment #31)
> The shown part does not become the active part. Will need to investigate.

The code below only fails at the end. There may be something wrong with the injection mechanism.

assertEquals(part, part.getContext().get(IServiceConstants.ACTIVE_PART));
assertEquals(part, window.getContext().get(
		IServiceConstants.ACTIVE_PART));
assertEquals(part, partService.getActivePart());
Comment 33 Remy Suen CLA 2010-01-11 08:02:04 EST
(In reply to comment #31)
> The shown part does not become the active part. Will need to investigate.

Actually, it looks like active(MPart) is broken in general. Note that this can only be observed if you actually use the SWT renderer. The headless renderer does not have this problem.
Comment 34 Remy Suen CLA 2010-01-12 11:42:13 EST
Created attachment 155880 [details]
EPartService activate bug

(In reply to comment #33)
> Actually, it looks like active(MPart) is broken in general. Note that this can
> only be observed if you actually use the SWT renderer. The headless renderer
> does not have this problem.

I'm not sure if PartRenderingEngineTests is the best place but since it seems to be a problem with the renderer, I've thrown it in there.
Comment 35 Remy Suen CLA 2010-01-13 12:55:22 EST
(In reply to comment #34)
> Created an attachment (id=155880) [details]
> EPartService activate bug

The bug surfaces with the SWT renderer because it is a scheduling/notification problem, see bug 295003.
Comment 36 Remy Suen CLA 2010-01-18 16:23:00 EST
Need to update showPart(String) to set the category to the auto-generated stack or else subsequent calls from other views (of the same category) will just keep spawning more stacks. :/
Comment 37 Remy Suen CLA 2010-01-22 08:47:30 EST
Created attachment 156933 [details]
EPartService v05

(In reply to comment #36)
> Need to update showPart(String) to set the category to the auto-generated stack
> or else subsequent calls from other views (of the same category) will just keep
> spawning more stacks. :/

1. Fixes this problem.
2. Add getDirtyParts() returning Collection<MSaveablePart>.
3. Add getSaveableParts() returning Collection<MSaveablePart>.
Comment 38 Remy Suen CLA 2010-01-22 08:48:08 EST
(In reply to comment #37)
> Created an attachment (id=156933) [details]
> EPartService v05

Released to HEAD.
Comment 39 Remy Suen CLA 2010-01-22 11:20:32 EST
In activate(MPart), we can get a null context for the part if the part hasn't been rendered which makes the code that adjusts the context chain incorrect/invalid, not too sure what to do here.
Comment 40 Eric Moffatt CLA 2010-01-22 11:53:15 EST
Remy, under what conditions would a non-rendered control ever become active?
Comment 41 Remy Suen CLA 2010-01-22 11:58:12 EST
(In reply to comment #40)
> Remy, under what conditions would a non-rendered control ever become active?

I start Eclipse, my 'Console' view is behind my 'Problems' view, the 'Console' hasn't been rendered yet. I run an application, now the 'Console' view needs to be rendered and activated.
Comment 42 Eric Moffatt CLA 2010-01-22 14:49:06 EST
Sure,

MPart console = findPart("o.e.ui.ConsoleView");

// 'bring to top' -> forces rendering
console.getParent().setActiveChild(console);

<...activate code goes here now that it's rendered and on top>
Comment 43 Remy Suen CLA 2010-01-22 15:36:45 EST
The context/rendering code needs to be swapped, see bug 295250.
Comment 44 Remy Suen CLA 2010-01-25 16:31:43 EST
Created attachment 157181 [details]
EPartService v06

This patch adds a new enum, PartState, specifying ACTIVATE, VISIBLE, and CREATE, which mirrors the 3.x version of our IWorkbenchPage API. Corresponding tests have been added.

The HCPE has been heavily modified because it was originally creating contexts for everything. This is of course not the case for our SWT renderer so I modified it to be "lazy" with regards to context creation.
Comment 45 Remy Suen CLA 2010-01-25 18:23:54 EST
How do we get the active editor? Do we depend on the model service? Or do we get it from the "activation service" (bug 300742)?
Comment 46 Paul Webster CLA 2010-01-25 18:58:55 EST
If there's one editor area, then the active editor is the editor on top ... if we are tracking the last active editor (and a part activation list) then it would be the last active editor still open.

If we were to maintain a navigation history (which is specific to editors) then on closing an editor we could walk back the navigation history ... but potentially not all editors participate in the current navigation history, right?

PW
Comment 47 Eric Moffatt CLA 2010-01-26 14:41:10 EST
The Model Service should talk only in 'data layer' terms such as MUIElement so that it's appropriate for any model definition. When you ask questions about 'Editor' or even 'Part' the correct place for these would be in the Part Service. So it'll have various types of 'find' as well as 'bringToTop' and a variety of 'insert' options (mirroring closely the API in IPageLayout). Whether we put 'activate' here or not will depend on what its final shape is I guess.

We might even want to add a layer specific to the legacy concepts; the Part service would deal with 'MPart', activation...while the legacy service would deal with the non-e4 concepts like Views and Editors.
Comment 48 Remy Suen CLA 2010-01-27 07:54:06 EST
(In reply to comment #44)
> Created an attachment (id=157181) [details]
> EPartService v06

Released to HEAD with some javadoc additions. In particular, I've added a @noreference to deactivate() given Brian's recent attempts to use this method (that we plan to delete).
Comment 49 Remy Suen CLA 2010-01-28 07:48:13 EST
Created attachment 157513 [details]
EPartService v07

The patch adds a new save(MSaveablePart, boolean) method, tests, and javadoc. To ensure that something was actually saved, I've modified MCPE to instantiate client objects (if there is one that is, see bug 299272).
Comment 50 Remy Suen CLA 2010-01-28 07:50:12 EST
(In reply to comment #49)
> Created an attachment (id=157513) [details]
> EPartService v07

Released to HEAD.
Comment 51 Remy Suen CLA 2010-01-28 12:37:38 EST
Created attachment 157542 [details]
EPartService v08

Implements saveAll(boolean) + javadocs + 252 corresponding tests. Released to HEAD.
Comment 52 Remy Suen CLA 2010-02-03 14:05:04 EST
Boris told me to think in terms of a DOM. So I tried.

The client is welcome to add the part by hand instead of going through the service but then if it's not a multi-instance views and bad things happen, that is not our problem. Using the 'showPart' APIs will also help take care of things like actually asking the renderer to render the part in addition to placing the part in the proper place (instead of some random spot in the window).

/**
 * Creates a new part of the given id.
 * 
 * @param id
 *            the identifier of the part, must not be <code>null</code>
 * @return a new part of the given id, or <code>null</code> if no part descriptors can be found
 *         that match the specified id
 */
public MPart createPart(String id);

/**
 * Shows a part with the identified by the given id. In the event that there are multiple parts
 * with the specified id, the client is recommended to use {@link #getParts()} and iterate over
 * the collection to find the interested part and invoke {@link #showPart(MPart, PartState)} on
 * it.
 * <p>
 * The behavior of this method is dictated by the supplied state. If <code>ACTIVATE</code> is
 * supplied, then the part is made visible and granted focus. If <code>VISIBLE</code> is
 * supplied, then the part will be made visible but not given focus. If <code>CREATE</code> is
 * supplied, then the part will be instantiated though its contents may not necessarily be
 * visible to the end user.
 * </p>
 * 
 * @param id
 *            the identifier of the part, must not be <code>null</code>
 * @param partState
 *            the desired state of the shown part to be in
 * @return the shown part, or <code>null</code> if no parts or part descriptors can be found
 *         that match the specified id
 */
public MPart showPart(String id, PartState partState);

/**
 * Shows the given part.
 * <p>
 * <ul>
 * <li>If there cannot be multiple parts of this type and a part already exists, the already
 * existing part will be shown and returned.</li>
 * <li>If multiple parts of this type is allowed, then the provided part will be shown and
 * returned</li>
 * </ul>
 * </p>
 * <p>
 * The behavior of this method is dictated by the supplied state. If <code>ACTIVATE</code> is
 * supplied, then the part is made visible and granted focus. If <code>VISIBLE</code> is
 * supplied, then the part will be made visible but not given focus. If <code>CREATE</code> is
 * supplied, then the part will be instantiated though its contents may not necessarily be
 * visible to the end user.
 * </p>
 * 
 * @param part
 *            the part to show
 * @param partState
 *            the desired state of the shown part to be in
 * @return the shown part
 */
public MPart showPart(MPart part, PartState partState);
Comment 53 Oleg Besedin CLA 2010-02-18 16:21:02 EST
The implementation of the ActivePartLookupFunction is incorrect.

IContextFunction's must not depend on elements outside of the context tree. However, it depends on MWindow/MApplication#getContext:

		IEclipseContext current = window.getContext();
		if (current == null) {
			return null;
		}

If the MApplication object is added to the context before its own context field is set, the ActivePartLookupFunction exist early and does not create dependency on the context(ACTIVE_CHILD). 

See bug 299529 comment 9, and bug 303218.
Comment 54 Remy Suen CLA 2010-02-25 11:54:05 EST
I've introduced a new REMOVE_ON_HIDE_TAG to remove the part from the model when hidePart(MPart) is called.
Comment 55 Eric Moffatt CLA 2010-03-02 13:58:57 EST
Now that we have tags we'll have to be careful that they dont' become a dumping ground for hacks...at some point (post EclipseCon) I'd recommend that we go over what we want to do with tags vs the model on a case by case basis.
Comment 56 Paul Webster CLA 2010-06-21 12:52:19 EDT
It's mostly there
PW