Creating Customized Widget

1. Introduction

BOY is able to load customized widgets using Eclipse Plugin mechanism, so user can implements their own widgets by creating a new Eclipse plugin without touching BOY code. If you do think your widget can benefit other people, you can send your widget code to chenx1@ornl.gov so we can integrate it into BOY as a native BOY widget. This chapter will give you the concept of BOY widget and a step-by-step example. Before start, you should have some basic knowledge in Java and Eclipse plugin. The BOY widget figure is drawn by Draw2d, so you may also need to learn the basics of Draw2d.

The code of this example is availabe in BOY Examples->Miscellaneous->CustomizedWidget.

2. Concepts

All BOY widgets should rely on the MVC (Model-View-Controller) pattern to separate responsibilities among their single components.

3. A Step-by-Step Example

This example will create a simple bar graph widget which can display a numeric PV value.

3.1 Pre-requirements

The widget plugin will need to have BOY plugin (org.csstudio.opibuilder) as dependecy, so you need to have BOY plugin and its dependent CSS plugins installed in your Eclipse environment. You can simply copy BOY plugin and its dependencies from your CSS plugin folder to your Eclipse dropin folder. If you are a CSS developer, you just need to import these plugins into your workspace from your Mercurial repository.

3.2 Creating a new plugin

Create a new plugin project with all the default settings. Add plugin org.csstudio.opibuilder as dependency.

3.3 Creating the widget model

The class org.csstudio.opibuilder.model.AbstractWidgetModel is the base class of all BOY widget models. All the common widget properties are defined in AbstractWidgetModel:

AbstractWidgetModel defines two abstract methods you have to implement within your custom model.The getTypeID() method should return a unique ID which is used by the BOY framework to identify your model class. The configureProperties() method is called by the BOY framework during the instantiation of your model class. You can use it to equip your own widget model with custom properties. To do so, you need to call addProperty(..) to add a new property to the model’s property set. The signature of the addProperty(..) method requests a property ID that must be unique within the widget model and an instance of org.csstudio.opibuilder.properties.AbstractWidgetProperty that defines the property type. The supported property types are described at here. You can also create your own property type by extending org.csstudio.opibuilder.properties.AbstractWidgetProperty.

Note: There is a convention between property id and property name, which means the property id should be lower case of property name and the space in property name should be replaced with underscore. For example, the property id of PV Name is pv_name. With this convention, user can easily guess the property id in script.

The class org.csstudio.opibuilder.model.AbstractPVWidgetModel is the base class of all widgets that have the capability of connecting PVs. It provides additional common widget properties:

All Container Widgets must subclass org.csstudio.opibuilder.model.AbstractContainerModel.

Our simple bar graph widget will need to connect PV, so it needs to subclass AbstractPVWidgetModel. We will add two double properties to this widget: Min and Max.

public class SimpleBarGraphModel extends AbstractPVWidgetModel{

	/** Lower limit of the widget. */
	public static final String PROP_MIN = "max"; //$NON-NLS-1$		
	
	/** Higher limit of the widget. */
	public static final String PROP_MAX = "min"; //$NON-NLS-1$		
	
	public final String ID = "org.csstudio.opibuilder.widgetExample.SimpleBarGraph"; //$NON-NLS-1$
	
	/**
	 * Initialize the properties when the widget is first created.
	 */
	public SimpleBarGraphModel() {
		setForegroundColor(new RGB(255, 0, 0));
		setBackgroundColor(new RGB(0,0,255));
		setSize(50, 100);
	}
	
	@Override
	protected void configureProperties() {
		addProperty(new DoubleProperty(PROP_MIN, "Min", WidgetPropertyCategory.Behavior, 0));
		addProperty(new DoubleProperty(PROP_MAX, "Max", WidgetPropertyCategory.Behavior, 100));
	}

	@Override
	public String getTypeID() {
		return ID;
	}
	
	/**
	 * @return the lower limit
	 */
	public double getMin(){
		return getCastedPropertyValue(PROP_MIN);
	}
	
	/**
	 * @return the higher limit
	 */
	public double getMax(){
		return getCastedPropertyValue(PROP_MAX);
	}
}

3.4 Creating the view

The view is responsible for the graphical representation of the widget. All widget view must implement the interface of org.eclipse.draw2d.IFigure. The class org.eclipse.draw2d.Figure has implemented most of the functions, most of BOY widgets figure extended this class.In most cases, the widget figure should be drawn in the function of paintClientArea(). The client area is the area that is inside the border. So paint in this area will not affect the border area. BOY will automatically draw borders for all widgets based on the border setting, so you do not need to consider the border in most cases.

public class SimpleBarGraphFigure extends Figure {	
	
	private double min =0;
	private double max = 100;
	private double value = 50;
	
	@Override
	protected void paintClientArea(Graphics graphics) {
		super.paintClientArea(graphics);
		//fill background rectangle
		graphics.setBackgroundColor(getBackgroundColor());
		graphics.fillRectangle(getClientArea());
		
		//fill foreground rectangle which show the value's position
		graphics.setBackgroundColor(getForegroundColor());
		//coerce drawing value in range
		double coercedValue = value;
		if(value < min)
			coercedValue = min;
		else if (value > max)
			coercedValue = max;
		int valueLength = (int) ((coercedValue-min)*getClientArea().height/(max-min));
		graphics.fillRectangle(getClientArea().x, 
				getClientArea().y + getClientArea().height -valueLength, 
				getClientArea().width, valueLength);		
	}
	
	public void setMin(double min) {
		this.min = min;
		repaint();
	}
	
	public void setMax(double max) {
		this.max = max;
		repaint();
	}
	
	public void setValue(double value) {
		this.value = value;
		repaint();
	}
	
	public double getValue() {
		return value;
	}	
}

3.5 Creating the controller (editpart)

BOY widget controller basically are special GEF edit parts. You don't need in-depth knowledge about GEF for most regular widgets. BOY provides a comfortable framework for that. If you want to have special behaviors on your widgets, for example, changing the behavior when you are clicking or draging the widget, you may need to have further knowledge about GEF.

All BOY widget controllers must subclass org.csstudio.opibuilder.editparts.AbstractWidgetEditPart. For widget that needs to connect to PV, its controller need to subclass org.csstudio.opibuilder.editparts.AbstractPVWidgetEditPart.

public class SimpleBarGraphEditpart extends AbstractPVWidgetEditPart {

	/**
	 * Create and initialize figure.
	 */
	@Override
	protected IFigure doCreateFigure() {
		SimpleBarGraphFigure figure = new SimpleBarGraphFigure();
		figure.setMin(getWidgetModel().getMin());
		figure.setMax(getWidgetModel().getMax());
		return figure;
	}
	
	/**Get the widget model.
	 * It is recommended that all widget controller should override this method.
	 *@return the widget model.
	 */
	@Override
	public SimpleBarGraphModel getWidgetModel() {
		return (SimpleBarGraphModel) super.getWidgetModel();
	}

	@Override
	protected void registerPropertyChangeHandlers() {
		// The handler when PV value changed.
		IWidgetPropertyChangeHandler valueHandler = new IWidgetPropertyChangeHandler() {
			public boolean handleChange(final Object oldValue,
					final Object newValue,
					final IFigure figure) {
				if(newValue == null)
					return false;
				 ((SimpleBarGraphFigure) figure).setValue(ValueUtil.getDouble((IValue)newValue));
				return false;
			}
		};
		setPropertyChangeHandler(AbstractPVWidgetModel.PROP_PVVALUE, valueHandler);
		
		//The handler when max property value changed.
		IWidgetPropertyChangeHandler maxHandler = new IWidgetPropertyChangeHandler() {
			
			public boolean handleChange(Object oldValue, Object newValue, IFigure figure) {
				((SimpleBarGraphFigure) figure).setMax((Double)newValue);
				return false;
			}
		};
		setPropertyChangeHandler(SimpleBarGraphModel.PROP_MAX, maxHandler);
		
		//The handler when min property value changed.
		IWidgetPropertyChangeHandler minHandler = new IWidgetPropertyChangeHandler() {
			
			public boolean handleChange(Object oldValue, Object newValue, IFigure figure) {
				((SimpleBarGraphFigure) figure).setMin((Double)newValue);
				return false;
			}
		};
		setPropertyChangeHandler(SimpleBarGraphModel.PROP_MIN, minHandler);
		
	}
	
	@Override
	public Object getValue() {
		return ((SimpleBarGraphFigure)getFigure()).getValue();
	}

	@Override
	public void setValue(Object value) {
		if(value instanceof Double)
			((SimpleBarGraphFigure)getFigure()).setValue((Double)value);
	}
}

The doCreateFigure() method creates figure by instantiating a SimpleBarGraphFigure. It is also reponsible for initializing the figure with the property value in model. The registerPropertyChangeHandlers() method is responsible for the behavior of the widget when its property value changed. A property can have more than one handler. All handlers must implement the interface of IWidgetPropertyChangeHandler. The handleChange()method will handle the property value change in which oldValue is the old property value, newValue is the new property value, figure is the widget figure.

The editpart of a widget is the widgetController in BOY javascript. You can add additional methods here for javascript use.

3.6 Register the widget with BOY

So far, we have finished the coding part. Now, we need to register this widget with BOY so that BOY can recognize and list this widget in its widget palette.

