User Tools

Site Tools


devel:plugins:start

This is an old revision of the document!


Plugin development

This exercise aims at developing a basic plugin that enables editing of a new type of graph model in Workcraft. Follow the step-by-step instructions to proceed from the initial setup of the project all the way to the implementation of the model components. If you want to skip some of the steps, then just download the complete plugin source code: exercise01plugin.zip ( B)

Project setup

To begin, create a new project directory called _Exercise01Plugin. Note that the directories starting with an underscore are automatically ignored both by Git and the distribution builder script, therefore this is a good way to add experimental plugins that should not become part of the main Workcraft build.

Next either create or download and add the following build.gradle file below into the project directory to specify the project dependency on the Workcraft framework. For the purpose of our exercise, we will make our Exercise01Plugin only depend on the WorkcraftCore plugin:

build.gradle
dependencies {
    compile project(':WorkcraftCore')
}

Now let us call the package for our Exercise01 plugin: org.workcraft.plugins.basic. This follows Workcraft's standard of any new plugin to be packaged as org.workcraft.plugins.NewPluginName and is typically where all the plugin classes will live. For this step, we create the directory src/org/workcraft/plugins/basic under _Exercise01Plugin.

To complete the set up, go to the top directory of Workcraft and follow either step depending on your development IDE:

For IntelliJ IDEA

  1. Run the $./gradlew idea command to generate the IntelliJ IDEA project with the newly created plugin.
  2. Open the workcraft.ipr file in IDEA to load the project.

For Eclipse

  1. Run the $./gradlew eclipse command to generate the Eclipse project with the newly created plugin.
  2. Once opening Eclipse, ensure that you have selected workcraft as the current Workspace directory and imported the project as necessary. For further information on this, see the Eclipse Integration page.

If everything has went smoothly, you are now set to go.

Module class

As a first step to creating any new Workcraft plugin, a module class needs to be created. This class is essentially the plugin's top-most class, which implements the Module interface that allows Workcraft to detect the plugin as a new (available) model type. For this exercise, we simply just create the BasicModule class as follows:

The plugin topmost class should implement Module interface, as follows:

BasicModule.java
package org.workcraft.plugins.basic;
 
import org.workcraft.Framework;
import org.workcraft.Module;
import org.workcraft.PluginManager;
 
public class BasicModule implements Module {
 
    @Override
    public String getDescription() {
        return "Basic Module";
    }
 
    @Override
    public void init() {
        final Framework framework = Framework.getInstance();
        final PluginManager pm = framework.getPluginManager();
        pm.registerModelDescriptor(BasicDescriptor.class);
    }
}

One may notice that inside the BasicModule class, the PluginManager registers a new class called BasicDescriptor. The purpose of this class is to define the name of the model and how its mathematical and visual representations are given, by making it implement the ModelDescriptor class.

In the case of the BasicDescriptor class, we simply set the display name as Basic Model and make it create a new Basic class that will represent its mathematical representation (which will later be discussed in the Mathematical layer classes section) like below:

BasicDescriptor.java
package org.workcraft.plugins.basic;
 
import org.workcraft.dom.ModelDescriptor;
import org.workcraft.dom.VisualModelDescriptor;
import org.workcraft.dom.math.MathModel;
 
public class BasicDescriptor implements ModelDescriptor {
 
    @Override
    public String getDisplayName() {
        return "Basic Model";
    }
 
    @Override
    public MathModel createMathModel() {
        return new Basic();
    }
 
    @Override
    public VisualModelDescriptor getVisualModelDescriptor() {
        return new VisualBasicDescriptor();
    }
}

However, unlike how the BasicDescriptor handles the mathematical representation directly, it directly calls the VisualBasicDescriptor class instead to handle its visual representation. Hence, it essentially specifies the visual layer that is associated with the model.

The class does this by implementing the VisualModelDescriptor interface, which has a method that takes in the mathematical model as an argument and uses its data to create a visual model of it. In our case, the class checks if the provided math model is a Basic model and produces either a new VisualBasic model (if successful) or an exception (if unsuccessful):

VisualBasicDescriptor.java
package org.workcraft.plugins.basic;
 
import org.workcraft.dom.VisualModelDescriptor;
import org.workcraft.dom.math.MathModel;
import org.workcraft.dom.visual.VisualModel;
 
public class VisualBasicDescriptor implements VisualModelDescriptor {
 
    @Override
    public VisualModel create(MathModel mathModel) {
        if (mathModel instanceof Basic) {
            return new VisualBasic((Basic) mathModel);
        }
        throw new RuntimeException("Unsupported math model type");
    }
}

As a result, we have registered the Basic and VisualBasic classes as the implementations of the mathematical and visual layers of our basic model respectively.

Mathematical layer classes

The mathematical model extends the AbstractMathModel class as follows:

Basic.java
package org.workcraft.plugins.basic;
 
