NOW Hiring

Quick links

Share

This tutorial describes how to create an editor and how this editor can be filled from a view. The tutorial is based on Eclipse 4.6 (Eclipse Neon).

1. Eclipse Editors

1.1. Overview

Eclipse uses editors and views to maintain data. An editor typically requires that the user explicitly select "save" to apply the changes to the data while a view typically changes the data immediately.

All editors are opened in the same area. Via the perspective you can configure if the editor area is visible or not.

1.2. Steps required to create an editor

The following steps are required to create and use an editor within an RCP application.

  • Make the editor area visible in your perspective

  • Create an IEditorInput class

  • Define an extension for the "org.eclipse.ui.editors" extension point

  • Implement the class for the editor, this class must implement IEditorPart

1.3. IEditorInput

IEditorInput serves as the model for the editor and is supposed to be a light-weight representation of the model. Eclipse will buffer IEditorInput objects therefore this object should be relatively small.

For example the Eclipse IDE uses IEditorInput objects to identify files without handling with the complete file.

Based on the equals() method of the IEditorInput the system will determine if the corresponding editor is already open or if a new editor must be opened.

1.4. IEditorPart and EditorPart

The editor is defined via the extension point "org.eclipse.ui.editors". The class which implement the editor must implement the interface "IEditorPart". In most cases it extends the abstract class "EditorPart".

The editor receives the IEditorSite and the IEditorInput in the init() method. It must set the input via the setInput() method and the side via the setSite() method.

init() is called before createPartControl() therefore you can use the input during your UI creation (which happens in createPartControl()).

1.5. Setting the Editor Title and Tooltip

By default the editor will use the tooltip and title from the IEditorInput.

Typically the EditorInput is only a light-weight representation of the real object. Therefore you may want to change the title and tooltip in your Editor. Use setPartName() to set the title of the Editor. To set the tooltip you have to override the method getTitleToolTip() (despite the Javadoc description). See Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=107772 for details.

1.6. Saving the Editor Content

In an editor the method isDirty() is used to inform the workbench if the content of the editor is changed. For inform the workbench that the dirty property of the editor has changed you fired an event.

firePropertyChange(IEditorPart.PROP.DIRTY);

1.7. Editor area

In a standalone RCP application you can make the editor area visible by changing your Perspective.java.

import org.eclipse.ui.IPageLayout;

public class Perspective implements IPerspectiveFactory {

        public void createInitialLayout(IPageLayout layout) {
                //layout.setEditorAreaVisible(false);
                layout.setFixed(true);
        }

}

1.8. API for working with Editors

You can open an Editor via the current active page. For this you need the EditorInput object and the ID for the editor which is defined in the "org.eclipse.ui.editors" extension point.

page.openEditor(new YourEditorInput(), ID_OF_THE_EDITOR);

To get the page you can use:

// If you are in a view
getViewSite().getPage();
// If you are in an command
HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
// Somewhere else
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();

2. Exercise: Creation of an editor

This tutorials describes the creation of an editor. It also shows how an editor can interact with a view. We will create a view which shows several tasks.

2.1. Create project and data model

Create a new empty default RCP project com.vogella.rcp.editor.example. Create the package com.vogella.rcp.editor.example.model and create the following classes in this package.

