blob: a2c56796e6aca23f7f10e049e640bd16e66a7291 [file] [log] [blame]
<?xml version='1.0' encoding='utf-8' ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>extensions-provide_custom_style</title>
<link type="text/css" rel="stylesheet" href="../resources/bootstrap.css"/>
<link type="text/css" rel="stylesheet" href="../resources/custom.css"/>
</head>
<body>
<h1 id="SiriusProvidecustomstyle">Sirius &#8211; Provide custom style</h1>
<h2 id="Goals">Goals</h2>
<p>Sirius provides tools to design and display diagrams. These diagrams are essentially composed of figures of distinct styles
<br/>and shapes and, even though the tooling initially allows for the usage of a wide array of styles, many diagrams will still require
<br/>customized styles to conform to the company&#8217;s theme.
</p>
<p>Sirius offers three distinct ways of customizing its graphical styles:</p>
<ol>
<li>the
<code>StyleConfiguration</code> extension point.
</li>
<li>the
<code>CustomStyle</code> in addition to the
<code>EditPartProvider</code> GMF extension point.
</li>
<li>the GMF-provided extension points.</li>
</ol>
<h2 id="StyleConfiguration">StyleConfiguration</h2>
<p>StyleConfiguration is a mechanism that enables the customization of a specific style. It aims at providing support for different
<br/>ways of displaying labels, anchors, ...
</p>
<h3 id="TheStyleConfigurationinterface">The StyleConfiguration interface</h3>
<p>This interface defines a set of methods that need be implemented in order for the syle to be customizable:</p>
<ul>
<li>
<code>void adaptNodeLabel(DNode node, WrapLabel nodeLabel)</code>
</li>
<li>
<code>BorderItemLocatorProvider getBorderItemLocatorProvider()</code>
</li>
<li>
<code>IBorderItemLocator getNameBorderItemLocator(DNode node, IFigure mainFigure)</code>
</li>
<li>
<code>int adaptViewNodeSizeWithLabel(DNode viewNode, WrapLabel nodeLabel, int nodeWidth)</code>
</li>
<li>
<code>AnchorProvider getAnchorProvider()</code>
</li>
<li>
<code>Image getLabelIcon(DDiagramElement : element)</code>
</li>
<li>
<code>Dimension fitToText(DNode node, WrapLabel nodeLabel, DefaultSizeNodeFigure defaultSizeNodeFigure)</code>
</li>
</ul>
<h4 id="adaptNodeLabel">adaptNodeLabel</h4>
<p>This method can be used to alter the size and location of a given label according to a given node.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>viewNode</code> : The node representation.
</li>
<li>
<code>nodeLabel</code> : Label of the node.
</li>
</ul>
</li>
</ul>
<h4 id="getBorderItemLocatorProvider">getBorderItemLocatorProvider</h4>
<p>This method returns the instance currently providing border item locators for the node. A border item is a node located on the border of another node.</p>
<h4 id="getNameBorderItemLocator">getNameBorderItemLocator</h4>
<p>This method returns the item locator for the label of the node. This method is meaningless if the position of the label isn&#8217;t
<code>BORDER</code>.
</p>
<ul>
<li>Parameters
<ul>
<li>
<code>node</code> : The node representation.
</li>
<li>
<code>mainFigure</code> : The Draw2D figure representing the node.
</li>
</ul>
</li>
</ul>
<h4 id="adaptViewNodeSizeWithLabel">adaptViewNodeSizeWithLabel</h4>
<p>This method returns the minimum width a node needs to be correctly displayed with its label.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>viewNode</code> : The node representation.
</li>
<li>
<code>nodeLabel</code> : Label of the node.
</li>
<li>
<code>nodeWidth</code> : Current width of the node.
</li>
</ul>
</li>
</ul>
<h4 id="getAnchorProvider">getAnchorProvider</h4>
<p>This method returns the instance providing connections anchors.</p>
<h4 id="getLabelIcon">getLabelIcon</h4>
<p>This method returns the icon that will displayed for this element&#8217;s representations.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>vpElement</code> : The element for which we need an icon.
</li>
</ul>
</li>
</ul>
<h4 id="fitToText">fitToText</h4>
<p>This method returns the optimal dimension of the node.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>node</code> : The node representation.
</li>
<li>
<code>nodeLabel</code> : Label of the node.
</li>
<li>
<code>defaultSizeNodeFigure</code> : The draw2d representation of the node.
</li>
</ul>
</li>
</ul>
<h3 id="ProvidingcustomStyleConfiguration">Providing custom StyleConfiguration</h3>
<p>The
<code>StyleConfiguration</code> mechanism is only available for
<code>ViewNode</code> at the moment. Default configurations are initially installed on nodes.
</p>
<p>
<b>SimpleStyleConfiguration</b>
</p>
<p>This configuration is the simplest implementation.</p>
<p>
<b>SquareStyleConfiguration</b>
</p>
<p>This is the minimal configuration for nodes using the
<code>BundledImage</code> style with a square shape.
</p>
<p>
<b>SimpleSquareStyleConfiguration</b>
</p>
<p>This is the minimal configuration for nodes with a
<code>Square</code> style.
</p>
<h4 id="ProvidersofStyleConfiguration">Providers of StyleConfiguration</h4>
<p>Installing your own configurations can be done through the
<code>org.eclipse.sirius.diagram.styleConfigurationProvider</code> extension
<br/>point. This extension point requires only one attribute : the qualified name of the provider class.
</p>
<pre><code>&lt;extension point="org.eclipse.sirius.diagram.styleConfigurationProvider"&gt;
&lt;styleConfigurationProvider providerClass="com.example.diagseq.style.DiagSeqStyleConfigurationProvider" /&gt;
&lt;/extension&gt;
</code></pre>
<p>Here is a sample of the extension tab of the PDE editor when filling in this extension point :</p>
<p>
<img border="0" src="./images/style/styleConfigurationProviderExt.png"/>
</p>
<p>This example records the
<code>DiagSeqStyleConfigurationProvider</code> as a configuration provider. This class must implement the
<br/>
<code>IStyleConfigrationProvider</code> interface.
</p>
<p>The latter interface defines two methods :</p>
<p>
<strong>
<code>boolean provides(DiagramElementMapping mapping, Style style)</code>
</strong>
</p>
<p>This method returns true if the provider provides a configuration for the given mapping and style.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>mapping</code> : The mapping of the node.
</li>
<li>
<code>style</code> : Style of the node.
</li>
</ul>
</li>
</ul>
<p>
<strong>
<code>StyleConfiguration createStyleConfiguration(DiagramElementMapping mapping, Style style)</code>
</strong>
</p>
<p>This method creates a StyleConfiguration for the specified mapping and style.</p>
<ul>
<li>Parameters
<ul>
<li>
<code>mapping</code> : Mapping of the node.
</li>
<li>
<code>style</code> : Style of the node.
</li>
</ul>
</li>
</ul>
<p>Here is an example of a provider :</p>
<pre><code>public class DiagSeqStyleConfigurationProvider implements IStyleConfigurationProvider {
public StyleConfiguration createStyleConfiguration(DiagramElementMapping mapping, Style style) {
if (mapping instanceof NodeMapping) {
NodeMapping nodeMapping = (NodeMapping) mapping;
if (nodeMapping.getName() != null &amp;&amp; nodeMapping.getName().equals(DiagSeqConstants.INSTANCE_ROLE_MAPPING_NAME)) {
return new InstanceRoleStyleConfiguration();
}
if (nodeMapping.getName() != null &amp;&amp; nodeMapping.getName().equals(DiagSeqConstants.EXECUTION_MAPPING_NAME)) {
return new ExecutionSpecificationStyleConfiguration();
}
}
return null;
}
public boolean provides(DiagramElementMapping mapping, Style style) {
return mapping instanceof NodeMapping &amp;&amp; ((NodeMapping) mapping).getName() != null
&amp;&amp; (((NodeMapping) mapping).getName().equals(DiagSeqConstants.INSTANCE_ROLE_MAPPING_NAME)
|| DiagSeqConstants.EXECUTION_MAPPING_NAME.equals(((NodeMapping) mapping).getName()));
}
}
</code></pre>
<h3 id="ExampleofaStyleConfigurationimplementation">Example of a StyleConfiguration implementation</h3>
<pre><code>public class SimpleStyleConfiguration implements StyleConfiguration {
public void adaptNodeLabel(DNode node, WrapLabel nodeLabel) {
if (nodeLabel.getParent() != null) {
Rectangle constraint = nodeLabel.getParent().getBounds().getCopy();
Insets borderDimension = this.getBorderDimension(node);
constraint.height -= (borderDimension.top + borderDimension.bottom);
constraint.width -= (borderDimension.left + borderDimension.right);
constraint.x += borderDimension.left;
constraint.y += borderDimension.top;
nodeLabel.setBounds(constraint);
nodeLabel.getParent().setConstraint(nodeLabel, constraint);
}
}
public int adaptViewNodeSizeWithLabel(DNode viewNode, WrapLabel nodeLabel, int nodeWidth) {
if (viewNode.getResizeKind() != ResizeKind.NONE_LITERAL) {
}
return nodeWidth;
}
public AnchorProvider getAnchorProvider() {
return null;
}
public BorderItemLocatorProvider getBorderItemLocatorProvider() {
return DefaultBorderItemLocatorProvider.getInstance();
}
public IBorderItemLocator getNameBorderItemLocator(DNode node, IFigure mainFigure) {
BorderItemLocator locator = new AirBorderItemLocator(mainFigure, PositionConstants.NSEW);
locator.setBorderItemOffset(new Dimension(1, 1));
return locator;
}
public Image getLabelIcon(DDiagramElement vpElement) {
EObject target = vpElement;
if (vpElement instanceof DSemanticDecorator) {
target = ((DSemanticDecorator) vpElement).getTarget();
}
if (isShowIcon(vpElement)) {
if (target != null) {
IItemLabelProvider labelProvider = (IItemLabelProvider) SiriusDiagramEditorPlugin.getInstance()
.getItemProvidersAdapterFactory().adapt(target, IItemLabelProvider.class);
if (labelProvider != null) {
ImageDescriptor descriptor = ExtendedImageRegistry.getInstance().getImageDescriptor(labelProvider.getImage(target));
if (descriptor == null) {
descriptor = ImageDescriptor.getMissingImageDescriptor();
}
return SiriusDiagramEditorPlugin.getInstance().getImage(descriptor);
}
}
}
return null;
}
protected boolean isShowIcon(DDiagramElement vpElement) {
if (vpElement instanceof MappingBased) {
DiagramElementMapping vpElementMapping = ((MappingBased) vpElement).getMapping();
if (vpElementMapping instanceof NodeMapping) {
return ((NodeMapping) vpElementMapping).isShowIcon();
}
if (vpElementMapping instanceof EdgeMapping) {
return ((EdgeMapping) vpElementMapping).isShowIcon();
}
if (vpElementMapping instanceof ContainerMapping) {
return true;
}
}
return false;
}
public Dimension fitToText(DNode node, WrapLabel nodeLabel, DefaultSizeNodeFigure defaultSizeNodeFigure) {
if (nodeLabel.getFont() != null) {
String text = node.getName();
int charHeight = FigureUtilities.getStringExtents("ABCDEF", nodeLabel.getFont()).height + 5;
int charWidth = FigureUtilities.getTextWidth("ABCDEFGHIJKLMNOPQRSTUVWXYZ", nodeLabel.getFont()) / 26;
double ratio = charHeight / charWidth;
int nbLines = (int) (Math.sqrt(text.length()) / ratio) + 1;
int nbCols = (int) (Math.sqrt(text.length()) * ratio) + 1;
int longestWord = this.getTheLongestWord(text.split("\\s"));
nbCols = Math.max(nbCols, longestWord);
int hHeight = nbLines * charHeight;
int hWidth = nbCols * charWidth;
Dimension size = nodeLabel.getPreferredSize(hWidth + nodeLabel.getIconBounds().width +
nodeLabel.getIconTextGap(), hHeight).getCopy();
size.width += 20;
size.height += 30;
Insets borderDimension = this.getBorderDimension(node);
size.width += (borderDimension.left + borderDimension.right);
size.height += (borderDimension.top + borderDimension.bottom);
//
// Square ?
if (node.getHeight().intValue() == node.getWidth().intValue()) {
// size.width = Math.max(size.height, size.width);
// size.height = Math.max(size.height, size.width);
}
return size;
}
return defaultSizeNodeFigure.getBounds().getSize().getCopy();
}
private int getTheLongestWord(String[] strings) {
int max = -1;
for (int i = 0; i &lt; strings.length; i++) {
if (strings[i].length() &gt; max) {
max = strings[i].length();
}
}
return max;
}
/**
* Return the dimension of the border.
*
* @param nodeth
* view node.
* @return the dimension of the border.
*/
public Insets getBorderDimension(DNode node) {
return new Insets(0, 0, 0, 0);
}
}
</code></pre>
<p>The easiest way to implement your own configuration is to make it a sub class of
<code>SimpleStyleConfiguration</code>.
</p>
<h2 id="GMFextensionpointsCustomStyleandEditPartProvider">GMF extension points, CustomStyle and EditPartProvider</h2>
<p>
<code>CustomStyle</code> is a style that can be applied to a node. Its only property is
<code>id</code> that is a string value. By default,
<br/>applying a CustomStyle will have a Green Square be displayed. A custom
<code>EditPart</code> describing the appearance of the node
<br/>must be implemented.
</p>
<p>Here is how to apply a
<code>CustomStyle</code>:
</p>
<p>
<img border="0" src="./images/style/customStyle.png"/>
</p>
<p>Next step is to create and record an
<code>EditPart</code> to define the way this style will be managed.
</p>
<p>An
<code>EditPart</code> is a GEF class. It references both a model element and the shape that represents it. GMF adds a layer to GEF
<br/>and exposes its own
<strong>EditPart</strong> API. The base type of this API is
<code>IGraphicalEditPart</code> (in package
<br/>
<code>org.eclipse.gmf.runtime.diagram.ui.editparts</code>). Here are the main methods this interface defines :
</p>
<ul>
<li>
<code>EObject resolveSemanticElement()</code>: This method returns the semantic element of the edit part. The type of this semantic element is a type contained by the Sirius package (see
<code>SiriusPackage</code>). For instance, if we were to write an
<code>EditPart</code> for our
<code>CustomStyle</code>, the semantic element would be the said
<code>CustomStyle</code>. It is possible to get the ViewNode with it and, then, the target element.
</li>
<li>
<code>View getNotationView()</code>: This method returns the GMF View of the edit part. This is the view that will be saved into the GMF Diagram.
</li>
</ul>
<p>All Custom Style Edit Part must implement the
<code>IStyleEditPart</code> interface. Semanticly, a Style Edit Part should not be selectable : it is selected when the Shape or Connector that contains it is selected. Therefore,
<strong>all Custom Style Edit Part should override the
<code>isSelectable()</code> method and return false
</strong>. If you can, inherit from the abstrac class
<code>AbstractNotSelectableShapeNodeEditPart</code> that already overrides correctly this method.
</p>
<p>Here is a sample of a Custom Style Edit Part :</p>
<pre><code>public class InstanceRoleStyleEditPart extends AbstractNotSelectableShapeNodeEditPart implements IStyleEditPart {
/**
* the content pane.
*/
protected IFigure contentPane;
/**
* the primary shape.
*/
protected ImageFigure primaryShape;
/**
* Create a new {@link ChangingImageEditPart}.
*
* @param view
* the view.
*/
public InstanceRoleStyleEditPart(View view) {
super(view);
}
public DragTracker getDragTracker(Request request) {
return getParent().getDragTracker(request);
}
protected NodeFigure createNodeFigure() {
NodeFigure figure = createNodePlate();
figure.setLayoutManager(new XYLayout());
IFigure shape = createNodeShape();
figure.add(shape);
contentPane = setupContentPane(shape);
return figure;
}
private NodeFigure createNodePlate() {
DefaultSizeNodeFigure result = new AirStyleDefaultSizeNodeFigure(getMapMode().DPtoLP(40), getMapMode().DPtoLP(40));
return result;
}
/**
* Create the instance role figure.
*
* @return the created figure.
*/
protected ImageFigure createNodeShape() {
if (primaryShape == null) {
primaryShape = new ImageFigure();
}
return primaryShape;
}
/**
* Return the instance role figure.
*
* @return the instance role figure.
*/
public ImageFigure getPrimaryShape() {
return primaryShape;
}
/**
* Default implementation treats passed figure as content pane. Respects
* layout one may have set for generated figure.
*
* @param nodeShape
* instance of generated figure class
* @return the figure
*/
protected IFigure setupContentPane(IFigure nodeShape) {
return nodeShape; // use nodeShape itself as contentPane
}
public IFigure getContentPane() {
if (contentPane != null) {
return contentPane;
}
return super.getContentPane();
}
protected void refreshVisuals() {
CustomStyle customStyle = (CustomStyle) this.resolveSemanticElement();
if (customStyle.eContainer() instanceof DNode) {
this.getPrimaryShape().setImage(SiriusPlugin.getDefault().getBundledImage(((DNode) customStyle.eContainer()).getName()));
}
}
protected void createDefaultEditPolicies() {
// empty.
}
}
</code></pre>
<p>We now have to notify Sirius that we are providing a new
<code>EditPart</code> for nodes having a CustomStyle of that id.
<br/>The GMF extension mechanism can be used to achieve this.
</p>
<p>plugin.xml:</p>
<p>
<img border="0" src="./images/style/editPartProvider_plugin1.png"/>
</p>
<pre><code>&lt;extension point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders"&gt;
&lt;editpartProvider class="com.example.diagseq.provider.DiagSeqEditPartProvider"&gt;
&lt;Priority name="High"/&gt;
&lt;/editpartProvider&gt;
&lt;/extension&gt;
</code></pre>
<p>The provider class (
<code>com.example.diagseq.provider.DiagSeqEditPartProvider</code>) extends the GMF class
<br/>
<code>org.eclipse.gmf.runtime.diagram.ui.services.editpart.AbstractEditPartProvider</code>. Our custom Edit part will be
<br/>provided by overriding
<code>getNodeEditPartClass</code>. Here is how this class looks like :
</p>
<pre><code>public class DiagSeqEditPartProvider extends AbstractEditPartProvider {
@Override
protected Class getNodeEditPartClass(View view) {
if (view.getElement() instanceof CustomStyle) {
CustomStyle customStyle = (CustomStyle) view.getElement();
if (customStyle.getId().equals(DiagSeqConstants.INSTANCE_ROLE_STYLE_ID)) {
return InstanceRoleStyleEditPart.class;
}
}
return super.getNodeEditPartClass(view);
}
}
</code></pre>
<p>Where
<code>DiagSeqConstants.INSTANCE_ROLE_STYLE_ID</code> is the id of our custom style.
</p>
<p>In the modeler, the result looks like this:</p>
<p>
<img border="0" src="./images/style/instanceRole.png"/>
</p>
<h2 id="GMFextensionpoints">GMF extension points</h2>
<p>GMF exposes all of the necessary API to extend the default behavior of a modeler.</p>
<ul>
<li>Edit Part provider.</li>
<li>Edit Policy Provider.</li>
<li>Layout Provider</li>
</ul>
<h3 id="EditPartProvider">Edit Part Provider</h3>
<p>We saw earlier how to provide custom edit parts. Here is how the result looks like in Sirius.</p>
<p>Snapshot of the Sirius meta-model:</p>
<p>
<img border="0" src="./images/style/viewpoint.jpg"/>
</p>
<p>The types DNode, DNodeContainer, DNodeList, DNodeListElement, etc. have their own edit parts:</p>
<p>
<img border="0" src="./images/style/editParts.jpg"/>
</p>
<h3 id="EditPolicyProvider">Edit Policy Provider</h3>
<p>Edit Policies are objects responsible for the handling of user actions. There are Edit Policies for different behaviors:</p>
<ul>
<li>Creation</li>
<li>Deletion</li>
<li>Move elements</li>
<li>Selection</li>
<li>etc.</li>
</ul>
<p>As with edit parts, it is necessary to write an Edit Policy Provider as well as the Edit Policy that is to be provided. The
<br/>Edit Policy Provider must implement the
<code>IEditPolicyProvider</code> interface:
</p>
<ul>
<li>
<code>public void addProviderChangeListener(IProviderChangeListener listener)</code>: Registers a listener on the provider.
</li>
<li>
<code>public boolean provides(IOperation operation)</code>: Returns true if this instance provides edit policies for the specified operation.
</li>
<li>
<code>public void removeProviderChangeListener(IProviderChangeListener listener)</code>: Removes a listener from this provider.
</li>
<li>
<code>public void createEditPolicies(EditPart editPart)</code>: Adds edit policies on the specified edit part.
</li>
</ul>
<p>Here is an example of an Edit Policy Provider</p>
<pre><code>/**
* Provides Edit Policy for Note Attachment.
*/
public class AirNoteAttachmentEditPolicyProvider implements IEditPolicyProvider {
/** the property change support. */
private List listeners;
/**
* Create a new {@link AirNoteAttachmentEditPolicyProvider}.
*/
public AirNoteAttachmentEditPolicyProvider() {
this.listeners = new ArrayList(2);
}
public void createEditPolicies(EditPart editPart) {
if (editPart instanceof NoteAttachmentEditPart) {
editPart.installEditPolicy(EditPolicy.CONNECTION_ROLE, new AirNoteAttachmentEditPolicy());
}
}
public void addProviderChangeListener(IProviderChangeListener listener) {
this.listeners.add(listener);
}
public boolean provides(IOperation operation) {
if (operation instanceof CreateEditPoliciesOperation) {
CreateEditPoliciesOperation castedOperation = (CreateEditPoliciesOperation) operation;
EditPart editPart = castedOperation.getEditPart();
Object model = editPart.getModel();
if (model instanceof View) {
View view = (View) model;
if (view.getDiagram() != null &amp;&amp; view.getDiagram().getElement() != null
&amp;&amp; view.getDiagram().getElement().eClass().getEPackage().getNsURI().equals(ViewpointPackage.eINSTANCE.getNsURI())) {
if ("NoteAttachment".equals(view.getType())) {
return true;
}
}
}
}
return false;
}
public void removeProviderChangeListener(IProviderChangeListener listener) {
this.listeners.remove(listener);
}
/**
* Fire a {@link ProviderChangeEvent}.
*/
protected void fireProviderChanged() {
ProviderChangeEvent event = new ProviderChangeEvent(this);
Iterator iterListener = this.listeners.iterator();
while (iterListener.hasNext()) {
IProviderChangeListener listener = (IProviderChangeListener) iterListener.next();
listener.providerChanged(event);
}
}
}
</code></pre>
<p>More flexibility can be achieved by writing an abstract provider :</p>
<pre><code>public abstract class AbstractEditPolicyProvider implements IEditPolicyProvider {
/** All listeners. */
private List listeners = new ArrayList(1);
public void addProviderChangeListener(IProviderChangeListener listener) {
this.listeners.add(listener);
}
public void removeProviderChangeListener(IProviderChangeListener listener) {
this.listeners.remove(listener);
}
protected void fireProviderChanged() {
ProviderChangeEvent event = new ProviderChangeEvent(this);
Iterator iterListener = this.listeners.iterator();
while (iterListener.hasNext()) {
IProviderChangeListener listener = (IProviderChangeListener) iterListener.next();
listener.providerChanged(event);
}
}
}
</code></pre>
<p>There only remains to override the methods
<code>provides()</code> and
<code>createEditPolicies()</code> in this provider&#8217;s implementations.
</p>
</body>
</html>