This is an old revision of the document!
Table of Contents
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 underscore are 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 Workcraft.
Either create or download and add the following build.gradle
file into the project directory to specify the project dependency on the Workcraft framework:
- build.gradle
dependencies { compile project(':WorkcraftCore') }
Let us call the package where all the plugin classes will live org.workcraft.plugins.basic
. For this create the following directory structure under _Exercise01Plugin
: src/org/workcraft/plugins/basic
.
Now in Workcraft top directory run $./gradlew idea
command to generate InteliJ IDEA project that includes the newly created plugin. Open workcraft.ipr
project in IDEA and you are set to go.
Module class
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); } }
It registers BasicDescriptor
as the descriptor of the model:
- 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(); } }
And VisualBasicDescriptor
class specifies the visual layer associated with the model:
- 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"); } }
Thus we have registered Basic
and VisualBasic
classes as the implementations of mathematical and visual layers of our basic model.
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); } }