Skip to content

Creating an OPM GEF Editor – Part 16: Displaying EMF Properties in a GEF Editor

Last updated on 2014-09-07

Previous Tutorial: Creating an OPM GEF Editor – Part 15: Adding Structural Links.

The eclipse workbench provides us with ready-made views, which can be populated by the editor to support the information that is provided by it. One very useful views is the properties view, which can show us name-value properties of the diagram elements.
There are some properties tutorials in the net, like this, this and this; But none of these seemed to match my requirements. I also found that in the past there were problems mixing EMF and GEF (see here) but there was no elegant solution to the problem that didn’t require serious rewriting of the classes provided by either of the frameworks. So against my will I had to do some custom-made wrapper classes for things to work. But I’m getting ahead of myself…

When implementing a GEF editor, there are (at least) two ways to display properties in the properties view, both of them by use of the adapter pattern that is strongly used in eclipse (both the GEF GraphicalEditor and GraphicalEditPart implement the IAdaptable interface).

  1. Providing a IPropertySheetPage when the editor is requested to adapt to this type.
  2. Providing a IPropertySource when an EditPart is requested to adapt to this type.

Implementing the second one is fairly straightforward but probably requires a lot of code (or changes in the hierarchy and stuff). The first options is more generic and suited me more, and it was also partly solved in the net (undo didn’t work), so I decided to complete the implementation here. BTW, the second option is implemented in the ecore editor example that can be found in the eclipse GEF repository.

The GEF framework provides a IPropertySheetPage implementation called UndoablePropertySheetPage, which is called such because in its implementation it automatically creates SetPropertyValueCommand instances to change the properties, and these commands can be used to undo changes. A IPropertySheetPage requires a IPropertySourceProvider which is used to get the properties of an object (which come as IPropertySource instances) to display them in a property view.
First problem I encountered is that for some (sad) reason the EMF framework works very similar interfaces to the standard eclipse interfaces, using what they call a IItemPropertySource, so this is the first place where we start building bridges. Thankfully, EMF provides a class called AdapterFactoryContentProvider which receives the IItemPropertySource factory genereated by EMF and transforms the output into regular eclipse IPropertySource instances.
The second obstacle in the implementation was caused by the fact that when a diagram element is selected, the IPropertySourceProvider receives the EditPart as a parameter but we want to fetch the properties from the model that is inside the EditPart. Therefore we had to implement our own IPropertySourceProvider which retrieved the model from the EditPart before adapting it to a IPropertySource
Third and last hurdle was fixing the undo problem. This problem shows up as a ClassCastException like this:

java.lang.ClassCastException: org.eclipse.emf.edit.provider.ItemPropertyDescriptor$PropertyValueWrapper cannot be cast to java.lang.String
	at com.vainolo.phd.opm.model.impl.OPMThingImpl.eSet(OPMThingImpl.java:131)
	at org.eclipse.emf.ecore.impl.BasicEObjectImpl.eSet(BasicEObjectImpl.java:1081)
	at org.eclipse.emf.edit.provider.ItemPropertyDescriptor.setPropertyValue(ItemPropertyDescriptor.java:1437)
	at org.eclipse.emf.edit.ui.provider.PropertySource.setPropertyValue(PropertySource.java:116)
	at com.vainolo.phd.opm.gef.editor.OPMGraphicalEditor$UnwrappingPropertySource.setPropertyValue(OPMGraphicalEditor.java:207)
	at org.eclipse.gef.ui.properties.SetPropertyValueCommand.undo(SetPropertyValueCommand.java:195)
	at org.eclipse.gef.commands.CompoundCommand.undo(CompoundCommand.java:173)
	at org.eclipse.gef.commands.ForwardUndoCompoundCommand.undo(ForwardUndoCompoundCommand.java:52)
	at org.eclipse.gef.commands.CommandStack.undo(CommandStack.java:395)
	at org.eclipse.gef.ui.actions.UndoAction.run(UndoAction.java:94)
	at org.eclipse.jface.action.Action.runWithEvent(Action.java:498)
	at org.eclipse.ui.actions.RetargetAction.runWithEvent(RetargetAction.java:230)
	at org.eclipse.jface.commands.ActionHandler.execute(ActionHandler.java:119)

