Org.simantics.browsing.ui Manual

From Developer Documents
Jump to: navigation, search

The feature org.simantics.browsing.ui contains plug-ins that form a framework for writing UI components visualizing trees. The benefits of using the framework instead of SWT components directly, include

  • Asynchronous evaluation
  • Listening multiple data sources
  • Listening semantic graph
  • Multiple viewpoints
  • The browser component can be enhanced using contributions

Important pointers

The following classes are most often needed

  • Graph Explorer contributions (org.simantics.browsing.ui.graph)
    • ViewpointContributor<T>
    • LabelerContributor<T>
  • Widgets (org.simantics.browsing.ui.swt.widgets)
    • GraphExplorerComposite
    • TrackedText
    • Combo
  • Selection view (org.simantics.browsing.ui.swt)
    • StandardPropertyPage
    • SelectionProcessor
  • Dialogs (org.simantics.browsing.ui.swt)
    • Dialogs
    • SimanticsDialog
    • GraphExplorerDialog
  • Views and Editors (org.simantics.browsing.ui.swt)
    • SimanticsView
  • Graph Explorer nodes (org.simantics.browsing.ui.common.node)
    • AbstractNode
    • StandardNodes
    • SWTConstantInput

Creating a view part

We assume that reader knows how to create view parts and attach them to perspectives. To gain access to the necessary components, add a dependency to org.simantics.browsing.ui.swt.

The skeleton of the view part looks like:

public class Browser extends GraphExplorerView {
    private static final String CONTEXT_MENU_ID       = "#BrowserPopup";
    private static final Set<String> uiContexts       = Collections.singleton("org.simantics.example.browser");
    private static final Set<String> browseContexts   = Collections.singleton("http://www.simantics.org/Example-1.0/Browser");

    // Defines an id for context menu contributions,
    // use org.eclipse.ui.menus extension-point to contribute.
    @Override
    protected String getContextMenuId() {
        return CONTEXT_MENU_ID;
    }

    // Control which menu / handler extensions are active through
    // org.eclipse.ui.contexts extension point.
    @Override
    protected Set<String> getUiContexts() {
        return uiContexts;
    }

    // Defines the content of the browser.
    // The browser will be configured based on extensions
    // contributed to the contexts returned by this method.
    // See org.simantics.browsing.ui.common.* extension points.
    @Override
    protected Set<String> getBrowseContexts() {
        return browseContexts;
    }

    // Custom handling of drops to model browser (not required).
    // Default implementation supports IDropTargetNode.
    @Override
    protected void handleDrop(Object data, NodeContext target) {
        super.handleDrop(data, target);
    }

    // Contribute contents to other views, etc.
    @Override
    public Object getAdapter(Class adapter) {
        // Gives the page should be shown in property view when an item
        // in model browser is selected (not required)
        if (adapter == IPropertyPage.class) 
            return ...;
        return super.getAdapter(adapter);
    }
}

Viewpoints

The graph explorer visualizes some kind of configuration that is stored possibly in many different data sources. The framework does not make any assumptions about how the configuration is represented. Each entity of the configuration is for the framework a Java object.

A viewpoint makes the configuration a tree by describing which entities should be represented as children of other entities. The NodeContext objects corresponds to tree nodes that are already opened and possible visualized in the graph explorer. The configuration entity corresponding to each NodeContext can be obtained by NodeContext.getConstant(BuiltinKeys.INPUT) method.

To summarize, viewpoints define the data that will be shown in the UI in some way. Viewpoints do not define labeling and imaging of UI elements - that is a task for labelers, label decorators and imagers.

Example

A simplified version of the structural model browser (svn:modeling/trunk/org.simantics.structural.ui) will be used for this example. After viewing this example, you may want to look at the source for more details.

Data

Consider the following database contents that should be shown in a tree browser user interface:

L0 = <http://www.simantics.org/Layer0-1.0>
SIMU = <http://www.simantics.org/Simulation-1.0>
STR = <http://www.simantics.org/Structural-1.0>
DEVS = <http://www.simantics.org/DEVS-1.0>

PROJ = <http://Projects/DevelopmentProject>

PROJ.M1 : DEVS.DevsModel
    L0.ConsistsOf
        Configuration : STR.Composite
        E1 : SIMU.Experiment
    SIMU.HasConfiguration Configuration
PROJ.M2 : DEVS.DevsModel

We want to show the data in the tree like this:

- M1
    Configuration
  - Experiments
      E1
  M2

Each node is a UI element that has a certain labeling and possibly an associated image. A GraphExplorer always has a single input object called root. In the above example the viewpoint is such that visible children are produced as:

root        : [Model M1, Model M2]
Model M1    : [Configuration, Experiments]
Experiments : [Experiment E1]

The viewpoint's task is to produce some Java object to represent each of these UI elements. For this example, lets assign imaginary Java objects to each (shown as node-class(node name)):

