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 editor 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 value which is not null in the following order:

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

 

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

 

Creating a Style Utility Class

 

In this example we want to associate the same style to the graphics algorithms of all EClasses. The 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 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.

Note, that we have two styles: one main style for all graphics algorithms of the EClass, and one child style for the Text of the EClass. 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 a “foreground” color.

 

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(51, 51, 153);

 

    private static final IColorConstant E_CLASS_FOREGROUND =

        new ColorConstant(255, 102, 0);

 

    private static final IColorConstant E_CLASS_BACKGROUND =

        new ColorConstant(255, 204, 153);

 

    public static Style getStyleForEClass(Diagram diagram) {

        final String styleId = "E-CLASS";

 

        Style style = findStyle(diagram, styleId);

 

        if (style == null) { // style not found - create new style

            IGaService gaService = Graphiti.getGaService();

            style = gaService.createStyle(diagram, styleId);

            style.setForeground(gaService.manageColor(diagram,

                E_CLASS_FOREGROUND));

            style.setBackground(gaService.manageColor(diagram,

                E_CLASS_BACKGROUND));

            style.setLineWidth(2);

        }

        return style;

    }

 

    public static Style getStyleForEClassText(Diagram diagram) {

        final String styleId = "E-CLASS-TEXT";

 

        // this is a child style of the e-class-style

        Style parentStyle = getStyleForEClass(diagram);

        Style style = findStyle(parentStyle, styleId);

 

        if (style == null) { // style not found - create new style

            IGaService gaService = Graphiti.getGaService();

            style = gaService.createStyle(diagram, styleId);

            // "overwrites" values from parent style

            style.setForeground(gaService.manageColor(diagram,

                E_CLASS_TEXT_FOREGROUND));

        }

        return style;

    }

 

    // find the style with a given id in the style-container, can return null

    private static Style findStyle(StyleContainer styleContainer, String id) {

        // find and return style

        Collection<Style> styles = styleContainer.getStyles();

        if (styles != null) {

            for (Style style : styles) {

                if (id.equals(style.getId())) {

                    return style;

                }

            }

        }

        return null;

    }

}

 

 

Additionally we have to associate the styles to the graphics algorithms in the add method 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

            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.createRoundedRectangle(invisibleRectangle, 5, 5);

            roundedRectangle.setStyle(StyleUtil

                .getStyleForEClass(getDiagram()));

            gaService.setLocationAndSize(roundedRectangle, 0, 0, width, height);

 

            // create link and wire it

            link(containerShape, addedClass);

        }

 

        // SHAPE WITH LINE

        {

            // create shape for line

            Shape shape = peCreateService.createShape(containerShape, false);

 

            // create and set graphics algorithm

            Polyline polyline =

                gaService.createPolyline(shape, new int[] { 0, 20, width, 20 });

            polyline.setStyle(StyleUtil.getStyleForEClass(getDiagram()));

        }

 

        // SHAPE WITH TEXT

        {

            // create shape for text

            Shape shape = peCreateService.createShape(containerShape, false);

 

            // create and set text graphics algorithm

            Text text = gaService.createDefaultText(shape, addedClass.getName());

            text.setStyle(StyleUtil.getStyleForEClassText(getDiagram()));

            text.setHorizontalAlignment(Orientation.ALIGNMENT_CENTER);

            text.setVerticalAlignment(Orientation.ALIGNMENT_CENTER);

            text.getFont().setBold(true);

            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)

            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);

        }

 

        // ... EXISTING CODING ...

 

        return containerShape;

    }

 

 

 

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

The implementation can be seen here:

 

 

package org.eclipse.graphiti.examples.tutorial.features;

 

public class TutorialChangeColorEClassFeature extends AbstractCustomFeature {

 

    private boolean background;

 

    public TutorialChangeColorEClassFeature(IFeatureProvider fp,

        boolean background) {

        super(fp);

        this.background = background;

    }

 

    @Override

    public String getName() {

        String colorType = background ? "&background" : "&foreground";

        return "Change " + colorType + " color";

    }

 

    @Override

    public String getDescription() {

        String colorType = background ? "background" : "foreground";

        return "Change the " + colorType + " 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;

        if (background) {

            currentColor = style.getBackground();

        } else {

            currentColor = style.getForeground();

        }

        Color newColor = ExampleUtil.editColor(currentColor);

        if (newColor == null) { // user did not choose new color

            return;

        }

 

        // set new color

        if (background) {

            style.setBackground(newColor);

        } else {

            style.setForeground(newColor);

        }

    }

}

 

 

Finally the feature provider has to deliver our newly created custom feature (overwrite 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 TutorialChangeColorEClassFeature(this, true),

            new TutorialChangeColorEClassFeature(this, false) };

    }

 

 

 

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
  3. open the context menu on one of the EClasses; choose “Change background color”
  4. verify that the color of all EClasses is changed accordingly

 


Copyright (c) SAP AG 2005, 2010.