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.
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.
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>
Automaton
initial-status: Name of the intial status
package: Package where the class will be generated
name: Name of the generated class
icon: Name of the icon for the button on the toolbar
cursor: Name of the icon for the mouse when the tool is selected
tooltip: Tooltip of the tool button
command: Name of the command in the console that will select the tool
node: Automaton status
name: Status name
text: Text to show in the console when the automaton is at this status
transition: outgoing transition
to: Name of the status where the transition moves
code: Code of the transition. The code is the input text typed by the user in the console. It can also be the special transitions: “point”, “number” and “esc”, which are triggered when the user clicks on the map (or inputs a point in the console), when inputs a number in the console and when cancels the operation respectively.
label: Text shown on the popup menu in order to let the user trigger a transition through that menu
on-exit: If the transition will be triggered when another tool is selected while this one is selected. Only a transition can have this attribute setted to true
transition: outgoing transition from all status to the specified status. It can have all the attributes a normal transition can have. It's usually used to give the cancel option
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;
}
}