- ModelNode(Model M1)
    CompositeNode(Configuration)
  - ExperimentsNode(Experiments)
        ExperimentNode(Experiment E1)
ModelNode(Model M2)

By comparing the database contents with the tree node structure it can observed that ExperimentsNode is considered a virtual node since there's really no unique resource related to it. It is a collecting folder for all model experiments. All other tree nodes have their own backing database resource.

NOTE: Separate node model classes are not necessarily always needed for graph explorer items, plain database resources can function as model objects also. Model classes are truly needed when multiple tree nodes are backed by a single resource but you want the explorer to consider them as two completely separate items. For example, if you have multiple explorer items with the same data (e.g. same resource), the explorer will only have a single expanded state for that data. This means all the nodes carrying the same data will be expanded when one of them is expanded which from the user's point of view is seldom desirable.

The following class hierarchy is created for this example:

Initial tree browser model class hierarchy (image source)

Defining the viewpoint

The basic formula for defining viewpoints is as follows:

  • Define browse contexts, i.e. arbitrary string identifiers which are preferably URI's that may point to discoverable resources in the graph database.
  • Define viewpoint contribution bindings to certain browse contexts using the org.simantics.browsing.ui.common.ViewpointContributionBinding

extension-point to bind viewpoint contributions to browse contexts.

The browsing framework offers users the possibility to define viewpoints extensibly through contributions. Contributions can come from any number of separate plug-ins. A viewpoint's purpose is to return the children of a parent node. In this case a viewpoint is made up of 0..n viewpoint contributions which are appropriately consulted for their contributions which are then gathered by the framework into a single result.

In this example, the tree input data is the Project instance in the database (URI: http://Projects/TestProject). First, lets define the necessary viewpoint contributions to get us the following tree:

- M1
    Configuration
  M2
   <!-- an extract from a plugin.xml -->
   <extension point="org.simantics.browsing.ui.common.viewpointContributionBinding">
      <!-- Bind these contributions to the specified browse context -->
      <binding browseContext="http://www.simantics.org/Structural-1.0/ModelBrowser">
        <!-- Contribute all Model instances from the project instances -->
        <implementation class="org.simantics.structural.ui.modelBrowser.contributions.ProjectModels" preference="1.0"/>
        <!-- Find the configuration of each model (SIMU.HasConfiguration) -->
        <implementation class="org.simantics.structural.ui.modelBrowser.contributions.ModelConfiguration" preference="1.0"/>
      </binding>
   </extension>
/**
 * The ViewpointContributor generic parameter Resource defines
 * the type of input a contributor can handle. This particular
 * contributor can potentially contribute when the input is
 * a database resource.
 */
public class ProjectModels extends ViewpointContributor<Resource> {
    @Override
    public Collection<?> getContribution(ReadGraph graph, Resource project) throws DatabaseException {
        ArrayList<AbstractNode> result = new ArrayList<AbstractNode>();
        Builtins b = graph.getBuiltins();
        for (Resource r : graph.syncRequest(new ObjectsWithType(project, b.ConsistsOf, b.Model))) {
            result.add(new ModelNode(r));
        }
        return result;
    }
 
    /**
     * Identifies the viewpoint where this contribution goes to.
     */
    @Override
    public String getViewpointId() {
        return "Standard";
    }
}
/**
 * This contributor can only work when the input is a ModelNode.
 */
public class ModelConfiguration extends ViewpointContributor<ModelNode> {
    @Override
    public Collection<?> getContribution(ReadGraph graph, ModelNode model) throws DatabaseException {
        ArrayList<AbstractNode> result = new ArrayList<AbstractNode>();
        for (Resource r : graph.getObjects(model.resource, graph.getBuiltins().HasConfiguration)) {
            result.add(new CompositeNode(r));
        }
        return result;
    }
 
    /**
     * Identifies the viewpoint where this contribution goes to.
     */
    @Override
    public String getViewpointId() {
        return "Standard";
    }
}

Defining the labeling

Defining imaging

Extending the viewpoint

As mentioned previously, viewpoints can be extended. This happens by adding contributions to any of the browse contexts that are active for the browser in question.

For the sake of this example, let's now add support for showing the Experiments subtree under the model tree.

Tabbed Selection View

The browsing framework contains an SWT-based tabbed Eclipse workbench view for visualizing the currently active workbench selection. These are also known as property pages. The view is paged, which means that every separate workbench part that is adaptable to IPropertyPage will have its own page within the selection view. Pages are similar to a TabFolder control in the sense that they host a set overlaid controls. The only difference is that in a TabFolder the layers are visible as tab headings whereas with pages they are not visible. This provides the possibility to rapidly switch between different selection view pages without having to always recreate the whole UI when the active workbench part, i.e. the active workbench selection provider changes.

Selection View Structure (File:Selection view.xcf)
Selection View Model (File:Selection view model.graphml)

The selection view framework provides a mechanism for specifying selection processors (interface) through the selectionProcessor and selectionProcessorBinding extension points in namespace org.simantics.browsing.ui.common. Selection processors encapsulate the logic of transforming an input workbench selection (ISelection) into labeled tabs as shown in the screenshot above.

The contents of a standard tabbed selection view are determined by first selecting a set of browse contexts to be used by the contributed workbench view. Each time the active workbench selection changes, the framework will give each selection processor bound to the used browse contexts a chance to contribute tabs to the page.

The tabs are defined by selection processors as instances of ComparableTabContributor.

The ID of the standard selection view is org.simantics.browsing.ui.graph.propertyView and it can be found by the name Graph Browsing/Properties in the workbench show view dialog.

Example

This example shows how to provide a most basic property table for any incoming selection that is adaptable to a single database resource.

In this example we select http://www.example.org/SelectionView to be the browse context to bind the property page to.

Assuming another workbench view/editor that provides a selection, modify the code of that view/editor as follows to provide a standard property page for it:

    @SuppressWarnings("unchecked")
    @Override
    public Object getAdapter(Class adapter) {
        ...
        // Property view support
        if (adapter == IPropertyPage.class)
            return new StandardPropertyPage(getSite(), Collections.singleton("http://www.example.org/SelectionView"));
        ...
    }

After this modification your view/editor should provide an empty page without any tabs for any selection since no selection processors are yet bound to the selected browse context.

Next we will bind a simple selection processor for our browse context that contributes a single tab named Resource Properties showing the direct properties of the selected resource in the graph database.

package org.simantics.selectionViewExample;
 
import org.simantics.db.Resource;
import org.simantics.db.RequestProcessor;
import org.simantics.ui.utils.AdaptionUtils;
 
public class ExampleSelectionProcessor implements SelectionProcessor<Object, ReadGraph> {
    @Override
    public Collection<?> process(Object selection, ReadGraph backend) {
        Resource r = AdaptionUtils.adaptToSingle(selection, Resource.class);
        if (r != null)
            return Collections.singleton(
                new ComparableTabContributor(
                    new TabContributor(),
                    0,
                    r,
                    "Resource Properties"));
        return Collections.emptyList();
    }
 
    static class TabContributor implements PropertyTabContributor {
        @Override
        public IPropertyTab create(Composite parent, IWorkbenchSite site, ISessionContext context, Object input) {
            IPropertyTab tab = new PropertyTable(site, parent, 0);
            tab.createControl((Composite) tab.getControl(), context);
            return tab;
        }
    }
}

and insert the binding in your plug-ins plugin.xml:

   <!-- an extract from a plugin.xml -->
   <extension point="org.simantics.browsing.ui.common.selectionProcessorBinding">
      <!-- Bind these contributions to the specified browse context -->
      <binding browseContext="http://www.example.org/SelectionView">
         <implementation class="org.simantics.selectionViewExample.ExampleSelectionProcessor"/>
      </binding>
   </extension>

Note that a single SelectionProcessor can be used to contribute multiple tabs. Also, many selection processors can be bound to a single browse context. The framework will consult each selection processor in some undefined order. Notice that this makes it possible for multiple selection processors to contribute the same tab twice. This can be prevented by making your PropertyTabContributor implementations equals-comparable in which case tabs will not be added twice if the contributed ComparableTabContributor implementations have equal attributes.

Property tab implementers should always extend org.simantics.selectionview.PropertyTabContributorImpl to simplify implementation.

TODO: things to cover

Modifiers for Labels

SelectionProcessor details

Auto-expansion

Just as JFace tree viewers have support for level-based automatic tree expansion, GraphExplorer provides API to control this. See GraphExplorer.setAutoExpandLevel and the method's documentation to see how it works.

Low-level framework details

EvaluatorData

The viewpoint of the graph explorer is defined in EvaluatorData. The main function of this object is to calculate for a given NodeContext, prioritized lists of the following factories:

  • ViewpointFactory
  • LabelerFactory
  • LabelDecoratorFactory
  • ImagerFactory
  • ComparableFactory

The EvaluatorData object contains a function that gives a collection of Evaluators for each configuration entity. The implementation does this by based on the class of the entity. So if we define

EvaluatorData data = new EvaluatorDataImpl();
data.addEvaluator(Resource.class, resourceEvaluator);
data.addEvaluator(INode.class, nodeEvaluator);

the data.get(input) returns resourceEvaluator for all objects that are instances of Resource and nodeEvaluator for all objects that are instance of INode.

The Evaluator object is a collection of different factories that are organized in trees so that each node of the tree contains a Tester that prunes the subtree if NodeContext does not satisfy some condition. The factories also have an attached preference value that can be used to sort them.

The procedures that calculate the sorted lists of factories are implemented in AbstractFactoryResolverQueryProcessor and its subclasses.