package com.vogella.rcp.editor.example.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Task {
        private PropertyChangeSupport changes = new PropertyChangeSupport(this);

        public static final String FIELD_ID = "id";
        public static final String FIELD_SUMMARY = "summary";
        public static final String FIELD_DESCRIPTION = "description";
        public static final String FIELD_DONE = "done";
        public static final String FIELD_DUEDATE = "dueDate";
        public static final String FIELD_DEPENDENTS = "dependents";

        private long id;
        private String summary;
        private String description;
        private boolean done;
        private Date dueDate;
        private List<Long> dependents = new ArrayList<>();

        public Task(long i) {
                id = i;
        }

        public Task(long i, String summary, String description, boolean b, Date date) {
                this.id = i;
                this.summary = summary;
                this.description = description;
                this.done = b;
                this.dueDate = date;
        }

        public long getId() {
                return id;
        }

        public String getSummary() {
                return summary;
        }

        public void setSummary(String summary) {
                changes.firePropertyChange(FIELD_SUMMARY, this.summary, this.summary = summary);
        }

        public String getDescription() {
                return description;
        }

        public void setDescription(String description) {
                changes.firePropertyChange(FIELD_DESCRIPTION, this.description, this.description = description);
        }

        public boolean isDone() {
                return done;
        }

        public void setDone(boolean isDone) {
                changes.firePropertyChange(FIELD_DONE, this.done, this.done = isDone);
        }

        public Date getDueDate() {
                return dueDate;
        }

        public void setDueDate(Date dueDate) {
                changes.firePropertyChange(FIELD_DUEDATE, this.dueDate, this.dueDate = dueDate);
        }

        public List<Long> getDependentTasks() {
                return dependents;
        }

        public void setDependentTasks(List<Long> dependents) {
                this.dependents = dependents;
        }

        @Override
        public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + (int) (id ^ (id >>> 32));
                return result;
        }

        @Override
        public boolean equals(Object obj) {
                if (this == obj)
                        return true;
                if (obj == null)
                        return false;
                if (getClass() != obj.getClass())
                        return false;
                Task other = (Task) obj;
                if (id != other.id)
                        return false;
                return true;
        }

        @Override
        public String toString() {
                return "Todo [id=" + id + ", summary=" + summary + "]";
        }

        public Task copy() {
                return new Task(id, summary, description, done, dueDate);
        }

        public void addPropertyChangeListener(PropertyChangeListener l) {
                changes.addPropertyChangeListener(l);
        }

        public void removePropertyChangeListener(PropertyChangeListener l) {
                changes.removePropertyChangeListener(l);
        }
}

The following class will serve as a data model content provider.

package com.vogella.rcp.editor.example.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TaskService {

        private static TaskService taskService = new TaskService();
        private List<Task> tasks = new ArrayList<Task>();

        private TaskService() {
                Task task = new Task(0, "Fix Bugs", "Fix Eclipse Bug 123", false, new Date());
                tasks.add(task);
                task = new Task(1, "Write Tutorial", "Write a tutorial about editors", true, new Date());
                tasks.add(task);
        }

        public static TaskService getInstance() {
                return taskService;
        }

        public List<Task> getTasks() {
                return tasks;
        }

        public Task getTaskById(long id) {
                for (Task todo : tasks) {
                        if (todo.getId() == id) {
                                return todo;
                        }
                }
                return null;
        }
}

2.2. Editor Input

Create the following new class TaskEditorInput which implements IEditorInput.

package com.vogella.rcp.editor.example.editor;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPersistableElement;

public class TaskEditorInput implements IEditorInput {

        private final long id;

        public TaskEditorInput(long id) {
                this.id = id;
        }

        public long getId() {
                return id;
        }

        @Override
        public boolean exists() {
                return true;
        }

        @Override
        public ImageDescriptor getImageDescriptor() {
                return null;
        }

        @Override
        public String getName() {
                return String.valueOf(id);
        }

        @Override
        public IPersistableElement getPersistable() {
                return null;
        }

        @Override
        public String getToolTipText() {
                return "Displays a task";
        }

        @Override
        public <T> T getAdapter(Class<T> adapter) {
                return null;
        }

        @Override
        public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + (int) (id ^ (id >>> 32));
                return result;
        }

        @Override
        public boolean equals(Object obj) {
                if (this == obj)
                        return true;
                if (obj == null)
                        return false;
                if (getClass() != obj.getClass())
                        return false;
                TaskEditorInput other = (TaskEditorInput) obj;
                if (id != other.id)
                        return false;
                return true;
        }
}

