2

We want to achieve an RCP application which may have multiple windows (MWindow) for distinct data. The Windows must be independent (unlike the Eclipse IDE new window menu entry), but it must be possible to copy & paste, drag & drop things from one window into another one. Imagine an application like Word where you can have multiple documents open. We tried various approaches, but it is quiet difficult to find out the right e4 way:

1. Creating a new E4Application for each window

Our first approach was to create and run a complete new E4Application for each new window. But this sounds not to be the right e4 way. Also it is buggy: Key bindings does not work correct and also the LifecycleManager is called for each new application and therefor for each new window, which should not be.

E4Application application = new E4Application();
BundleContext context = InternalPlatform.getDefault().getBundleContext();
ServiceReference<?> appContextService = context.getServiceReference(IApplicationContext.class);
IApplicationContext iac = (IApplicationContext) context.getService(appContextService);
IWorkbench workbench = application.createE4Workbench(iac, display);
 final E4Workbench implementation = (E4Workbench) workbench;
implementation.createAndRunUI(workbench.getApplication());

This seems not the right approach to do it.

2. The Eclipse IDE approach

In the Eclipse IDE you can go to the menu and click Window -> New Window which will open a complete new top level window. But it is synchronized: Open the same text file in both windows and editing it in the first one will alter it in the other one too. Albeit we tried that approach by simply copy and pasting it from org.eclipse.ui.actions.OpenInNewWindowAction#run():

// Does not work because we do not have the RCP3 workbench in RCP4.
final IWorkbench workbench = PlatformUI.getWorkbench();
final IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
final IWorkbenchPage activePage = workbenchWindow.getActivePage();
final String perspectiveId;

if (activePage != null && activePage.getPerspective() != null) {
    perspectiveId = activePage.getPerspective().getId();
} else {
    perspectiveId = workbenchWindow.getWorkbench().getPerspectiveRegistry().getDefaultPerspective();
}

workbenchWindow.getWorkbench().openWorkbenchWindow(perspectiveId, null);

It looks like that the Eclipse IDE uses the RCP3 compatibility layer. We didn't found a way to obtain the IWorkbench object. Neither by PlatformUI#getWorkbench(), nor via the application context, nor the bundle context.

3. Clone the main window

We stumbled upon Opening multiple instances of an MTrimmedWindow complete with perspectives etc n-mtrimmedwindow-complete-with-perspectives-etc and did a lot of trial and error and came up with this muddy code:

class ElementCloningBasedCreator {

    EModelService models = ...; // injected
    MApplication app = ...; // injected

    public void openNewWindow() {
        MWindow originWindow = (MWindow) models.find("the.main.window.id", app);
        MWindow newWindow = (MWindow) models.cloneElement(originWindow, null);

        MPerspectiveStack newPerspectiveStack =
            (MPerspectiveStack) models.find(the.main.perspective.stack.id, newWindow);
        newPerspectiveStack.setParent((MElementContainer) newWindow);

        addTo(app, newWindow);

        // Clone the shared elements. If we don't do that the rendering somewhere 
        // deep in the rabbit hole throws assertion erros because the recurisve 
        // finding of an element fails because the search root is null.
        for (final MUIElement originSharedElement : originWindow.getSharedElements()) {
            final MUIElement clonedSharedElement = models.cloneElement(originSharedElement, null);
            clonedSharedElement.setParent((MElementContainer) newWindow);
            newWindow.getSharedElements().add(clonedSharedElement);
        }

        cloneSnippets(app, originWindow, newPerspectiveStack, newWindow);
        newWindow.setContext(createContextForNewWindow(originWindow, newWindow));
        newWindow.setToBeRendered(true);
        newWindow.setVisible(true);
        newWindow.setOnTop(true);
        models.bringToTop(newWindow);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void addTo(MElementContainer target, MUIElement child) {
        child.setParent(target);
        target.getChildren().add(child);
    }

    /**
     * Clone each snippet that is a perspective and add the cloned perspective 
     * into the main PerspectiveStack.
     */
    private void cloneSnippets(MApplication app, MWindow originWindow,
        MPerspectiveStack newPerspectiveStack, MWindow newWindow) {
        boolean isFirstSnippet = true;

        for (MUIElement snippet : app.getSnippets()) {
            if (ignoreSnippet(snippet)) {
                continue;
            }

            String snipetId = snippet.getElementId();
            MPerspective clonedPerspective = 
                (MPerspective) models.cloneSnippet(app, snipetId, originWindow);
            findPlaceholdersAndCloneReferencedParts(clonedPerspective, newWindow);
            addTo(newPerspectiveStack, clonedPerspective);

            if (isFirstSnippet) {
                newPerspectiveStack.setSelectedElement(clonedPerspective);
                isFirstSnippet = false;
            }
        }
    }

    private boolean ignoreSnippet(MUIElement snippet) {
        return !(snippet instanceof MPerspective);
    }

    private void findPlaceholdersAndCloneReferencedParts(MPerspective clonedPerspective, MWindow newWindow) {
        List<MPlaceholder> placeholders = 
            models.findElements(clonedPerspective, null, MPlaceholder.class, null);

        for (MPlaceholder placeholder : placeholders) {
            MUIElement reference = placeholder.getRef();

            if (reference != null) {
                placeholder.setRef(models.cloneElement(placeholder.getRef(), null));
                placeholder.getRef().setParent((MElementContainer) newWindow);
            }
        }
    }
}

This code does not really work and we really need some hints/advices how to do it right, because of the lack of official documentation. The questions open are:

  1. Do we need to clone the shared objects and if not how do we prevent the errors during rendering)?
  2. We only saw code where the cloned elements are added to the parent via getChildren().add(), but we found out that the children din't get the parent automatically and it is null though. Is it the right pattern to add the parent to the child too?
  3. We have the deep feeling that we are doing it not right. It looks way too complicated what we do here. Is there a simpler/better approach?
Community
  • 1
  • 1
Weltraumschaf
  • 408
  • 4
  • 17

1 Answers1

3

You can use the EModelService cloneSnippet method to do this.

Design your MTrimmedWindow (or whatever type of window you want) in the Snippets section of the Application.e4xmi. Be sure that the To Be Rendered and Visible flags are checked. You may need to set the width and height bounds (and you may want to set the x and y position as well).

Your command handler to create the new window would simply be:

@Execute
public void execute(EModelService modelService, MApplication app)
{
  MTrimmedWindow newWin = (MTrimmedWindow)modelService.cloneSnippet(app, "id of the snippet", null);

  app.getChildren().add(newWin);
}
greg-449
  • 109,219
  • 232
  • 102
  • 145
  • Thanks, good hint. Is it not necessary to set the app as parent to the newWin? – Weltraumschaf Apr 14 '16 at 11:45
  • With this approach we have the problem that SWT complains that in the Application.e4xmi no top level window is defined. – Weltraumschaf Apr 14 '16 at 12:01
  • 1
    You need to have one main window in the normal place in the e4xmi. The Snippet is for the second and subsequent windows. It might be possible to create the first window from the snippet during the app startup but I have not seen that done. – greg-449 Apr 14 '16 at 12:11
  • We only have one type of window, is it somehow possible to defien it as a snippet and use this snippet in the Application.e4xmi for the main window? – Weltraumschaf Apr 14 '16 at 13:01
  • As I said it might be possible in the LifeCycle class but I haven't seen any code that does it. You can also use Shared Elements with Placeholders to share things between different windows. – greg-449 Apr 14 '16 at 13:23
  • Ok. Thanks so far. If we have a solution I show it here. – Weltraumschaf Apr 14 '16 at 13:32