Search code examples
javaeclipseuser-interfaceeclipse-rcpe4

Open multiple instances of same view (part in Eclipse E4 terms) next to each other in the same part stack on Eclipse E4


I have my view and the corresponding placeholder (to support opening multiple instances of the same view) created by implementing IPerspectiveFactory as follows -

 @Override
  public void createInitialLayout(final IPageLayout layout)
  {
    String editorArea = layout.getEditorArea();
    IFolderLayout right = layout.createFolder("myViewLayout", IPageLayout.RIGHT, 0.22, IPageLayout.ID_PROJECT_EXPLORER);
    right.addView("com.example.myView");
    right.addPlaceholder("com.example.myView:*");
 }

Then I have a simple menu button attached to my view that when triggered should open the duplicate instance of the view next to the original view location (same partstack) like 'tabbed' views and I tried the following in Eclipse e4 way -

 public class NewMyView extends AbstractHandler {
 int counter = 1;
 @Override
  public Object execute(final ExecutionEvent event) throws ExecutionException
  {
    IWorkbenchPart activePart = HandlerUtil.getActivePart(event);
    for (; activePart.getSite().getPage().findViewReference("com.example.myView", getNextSecondaryId()) != null; this.counter++) 
    {
    
    }

    IEclipseContext serviceContext = E4Workbench.getServiceContext();
    final IEclipseContext appContext = serviceContext.getActiveChild();

    EModelService modelService = appContext.get(EModelService.class);
    MApplication app = serviceContext.get(MApplication.class);
    EPartService partService = serviceContext.get(EPartService.class);

    MPart part = partService.findPart("com.example.myView");
    if (part == null)
    {
      MPlaceholder placeholder = partService.createSharedPart("com.example.myView");
      part = (MPart) placeholder.getRef();
    }
    partService.showPart(part, PartState.ACTIVATE);
    this.counter++;
 }

private String getNextSecondaryId()
  {
    return "myView#" + this.counter;
  }
}

I'm not sure if what I'm doing is right using Eclipse e4 way to open the duplicate instance of 'myView' next to each other in the same part stack, ideally I want the duplicate instances of the view to open in the same location next to each other from where I trigger the action to open the duplicate instance of the view, so if I drag and drop one of the instances of my view to a different layout/part stack and then again I try to open the duplicate instance of my view then it should open the new location/currently active part from where I trigger the action to open the view. This can be achieved partially using Eclipse E3 way (for example Eclipse's Console view in E3 works this way) -

page.showView("com.example.myView", getNextSecondaryId(), IWorkbenchPage.VIEW_ACTIVATE);
this.counter++

But the problem with Eclipse E3 approach is that when I drag and drop one of the instances of the view to another part stack and then I again trigger the command to open a duplicate instance of my view from the new location then the new instance would be opened at the default location where it was initially added using IFolderLayout. Eclipse's Console view in E3 where it supports opening multiple instances behaves in a similar way.

Is this behavior achievable using Eclipse E4? If so then am I doing it correctly in my code by using EPartService and MPlaceHolder?


Solution

  • This should work (and does not even use internal API):

    // We need to find the PartStack of our activePart so we know where we need to create the new view
    // get the active part site to access to the ServiceLocator
    IWorkbenchPartSite currentPartSite = activePart.getSite();
    
    // get the IEclipseContext via the ServiceLocator - this is the context that is associated with the active part
    IEclipseContext context = currentPartSite.getService(IEclipseContext.class);
    
    // get the current perspective (since we are in the context of the active part, the MPerspective key gives us the current perspective)
    // The current perspective is used below in the find() calls to search for placeholders only in the current perspective
    MPerspective currentPerspective = context.get(MPerspective.class);
    
    // get the current MPart (as with the perspective, this can be acquired simply via the context)
    MPart currentPart = context.get(MPart.class);
    
    // the MPart is actually shared across multiple perspectives. So it is not contained directly in the part stack itself.
    // Instead it is contained in a special place in the model and referenced by a placeholder in the actual PartStack where the view is rendered.
    // So, we get the placeholder for this shared view by calling getCurSharedRef()
    MPlaceholder currentPlaceholder = currentPart.getCurSharedRef();
    
    // the parent of that placeholder is the PartStack that we want to create our new View in
    var targetPartStack = currentPlaceholder.getParent();
    
    // The next step is to find our placeholder in the current perspective
    EModelService modelService = context.get(EModelService.class);
    MUIElement placeholder = modelService.find("com.example.myView:*", currentPerspective);
    
    // now that we have identified the target stack we want our View to appear in
    // and the placeholder that will be used to create the View, we can move the placeholder
    // to the target part stack (if it is not yet there anyways)
    if (!placeholder.getParent().equals(targetPartStack)) {
      // this is an EMF add operation. It will automatically remove placeholder from its previous parent,
      // so this is actually a MOVE operation:
      targetPartStack.getChildren().add(placeholder);
    }
    
    // and finally, we can create our secondary view with the default API
    page.showView("com.example.myView", getNextSecondaryId(), IWorkbenchPage.VIEW_ACTIVATE);
    

    Note that if a view is opened and cloded again, it will leave an MPlaceholder behind. So only views that did not exist before will be created in the correct Part Stack. If you want to make this work even after opening and closing views, you could try to find the concrete MPlaceholder for the newly created View first and if it exists, then place that instead of the general placeholder.