2.3. Adding the editor

Go to plugin.xml and add the extension org.eclipse.ui.editors.

editor30

Do not use a template. Use the ID com.vogella.rcp.editor.example.editor.taskeditor, any name you want and the class "com.vogella.rcp.editor.example.editor.TaskEditor".

task editor extension

Click on the class hyperlink to create the class. Make sure the variable ID matches the ID of the editor extension.

package com.vogella.rcp.editor.example.editor;

import java.util.Date;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;

import com.vogella.rcp.editor.example.model.Task;
import com.vogella.rcp.editor.example.model.TaskService;

public class TaskEditor extends EditorPart {

        public static final String ID = "com.vogella.rcp.editor.example.editor.taskeditor";
        private Task todo;
        private TaskEditorInput input;

        // Will be called before createPartControl
        @Override
        public void init(IEditorSite site, IEditorInput input) throws PartInitException {
                if (!(input instanceof TaskEditorInput)) {
                        throw new RuntimeException("Wrong input");
                }

                this.input = (TaskEditorInput) input;
                setSite(site);
                setInput(input);
                todo = TaskService.getInstance().getTaskById(this.input.getId());
                setPartName("Todo ID: " + todo.getId());
        }

        @Override
        public void createPartControl(Composite parent) {
                GridLayout layout = new GridLayout();
                layout.numColumns = 2;
                parent.setLayout(layout);
                new Label(parent, SWT.NONE).setText("Summary");
                Text text = new Text(parent, SWT.BORDER);
                text.setText(todo.getSummary());
                text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));

                new Label(parent, SWT.NONE).setText("Description");
                Text lastName = new Text(parent, SWT.BORDER);
                lastName.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
                lastName.setText(todo.getDescription());

                new Label(parent, SWT.NONE).setText("Done");
                Button doneBtn = new Button(parent, SWT.CHECK);
                doneBtn.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
                doneBtn.setSelection(todo.isDone());

                new Label(parent, SWT.NONE).setText("Due Date");
                DateTime dueDate = new DateTime(parent, SWT.CHECK);
                dueDate.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
                Date date = todo.getDueDate();
                dueDate.setDate(date.getYear(), date.getMonth(), date.getDay());
        }

        @Override
        public void doSave(IProgressMonitor monitor) {
        }

        @Override
        public void doSaveAs() {
        }

        @Override
        public boolean isDirty() {
                return false;
        }

        @Override
        public boolean isSaveAsAllowed() {
                return false;
        }

        @Override
        public void setFocus() {
        }

}

2.4. Command for opening the editor

Create a command com.vogella.rcp.editor.example.openEditor with the default handler com.vogella.rcp.editor.example.handler.CallEditor.

open task editor command

Create the following class "com.vogella.rcp.editor.example.handler.CallEditor".

package com.vogella.rcp.editor.example.handler;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.HandlerUtil;

import com.vogella.rcp.editor.example.editor.TaskEditor;
import com.vogella.rcp.editor.example.editor.TaskEditorInput;
import com.vogella.rcp.editor.example.model.Task;

public class CallEditor extends AbstractHandler {

        @Override
        public Object execute(ExecutionEvent event) throws ExecutionException {
                // get the page
                IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
                IWorkbenchPage page = window.getActivePage();
                // get the selection
                ISelection selection = HandlerUtil.getCurrentSelection(event);
                if (selection != null && selection instanceof IStructuredSelection) {
                        Object obj = ((IStructuredSelection) selection).getFirstElement();
                        // if we had a selection lets open the editor
                        if (obj != null) {
                                Task todo = (Task) obj;
                                TaskEditorInput input = new TaskEditorInput(todo.getId());
                                try {
                                        page.openEditor(input, TaskEditor.ID);
                                } catch (PartInitException e) {
                                        throw new RuntimeException(e);
                                }
                        }
                }
                return null;
        }
}

2.5. Task overview part