import org.workcraft.dom.Container;
import org.workcraft.dom.Node;
import org.workcraft.dom.math.AbstractMathModel;
import org.workcraft.dom.math.MathConnection;
import org.workcraft.dom.math.MathNode;
import org.workcraft.dom.references.HierarchicalUniqueNameReferenceManager;
import org.workcraft.dom.references.ReferenceManager;
import org.workcraft.serialisation.References;
import org.workcraft.util.Hierarchy;
 
public class Basic extends AbstractMathModel {
 
    public Basic() {
        this(null);
    }
 
    public Basic(Container root) {
        super(root);
    }
 
    public MathConnection connect(Node first, Node second) {
        MathConnection con = new MathConnection((MathNode) first, (MathNode) second);
        Hierarchy.getNearestContainer(first, second).add(con);
        return con;
    }
}

This model has one type of node that is implemented by a trivial Vertex class:

Vertex.java
package org.workcraft.plugins.basic;
 
import org.workcraft.annotations.VisualClass;
import org.workcraft.dom.math.MathNode;
 
@VisualClass(VisualVertex.class)
public class Vertex extends MathNode {
}

Note that Vertex is tagged by VisualClass annotation to specify how it should be visualised.

Visual layer classes

The visual model extends the AbstractVisualModel and also specifies the tools available for editing the model:

VisualBasic.java
package org.workcraft.plugins.basic;
 
import org.workcraft.annotations.DisplayName;
import org.workcraft.annotations.ShortName;
import org.workcraft.dom.Container;
import org.workcraft.dom.Node;
import org.workcraft.dom.math.MathConnection;
import org.workcraft.dom.visual.AbstractVisualModel;
import org.workcraft.dom.visual.VisualComponent;
import org.workcraft.dom.visual.VisualGroup;
import org.workcraft.dom.visual.connections.VisualConnection;
import org.workcraft.exceptions.InvalidConnectionException;
import org.workcraft.exceptions.NodeCreationException;
import org.workcraft.gui.graph.generators.DefaultNodeGenerator;
import org.workcraft.gui.graph.tools.*;
import org.workcraft.util.Hierarchy;
 
import java.util.ArrayList;
import java.util.List;
 
@DisplayName("Basic")
@ShortName("basic")
public class VisualBasic extends AbstractVisualModel {
 
    public VisualBasic(Basic model) {
        this(model, null);
    }
 
    public VisualBasic(Basic model, VisualGroup root) {
        super(model, root);
        setGraphEditorTools();
        if (root == null) {
            try {
                createDefaultFlatStructure();
            } catch (NodeCreationException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    private void setGraphEditorTools() {
        List<GraphEditorTool> tools = new ArrayList<>();
        tools.add(new SelectionTool());
        tools.add(new CommentGeneratorTool());
        tools.add(new ConnectionTool());
        tools.add(new NodeGeneratorTool(new DefaultNodeGenerator(Vertex.class)));
        setGraphEditorTools(tools);
    }
 
    @Override
    public void validateConnection(Node first, Node second) throws InvalidConnectionException {
        if ((first instanceof VisualVertex) && (second instanceof VisualVertex)) return;
 
        throw new InvalidConnectionException("Invalid connection.");
    }
 
    @Override
    public VisualConnection connect(Node first, Node second, MathConnection mConnection)
            throws InvalidConnectionException {
        validateConnection(first, second);
 
        VisualComponent v1 = (VisualComponent) first;
        VisualComponent v2 = (VisualComponent) second;
        Node m1 = v1.getReferencedComponent();
        Node m2 = v2.getReferencedComponent();
 
        if (mConnection == null) {
            mConnection = ((Basic) getMathModel()).connect(m1, m2);
        }
        VisualConnection vConnection = new VisualConnection(mConnection, v1, v2);
        Container container = Hierarchy.getNearestContainer(v1, v2);
        container.add(vConnection);
        return vConnection;
    }
}

Visualisation of vertex node is implemented by VisualVertex class as follows:

VisualVertex.java
package org.workcraft.plugins.basic;
 
import org.workcraft.dom.visual.DrawRequest;
import org.workcraft.dom.visual.VisualComponent;
import org.workcraft.gui.Coloriser;
 
import java.awt.*;
import java.awt.geom.Rectangle2D;
 
public class VisualVertex extends VisualComponent {
 
    public VisualVertex(Vertex vertex) {
        super(vertex);
    }
 
    @Override
    public void draw(DrawRequest r) {
        Graphics2D g = r.getGraphics();
 
        double xy = -size / 2 + strokeWidth / 2;
        double wh = size - strokeWidth;
        Shape shape = new Rectangle2D.Double(xy, xy, wh, wh);
 
        Color background = r.getDecoration().getBackground();
        g.setColor(Coloriser.colorise(getFillColor(), background));
        g.fill(shape);
 
        Color colorisation = r.getDecoration().getColorisation();
        g.setColor(Coloriser.colorise(getForegroundColor(), colorisation));
        g.setStroke(new BasicStroke((float) strokeWidth));
        g.draw(shape);
 
        drawLabelInLocalSpace(r);
        drawNameInLocalSpace(r);
    }
}
Copyright © 2014-2024 workcraft.org

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki