Adding a View Plugin

VWoeltjen edited this page Jun 22, 2012 · 5 revisions

MCT includes a variety of views for data feeds and display composition. However, you may wish to add your own views to MCT, either because you need to visualize data feeds differently, or because your user objects have unique views that are appropriate to them (for instance, a graphical object only makes sense if you can view the image it references.) MCT makes it easy to add new views, and to coordinate them with other components.

What is a View?

A view is a way of looking at an object. For instance, a plot view permits us to see an object's feed values over time. Sometimes a view of one object will also involve information from other referenced objects. For instance, a plot view of a collection lets us see the feed values of all of the objects contained within the collection. Those contained objects (and therefore their feeds) are a part of that collection as an object, at least from the plot's perspective. It is up to the individual view to decide which information about an object is relevant and how it should be displayed: That is the purpose of views.

Objects in MCT are always exposed to the users as views. Some of the simplest views are the ones shown in the list view: Here, the only information about the object we see is its icon (indicating its type) and its base displayed name. (In this case, we only want a minimal amount of information to be shown so we can display the items concisely in the list.) On the other extreme, a whole window can be a view of an object.

Those sorts of views are already handled by the platform. More commonly, plug-in developers will be interested in views somewhere between those two extremes: Things like plot views, table views, et cetera, that are exposed when a user inspects an object.

Implementing a View

A new view should extend MCT's class View, which is a swing component. A view should have a public constructor which takes an AbstractComponent and a ViewInfo object as arguments. For instance, the constructor for a class ExampleView should look like:

public ExampleView(AbstractComponent ac, ViewInfo vi) {
    super(ac, vi);
    // . . . and then whatever set up this component requires
}

The component provided can be examined for whatever properties are relevant to the view (for instance, for capabilities like FeedProvider). The view info is generally only relevant to the superclass's constructor, but may also be used to determine some information about how the view is being used. Beyond that, the View is a swing component (specifically, a JPanel), and additional swing components can be added to it to change what it displays appropriately.

Since viewing incoming data feeds is a common task in MCT, an abstract intermediary view class, FeedView, is also available. A feed view maintains most of the underlying connections necessary to receive feed data, allowing its subclasses to implement the following methods:

public abstract Collection<FeedProvider> getVisibleFeedProviders();
public abstract void updateFromFeed(Map<String,List<Map<String,String>>> data);
public abstract void synchronizeTime(Map<String,List<Map<String,String>>> data, long syncTime);
  • The getVisibleFeedProviders method is used to identify which feed providers are relevant to this view. This might just be the feed provider returned by ac.getCapability(FeedProvider.class) in the constructor; or, if you are interested in multiple feeds, these may be feed providers found in objects referenced by the object being viewed. Providing this list allows the underlying feed view to subscribe to the appropriate feeds, and listen for incoming data values in order to notify the other view methods. (Note that it is useful to cache these feed providers locally, as they are also relevant to interpreting incoming data values.)
  • The updateFromFeed method is where this incoming data is received. See the section of Creating a Data Provider for information on how this data is structured. A feed provider's getRenderingInfo method may be used to pull useful, displayable information from a data point that is represented as a Map<String,String>.
  • Finally, the synchronizeTime method delivers the same sort of information as updateFromFeed, along with a time specified in milliseconds since the UNIX epoch. This is used to synchronize multiple views to a specific time, typically in the past.

As with other plug-ins, new views are made known to the MCT platform through a common interface. Since Views are commonly implemented alongside new components, they are made accessible via the ComponentProvider interface. This interface contains multiple methods which may be used to gather lists of new things added by a specific bundle. Since in this instance only new view types have been added, it make sense for the implementation of ComponentProvider to subclass from AbstractComponentProvider (which provides empty lists for all these things by default, as a convenience for just this situation) and override only one method:

public Collection<ViewInfo> getViews(String componentTypeId)

This should return a collection of objects which describe the view or views being made available by this plug-in. Information about each view is encapsulated in a ViewInfo object. There are a few constructors available for these, depending on the level of specificity you want to give to the view's description, but the simplest form is generally sufficient:

public ViewInfo(Class<? extends View> aViewClass, String viewName, ViewType viewType) 

Here, the information object is created with the view's class (needed to instantiate views as needed), the name of the view (as it will be displayed to users), and its enumerated type. There are many possible values, but most likely you will want ViewType.OBJECT (for views that can be displayed in the Inspector) or ViewType.EMBEDDED (for views that can be displayed within other views; for instance, on a canvas). In many cases you will want the same view to be available in multiple places, in which case you should supply separate ViewInfo instances about the same view for each relevant view type.

Once the component provider has been implemented, it can be made available through OSGi's declarative services. The easiest way to do this is to package your component in a Jar archive along with an XML document that describes what service you are offering (typically OSGI-INF/services.xml). An example of how such an XML file should look:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<scr:component name="org.acme.example.component.ExampleComponentProvider">
	<implementation class="org.acme.example.component.ExampleComponentProvider" />
	<service>
		<provide interface="gov.nasa.arc.mct.services.component.ComponentProvider" />
	</service>
</scr:component>
</root>

Finally, you should add a line to your Jar's manifest to identify this service description:

Service-Component: OSGI-INF/services.xml