Open the MANIFEST.MF file in your plugin project. Go to the tab of extensions. Add an extension org.csstudio.opibuilder.widget. Configure the extension properties as shown in the figure.

BOY Widget Extension

3.7 Hook widget help with BOY online help

You can hook up the widget's help page with BOY online help from toc anchor ../org.csstudio.opibuilder/toc.xml#thirdPartyWidgets.

3.8 Integrate the widget into CSS

If you are a CSS developer, now your have finished the plugin project. You can integrate it into CSS by including this plugin into your CSS build.

Here we will discuss how to add this widget to an existing CSS.

Open the MANIFEST.MF file and go to the overview tab. Click Export Wizard. Change the Destination Directory to the any directory you want. Leave other settings as default and click Finish. When exporting finished, go to the directory you set and copy the jar file in plugin folder to the CSS dropins folder. Start CSS, you will see the Simple Bar Graph widget in you CSS BOY palette! You can test it by setting PV Name to sim://noise, setting max to 5, min to -5. You will see it already has all the BOY PV widget functions such as PV connection, Border alarm sensitive and so on.

Simple Bar Graph widget

*This project zip file can be found at BOY Examples->Miscellaneous->CustomizedWidgetExample.zip. You can import it into your Eclipse workspace by selecting File->Import...->Existing Project into workspace.

3.9 Resources

There are lots of exist resources come with BOY, such as linear scale, round scale, AbstractBoolFigure, AbstractScaledWidgetFigure and so on. They are included in the plugins of org.csstudio.swt.widgets and org.csstudio.swt.xygraph. A draw2d figure can be composed of several other figures, so you can build your figure easily with these figure parts. For example, the Byte Monitor widget is composed of an array of LED figures. It is recommended to check if there are existing figure parts available for your new widget before you start to create your widget figure from scratch.

4. Advanced Topics

4.1 Create Popup Menu for the Widget

The selected widgets in edit mode or the widget on which the context menu was actived in run mode is the editpart of the widget. So it is easy to add a popup menu to this widget by adding an objectContribution.

Popup Menu Extension

The Action Class can simply extend org.csstudio.opibuilder.actions.AbstractWidgetTargetAction, which provided method getSelection() to get the selected widgets.

public class WidgetContextMenuExample extends AbstractWidgetTargetAction{

	public void run(IAction action) {
		MessageDialog.openInformation(null, getSelectedWidget().getWidgetModel().getName(),
				"This dialog is opened from the Context menu of SimpleBar Graph.");
		
	}	
	
	/**
	 * @return the selected widget. In runmode, it is the editpart of the 
	 * widget on which the context menu was activated.
	 */
	protected final SimpleBarGraphEditpart getSelectedWidget() {
		return (SimpleBarGraphEditpart)getSelection().getFirstElement();
	}
}

If you want the action can be Undoable/Redoable in editing mode, you need to create your org.eclipse.gef.commands.Command and execute the command by calling execute(Command command). Please see org.csstudio.opibuilder.widgets.actions.AddTabAfterAction.java for example.

You can also make the popup menu only visible for editing mode or run mode by adding a objectState visibility. To make it only visible in editing mode, set name to executionMode and value to EDIT_MODE. To make it only visible in run mode, set name to executionMode and value to RUN_MODE. Here is the result of this example:

Popup Menu Example

4.2 Create BOY widget based on native SWT widget

To use SWT widgets in BOY, you need to subclass org.csstudio.opibuilder.widgets.AbstractSWTWidgetFigure for the figure. The model and editpart are same as regular BOY widgets. For an SWT widget, there are few drawbacks:

Please see BOY Combo widget or Web Browser widget as examples.

4.3 Customized Graphical Feedback

If you need some special behavior on the widget in editing mode, for example, keep the size of the widget in fixed ratio when user is resizing it, you can create you own customized graphical feedback by extending the extension point org.csstudio.opibuilder.graphicalFeedbackFactory. The class must implement org.csstudio.opibuilder.feedback.IGraphicalFeedbackFactory. Two abstract graphical feedback factory are provided in org.csstudio.opibuilder.widgets. org.csstudio.opibuilder.widgets.feedback.AbstractFixRatioSizeFeedbackFactory will provide the fixed size ratio behavior when user resizes it. org.csstudio.opibuilder.widgets.feedback.AbstractPolyFeedbackFactory will provide the behavior like Polyline or Polygon widgets.

Graphical Feedback Factory

Note: It is not recommended to give widget customized graphical feedback or implement your own IGraphicalFeedbackFactory if it is not super necessary, because the details are very complicated and need lots of GEF knowledge. If it is not desinged carefully, it is very possible to have bugs introduced.