Writing an AttributeComponent Plugin

Preliminaries

An attribute component is a component for visualizing a specified attribute.
This document explains how a new attribute component can be realized using the plugin mechanism of Gravisto. For the following example(s) it is necessary to have Graffiti_Core and Graffiti_Editor in your classpath.
For reading this document it is assumed that the reader is familiar with Writing an Attribute Plugin and Writing a New Component for Editing Attribute Values.

Getting Started

Like for every plugin it is assumed that you provide a valid plugin description file first. The next step is creating a new plugin adapter, e.g. AttributeComponentTestPlugin. For an attribute component plugin which is member of the package de.chris.plugins.attributecomponents.test it looks similar to the following:

package de.chris.plugins.attributecomponents.test;

import org.graffiti.plugin.EditorPluginAdapter;

public class AttributeComponentTestPlugin
    extends EditorPluginAdapter
{
    public AttributeComponentTestPlugin()
    {
        this.attributes = new Class[1];
        this.attributes[0] = TestAttribute.class;

        this.valueEditComponents.put(TestAttribute.class,
            TestEditComponent.class);

        this.attributeComponents.put(TestAttribute.class,
            TestAttributeComponent.class);
    }
}
      

Because a single attribute component plugin can contain more than one attribute component, the inherited member map attributeComponents can be filled with an arbitrary number of attribute component classes. For each attribute component, which must extend the abstract class AttributeComponent, the class must be specified which defines the attribute which should be visualized. The above example adds only TestAttributeComponent.class for visualizing the attribute TestAttribute.class which is described in Writing an Attribute Plugin. Here it is assumed that TestAttribute is in the same package as TestAttributeComponent. For editing a TestAttribute the edit component described in Writing a New Component for Editing Attribute Values is added, too. Every attribute component plugin class must extend org.graffiti.plugin.EditorPluginAdapter.

Writing an Attribute Component

The following class TestAttributeComponent is an implementation of an attribute component for TestAttribute. If this attribute is used for a node TestAttributeComponent shows an annotation with the attribute value at the lower right corner of the node. Please note that TestAttributeComponent indirectly extends org.graffiti.plugin.editcomponent.AttributeComponent because it extends for convenience reasons org.graffiti.plugin.attributecomponent.AbstractAttributeComponent. TestAttributeComponent implements the interface GraphicAttributeConstants which defines some attribute names and paths.

package de.chris.plugins.attributecomponents.test;

import java.awt.FlowLayout;

import javax.swing.JLabel;

import org.graffiti.attributes.Attribute;
import org.graffiti.attributes.CollectionAttribute;
import org.graffiti.graph.GraphElement;
import org.graffiti.graph.Node;
import org.graffiti.graphics.CoordinateAttribute;
import org.graffiti.graphics.DimensionAttribute;
import org.graffiti.graphics.GraphicAttributeConstants;
import org.graffiti.plugin.attributecomponent.AbstractAttributeComponent;
import org.graffiti.plugin.view.ShapeNotFoundException;

