Eclipse RCP - Change Your Perspective

The benefit of Eclipse perspectives is to group and organize views, actions etc. relative to specific user tasks and workflows. Every Eclipse developer switches over to different perspectives several times a day, e.g. for developing an Eclipse RCP application in the "Plug-In Development perspective", for debugging it in the "Debug perspective" and for committing or updating  changes to/from SVN in "Team Synchronizing perspective". Users can arrange the views, add or close views and move or minimize them to the user's preferred layout and usage. Additional custom perspectives can be saved with a custom name, so users can create and switch over to their individual UI layouts.

For developers it is pretty simple to add the default functionality around perspectives given by the Eclipse IDE to custom applications. But is it really what users want, need and understand?

Perspective

To add the default perspective actions for opening, saving and resetting perspectives, only a few lines of code are needed in the ActionBarAdvisor of the RCP application:

public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
    private IWorkbenchAction openPerspectiveAction;
    private IWorkbenchAction savePerspectiveAsAction;
    private IWorkbenchAction resetPerspectiveAction;
    
    @Override
    protected void makeActions(IWorkbenchWindow window) {
        // create and register the actions
        openPerspectiveAction = ActionFactory.OPEN_PERSPECTIVE_DIALOG.create(window);
        register(openPerspectiveAction);
        savePerspectiveAsAction = ActionFactory.SAVE_PERSPECTIVE.create(window);
        register(savePerspectiveAsAction);
        resetPerspectiveAction = ActionFactory.RESET_PERSPECTIVE.create(window);
        register(resetPerspectiveAction);
    }
    
    @Override
    protected void fillMenuBar(IMenuManager menuBar) {
        // create and fill the window menu
        MenuManager windowMenu = new MenuManager("&Window", WorkbenchActionConstants.M_WINDOW);
        menuBar.add(windowMenu);
        windowMenu.add(openPerspectiveAction);
        windowMenu.add(savePerspectiveAsAction);
        windowMenu.add(resetPerspectiveAction);
    }
}

After adding these lines to the code, the menu entries "Open Perspective...", "Save Perspective As..." and "Reset Perspective..." appear in the window menu of the main tool bar. Let us see what the user can do with these:

  • "Open Perspective..." - A dialog to choose the perspective to open. The selected perspective will be opened in its last arranged (may be not saved) state after confirming the dialog.
  • "Save Perspective As..." - A dialog to choose an existing perspective or to enter a new perspective's name to save or overwrite with the current arrangement of views etc.
  • "Reset Perspective..." - Resets the currently opened perspective to its previously saved state, if confirmed.

Does the user of your application understand the handling of these three little but powerful functions? Does he really need to be able to create several custom perspectives with custom names? Does he want to/Should he be able to overwrite the default perspective you defined?

For our RCP applications Sophora and Toromiro we answered all these questions with "No!". Instead our users...

  • ...want to change the default perspective, but they also want to switch back to the default layout at any time.
  • ...want to save their sole individual perspective, for switching between the default and custom layout, but without giving it a name or saving several different layouts.
  • ...do not know what a "perspective" is.

For this reasons we implemented our own perspective actions "SavePerspectiveAction" and "RestorePerspectiveAction":

  • "SavePerspectiveAction" - Saves the current window layout with the label and ID given by the constructor for the perspective to save. If the perspective already exists, the user is asked to overwrite it.
  • "RestorePerspectiveAction" - Opens the perspective given by constructor and resets it to its last saved state. This action is only enabled, if the perspective already exists.

Afterwards we added these actions to the window menu while avoiding the word "perspective" in the action texts.

The window menu then contains the following entries:

  • "Save as personal window layout" - Saves the current window layout with an internally defined label and ID for a "personal" perspective (a "SavePerspectiveAction")
  • "Restore personal window layout" - Opens and resets the "personal" perspective (a "RestorePerspectiveAction")
  • "Restore default window layout" - Opens and resets the default perspective (another "RestorePerspectiveAction")

So users can not override the default perspective, but change it and save it as personal window layout. If changed again, they can return to the default layout or to the last saved state of the personal layout at any time. It is also possible to switch between the only two perspectives of the application - default and personal. The replacement of the label "perspective" by "window layout" makes it easier for users to imagine and understand what the action behind the menu entry does.

Code

Here is the code of the two actions and how they are added to the window menu:

public class SavePerspectiveAction extends Action {
    
    public static final String ID = "my.company.app.savePerspectiveAction";
    
    private final IWorkbenchWindow window;
    private final String perspectiveId;
    private final String perspectiveLabel;
    private final String confirmMessageText;
    
    /**
    * An action to save the current window layout.
    * @param window the workbench window
    * @param text label of the action
    * @param perspectiveId the ID of the perspective to save
    * @param perspectiveLabel the label of the perspective to save
    * @param confirmMessageText the text for the confirm message
    */
    public SavePerspectiveAction(IWorkbenchWindow window, String text, String perspectiveId,
    String perspectiveLabel, String confirmMessageText) {
        
        super(text);
        this.window = window;
        this.perspectiveId = perspectiveId;
        this.perspectiveLabel = perspectiveLabel;
        this.confirmMessageText = confirmMessageText;
        setId(ID);
    }
    
