# Diagram Services Layer - How-to Guide

Version: 0.1 | Date: August 11, 2005

## Contents

- Introduction
- References
- How to add new notation styles to an existing shape
- How to override an existing shapes look
- How to change shape appearances based on a global preference change
- How to override the look of a connection

- How to open multiple read-only views on a diagram
- How to change the color of a shape dynamically
- How to make a shape resizable vs. non-resizable

##

Introduction

[back to top]

This guide is a repository for questions on how to accomplish specific tasks within the diagram services layer.

## References

[back to top]

## How to add notation styles to an existing shape

[back to top]

Each shape node view class (Node) will typically need to install a set of styles that allow a certain look of the notation to be persisted. Usually the default style ShapeStyle will be adequate since this covers colors, text, etc. However, it may be useful to add additional styles and/or have custom styles not accounted for in the notation meta-model (org.eclipse.gmf.runtime.notation). For instance, a domain editor may wish to toggle the display of a particular shape to have different looks. The property to store this is notational and consequently should be part of the notation meta-model.

Create a new notation meta-model sub-class. Since the property is typically an attribute of a node, this can be represented in a Style subclass.

-

-

- org.eclipse.gmf.runtime.notation plug-in and fine in the source directories the cat file \src\rosemodel\org.eclipse.gmf.runtime.notation.Notation.cat and the org.eclipse.emf.Ecore.cat files.

- LogicalView describing your notation meta-model (i.e. mynotation)

-

-

- mynotation"

-

-

- mynotation

-

- Hit "Next" until you get to the "Package Selection" page.

-

- org.eclipse.gmf.runtime.notation/src/model) and in the right hand pane select notation.genmodel.

- Ecore check-boxes under the notation root.

- mynotation

-

- mynotation.genmodel. Right click on the root in the editor and choose "Generate Model Code" and the "Generate Edit Code" menu items.

#### Modifying an already existing and generated notation meta-model.

- In the directory/src/model, right click on the mynotation.genmodel file and choose "reload" to reflect the changes.

-

- mynotation.genmodel. Right click on the root in the editor and choose "Generate Model Code" and the "Generate Edit Code" menu items.

#### Reflect the changes of the new style value on the diagram editor

- createStyles" function of View Factory subclass that creates the top level view you wish to store the style on.

- refreshVisuals" and "handlePropertyChangeEvent" functions in the EditPart class associated with the notation view and create new function "refreshValue" called within the "refreshVisuals" override which will perform the update of the figures based on the style change.

Code Snippet:
protected void handlePropertyChangeEvent( PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(MetaModelUtil.getID(MyNotationPackage.eINSTANCE.getMyStyle_Value())))
refreshValue();
else
super.handlePropertyChangeEvent(evt);
}
/**
* Apart from the usual visual update, it also
* updates any necessary values in the figure world
*/
protected void refreshVisuals() {
super.refreshVisuals();
refreshValue();
}
/**
* Refreshes the style value property
*/
protected void refreshValue(){
MyStyle style = (MyStyle)getView().getStyle(mynotationPackage.eINSTANCE.getMyStyle());
if(style != null)
getMyFigure().setValue(style.getValue());
}

## How to override an existing shapes look?

[back to top]

1. First you'll need to determine the AbstractViewFactory subclass that represents the shape that you are creating. This hierarchy is responsible for the construction and initialization of the notation that persists the shape. To determine this, find the ViewProvider that provides the notation for the View and the class should be in a map with the semantic hint as a key. Alternatively you could set a breakpoint in the AbstractViewFactory#createStyles method and see what type of View factory "this" is after a palette creation or drag/drop operation. This will be the View factory class that you will need to inherit from in your provider.

2. If you don't already have a custom view provider for your shape the following steps are necessary. If you do, then proceed to step (3).
3. 1. Create a new subclass of AbstractViewProvider to provide for your shape. This is necessary because you wish to customize the initialization of the shape for your semantic domain.
   2. Add xml code in your plug-in.xml to allow your new provider to provide for the semantic element. To provide for a semantic type some condition about the element the view is for, there is a "method" tag that allows qualification of the provider (see below). The xml descriptor is a first line of defense to avoid loading your plug-in.

Ex 1. Using the semantic element as the element type to provide against.

<extension

point="org.eclipse.gmf.runtime.diagram.ui.viewProviders">

<viewProvider

class="<YourFullyQualfiedClass">

<Priority

name="Highest">

</Priority>

<object

class="<YourFullyQualfiedSemanticClassToProvideFor>"

id="Nodes">

</object>

