Tutorial: Extensible Type Registry

Version: 0.1 Date: December 28, 2005

Contents


Overview

[back to top]

The GMF Extensible Type Registry provides an extension point for plug-ins to add new element types to the registry. This extension point provides a way for GMF clients to define an application-specific classification system based on, but alternative to, the metaclasses defined by an Ecore metamodel. You can use this extension point when you wish to extend the metamodel and provide specialized model elements in your GMF application. You may also use this extension point to extend the editing behaviour of existing element types.

This tutorial will illustrate how to contribute element types and edit behaviour using the Extensible Type Registry.

References

[back to top]

To see the complete source code for the examples shown in this tutorial, install the Logic Example plug-in into your workspace.

Other references:


Introduction

[back to top]

In this tutorial, we will use the Logic Diagram Example to illustrate how to create element types used by a graphical editor for the digital logic domain. To gain familiarity with the Logic Diagram Example, refer to the Logic Example Guide. The example provides other element types, but will also include the element types and menu items that we add in this tutorial

For reference, here is the ECore model for the Logic Diagram Example:

This tutorial will guide you through the specification of the metamodel type for the Circuit metaclass and show you how to create a specialization of the Circuit type that describes a Half Adder. You will learn how to add context menu items that allow you to create Circuit and a Half Adder elements using the edit behaviour that you have associated with these two element types.

Specifying a Metamodel Type

[back to top]

We start with the extension declaration in the plugin.xml file, in which we indicate the EPackage for which we are contributing our metamodel type by referencing the logic example's namespace URI:

<extension
      point="org.eclipse.gmf.runtime.emf.type.core.elementTypes">
   <metamodel
         nsURI="http://www.eclipse.org/gmf/examples/runtime/1.0.0/logicsemantic">

Next, we define a metamodel type for the Circuit metaclass in the digital logic ECore model. Inside of the <metamodel> element, we declare the Circuit metamodel type with the following data:

<metamodelType
      id="logic.circuit"     
      icon="icons/circuit16.gif"
      name="%CircuitTool.Label"
      eclass="Circuit"
      edithelper="org.eclipse.gmf.examples.runtime.diagram.logic.internal.edithelpers.CircuitEditHelper" 
      kind="org.eclipse.gmf.runtime.emf.core.internal.util.IHintedType"> 
   <param name="semanticHint" value="circuit"/>
</metamodelType>

The Circuit type name is localized in the plugin.properties file:

CircuitTool.Label = Circuit

Next, we implement the edit helper for the Circuit element type. The CircuitEditHelper extends the edit helper for all ContainerElements, since the Circuit EClass extends the ContainerElement EClass. The ContainerElementEditHelper defines behaviour common to all ContainerElements. The CircuitEditHelper extends this behaviour to configure a new Circuit with its eight terminals.

public class CircuitEditHelper extends ContainerElementEditHelper {

   /**
    * Gets a command to configure a circuit after it has been created.
    */
   protected ICommand getConfigureCommand(final ConfigureRequest req) {

      return new ConfigureLogicElementCommand(req, 
                                              SemanticPackage.eINSTANCE.getCircuit()) {

         protected CommandResult doExecute(IProgressMonitor progressMonitor) {

            Circuit oCircuit = (Circuit) req.getElementToConfigure();

            createInputOutputTerminal(oCircuit, "A", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "B", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "C", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "D", progressMonitor); //$NON-NLS-1$

            createInputOutputTerminal(oCircuit, "1", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "2", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "3", progressMonitor); //$NON-NLS-1$
            createInputOutputTerminal(oCircuit, "4", progressMonitor); //$NON-NLS-1$

            return newOKCommandResult(oCircuit);
         }
      };
   }
}

Specifying a Specialization Type

[back to top]

Creating a specialization type is very similar; the main differences are that

The following specialization type defines a half adder circuit. The half adder is a digital logic circuit built from two logic gates that adds two one-bit binary numbers and produces two outputs: the sum and the carry. Add this into the same <metamodel> element as the previous metamodel type:

<specializationType
      id="logic.halfAdder"     
      icon="icons/halfadder16.gif"
      name="%HalfAdderTool.Label"
      edithelperadvice="org.eclipse.gmf.examples.runtime.diagram.logic.internal.edithelpers.HalfAdderAdvice"
      kind="org.eclipse.gmf.runtime.emf.core.internal.util.IHintedType"> 
   <specializes id="logic.circuit"/>
   <param name="semanticHint" value="circuit"/>
</specializationType>

And, of course, the localized string for the name:

HalfAdderTool.Label: HalfAdder

The HalfAdderAdvice class is responsible for configuring a new circuit element to look like a half adder. The half adder has four terminals; two for the 1-bit binary numbers that it adds (A and B), one for the sum of the two bits (1), and one for the carry (2). Inputs A and B are directed through an XOR gate to produce the sum bit, at the same time also being directed through an AND gate to produce the carry bit.

Because the Circuit configuration provided by the CircuitEditHelper creates eight terminals and the half adder has only four terminals, it is easiest in this case configure our half adder by completely replacing the default configuration behaviour for Circuits. We replace the default configuration behaviour by setting the REPLACE_DEFAULT_EDIT_COMMAND to TRUE.

public class HalfAdderAdvice extends AbstractEditHelperAdvice {

