# Using Styles

Sometimes graphical tools give the user the possibility to freely change the graphical attributes to paint each shape (color, line width, …). But more often all shapes of the same kind shall be painted in the same way.

For example in a Ecore diagram it makes sense, that all EClasses look identical. The user might still have the possibility to change the color of a EClass, but then the color of all other EClasses should be changed, too.

The graphics framework supports this by providing "styles". A style is a container for graphical attributes (color, line width, …), which can be associated by a graphics algorithm. A style can have a parent-style, from which a style can "inherit" graphical attributes (similar to cascading style sheets).

Concretely the value of a graphical attribute is determined by finding the first attribute which has no value in the following order:

1. The graphics algorithm itself
2. The parent style of a style assigned to the graphics algorithm, if parent style(s) exists
3. The style, if no parent-style(s) exists

Note: a style cannot "overwrite" an existing attribute value in the graphics algorithm, which the style should be assigned to. This makes it particularly suitable for confusion if the attribute value is already set as a default value for the graphics algorithm. Default values are set from the framework, some of them are set in the metamodel. Let us show this with an example:

IGaService gaService = Graphiti.getGaService();
Polyline polyline = gaService.createPolyline(connection);
polyline.setLineWidth(2);
polyline.setForeground(manageColor(IColorConstant.BLACK));
polyline.setLineStyle(LineStyle.DASH);

The above coding will create a dashed connection with a line width of 2. But, if a style is created (to achieve the same result) and then set the style on the polyline as shown below, it doesn't work:

IGaService gaService = Graphiti.getGaService();
Style dashedStyle = gaService.createStyle(diagram, styleId);
dashedStyle.setLineStyle(LineStyle.DASH);
dashedStyle.setLineWidth(2);
dashedStyle.setForeground(gaService.manageColor(diagram,IColorConstant.BLACK));
Polyline polyline = gaService.createPolyline(connection);
polyLine.setStyle(dashedStyle);

The graphics algorithm above has for the attribute line style "SOLID" as default value as well as line width has "1" as default value. The value "SOLID" as a line styles' default value makes sense, while for most of all Graphiti users this is the right one. You can find the default attribute values of graphics algorithms here. See also the JavaDoc of IGaCreateService.

To avoid the above confusion the "createPlain"-methods for graphics algorithms were introduced; graphics algorithms created with these methods have no default values. All values have to be set explicitly and are documented in the coding. We recommend using them, when working with styles.

It has several advantages if the same style is associated by many graphics algorithms:

- When a graphical attribute value of the style is changed, all graphics algorithms are repainted using this new style
- A tool could provide a predefined set of styles from which the user can choose (similar to the "themes" available in many programs)
- Redundant storage of identical information is avoided

## Creating a Style Utility Class

In this example we want to associate the same style to the graphics algorithms of all EClasses and EReferences. All graphical attributes (color, line-width, …) are then set on the style and no longer on the different graphics algorithm.

Additionally we will provide functionality to change the foreground-color-value of that style, and as a result all EClasses will be painted in this color.

We start by implementing a utility class for the handling of styles.

We store the styles in the diagram with a given ID. The utility class has methods to return a specific style by either finding it in the diagram or by creating a new style and assigning it to the diagram. When creating styles, the method createPlainStyle is used to set explicitly all graphical values here.

Note, that we have four styles: one main style with common values for all other child styles. There are three child styles: a child style for graphics algorithms of the EClass and EReference, a child style for the Text of the EClass, and a child style for the Text decorator of the EReference. The reason is, that we want to have a different color for the text and for the lines, but in both cases the color is stored as "foreground" color. The Text of the EClass has a bold Arial font, while the Text decorator has an italic Arial font.

We have a cascading design of styles here . Complemental we could introduce another style for the common values ​​of text. The opposite of this cascading approach is to put all the styles flat side by side. The present arrengment was choosen because of the intrinsic clarity and the economical serialization into the diagram file. Do not hesitate to have a look on this by opening the diagram file with a text editor and then search for "styles".

You can see the complete implementation of the style utility class here:

package org.eclipse.graphiti.examples.tutorial;
public class StyleUtil {
private static final IColorConstant E_CLASS_TEXT_FOREGROUND =
new ColorConstant(0, 0, 0);
private static final IColorConstant E_CLASS_FOREGROUND =
new ColorConstant(98, 131, 167);
private static final IColorConstant E_CLASS_BACKGROUND =
new ColorConstant(187, 218, 247);
public static Style getStyleForCommonValues(Diagram diagram) {
final String styleId = "COMMON-VALUES";
IGaService gaService = Graphiti.getGaService();
// Is style already persisted?
Style style = gaService.findStyle(diagram, styleId);
if (style == null) { // style not found - create new style
style = gaService.createPlainStyle(diagram, styleId);
setCommonValues(style);
}
return style;
}
public static Style getStyleForEClass(Diagram diagram) {
final String styleId = "E-CLASS";
IGaService gaService = Graphiti.getGaService();
// this is a child style of the common-values-style
Style parentStyle = getStyleForCommonValues(diagram);
Style style = gaService.findStyle(parentStyle, styleId);
if (style == null) { // style not found - create new style
style = gaService.createPlainStyle(parentStyle, styleId);
style.setFilled(true);
style.setForeground(gaService.manageColor(diagram,
E_CLASS_FOREGROUND));
style.setBackground(gaService.manageColor(diagram,
E_CLASS_BACKGROUND));
}
return style;
}
public static Style getStyleForEClassText(Diagram diagram) {
final String styleId = "E-CLASS-TEXT";
IGaService gaService = Graphiti.getGaService();
// this is a child style of the common-values-style
Style parentStyle = getStyleForCommonValues(diagram);
Style style = gaService.findStyle(parentStyle, styleId);
if (style == null) { // style not found - create new style
style = gaService.createPlainStyle(parentStyle, styleId);
setCommonTextValues(diagram, gaService, style);
style.setFont(gaService.manageDefaultFont(diagram, false, true));
}
return style;
}
public static Style getStyleForTextDecorator(Diagram diagram) {
final String styleId = "TEXT-DECORATOR-TEXT";
IGaService gaService = Graphiti.getGaService();
// this is a child style of the common-values-style
Style parentStyle = getStyleForCommonValues(diagram);
Style style = gaService.findStyle(parentStyle, styleId);
if (style == null) { // style not found - create new style
style = gaService.createPlainStyle(parentStyle, styleId);
setCommonTextValues(diagram, gaService, style);
style.setFont(gaService.manageDefaultFont(diagram, true, false));
}
return style;
}
private static void setCommonTextValues(Diagram diagram,
IGaService gaService, Style style) {
style.setFilled(false);
style.setAngle(0);
style.setHorizontalAlignment(Orientation.ALIGNMENT_CENTER);
style.setVerticalAlignment(Orientation.ALIGNMENT_CENTER);
style.setForeground(gaService.manageColor(diagram,
E_CLASS_TEXT_FOREGROUND));
}
private static void setCommonValues(Style style) {
style.setLineStyle(LineStyle.SOLID);
style.setLineVisible(true);
style.setLineWidth(2);
style.setTransparency(0.0);
}
}

Additionally we have to associate the styles to the graphics algorithms in the addmethod of the TutorialAddEClassFeature.

We do this exactly at those places, where previously the graphical attributes were set directly on the graphics algorithms.

You can see the changed add method in the following code snippet:

public PictogramElement add(IAddContext context) {
// ... EXISTING CODING ...
IGaService gaService = Graphiti.getGaService();
RoundedRectangle roundedRectangle; // need to access it later
{
// create invisible outer rectangle expanded by
// the width needed for the anchor
final Rectangle invisibleRectangle =
gaService.createInvisibleRectangle(containerShape);
gaService.setLocationAndSize(invisibleRectangle, context.getX(),
context.getY(), width + INVISIBLE_RECT_RIGHT, height);
// create and set visible rectangle inside invisible rectangle
roundedRectangle =
gaService.createPlainRoundedRectangle(invisibleRectangle, 5, 5);
roundedRectangle.setStyle(StyleUtil.getStyleForEClass(getDiagram()));
gaService.setLocationAndSize(roundedRectangle, 0, 0, width, height);
// if addedClass has no resource we add it to the resource of the diagram
// in a real scenario the business model would have its own resource
if (addedClass.eResource() == null) {
getDiagram().eResource().getContents().add(addedClass);
}
// create link and wire it
link(containerShape, addedClass);
}
// SHAPE WITH LINE
{
// create shape for line
final Shape shape = peCreateService.createShape(containerShape, false);
// create and set graphics algorithm
final Polyline polyline =
gaService.createPlainPolyline(shape, new int[] { 0, 20, width, 20 });
polyline.setStyle(StyleUtil.getStyleForEClass(getDiagram()));
}
// SHAPE WITH TEXT
{
// create shape for text
final Shape shape = peCreateService.createShape(containerShape, false);
// create and set text graphics algorithm
final Text text = gaService.createPlainText(shape, addedClass.getName());
text.setStyle(StyleUtil.getStyleForEClassText(getDiagram()));
gaService.setLocationAndSize(text, 0, 0, width, 20);
// create link and wire it
link(shape, addedClass);
// provide information to support direct-editing directly
// after object creation (must be activated additionally)
final IDirectEditingInfo directEditingInfo = getFeatureProvider().getDirectEditingInfo();
// set container shape for direct editing after object creation
directEditingInfo.setMainPictogramElement(containerShape);
// set shape and graphics algorithm where the editor for
// direct editing shall be opened after object creation
directEditingInfo.setPictogramElement(shape);
directEditingInfo.setGraphicsAlgorithm(text);
}
// add a chopbox anchor to the shape
// ... EXISTING CODING ...
// assign a graphics algorithm for the box relative anchor
Ellipse ellipse = gaService.createPlainEllipse(boxAnchor);
ellipse.setStyle(StyleUtil.getStyleForEClass(getDiagram()));
// ... EXISTING CODING ...
return containerShape;
}

