Search code examples
eclipsercpe4

Custom ISaveHandler and IWindowCloseHandler for Eclipse e4 text editor app


Related to question "Custom message when closing a part in Eclipse RCP 4" I also have a Eclipse RCP 4 application with multiple editor parts (implementing MDirtyable and @Persist).

The parts are closable. When the user is closing a part there should be a custom pop-up which is asking the user if he really wants to save the part or not.

Also when the user close the appliaction a pop-up should prompt the user to close/save the dirty parts. Basically it is intended to remove the default close eclipse e4 dialogs.

I have implemented custom ISaveHandler and IWindowCloseHandler subscribed to the application startup complete event UIEvents.UILifeCycle.APP_STARTUP_COMPLETE in the life cycle class.

Custom IWindowCloseHandler works fine (in terms of dialogs) but custom ISaveHandler is not.

ISaveHandler.save returns stackoverflow error when defined as follows:

@Override
public boolean save(MPart dirtyPart, boolean confirm) {
    EPartService partService = dirtyPart.getContext().get(EPartService.class);
    //Try to close the part and save the document to disc by 
    //calling the @Persist method
    return partService.savePart(dirtyPart, confirm);

}

I have attached the complete LifeCycleManager class:

public class LifeCycleManager {
@Inject IEventBroker eventBroker;

@ProcessAdditions
public void processAdditions(MApplication application, EModelService modelService){
     MWindow window =(MWindow)modelService.find("application-trimmedwindow", application);
     eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE,
                          new AppStartupCompleteEventHandler(window, modelService, application));
}
public class AppStartupCompleteEventHandler implements EventHandler {
    private MWindow theWindow;
    private MApplication app;
    private ISaveHandler saveHandler;

    AppStartupCompleteEventHandler(MWindow window, EModelService modelService, MApplication application){
        theWindow = window;
        app = application;
    }

    @Override
    public void handleEvent(Event event) {
        theWindow.getContext().set(ISaveHandler.class, new ISaveHandler() {
            @Override
            public boolean save(MPart dirtyPart, boolean confirm) {
                System.out.println("PARTE PARA SALVAR..." + dirtyPart.getLabel());
                EPartService partService = dirtyPart.getContext().get(EPartService.class);
                //partService.hidePart(dirtyPart,true);
                return partService.savePart(dirtyPart, confirm);
                //return true;
            }

            @Override
            public boolean saveParts(Collection<MPart> dirtyParts, boolean confirm) {
                return false;
            }
            @Override
            public Save promptToSave(MPart dirtyPart) {
                return promptToSaveDialog(dirtyPart);
            }
            @Override
            public Save[] promptToSave(Collection<MPart> dirtyParts) {
                return null;
            }
        });
        saveHandler  = (ISaveHandler)theWindow.getContext().get(ISaveHandler.class);
        theWindow.getContext().set(IWindowCloseHandler.class, new IWindowCloseHandler() {
            @Override
            public boolean close(MWindow window) {
                List<MHandler> listHandlers = window.getHandlers();
                System.out.println(listHandlers.size());
                Shell shell = (Shell) window.getWidget();
                if (MessageDialog.openConfirm(shell, "Close Nastran Editor", "Do you really want to close the entire application?")) {
                    Collection<EPartService> allPartServices = getAllPartServices(app);
                    if (containsDirtyParts(allPartServices)) {
                        return iterateOverDirtyParts( allPartServices);
                    }
                else {
                    return true;
                }
            }
            return false;
        }});
}
private Collection<EPartService> getAllPartServices(MApplication application) {
    List<EPartService> partServices = new ArrayList<EPartService>();
    EModelService modelService = application.getContext().get(EModelService.class);
    List<MWindow> elements = modelService.findElements(application, MWindow.class, EModelService.IN_ACTIVE_PERSPECTIVE,
            new ElementMatcher(null, MWindow.class, (List<String>) null));
    for (MWindow w : elements) {
        if (w.isVisible() && w.isToBeRendered()) {
            EPartService partService = w.getContext().get(EPartService.class);
            if (partService != null) {
                partServices.add(partService);
            }
        }
    }
    return partServices;
}
private boolean containsDirtyParts(Collection<EPartService> partServices) {
    for (EPartService partService : partServices) {
        if (!partService.getDirtyParts().isEmpty()) return true;
    }
    return false;
}
private  boolean iterateOverDirtyParts(Collection<EPartService> allPartServices) {
    for (EPartService partService : allPartServices) {
        Collection<MPart> dirtyParts = partService.getDirtyParts();
        for(MPart dirtyPart : dirtyParts) {
            switch(saveHandler.promptToSave(dirtyPart)) {
                case NO: break;
                case YES:
                    saveHandler.save(dirtyPart, false);
                    break;
                case CANCEL:return false;
            }
        }
    }
    return true;
}
private Save promptToSaveDialog(MPart dirtyPart) {
    MessageDialog dialog = new MessageDialog( (Shell)theWindow.getWidget(), "Save file", null,
            "'"+dirtyPart.getLabel()+"' has been modified. Save changes?", MessageDialog.QUESTION, new String[] { "YES", "NO", "CANCEL" }, 0);
        switch (dialog.open()){
            case 0: return Save.YES;
            case 1: return Save.NO;
            case 2: return Save.CANCEL;
            default:return Save.CANCEL;
        }
}
}
}///END of LifeCycleManager

Solution

  • The save method of ISaveHandler is called from within the EPartService savePart method so you cannot call savePart again.

    Instead you should just call the @Persist method of the part. So something like:

    @Override
    public boolean save(final MPart dirtyPart, final boolean confirm)
    {
      if (confirm)
       {
         switch (promptToSave(dirtyPart))
         {
           default:
           case NO:
             return true;
           case CANCEL:
             return false;
           case YES:
             break;
         }
       }
    
      try
       {
         ContextInjectionFactory.invoke(dirtyPart.getObject(), Persist.class, dirtyPart.getContext());
       }
      catch (final InjectionException ex)
       {
         // TODO ignore or log error
       }
    
      return true;
    }