   protected ICommand getBeforeConfigureCommand(final ConfigureRequest request) {

      // The Half Adder advice completely replaces the default circuit
      // configuration.
      request.setParameter(IEditCommandRequest.REPLACE_DEFAULT_EDIT_COMMAND,
                           Boolean.TRUE);
        
      return new ConfigureLogicElementCommand(request,
                               SemanticPackage.eINSTANCE.getCircuit()) {

         protected CommandResult doExecute(IProgressMonitor progressMonitor) {

            Circuit circuit = (Circuit) request.getElementToConfigure();

            // Input two 1-bit binary numbers
            InputOutputTerminal a = createInputOutputTerminal(
                                          circuit, "A", progressMonitor); //$NON-NLS-1$
            InputOutputTerminal b = createInputOutputTerminal(
                                          circuit, "B", progressMonitor); //$NON-NLS-1$

            // Sum of the the two bits
            InputOutputTerminal s = createInputOutputTerminal(
                                          circuit, "1", progressMonitor); //$NON-NLS-1$

            // Carry
            InputOutputTerminal c = createInputOutputTerminal(
                                          circuit, "2", progressMonitor); //$NON-NLS-1$

            // The half adder contains an XOR gate and an AND gate
            Gate xorGate = createGate(circuit, LogicSemanticType.XORGATE, progressMonitor);
            Gate andGate = createGate(circuit, LogicSemanticType.ANDGATE, progressMonitor);

            // Inputs A and B are directed into the XOR gate
            List xorInputs = xorGate.getInputTerminals();
            createWire(a, (InputTerminal) xorInputs.get(0), progressMonitor);
            createWire(b, (InputTerminal) xorInputs.get(1), progressMonitor);

            // The same inputs A and B are also directed into the AND gate
            List andInputs = andGate.getInputTerminals();
            createWire(a, (InputTerminal) andInputs.get(0), progressMonitor);
            createWire(b, (InputTerminal) andInputs.get(1), progressMonitor);

            // The XOR gate produces the sum bit
            List xorOutputs = xorGate.getOutputTerminals();
            createWire((OutputTerminal) xorOutputs.get(0), s, progressMonitor);

            // The AND gate produces the carry bit
            List andOutputs = andGate.getOutputTerminals();
            createWire((OutputTerminal) andOutputs.get(0), c, progressMonitor);

            return newOKCommandResult(circuit);
         }
      };
   }
}

Creating Model Elements

[back to top]

To see these two new element types in action, we will create context menu items that will create Circuits and Half Adders. We contribute the context menu items to all IGraphicalEditParts that adapt to ContainerElement, using the Eclipse popupMenus extension point:

<extension
         point="org.eclipse.ui.popupMenus">
   <objectContribution
         id="LogicContainerElementObjectContribution"
         objectClass="org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart">
      <enablement>
         <adapt type="org.eclipse.emf.ecore.EObject">
            <instanceof value="ContainerElement"/>
         </adapt>
      </enablement>
      <action
            id="logic.circuit"
            label="%CreateCircuit.ActionLabel"
            icon="icons/circuit16.gif"
            menubarPath="addGroup"
            class="org.eclipse.gmf.examples.runtime.diagram.logic.internal.actions.CreateLogicElementActionDelegate"
            enablesFor="1">
      </action>
      <action
            id="logic.halfAdder"
            label="%CreateHalfAdder.ActionLabel"
            icon="icons/halfadder16.gif"
            menubarPath="addGroup"
            class="org.eclipse.gmf.examples.runtime.diagram.logic.internal.actions.CreateLogicElementActionDelegate"
            enablesFor="1">
      </action>
   </viewerContribution>
</extension>

The ID of each action corresponds to the ID of the element type that the action will create. This way, we can use a single action delegate to create any kind of logic element.

Next, we implement the CreateLogicElementActionDelegate. It obtains a command to create a new logic element from the selected edit part:

public class CreateLogicElementActionDelegate extends AbstractActionDelegate
        implements IObjectActionDelegate {

   protected void doRun(IProgressMonitor progressMonitor) {

      // Action ID corresponds to the ID of the element type to be created
      String actionId = getAction().getId();
      IElementType elementType = ElementTypeRegistry.getInstance().getType(actionId);

      if (elementType == null) {
         // Problem in the Action contribution XML
         throw new IllegalArgumentException("Action id '" //$NON-NLS-1$
                                            + actionId
                          + "' does not correspond to an existing element type ID."); //$NON-NLS-1$
      }

      // Get the selected edit part
      IStructuredSelection structuredSelection = getStructuredSelection();
      Object selection = structuredSelection.getFirstElement();

      if (!(selection instanceof IGraphicalEditPart)) {
         // Action enablement criteria expected to prevent this
         throw new IllegalArgumentException("Selected element '" //$NON-NLS-1$
                          + selection + "' is not an IGraphicalEditPart."); //$NON-NLS-1$
      }

      IGraphicalEditPart container = (IGraphicalEditPart) selection;

      // Get the command to create the new element and its view
      CreateViewRequest createRequest = CreateViewRequestFactory
                                               .getCreateShapeRequest(elementType, container
                                               .getDiagramPreferencesHint());

      Command command = container.getCommand(createRequest);

      if (command == null || !(command.canExecute())) {
         // Action enablement criteria expected to prevent this
         throw new IllegalArgumentException("Command for '" //$NON-NLS-1$
                          + actionId + "' is not executable."); //$NON-NLS-1$
      }

      // Create the new element
      DiagramCommandStack commandStack = container.getDiagramEditDomain()
                                                  .getDiagramCommandStack();
      commandStack.execute(command);
   }
}

Summary

[back to top]

To illustrate how to implement editing behaviour in using the GMF Extensible Type Registry, we

  1. Created a metamodel type and its edit helper.
  2. Created a specialization type and its edit helper advice.
  3. Added context menu items to create these element types from the diagram.



Copyright (c) 2000,2005 IBM Corporation and others. All Rights Reserved.