To achieve a homogeneously appearence of our diagram, the styles must also be applied to our EReferences. To do this we have to change the add- and createArrow-method in class TutorialAddEReferenceFeature.

package org.eclipse.graphiti.examples.tutorial.features;
public class TutorialAddEReferenceFeature extends AbstractAddFeature {
// ... EXISTING CODING ...
public PictogramElement add(IAddContext context) {
IAddConnectionContext addConContext =
(IAddConnectionContext) context;
EReference addedEReference =
(EReference) context.getNewObject();
IPeCreateService peCreateService =
Graphiti.getPeCreateService();
// CONNECTION WITH POLYLINE
Connection connection =
peCreateService.createFreeFormConnection(getDiagram());
connection.setStart(addConContext.getSourceAnchor());
connection.setEnd(addConContext.getTargetAnchor());
IGaService gaService = Graphiti.getGaService();
Polyline polyline = gaService.createPlainPolyline(connection);
polyline.setStyle(StyleUtil.getStyleForEClass(getDiagram()));
// create link and wire it
link(connection, addedEReference);
// add dynamic text decorator for the reference name
ConnectionDecorator textDecorator =
peCreateService.createConnectionDecorator(connection,
true, 0.5, true);
Text text = gaService.createPlainText(textDecorator);
text.setStyle(StyleUtil.getStyleForTextDecorator((getDiagram())));
gaService.setLocation(text, 10, 0);
// set reference name in the text decorator
EReference eReference = (EReference) context.getNewObject();
text.setValue(eReference.getName());
// add static graphical decorators (composition and navigable)
ConnectionDecorator cd;
cd = peCreateService.createConnectionDecorator(connection,
false, 1.0, true);
createArrow(cd);
return connection;
}
// ... EXISTING CODING ...
private Polyline createArrow(GraphicsAlgorithmContainer gaContainer) {
Polyline polyline =
Graphiti.getGaCreateService().createPlainPolyline(gaContainer,
new int[] { -15, 10, 0, 0, -15, -10 });
polyline.setStyle(StyleUtil.getStyleForEClass(getDiagram()));
return polyline;
}
}

To test the just implemented styles we have to create a small custom feature, which allows changing the foreground color of the style.

The implementation can be seen here:

package org.eclipse.graphiti.examples.tutorial.features;
public class TutorialChangeColorEClassFeature extends AbstractCustomFeature {
public TutorialChangeColorEClassFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public String getName() {
return "Change &foreground color";
}
@Override
public String getDescription() {
return "Change the foreground color";
}
@Override
public boolean canExecute(ICustomContext context) {
PictogramElement[] pes = context.getPictogramElements();
if (pes == null || pes.length == 0) { // nothing selected
return false;
}
// return true, if all elements are EClasses
// note, that in execute() the selected elements are not even accessed,
// so theoretically it would be possible that canExecute() always
// returns true. But for usability reasons it is better to check
// if the selected elements are EClasses.
for (PictogramElement pe : pes) {
final Object bo = getBusinessObjectForPictogramElement(pe);
if (!(bo instanceof EClass)) {
return false;
}
}
return true;
}
public void execute(ICustomContext context) {
Style style = StyleUtil.getStyleForEClass(getDiagram());
// let the user choose the new color
Color currentColor = style.getForeground();
Color newColor = ExampleUtil.editColor(currentColor);
if (newColor == null) { // user did not choose new color
return;
}
style.setForeground(newColor);
}
}

Finally the feature provider has to deliver our newly created custom feature (overwrite or extend the method getCustomFeatures).

This implementation can be seen here:

@Override
public ICustomFeature[] getCustomFeatures(ICustomContext context) {
return new ICustomFeature[] { new TutorialRenameEClassFeature(this),
new TutorialDrillDownEClassFeature(this),
new TutorialAssociateDiagramEClassFeature(this),
new TutorialCollapseDummyFeature(this),
new TutorialChangeColorEClassFeature(this)};
}

## Test: Change background color of all EClasses

Now start the editor and test this:

1. create or open a new diagram
2. create several new EClasses and EReferences
3. open the context menu on one of the EClasses; choose "Change Foreground color"
4. verify that the color of all EClasses is changed accordingly