Saturday, June 28, 2008

Edit EMF Tree Nodes Inline

The problem to solve: to edit a specific property inline in a tree editor generated by EMF.

For this example, we will be using a tree editor for UML. For any UML NamedElement, we would like to be able to edit the name directly in the tree editor.

1. We will generate the UML.genmodel using the UML.ecore file from eclipse cvs

  • host: dev.eclipse.org
  • repository path: /cvsroot/modeling
  • location: org.eclipse.mdt/org.eclipse.uml2/plugins/org.eclipse.uml2.uml/model/UML.ecore

With the UML.genmodel file, emit the UML Model, Edit and Editor code. This custom UML editor is only used for this example. You may very well have a different model editor in mind for customization.


2. We have the UML model code (the domain objects and the factories to create those domain objects), UML edit code (the Item Providers and Item Provider adapter factory for the domain objects) and UML editor in three separate plug-ins. We can call these uml.inline.editing.example (for the model), uml.inline.editing.example.edit (for the item providers) and uml.inline.editing.example.editor (for the editor).

3. We want to make sure that the NamedElementItemProvider implements org.eclipse.emf.edit.provider.IUpdateableItemText because we want to be able to update the name property of NamedElements inline. The two methods we would like to include look like this:

  public void setText(Object o, String s) {
    if (!(o instanceof NamedElement)) {
      return;
    }
    NamedElement namedElement = (NamedElement) o;
    if ("".equals(s) && !namedElement.isSetName()) {
      return;
    }
    IEditorPart activeEditor = PlatformUI.getWorkbench()
      .getActiveWorkbenchWindow().getActivePage().getActiveEditor();
    if (activeEditor instanceof IEditingDomainProvider) {
      EditingDomain editingDomain = ((IEditingDomainProvider) activeEditor)
        .getEditingDomain();
      editingDomain.getCommandStack().execute(
        SetCommand.create(editingDomain, namedElement,
        UmlPackage.eINSTANCE.getNamedElement_Name(), s));
    } else {
      namedElement.setName(s);
    }  
  }
  
  @Override
  public String getUpdateableText(Object o) {
    if (o instanceof NamedElement) {
      NamedElement namedElement = (NamedElement) o;
      if (namedElement.getName() == null) {
        return "";
      }
      return namedElement.getName();
    }
    return super.getUpdateableText(o);
  }

 Keep in mind that the superclass org.eclipse.emf.edit.provider.ItemProviderAdapter already includes a method #getUpdateableText(Object).
 
4. We need to make sure the UMLItemProviderAdapterFactory will adapt NamedElements to this IUpdateableText item provider. For this, we will need to add IUpdateableText.class to the supported types in the constructor.  The generated code is in grey: 

  public UmlItemProviderAdapterFactory() {
    supportedTypes.add(IUpdateableItemText.class);
    supportedTypes.add(IEditingDomainItemProvider.class);
    supportedTypes.add(IStructuredItemContentProvider.class);
    supportedTypes.add(ITreeItemContentProvider.class);
    supportedTypes.add(IItemLabelProvider.class);
    supportedTypes.add(IItemPropertySource.class);
  }

 

What if we want to set the focus of the UMLEditor on the updateable field as soon as the NamedElement is created?

By default, if we were to hit SPACE when a NamedElement node is selected, the cell editor would gain focus.  Suppose we want the cell editor to have focus automatically whenever a new NamedElement is created.

For this we can modify the CommandStackListener in the generated UMLEditor to focus the cell editor in the tree whenever a new child or sibling NamedElement is created in the tree editor. Generated code is in grey:

  commandStack.addCommandStackListener(new CommandStackListener() {
    public void commandStackChanged(final EventObject event) {
      getContainer().getDisplay().asyncExec(new Runnable() {
        public void run() {
          firePropertyChange(IEditorPart.PROP_DIRTY);
   
          // Try to select the affected objects.
          //
          Command mostRecentCommand = ((CommandStack) event
            .getSource()).getMostRecentCommand();
          if (mostRecentCommand != null) {
            setSelectionToViewer(mostRecentCommand
              .getAffectedObjects());

            // set the focus on the editable tree cell
            if (currentViewer instanceof TreeViewer
              && (mostRecentCommand instanceof CreateChildCommand)) {
              Tree tree = ((TreeViewer) currentViewer)
                .getTree();
              TreeItem[] treeItems = tree.getSelection();
              if (treeItems.length == 1) {
                Event enableEditField = new Event();
                enableEditField.button = 1;
                enableEditField.x = treeItems[0].getBounds().x;
                enableEditField.y = treeItems[0].getBounds().y;
                tree.notifyListeners(SWT.Selection, enableEditField);
                tree.notifyListeners(SWT.MouseDown, enableEditField);
                tree.notifyListeners(SWT.MouseUp, enableEditField);
              }
            }
          }
          if (propertySheetPage != null
            && !propertySheetPage.getControl().isDisposed()) {
            propertySheetPage.refresh();
          }
        }
      });
    }
  });

It is just that easy to specify a default property for direct inline editing in a generated EMF editor!



The cell editor is focused.

The name cell editor is focused.


The tree node label is focused.

The label is focused.

No comments: