Simple extension creation

An extension is a collection of classes and resources which give OrbisCAD some functionality. From the user point of view, include an extension in the system is as easy as drop a jar file in the extension directory.


Lets see the creation process of an extension which installs a plugin that closes the application. The first step is to write the plugin. A plugin is a class that adds a menu and/or a button on the toolbar and listens to their events. A simple plugin that undoes the last edition operation can be seen here. Once the plugin is created, it is necessary to make the extension which loads it.


Working with plugins

Plugins are objects that performs a single action when the user selects the tool button or menu they have installed. OrbisCAD sources are full of samples since all menus and tool buttons are installed by a plugin. Plugins implements the interface org.estouro.ui.extensionSystem.Plugin, which has five methods: initialize, execute, run, isVisible e isEnable. initialize is invoked by the extension at the startup and all menus and tools must be installed here. execute and run are invoked when the user selects a menu or presses a button installed by the plugin. isEnabled e isVisible are invoked often to activate and hide/show the installed controls.


All this methods take as argument an instance of org.estouro.ui.extensionSystem.PluginContext which gives access to the edition context, (active theme, selected tool, ...), application window, and so on. See the javadoc to get more information.


Tool creation

From the user point of view, tools are a button on the toolbar which is selected to operate with the themes. Tools are defined by a finite state automaton. In each automaton status a snippet of code is executed. Also is executed code to draw the actions of the tool while the user navigates through the automaton.


In wikipedia , a good definition of what automata are can be found. The automaton gives the user which actions can be done in each status. Next example is quite clear. In it, a tool which creates a linestring is build. The user selects points again and again until the final transition is commited.





The first step is to define the tool behaviour with an automaton in a XML file. That file will be processed by the tool generator to obtain a class with the information of the automaton. The tool will be a second class extending the generated one and implementing one method for each status of the automaton. An example:


<automaton initial-status="Standby"

package="org.estouro.tools.generated"

name="Line"

icon="line.png"

tooltip="line_tooltip"

command="line">

<node name="Standby" text="line_standby">

<transition to="Point" code="point"/>

</node>

<node name="Point" text="line_point">

<transition to="Point" code="point"/>

<transition to="Done" code="t" label="line_point_to_done"

on-exit="true"/>

</node>

<node name="Done">

<transition to="Standby" code="init"/>

</node>

<node name="Cancel"/>

<transition to="Cancel" code="esc" label="cancel"/>

</automaton>


There is a tool generation that allows to obtaing the class that implements the automaton in the org.estouro.tools.AG class. It takes the following arguments: the directory where the automaton xml files are, the source folder and the package on the folder where the class will be generated.

java org.estouro.tools.AG ./automata/ ./src/ org.estouro.tools.generated


The name of the automata xml files must end with “.fsa.xml”. Once generated it is necessary to create a subclass of it which implements two methods for each status, one to perform the status code and another one to perform the drawing operation. In adition to these ones another two methods have to be implemented: isEnabled and isVisible which rule the visivility and activation of the tool. The next code shows the tool code though a lot of examples can be found in the source code in the org.estouro.tools package.


public class LineTool extends Line {


private ArrayList<Coordinate> points = new ArrayList<Coordinate>();


public void transitionTo_Standby(PluginContext ctx)

throws FinishedAutomatonException, TransitionException {

points.clear();

}


public void transitionTo_Point(PluginContext ctx)

throws FinishedAutomatonException, TransitionException {

EditionContext ec = ctx.getEditionContext();

points.add(new Coordinate(ec.getValues()[0], ec.getValues()[1]));

}


public void transitionTo_Done(PluginContext ctx)

throws FinishedAutomatonException, TransitionException {

if (points.size() < 2)

throw new TransitionException(“La linea ha de tener al menos dos puntos”);

try {

EditionContext ec = ctx.getEditionContext();

Theme t = ec.getActiveTheme();

DriverMetadata metadata = t.getDriverMetadata();

Value[] row = new Value[metadata.getFieldCount()];

for (int i = 0; i < row.length; i++) {

row[i] = ValueFactory.createNullValue();

}

LineString ls = new GeometryFactory().createLineString(points

.toArray(new Coordinate[0]));

com.vividsolutions.jts.geom.Geometry g = ls;

if (ctx.getEditionContext().getActiveTheme().getGeometryType() == SpatialDataSource.MULTILINE) {

g = new GeometryFactory()

.createMultiLineString(new LineString[] { ls });

}

if (!g.isValid()) {

throw new TransitionException(“No es una línea válida”);

}

row[t.getSpatialFieldIndex()] = FMapUtilities.getGeometry(g);

t.insertFilledRow(row);

} catch (DriverException e) {

throw new TransitionException(e);

}


transition(ctx, "init");

}


public void transitionTo_Cancel(PluginContext ctx)

throws FinishedAutomatonException, TransitionException {


}


public void drawIn_Standby(PluginContext ctx, Graphics g)

throws DrawingException {


}


public void drawIn_Point(PluginContext ctx, Graphics g)

throws DrawingException {

EditionContext ec = ctx.getEditionContext();

Point2D current = ec.getLastRealMousePosition();


ArrayList<Coordinate> tempPoints = (ArrayList<Coordinate>) points

.clone();

tempPoints.add(new Coordinate(current.getX(), current.getY()));

LineString ls = new GeometryFactory().createLineString(tempPoints

.toArray(new Coordinate[0]));

Geometry geom = FMapUtilities.getGeometry(ls);

try {

SpatialDataSource sds = ec.getDrawingDataSource();

sds.beginTrans();

sds.insertFilledRow(new Value[] { geom });

sds.commitTrans();

} catch (DriverException e) {

} catch (FreeingResourcesException e) {

}


if (!ls.isValid()) {

throw new DrawingException(“No es una línea válida”);

}

}


public void drawIn_Done(PluginContext ctx, Graphics g)

throws DrawingException {


}


public void drawIn_Cancel(PluginContext ctx, Graphics g)

throws DrawingException {


}


public boolean isEnabled(PluginContext ctx) {

EnableUtils eu = ctx.getEnableUtils();

return (eu.isGeometryTypeEqualTo(SpatialDataSource.LINE) || eu

.isGeometryTypeEqualTo(SpatialDataSource.MULTILINE))

&& eu.isThemeWritable();

}


public boolean isVisible(PluginContext ctx) {

return true;

}


}