    @Override
    public void run() {
        IWorkbenchPage page = window.getActivePage();
        if (page != null) {
            boolean doSavePerspective = false; // used for confirm message dialog result
            IPerspectiveRegistry perspectiveRegistry = window.getWorkbench().getPerspectiveRegistry();
            IPerspectiveDescriptor personalPerspectiveDescriptor =
            perspectiveRegistry.findPerspectiveWithId(perspectiveId);
            if (personalPerspectiveDescriptor == null) {
                // if the perspective doesn't exist, clone the default one and don't
                // show a confirm message dialog (just save)
                IPerspectiveDescriptor originalPerspectiveDescriptor =
                perspectiveRegistry.findPerspectiveWithId(PerspectiveConstants.DEFAULT_PERSPECTIVE_ID);
                personalPerspectiveDescriptor =
                perspectiveRegistry.clonePerspective(perspectiveId, perspectiveLabel,
                originalPerspectiveDescriptor);
                doSavePerspective = true;
                } else {
                // if the perspective exists, ask whether it should be overwritten
                doSavePerspective =
                MessageDialog.openQuestion(window.getShell(), getText(), confirmMessageText);
            }
            if (doSavePerspective) {
                // save the perspective
                page.savePerspectiveAs(personalPerspectiveDescriptor);
                MessageDialog.openInformation(window.getShell(), "Saved successfully",
                perspectiveLabel + " saved.");
            }
        }
    }
}
public class RestorePerspectiveAction extends Action implements IWorkbenchAction {
    
    public static final String ID = "my.company.app.restorePerspectiveAction";
    
    private final IWorkbenchWindow window;
    private final String perspectiveId;
    private final String confirmMessageText;
    
    private IPerspectiveListener perspectiveListener;
    
    /**
    * An action to restore a window layout.
    * @param window the workbench window
    * @param text label of the action
    * @param perspectiveId ID of the perspective to restore.
    * @param confirmMessageText the text of the confirm message
    */
    public RestorePerspectiveAction(IWorkbenchWindow window, String text, String perspectiveId,
    String confirmMessageText) {
        
        super(text);
        this.window = window;
        this.perspectiveId = perspectiveId;
        this.confirmMessageText = confirmMessageText;
        setId(ID);
        
        perspectiveListener = new PerspectiveAdapter() {
            @SuppressWarnings("synthetic-access")
            @Override
            public void perspectiveSavedAs(IWorkbenchPage page,
            IPerspectiveDescriptor oldPerspective, IPerspectiveDescriptor newPerspective) {
                
                updateEnabledState();
            }
        };
        window.addPerspectiveListener(perspectiveListener);
        
        updateEnabledState();
    }
    
    @Override
    public void run() {
        if (MessageDialog.openConfirm(window.getShell(), getText(), confirmMessageText)) {
            IWorkbenchPage page = window.getActivePage();
            if (page != null) {
                IPerspectiveDescriptor perspectiveDescriptor = getPerspectiveDescriptor();
                
                if (perspectiveDescriptor != null) {
                    page.setPerspective(perspectiveDescriptor);
                    page.resetPerspective();
                }
            }
        }
    }
    
    /**
    * Updates the enabled state of this action, depending on the existence of the perspective.
    */
    private void updateEnabledState() {
        IPerspectiveDescriptor perspectiveDescriptor = getPerspectiveDescriptor();
        setEnabled(perspectiveDescriptor != null);
    }
    
    /**
    * @return the perspective descriptor or null if it does not exist
    */
    private IPerspectiveDescriptor getPerspectiveDescriptor() {
        IPerspectiveRegistry perspectiveRegistry = window.getWorkbench().getPerspectiveRegistry();
        IPerspectiveDescriptor perspectiveDescriptor =
        perspectiveRegistry.findPerspectiveWithId(perspectiveId);
        return perspectiveDescriptor;
    }
    
    @Override
    public void dispose() {
        window.removePerspectiveListener(perspectiveListener);
    }
}
public final class PerspectiveConstants {
    
    public static final String DEFAULT_PERSPECTIVE_ID = "my.company.app.perspective";
    public static final String PERSONAL_PERSPECTIVE_ID = "my.company.app.personalPerspective";
    
    private PerspectiveConstants() {
        // nothing to do
    }
}
public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
    private IAction savePersonalPerspectiveAction;
    private IAction restorePersonalPerspectiveAction;
    private IAction restoreDefaultPerspectiveAction;
    
    @Override
    protected void makeActions(IWorkbenchWindow window) {
        // create and register the actions
        savePersonalPerspectiveAction = new SavePerspectiveAction(window,
        "Save personal window layout", PerspectiveConstants.PERSONAL_PERSPECTIVE_ID,
        "Personal window layout", "Do you really want to overwrite your personal window layout?");
        register(savePersonalPerspectiveAction);
        
        restorePersonalPerspectiveAction = new RestorePerspectiveAction(window,
        "Restore personal window layout", PerspectiveConstants.PERSONAL_PERSPECTIVE_ID,
        "Do you want to restore your personal window layout?");
        register(restorePersonalPerspectiveAction);
        
        restoreDefaultPerspectiveAction = new RestorePerspectiveAction(window,
        "Restore default window layout", PerspectiveConstants.DEFAULT_PERSPECTIVE_ID,
        "Do you want to restore the default window layout?");
        register(restoreDefaultPerspectiveAction);
    }
    
    @Override
    protected void fillMenuBar(IMenuManager menuBar) {
        // create and fill the window menu
        MenuManager windowMenu = new MenuManager("&Window", WorkbenchActionConstants.M_WINDOW);
        menuBar.add(windowMenu);
        windowMenu.add(savePersonalPerspectiveAction);
        windowMenu.add(restorePersonalPerspectiveAction);
        windowMenu.add(restoreDefaultPerspectiveAction);
    }
} 

Using Groovy in Eclipse RCP

How to use Groovy code in Eclipse RCP applications? more

Eclipse RCP: Hiding a View inside the View Dialog

more

Working with Eclipse, Maven and WTP

more

Sophora CMS: A New Take on Content Management

Fast. Powerful. Flexible. Easy to use. Comfortable. We built our CMS Sophora with these key challenges in mind. more

Torsten Witte

2011-08-03 • 01:43 CH

Java, Eclipse