We will create the class TaskOverview to use JFace Viewers and add a double-click listener to call the command. The view makes his viewer available as selection provider via the following line: getSite().setSelectionProvider(viewer);. This make is possible for the command which opens the editor to get the selection of the view. All workbench parts have a site, which can be accessed via the method getSite(). A site is a Facade, which allows access to other parts of the workbench, e.g., the shell, the workbench window, etc.. Whenever possible use the site to access Workbench objects.

package com.vogella.rcp.editor.example.parts;

import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.ViewPart;

import com.vogella.rcp.editor.example.model.Task;
import com.vogella.rcp.editor.example.model.TaskService;

public class TaskOverview extends ViewPart {
        public static final String ID = "com.vogella.rcp.editor.example.taskoverview";

        private ListViewer viewer;

        @Override
        public void createPartControl(Composite parent) {
                viewer = new ListViewer(parent);
                viewer.setContentProvider(ArrayContentProvider.getInstance());
                viewer.setLabelProvider(new LabelProvider() {
                        @Override
                        public String getText(Object element) {
                                Task p = (Task) element;
                                return p.getSummary();
                        };
                });
                viewer.setInput(TaskService.getInstance().getTasks());
                getSite().setSelectionProvider(viewer);
                hookDoubleClickCommand();

        }

        private void hookDoubleClickCommand() {
                viewer.addDoubleClickListener(new IDoubleClickListener() {
                        @Override
                        public void doubleClick(DoubleClickEvent event) {
                                IHandlerService handlerService = getSite().getService(IHandlerService.class);
                                try {
                                        handlerService.executeCommand("com.vogella.rcp.editor.example.openEditor", null);
                                } catch (Exception ex) {
                                        throw new RuntimeException(ex.getMessage());
                                }
                        }
                });
        }

        @Override
        public void setFocus() {
                viewer.getControl().setFocus();
        }
}

Add the org.eclipse.ui.views extension point to the plugin.xml and configure the com.vogella.rcp.editor.example.taskoverview like this:

task overview part

2.6. Validating

Run the Eclipse IDE together with your plugin and open the Task Overview part with the Show View menu or the Quick Access (Ctrl + 3).

If you double-click on an item in the view, the editor should opened in the editor area of your Eclipse IDE.

task editor in IDE

To participate in the global save command, set the isDirty property in your editor and call firePropertyChange(IEditorPart.PROP_DIRTY); to notify the workbench. I leave this as an exercise to the reader.

3. Texteditors and SourceViewer

4. Exercise: Create a TextEditor with content assist

We could implement an editor like before and place a Text or even a StyledText control on it, but Eclipse offers a lot more than this, which you can use and extend. Things like ContentAssist, Quickfixes, Markers and many more.

So let us create a text editor for our Task model objects and reuse our com.vogella.rcp.editor.example plugin project.

4.1. Add new dependencies

Now that we want to implement an enhanced editor, we also need org.eclipse.ui.editors and _org.eclipse.jface.text as plugin dependency, besides org.eclipse.ui and org.eclipse.core.runtime in the MANIFEST.MF.

text editor dependencies

4.2. Add Task Text Editor

A Text Editor is added to the plugin.xml in the same way, as a usual Editor. But we also add the task extension, so that our Task Text Editor is directly associated with *.task files.

task text editor extension

The id of the editor should be com.vogella.rcp.editor.example.editor.tasktexteditor and the editor class will be called com.vogella.rcp.editor.example.editor.text.TaskTextEditor.

The difference here is that we do not extend EditorPart directly, but the TextEditor class.

The TextEditor class is a default implementation so that you are not forced to override any methods right now.

4.3. Add simple content assist

In other words we define a custom SourceViewerConfiguration for our TaskTextEditor, where we configure a simple example for content assist.

At first we create a TaskSourceViewerConfiguration, which extends TextSourceViewerConfiguration and place it in the com.vogella.rcp.editor.example.editor.text.config package.

package com.vogella.rcp.editor.example.editor.text.config;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;

public class TaskSourceViewerConfiguration extends TextSourceViewerConfiguration {


        private ContentAssistant contentAssistant;

        public TaskSourceViewerConfiguration() {
                this(null);
        }

        public TaskSourceViewerConfiguration(IPreferenceStore preferenceStore) {
                super(preferenceStore);

                // Initialize ContentAssistant
                contentAssistant = new ContentAssistant();

                // define a default ContentAssistProcessor
                contentAssistant.setContentAssistProcessor (new TaskCompletionProcessor(),
                                IDocument.DEFAULT_CONTENT_TYPE);

                // enable auto activation
                contentAssistant.enableAutoActivation(true);

                // set a proper orientation for the content assist proposal
                contentAssistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
        }

        @Override
        public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {

                contentAssistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));

                return contentAssistant;
        }
}

Here we configure a ContentAssistant so that it can be returned by the getContentAssistant(ISourceViewer sourceViewer) method.

Now we need to implement the TaskCompletionProcessor, which is used in the TaskSourceViewerConfiguration.

Therefore we implement computeCompletionProposals(ITextViewer viewer, int offset) of the IContentAssistProcessor interface.

package com.vogella.rcp.editor.example.editor.text.config;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;

public class TaskCompletionProcessor implements IContentAssistProcessor {

        private String[] proposals = new String[] { "ID:", "Summary:", "Description:", "Done:", "Duedate:", "Dependent:" };

        @Override
        public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {

                IDocument document = viewer.getDocument();

                try {
                        int lineOfOffset = document.getLineOfOffset(offset);
                        int lineOffset = document.getLineOffset(lineOfOffset);

                        // do not show any content assist in case the offset is not at the
                        // beginning of a line
                        if (offset != lineOffset) {
                                return new ICompletionProposal[0];
                        }
                } catch (BadLocationException e) {
                        // ignore here and just continue
                }

                List<ICompletionProposal> completionProposals = new ArrayList<ICompletionProposal>();

                for (String c : proposals) {
                        // Only add proposal if it is not already present
                        if (!(viewer.getDocument().get().contains(c))) {
                                completionProposals.add(new CompletionProposal(c, offset, 0, c.length()));
                        }
                }

                return completionProposals.toArray(new ICompletionProposal[completionProposals.size()]);
        }

        @Override
        public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
                return null;
        }

        @Override
        public char[] getCompletionProposalAutoActivationCharacters() {
                return null;
        }

        @Override
        public char[] getContextInformationAutoActivationCharacters() {
                return null;
        }

        @Override
        public String getErrorMessage() {
                return null;
        }

        @Override
        public IContextInformationValidator getContextInformationValidator() {
                return null;
        }

}

We also applied some rules for the content assist, so that it is a little bit context sensitive and only allows completion proposals at the beginning of a line and only those proposals, which are not already present in the document.

Please feel free to add your own rules here.

To apply this TaskSourceViewerConfiguration to our TaskTextEditor, we set it in the constructor of the TaskTextEditor, like this:

package com.vogella.rcp.editor.example.editor.text;

import org.eclipse.ui.editors.text.TextEditor;

import com.vogella.rcp.editor.example.editor.text.config.TaskSourceViewerConfiguration;

public class TaskTextEditor extends TextEditor {

        public static final String ID = "com.vogella.rcp.editor.example.editor.tasktexteditor";

        public TaskTextEditor() {
                System.out.println("Task Text Editor opened");
                setSourceViewerConfiguration(new TaskSourceViewerConfiguration());
        }

}

4.4. Validating

Please start the IDE together with your plugin and create a project in the project explorer, where you create a example.task file. When you double click on this file, the TaskTextEditor should be opened.

validate content assist exercise

5. About this website

Nothing listed.

6.1. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT

The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”.

The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.