Some code debugging showed that EMF stores the property values in something called a PropertyValueWrapper. When the SetPropertyValueCommand is executed, it stores the property’s old value, and when undo is called, it tries to apply it, but because it is a wrapper and not a real value, an exception is thrown. Reading the SetPropertyValueCommand.execute code, it seems that this is not the only time things are wrapped, because when the command stores the old value of the property, it calls the unwrapValue method… but this only unwraps instances of IPropertySource. It took me the better part of half a day to think of an elegant solution to this problem which didn’t require changes either to the SetPropertyValueCommand or the UndoablePropertySheetPage or other framework classes, and at the end I decided to implement a new IPropertySource which automatically unwraps a property’s value (I called this class UnwrappingPropertySource. Good name, right?). After doing this and plugging all things together, voila!

Since I am not an eclipse expert, I may have missed something in my implementation, but it seems to work, so here is the full code of the OPMGraphicalEditor (properties code at the end).

package com.vainolo.phd.opm.gef.editor;

import java.io.IOException;
import java.util.EventObject;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor.PropertyValueWrapper;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.properties.UndoablePropertySheetEntry;
import org.eclipse.gef.ui.properties.UndoablePropertySheetPage;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.PropertySheetPage;

import com.vainolo.phd.opm.gef.editor.part.OPMEditPartFactory;
import com.vainolo.phd.opm.model.OPMPackage;
import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
import com.vainolo.phd.opm.model.provider.OPMItemProviderAdapterFactory;

public class OPMGraphicalEditor extends GraphicalEditorWithFlyoutPalette {

    private Resource opdResource;
    private OPMObjectProcessDiagram opd;

    PropertySheetPage propertyPage;

    /**
     * Initialize the {@link EditDomain} of the editor.
     */
    public OPMGraphicalEditor() {
        setEditDomain(new DefaultEditDomain(this));
    }

    @Override
    protected void initializeGraphicalViewer() {
        super.initializeGraphicalViewer();
        getGraphicalViewer().setContents(opd);
    }

    @Override
    protected void configureGraphicalViewer() {
        super.configureGraphicalViewer();
        getGraphicalViewer().setEditPartFactory(new OPMEditPartFactory());
    }

    @Override
    protected PaletteRoot getPaletteRoot() {
        return new OPMGraphicalEditorPalette();
    }

    /**
     * Save the model using the resource from which it was opened, and mark the
     * current location in the {@link CommandStack}.
     */
    @Override
    public void doSave(IProgressMonitor monitor) {
        if(opdResource == null) {
            return;
        }

        try {
            opdResource.save(null);
            getCommandStack().markSaveLocation();
        } catch (IOException e) {
            // TODO do something smarter.
            e.printStackTrace();
            opdResource = null;
        }
    }

    @Override
    public void init(IEditorSite site, IEditorInput input)
            throws PartInitException {
        super.init(site, input);

        loadInput(input);
    }

    private void loadInput(IEditorInput input) {
        OPMPackage.eINSTANCE.eClass(); // This initializes the OPMPackage
                                       // singleton implementation.
        ResourceSet resourceSet = new ResourceSetImpl();
        if(input instanceof IFileEditorInput) {
            IFileEditorInput fileInput = (IFileEditorInput) input;
            IFile file = fileInput.getFile();
            opdResource = resourceSet.createResource(URI.createURI(file.getLocationURI().toString()));
            try {
                opdResource.load(null);
                opd = (OPMObjectProcessDiagram) opdResource.getContents().get(0);
            } catch (IOException e) {
                // TODO do something smarter.
                e.printStackTrace();
                opdResource = null;
            }
        }
    }

    /**
     * Fire a {@link IEditorPart#PROP_DIRTY} property change and call super
     * implementation.
     */
    @Override
    public void commandStackChanged(EventObject event) {
        firePropertyChange(PROP_DIRTY);
        super.commandStackChanged(event);
    }

    /**
     * This methos implements adapting to {@link IPropertySheetPage}. All other requests are
     * forwarded to the {@link GraphicalEditorWithFlyoutPalette#getAdapter(Class) parent}
     * implementation.
     */
    @Override
    public Object getAdapter(@SuppressWarnings("rawtypes") Class type) {
        if(type.equals(IPropertySheetPage.class)) {
            if(propertyPage == null) {
                propertyPage = (UndoablePropertySheetPage) super
                        .getAdapter(type);
                // A new PropertySourceProvider was implemented to fetch the model
                // from the edit part when required. The property source is provided
                // by the generated EMF classes and wrapped by the AdapterFactoryContentProvider
                // to yield standard eclipse interfaces.
                IPropertySourceProvider sourceProvider = new IPropertySourceProvider() {
                    IPropertySourceProvider modelPropertySourceProvider = new AdapterFactoryContentProvider(new OPMItemProviderAdapterFactory());

                    @Override
                    public IPropertySource getPropertySource(Object object) {
                        IPropertySource source = null;
                        if(object instanceof EditPart) {
                            source = modelPropertySourceProvider.getPropertySource(((EditPart) object).getModel());
                        } else {
                            source = modelPropertySourceProvider.getPropertySource(object);
                        }

                        if(source != null) {
                            return new UnwrappingPropertySource(source);
                        } else {
                            return null;
                        }
                    }

                };
                UndoablePropertySheetEntry root = new UndoablePropertySheetEntry(getCommandStack());
                root.setPropertySourceProvider(sourceProvider);
                propertyPage.setRootEntry(root);
            }
            return propertyPage;
        }
        return super.getAdapter(type);
    }

    /**
     * A property source which unwraps values that are wrapped in an EMF
     * {@link PropertyValueWrapper}
     *
     * @author vainolo
     *
     */
    public class UnwrappingPropertySource implements IPropertySource {
        private IPropertySource source;

        public UnwrappingPropertySource(final IPropertySource source) {
            this.source = source;
        }

        @Override
        public Object getEditableValue() {
            Object value = source.getEditableValue();
            if(value instanceof PropertyValueWrapper) {
                PropertyValueWrapper wrapper = (PropertyValueWrapper) value;
                return wrapper.getEditableValue(null);
            } else {
                return source.getEditableValue();
            }
        }

        @Override
        public IPropertyDescriptor[] getPropertyDescriptors() {
            return source.getPropertyDescriptors();
        }

        @Override
        public Object getPropertyValue(Object id) {
            Object value = source.getPropertyValue(id);
            if(value instanceof PropertyValueWrapper) {
                PropertyValueWrapper wrapper = (PropertyValueWrapper) value;
                return wrapper.getEditableValue(null);
            } else {
                return source.getPropertyValue(id);
            }
        }

        @Override
        public boolean isPropertySet(Object id) {
            return source.isPropertySet(id);
        }

        @Override
        public void resetPropertyValue(Object id) {
            source.resetPropertyValue(id);
        }

        @Override
        public void setPropertyValue(Object id, Object value) {
            source.setPropertyValue(id, value);
        }
    }
}

As usual, I really hope that this tutorial has helped you in some way. I enjoy writing them and learning while preparing them.

Next Tutorial: Creating an OPM GEF Editor – Part 17: How to Define Container Edit Parts.

Published inProgramming

12 Comments

  1. Peter Neorr Peter Neorr

    Awesome article! I encountered the same ClassCastException problem and this was immensely helpful.

  2. Andreas Wortmann Andreas Wortmann

    Thank you very much for this tutorial series! I’m currently building an automaton editor based on this.
    I’m currently stuck a getting changes on the PropertySheet to update the models graphical representation. Could you give me a hint on this?

  3. Heizel Kim Heizel Kim

    Hello, Vainolo : )

    Is there any automatical method that display all attributes and events of model ?
    I have saw in GMF, but i couldn’t find associated code.

    Your post is so helpful for me.
    Always thank you : )

    Heizel.

    • I wrote something about adding a property view adapter for the model. GEF is used for the visual part, so you probably have to handle the other aspects yourself.

  4. Heizel Kim Heizel Kim

    Hello, Vainolo !

    My properties sheet page don’t be refreshed before more one click.

    For example, I implemented [AA] using ComboBoxPropertyDescriptor and [BB] using TextPropertyDescriptor.
    1. I change the combobox value of [AA].
    2. the value does updated to the model and reset value of [BB] at the same time.
    3-1. [BB] doesn’t be updated.
    (3-2. When I click [BB] area, then reset value is displayed)

    How Can I refresh propertiy page dynamically?

    Thank you always.
    Heizel.

  5. Samhith V Samhith V

    Hi Vainolo,

    I saw many gef developers are failing to implement the Tabbed Property View in gef including me and there is no proper steps defined in creating it anywhere. It would be of great help if you can help folks like me to implement it.

    Thanks in advance.

Leave a Reply to Andreas WortmannCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Musings of a Strange Loop

Subscribe now to keep reading and get access to the full archive.

Continue reading