Search code examples
javaeclipseeclipse-rcpe4

Add SWT.OpenDocument SWT Listener to E4Application of RCP app


I would like to use the launcher.openfile feature of eclipse. Therefore I read several documentation (e.g. https://help.eclipse.org/2020-03/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fproduct_open_file.htm) I implemented correctly a custom Application class but the content of this class is missing something I guess, because in my LifeCycle Class the main window can not be found anymore, which can normally found when I use the standard E4Application.

How can I use the common functionality of the E4Application class with only adding the the SWT Listener SWT.OpenDocument.

Here My Application Code:

package de.port.dsntool.ui.services;

import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;

public class MyE4Application implements IApplication{

    //Application with Listener to SWT.OpenDocument
    private Display display = null;

    public Display getApplicationDisplay() {
        if (display == null) {
            display = Display.getDefault();
        }
        return display;
    }

    @Override
    public Object start(IApplicationContext context) throws Exception {
        System.out.println("START My Application");
        OpenDocumentEventProcessor openDocProcessor = 
                new OpenDocumentEventProcessor();

        IProduct product = Platform.getProduct();
        if (product != null && product.getName() != null) {
            Display.setAppName(product.getName());
        }
        Display display = getApplicationDisplay();
        display.addListener(SWT.OpenDocument, openDocProcessor);

        try {
            int returnCode = PlatformUI.createAndRunWorkbench(display, new 
                    ApplicationWorkbenchAdvisor(openDocProcessor));

            if (returnCode == PlatformUI.RETURN_RESTART) {
                return IApplication.EXIT_RESTART;
            }
            return IApplication.EXIT_OK;
        } finally {
            if (display != null)
                display.dispose();
        }
    }

    @Override
    public void stop() {
        // TODO Auto-generated method stub

    }

}

ApplicationWorkbenchAdvisor.java:

package my.package;

import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.application.WorkbenchAdvisor;

public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
    private OpenDocumentEventProcessor openDocProcessor;

    public ApplicationWorkbenchAdvisor(
            OpenDocumentEventProcessor openDocProcessor) {
        this.openDocProcessor = openDocProcessor;
    }


    @Override
    public void eventLoopIdle(Display display) {
        openDocProcessor.openFiles();
        super.eventLoopIdle(display);
    }


    @Override
    public String getInitialWindowPerspectiveId() {
        // TODO Auto-generated method stub
        return null;
    }
}

OpenDocumentEventProcessor.java:

package my.package;

import java.util.ArrayList;

import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

public class OpenDocumentEventProcessor implements Listener {
    private ArrayList<String> filesToOpen = new ArrayList<String>(1);

    @Override
    public void handleEvent(Event event) {
        if (event.text != null)
            filesToOpen.add(event.text);
    }

    public void openFiles() {
        if (filesToOpen.isEmpty())
            return;

        String[] filePaths = filesToOpen.toArray(
            new String[filesToOpen.size()]);
        filesToOpen.clear();

        for (String path : filePaths) {
            // open the file path
        }
    }
}

LifeCycle.java ProcessAdditions snippet:

/**
 * Method to be invoked on ProcessAdditions life-cycle moment.
 * 
 * @param context Eclipse context.
 */
@ProcessAdditions
public void processAdditons(MApplication app, EModelService modelService,
        final IEclipseContext context,
        final IBrandingInfo branding) {
    /*obtain logger from context and publish it
     * to objects that require it*/
    final Logger logger = context.get(Logger.class);
    if (logger != null) {
        ProblemRegistry.INSTANCE.setLogger(logger);
    }

    /*obtain extension registry from context and publish
     * it to objects that require it*/
    final IExtensionRegistry registry = context
            .get(IExtensionRegistry.class);
    if (registry != null) {
        ProjectRegistry.INSTANCE.setExtensionRegistry(registry);
    }

    /* Push help service into context. */
    context.set(HelpService.class, new HelpServiceImpl(registry));


    MWindow window = (MWindow)modelService.find("my.package2.app.trimmedwindow.0", app);
    System.out.println("app: " + app);
    System.out.println("modelService: " + modelService);
    System.out.println("window: " + window);
    //ERROR: window is null here which is normally not when using standard E4Application
    window.setLabel(branding.getWindowTitle());


    ...
}

EDIT

I implemented your solution @greg-449 with the PostContextCreate Function in my Lifecycle and the EventloopAdvisor. But I found a strange bug with it: this solution works ONLY when a Dialog is opened before or after the implementing of the listener in PostContextCreate:

This is my actual Code Snippet from my Lifecycle:

@PostContextCreate
public void postContextCreate(final IEclipseContext context) {
    final Shell shell = new Shell(Display.getCurrent());
    new LicenseAgreementDialog(shell).open();

    if(!shell.isDisposed())
        shell.dispose();

    OpenDocumentEventProcessor openDocProcessor = new OpenDocumentEventProcessor();
    Display display = Display.getCurrent();
    display.addListener(SWT.OpenDocument, openDocProcessor);
    IEventLoopAdvisor eventLoopAdvisor = new EventLoopAdvisor(openDocProcessor);
    context.set(IEventLoopAdvisor.class, eventLoopAdvisor);
}

The Class LicenseAgreementDialog opens only a dialog once before the RCP app is started (opens while splashscreen is loading) and after the start of the app the SWT.OpenDocument event is properly triggered by other double clicked project files. But when I close the rcp app and started it again, the LicenseAgreementDialog is correctly not opened again and then there are no SWT.OpenDocument events any more who gets triggered. I tested through this bug and came to the solution that I always have to open a Dialog in the @PostContextCreateFunction, otherwise no SWT.OpenDocument event gets triggered. I tested it also with a normal MessageDialog (--> MessageDialog.openInformation(new Shell(Display.getCurrent()), "Opening", "Now");) instead of the LicenseAgreementDialog which opens every time at start which works but without any dialog before it won't.

Is there any chance to avoid to open always a dummy Dialog to get the event triggered?

FINAL Edit

After lots of trial and error I found finally an acceptable solution to avoid this dummy Dialog at start: I used the hint with adding a loop of readAndDispatch until its false but this loop alone makes no difference. I had to add a second loop which waits for readAndDispatch return true. I tested the two loops in different order and so on, but this is the one and only working solution:

@PostContextCreate
    public void postContextCreate(final IEclipseContext context,
            final IEventBroker eventBroker) {
        final Shell shell = new Shell(Display.getCurrent());
        new LicenseAgreementDialog(shell).open();

        /*check for clicked project file or only launcher.exe
         * when only launcher.exe is clicked there are no cmd arguments
         * when project file is double clicked and gets opened by file handler
         * the one and only cmd arg is the filepath from the clicked project file */
        if(Platform.getCommandLineArgs().length != 0) {
            while(Display.getCurrent().readAndDispatch()) { /* wait for false */ }
            while(!Display.getCurrent().readAndDispatch()) { /* wait for true */ }
        }

        if(!shell.isDisposed())
            shell.dispose();

        OpenDocumentEventProcessor openDocProcessor = new OpenDocumentEventProcessor(eventBroker);
        Display display = Display.getCurrent();
        display.addListener(SWT.OpenDocument, openDocProcessor);
        IEventLoopAdvisor eventLoopAdvisor = new EventLoopAdvisor(openDocProcessor);
        context.set(IEventLoopAdvisor.class, eventLoopAdvisor);
    }

With these two loops the SWT.OpenDocument event gets always properly triggered even if I don't show up a Dialog before. Thanks for the help @greg-449.

And here is a little hint which I had already: The .ini file has to have the -name attribute which have to match your standalone RCP app main window label when you use the openfile feature with SWT.OpenDocument event (for me I use the product name as window label):

when your main window label is e.g.: My Rcp App

then your launcher.ini file has to have the -name property with the same string:

--launcher.defaultAction
openFile
-name
My Rcp App

or you use the variable for product name of your rcp app:

--launcher.defaultAction
openFile
-name
%product.name

Solution

  • Using PlatformUI.createAndRunWorkbench makes your RCP a 3.x compatability mode RCP which uses the LegacyIDE.e4xmi so your window is not found.

    I think for a pure e4 RCP you can probably just set up the listener in the LifeCycle and use an IEventLoopAdvisor.

    So delete MyE4Application and use the standard E4Application. The ApplicationWorkbenchAdvisor should also be deleted.

    Set things up in the LifeCycle @PostContextCreate

    @PostContextCreate
    public void postContextCreate(final IEclipseContext context)
    {
      OpenDocumentEventProcessor openDocProcessor = new OpenDocumentEventProcessor();
    
      Display display = Display.getCurrent();
    
      display.addListener(SWT.OpenDocument, openDocProcessor);
    
      IEventLoopAdvisor eventLoopAdvisor = new EventLoopAdvisor(openDocProcessor);
    
      context.set(IEventLoopAdvisor.class, eventLoopAdvisor);
    }
    

    and use an event loop advisor like this:

    @SuppressWarnings("restriction")
    class EventLoopAdvisor implements IEventLoopAdvisor
    {
      private final OpenDocumentEventProcessor openDoc;
    
      EventLoopAdvisor(OpenDocumentEventProcessor openDoc)
      {
        this.openDoc = openDoc;
      }
    
    
      @Override
      public void eventLoopIdle(final Display display)
      {
        openDoc.openFiles();
    
        display.sleep();
      }
    
    
      @Override
      public void eventLoopException(final Throwable exception)
      {
        // TODO handle errors
      }
    }