public class TestAttributeComponent
    extends AbstractAttributeComponent
    implements GraphicAttributeConstants
{   
    protected JLabel label;

    public void attributeChanged(Attribute attr)
        throws ShapeNotFoundException
    {
        // attr is often a CollectionAttribute,
        // e.g. after pressing apply in the inspector.
        GraphElement ge = (GraphElement) this.attr.getAttributable();

        if(ge instanceof Node)
        {
            Node n = (Node) ge;

            if(attr instanceof CollectionAttribute)
            {
                if(attr.getPath().equals(""))
                {
                    changeParameters(((CollectionAttribute) attr)
                                      .getCollection().get(GRAPHICS), n);
                }
                else if(attr.getPath().equals(GRAPHICS))
                {
                    changeParameters(attr, n);
                }
                else
                {
                    recreate();
                }
            }
            else if(attr.getId().equals("annotation"))
            {
                label.setText((String) attr.getValue());
                setSize(getPreferredSize());
            }
            else if(attr.getPath().startsWith(Attribute.SEPARATOR + GRAPHICS +
                    Attribute.SEPARATOR + COORDINATE))
            {
                setLocation(getNewX(n), getNewY(n));
            }
            else
            {
                recreate();
            }
            
            repaint();
        }
    }

    public void recreate()
        throws ShapeNotFoundException
    {
        System.out.println("recreate");

        GraphElement ge = (GraphElement) this.attr.getAttributable();

        if(ge instanceof Node)
        {
            Node n = (Node) ge;

            removeAll();

            FlowLayout fl = new FlowLayout(FlowLayout.CENTER, 0, 0);
            setLayout(fl);
            label = new JLabel((String) this.attr.getValue());
            add(label);
            setSize(getPreferredSize());
            setLocation(getNewX(n), getNewY(n));

            validate();
        }
    }

    private int getNewX(Node n)
    {
        int x = (int) n.getDouble(GRAPHICS + Attribute.SEPARATOR + COORDINATE +
                Attribute.SEPARATOR + X);
        int wdiv2 = (int) (n.getDouble(GRAPHICS + Attribute.SEPARATOR +
                DIMENSION + Attribute.SEPARATOR +
                GraphicAttributeConstants.WIDTH) / 2d);

        return x + wdiv2;
    }

    private int getNewY(Node n)
    {
        int y = (int) n.getDouble(GRAPHICS + Attribute.SEPARATOR + COORDINATE +
                Attribute.SEPARATOR + Y);
        int hdiv2 = (int) (n.getDouble(GRAPHICS + Attribute.SEPARATOR +
                DIMENSION + Attribute.SEPARATOR +
                GraphicAttributeConstants.HEIGHT) / 2d);

        return y + hdiv2;
    }

    private void changeParameters(Object graphicsAttr, Node n)
        throws ShapeNotFoundException
    {
        if((graphicsAttr != null) &&
            (graphicsAttr instanceof CollectionAttribute))
        {
            CollectionAttribute cAttr = (CollectionAttribute) graphicsAttr;
            Object annotationObject = cAttr.getCollection().get("annotation");

            if((annotationObject != null) &&
                (annotationObject instanceof TestAttribute))
            {
                System.out.println("annotation changed");

                TestAttribute testAttr = (TestAttribute) annotationObject;
                label.setText((String) testAttr.getValue());
                setSize(getPreferredSize());
            }

            Object coordinateObject = cAttr.getCollection().get(COORDINATE);
            Object dimensionObject = cAttr.getCollection().get(DIMENSION);

            if(((coordinateObject != null) &&
                (coordinateObject instanceof CoordinateAttribute)) ||
                ((dimensionObject != null) &&
                (dimensionObject instanceof DimensionAttribute)))
            {
                System.out.println("coordinates or dimension changed");

                setLocation(getNewX(n), getNewY(n));
            }
        }
        else
        {
            recreate();
        }
    }
}
      

This example is a new attribute component which displays the string wrapped by TestAttribute attribute. For this it wraps a JLabel. Its first method is the attributeChanged method which is at any time called when an attribute regarding the visualization component has changed. The if clause checks if the GraphElement of attr is a Node because only annotations of nodes should be drawn. The parameter attr is not necessarily the TestAttribute which contains the string to display. It may be also an other attribute like the graphic attribute if the node has been removed or resized, or even the whole collection of attributes if the apply button of the inspector has been pressed. Thus attributeChanged distinguishes different cases for efficiency reasons. For all other cases the else branch of this case differentiation simply recreates the whole TestAttributeComponent. The first branch tests if attr is a CollectionAttribute. If yes then it tests if this is the root collection or if it is the graphics subcollection. In both cases the helper method changeParameters is called. Otherwise, the component is recreated. The second branch test if attr is the annotation attribute itself. If yes then the label is updated with the new text and its size is adapted for being large enough to contain the new text. The next branch checks if attr is the .graphics.coordinate collection. If yes, the location is updated with the help of the two functions getNewX and getNewY which compute the new coordinates. At the end all is redrawn to visualize the changes.
The next method which must be implemented to fulfill the abstract class AttributeComponent is recreate. This method is always called when the component should be created in memory. Again first it is checked if the attributed graph element is a Node, otherwise nothing should be done. After removing all components from the current Container a new layout is set and a new label with the text of the attribute is created. The container is in this case a JPanel since each AttributeComponent is a JPanel. Afterwards the label is added to the panel and its new size and location is set. At the end validate causes the panel to lay out its subcomponents.
The remaining three methods are only helpers. getNewX and getNewY compute the new x and y coordinates according to the attributes of the node. changeParameters realizes changes for the collection attribute graphics.

Using Attribute Components

In order to use the newly written attribute component load this plugin via the plugin manager. Afterwards load the attribute for a node as described in Using Attributes. In the case of the above example the attributes class name to load is de.chris.plugins.attributecomponents.test.TestAttribute. Its path must be graphics.annotation. After inserting a string and pressing the Apply button in the inspector you see the result.