<method name=?getSomeMethodCallValue(?)? notValue=?null?>

<context> viewClass="org.eclipse.gmf.runtime.notation.Node"

semanticHints=""

elements="Nodes">

</context>

</viewProvider>

</extension>

Ex 2. Alternative is to check against the proxy interface and retrieve the type ID. This is useful if you wish to provide your view against unresolved elements. When an element is a proxy you wouldn't be able to make a conditional check against some semantic property because it wouldn't be accessible in the case of an unresolved reference. If the element were unresolved the other overridden provider would kick-in instead.

<extension

point="org.eclipse.gmf.runtime.diagram.ui.viewProviders">

<viewProvider

class="<YourFullyQualfiedClass">

<Priority

name="Highest">

</Priority>

<object class="org.eclipse.gmf.runtime.emf.core.util.IProxyEObject(org.eclipse.gmf.runtime.emf.core)"id="YourSemanticType">

<method name="getProxyClassID()"value="<YourSemanticType>">

</method>

</object>

<context> viewClass="org.eclipse.gmf.runtime.notation.Node"

semanticHints=""

elements="Nodes">

</context>

</viewProvider>

</extension>

3. In your new ViewProvider class, override the method getNodeViewClass to provide based on semantic element type. I'm assuming there's something specific about your semantic model that would allow you to do this in a mutually exclusive manner. Consequently you should make the same conditional check against the semantic property that was in the xml to avoid the other provider from kicking in.

i.e.

protected Class getNodeViewClass(IAdaptable semanticAdapter,

View containerView, String semanticHint) {

Element el = getSemanticElement(semanticAdapter);

If (el != null) {

...

If (? /* check condition)

Return <YourNewViewClass.class>

}

return null;

}

4. Finally, you need to create a new View notation class of the class you discovered in (1.). Override AbstractNodeViewFactory#decorateView in order to change the default settings on the shape. For instance if you want to change the default visibility of certain compartments, you would do the following

protected void decorateView(View containerView, View view,

IAdaptable semanticElement, String semanticHint, int index,

boolean persisted) {

super.decorateView(containerView, semanticAdapter, semanticHint, index, persisted);

View subView =

ViewUtil.getChildBySemanticHint(containerView, <MySemanticHintString>);

if (subView!= null) {

subView.setVisible(false);

}

}

Alternatively you could override the method initializeFromPreferences to retrieve values from the users preference store and then initialize settings accordingly. However, this will get called before population of the contained views, so it can only be used to initialize top level view settings. i.e. fill / outline color.

## How to change shape appearances based on a global preference change

[back to top]

Often in a domain application it is useful to control shape appearances at a global level. This alleviates user management of the individual shapes appearance and avoids persistence issues.

The way to accomplish this is to have a global workspace preference that your EditPart listens to and responds accordingly.

1. First you need to add a listener on your EditPart controller (please refer to Eclipse on-line help for adding an application specific preference store). To do this add a nested class in your EditPart that implements the IPropertyChangeListener interface for listening to the Preference store.

/**

* Listener for the PreferenceStore.

* Listen and respond for changes to the

* preference store value.

*

*/

protected class PreferencePropertyChangeListener

implements IPropertyChangeListener {

public void propertyChange(PropertyChangeEvent event) {

// if the property is not the event we're interested in then

// do nothing, return

if (event

.getProperty()

.equals(<MyPreferenceIdentifier>)) {

/* call appropriate refresh method */

refreshGlobalPreferenceAttribute();

getFigure().repaint();

}

}

}

2. Next you need to add this new class as a listener to the property store.

/**

* Initializes the preferenceStore property change

* listener.

*/

private void initPreferenceStoreListener() {

preferenceListener = new PreferencePropertyChangeListener();

IPreferenceStore preferenceStore = (IPreferenceStore) getDiagramPreferencesHint().getPreferenceStore();

preferenceStore.addPropertyChangeListener(preferenceListener);

}

protected void addNotationalListeners() {

super.addNotationalListeners();

initPreferenceStoreListener();

}

3. Then you need to handle the property change event in a method. This requires retrieving the preference global value from the store and then making the appropriate changes to the figure to reflect the global value.

/**

* Refreshes this classifier node figure's gradient fill to reflect

* the preference store value for gradient fill.

*/

protected void refreshGlobalPreferenceAttribute() {

IPreferenceStore preferenceStore = (IPreferenceStore) getDiagramPreferencesHint().getPreferenceStore();

//refresh gradient

boolean myGlobalPreference = true;

myGlobalPreference = preferenceStore.getBoolean( <MyPreferenceIdentifier>);

?

/* do figure synchronization */

getFigure().repaint();

}

## How to override the look of a connection

[back to top]

To accomplish this you need to hook into the controller of the connection which is the EditPart which synchronizes the model and figure worlds. You need to create a new EditPart provider which is registered against the EditPartService that will override the existing EditPart provider to provide a new EditPart for the connection shape you're interested in.

Similar to the View service extensions, the EditPart provider consists of 3 different components.

1. The xml descriptor specification of the extension in the plug-in.xml file.

2. The provider class which is specified by the xml and provides the mapping of the type to your EditPart class

3. The actual EditPart class that will override the existing behavior.

First the xml:

</extension>

<extension

point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders">

<editpartProvider

class="com.<myPath>.MyOverrideProvider">

<Priority

name="Low">

</Priority>

<object

class="org.eclipse.gmf.runtime.notation.Edge"

id="MyConnectionOverride">

<method

name="getElement()">

<value class="<class path to semantic element>"/>

</method>

</object>

<context

views="MyConnectionOverride">

</context>

</editpartProvider>

</extension>

In this descriptor we are specifying the provider class that and a priority level that the provider will be at relative to other providers. In this case, we know that the existing provider for the relationship is at the "Lowest" priority, so we can provide ours at one level higher - i.e. "Low". The object tag lets us specify the view category we're supplying the EditPart for and the criteria under which our provider will be loaded. In this case, we chose a broad criteria and our provider will be loaded when the connection is of the semantic relationship we expect. In the actual java code of our provider class we can restrict the actual criteria to be based on a more strict criteria, such as some aspect or property of the semantic relationship.

Next the provider class is fairly straightforward. It extends from the abstract class AbstractEditPartProvider and overrides the method for retrieving the connection editpart. In the following example, it's providing a custom EditPart when the shape has a particular keyword installed. More commonly, you would switch based on the semantic type which is set to the value you've defined in your IElementType.

public class MyEditPartProvider extends AbstractEditPartProvider {

protected Class getEdgeEditPartClass(View view) {

EObject el = view.getElement();

if (el != null && el instanceof <MySemanticElement class> ) {

if (? <some condition is satisified> )

return MyCustomEditPart.class;

}

}

return null;

}

}

Finally we can look at the EditPart class itself. In this case we are only interested in overriding the look of the connection. Consequently, we have to override the method that creates the figure in the connection edit part. This method is the createConnectionFigure(). Then we simply use the appropriate Draw2d API's to instantiate a different polyline figure with the look we desire:

public class MyCustomEditPart extends OriginalEditPart {

/**

* @param view

*/

public MyCustomEditPart (View view) {

super(view);

// TODO Auto-generated constructor stub

}

protected Connection createConnectionFigure() {

PolylineConnectionEx conn = new PolylineConnectionEx();

conn.setLineStyle(Graphics.LINE_SOLID);

OpenArrowDecoration sourceDecorative = new OpenArrowDecoration();

sourceDecorative.setTemplate(OpenArrowDecoration.TRIANGLE_TIP);

sourceDecorative.setScale(MapMode.DPtoLP(10),MapMode.DPtoLP(5));

sourceDecorative.setLineStyle(Graphics.LINE_SOLID);

conn.setSourceDecoration(sourceDecorative);

return conn;

}

}

Then when you create the relationship which fits the new critera of the xml and provider, then it will render using the new figure you created in the custom EditPart.

## How to open multiple read-only views on a diagram
[back to top]

It is possible to view the same notation data in 2 different viewers. Since GEF / GMF implements the MFC design pattern, it is a simple matter to view the same data in different viewers. The "Model" (Notation) is the same in both viewers and synchronized with different "View" (Figure) data through unique controllers (EditParts) in each viewer.

Creating a read-only viewer of an existing diagram

If you wish to have a read-only view of the diagram, this is probably well suited to be displayed in an Eclipse View instead of a full-fledged editor.

In your implementation of the IViewPart the createPartControl would create the Viewer to display the diagram. The DiagramEditPart is retrieved and explicitly set to disable the EditMode capability. It is a perquisite that a DiagramView object is ?in-hand?. This could be retrieved by invoking an action from the Diagram element in the Model Explorer.

// the assumption of this method is that the DiagramView has been pre-loaded

private GraphicalViewer viewer;

private GraphicalViewer viewer;

public class TraceDiagramGraphicalViewer extends DiagramGraphicalViewer {

// no implementation. This class is extended from

// DiagramGraphicalViewer for type information only.

}

public void createPartControl(Composite comp) {

viewer = new TraceDiagramGraphicalViewer();

viewer.createControl(comp);

viewer.getControl().setBackground(ColorConstants.listBackground);

DiagramEditDomain editDomain = new DiagramEditDomain(null);

editDomain.setCommandStack(new DiagramCommandStack(editDomain));

viewer.setEditDomain(editDomain);

viewer.setRootEditPart(new DiagramRootEditPart());

viewer.setEditPartFactory(EditPartService.getInstance());

viewer.setContents(GeoDiagramEditor.diagView));

viewer.flush();

// now disable editing

Assert.isTrue(viewer.getContents() instanceof DiagramEditPart);

DiagramEditPart diagEP = (DiagramEditPart) viewer.getContents();

diagEP.disableEditMode();

###
Screen snapshot of multiple views of diagram:

## How to change the color of a shape dynamically
[back to top]

In the example #Creating a read-only viewer of an existing diagram it is necessary to create a new GraphicalViewer class (TraceDiagramGraphicalViewer) for the type information to distinguish EditParts hosted in a regular editor vs. a custom Viewer to allow for animation. Changing the colors of individual EditParts can be achieved dynamically by installing an EditPolicy on the EditParts condition on them being owned by the new Viewer class above.
The EditPolicy will then listen to the appropriate condition and change the color of the EditPart's figures accordingly.
First we need to define an EditPolicyProvider that will install this new editpolicy. The Extension code in the plugin.xml would look something like the following:

<extension
id="TraceEditPolicyProvider"
name="TraceEditPolicyProvider"
point="org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders">
<editpolicyProvider
class="<package namespace>.TraceDiagramEditPolicyProvider">
<Priority
name="Low">

</Priority>

<object

class="org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart"

id="TraceEditPart">

<method name="getViewer()">

<value class="="<package namespace>.TraceDiagramGraphicalViewer"/>

</method>

</object>

<context

editparts="TraceEditPart">

</context>

</editpolicyProvider>

</extension>

Finally, the EditPolicy class itself can be defined. In this particular example, we are adding a mouse listener the allows us to trace mouse movement entering and exiting the host EditPart. On entry, we color the figure to red and on exit set it back to the original color.

public class TraceDiagramEditPolicy extends GraphicalEditPolicy {

private class TraceMouseMotionListener extends MouseMotionListener.Stub {

private Color color;

/**

* @see com.ibm.etools.draw2d.MouseMotionListener#mouseEntered(MouseEvent)

*/

public void mouseEntered(MouseEvent me) {

color = getHostFigure().getForegroundColor();

getHostFigure().setForegroundColor(new Color(null, new RGB(255, 0, 0)));

getHostFigure().invalidate();

}

/**

* @see com.ibm.etools.draw2d.MouseMotionListener#mouseExited(MouseEvent)

*/

public void mouseExited(MouseEvent me) {

getHostFigure().setForegroundColor(color);

getHostFigure().invalidate();

}

}

/** mouse motion listener for the owner shape and handles */

private TraceMouseMotionListener myMouseListener = new TraceMouseMotionListener();

/**

*

* @see org.eclipse.gef.EditPolicy#activate()

*/

public void activate() {

super.activate();

getHostFigure().addMouseMotionListener(myMouseListener);

}

/**

*

* @see org.eclipse.gef.EditPolicy#deactivate()

*/

public void deactivate() {

getHostFigure().removeMouseMotionListener(myMouseListener);

super.deactivate();

}

}

## How to make a shape resizable vs. non-resizable
[back to top]

#### How do I make a shape resizable?

Shapes subclassed from ShapeEditPart are resizable by default. The resizable editpolicy is a special case in that it is installed via the LayoutEditPolicy on the diagram in the routine createChildEditPolicy. The GMF diagram layer has it's own LayoutEditPolicy called XYLayoutEditPolicy which calls inside createChildEditPolicy a public method of ShapeEditPart called getPrimaryDragEditPolicy. The default implementation returns a ResizableShapeEditPolicy which supports the drag resize handles.
If the shape doesn't appear to be resizable, make sure that you're not overriding the getPrimaryDragEditPolicy method in your EditPart and / or set a breakpoint there to make sure it's returning the ResizableShapeEditPolicy.

#### How do I make a shape non-resizable?
Override the getPrimaryDragEditPolicy in your EditPart to return the NonResizableEditPolicyEx

Example (in LEDEditPart):

/**

* @see org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeEditPart#getPrimaryDragEditPolicy()

*/

public EditPolicy getPrimaryDragEditPolicy() {

return new NonResizableEditPolicyEx();

}

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