Difference between revisions of "Org.simantics.browsing.ui Manual"

From Developer Documents
Jump to navigation Jump to search
m
m
Line 1: Line 1:
'''<font style="color:red">This document is no longer maintained here, but instead in [https://www.simantics.org/developer_wiki/index.php/Main_Page Developer Wiki]</font>'''
 
 
 
 
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
 
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
 
* Asynchronous evaluation

Revision as of 14:01, 1 October 2010

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:

%import "layer0.graph" L0
%import "project.graph" PROJ
%import "simulation.graph" SIMU
%import "structural2.graph" STR
%import "devs.graph" DEVS

TestProject : PROJ.Project2
    L0.PartOf L0.Projects
    PROJ.HasFeature DEVS.DevsProject
    L0.ConsistsOf
        M1 : DEVS.DevsModel
            L0.ConsistsOf
                Configuration : STR.Composite
                E1 : SIMU.Experiment
            SIMU.HasConfiguration Configuration
        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 (File:Structural-example-model-classes.graphml)

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

<source lang="xml">

  <extension point="org.simantics.browsing.ui.common.viewpointContributionBinding">
     <binding browseContext="http://www.simantics.org/Structural-1.0/ModelBrowser">
       <implementation class="org.simantics.structural.ui.modelBrowser.contributions.ProjectModels" preference="1.0"/>
       <implementation class="org.simantics.structural.ui.modelBrowser.contributions.ModelConfiguration" preference="1.0"/>
     </binding>
  </extension>

</source>

<source lang="java"> /**

* 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";
   }

} </source>

<source lang="java"> /**

* 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";
   }

} </source>

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 (
Error creating thumbnail: /bin/bash: /usr/bin/convert: No such file or directory Error code: 127
)
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: <source lang="java">

   @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"));
       ...
   }

</source>

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.

<source lang="java"> 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;
       }
   }

} </source>

and insert the binding in your plug-ins plugin.xml: <source lang="xml">

  <extension point="org.simantics.browsing.ui.common.selectionProcessorBinding">
     <binding browseContext="http://www.example.org/SelectionView">
        <implementation class="org.simantics.selectionViewExample.ExampleSelectionProcessor"/>
     </binding>
  </extension>

</source>

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.

Low-level framework details

THIS MAY BE (RE)MOVED SINCE THE BASIC USER DOES NOT NEED TO KNOW THIS.

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.

Contributing Evaluators

New Evaluators must be contributed, if browser cannot handle objects returned by ViewpointContributor.getContribution(). For example, cuurent Model Browser cannot handle INode implementations by default. This example shows how to create an Evaluator for that.

Extension:

<source lang="xml">

  <extension
        point="org.simantics.browsing.ui.common.evaluatorBinding">
     <binding
           browseContext="http://www.simantics.org/Structural-1.0/ModelBrowser">
        <implementation
              class="org.simantics.external.comos.handlers.EvaluatorFactory1"
              preference="1">
        </implementation>
     </binding>
  </extension>

</source>

EvaluatorFactory implementation:

<source lang="java"> public class EvaluatorFactory1 implements EvaluatorFactory {

public EvaluatorFactory1() {

}

@Override public Evaluator create(Collection<String> browseContexts) { return ModelEvaluators.createNodeEvaluator(SimanticsUI.getSession(), new LocalResourceManager(JFaceResources.getResources(Display.getDefault()))); }

@Override public Class<?> getClazz() { return INode.class; }

} </source>

ViewpointFactory

ViewpointFactory creates a Viewpoint for a given NodeContext:

public interface ViewpointFactory {
    Viewpoint create(PrimitiveQueryUpdater updater, NodeContext context, ViewpointKey key);
}
public interface Viewpoint {
   static final NodeContext[] PENDING_CHILDREN     = {};
   static final Boolean       PENDING_HAS_CHILDREN = new Boolean(false);
   NodeContext[] getChildren();
   Boolean getHasChildren();
}

If the ViewpointFactory can compute the Viewpoint from the context synchronously, it may ignore the updater and key parameters. If it cannot calculate the value immediately, the Viewpoint it creates first returns no children and when the correct result is available, it updates the viewpoint using updater and key parameters given to it in ViewpointFactory.create.

For viewpoints browsing the graph database, lazy updating is implemented in LazyViewpoint that is used by extending it:

public class ConsistsOfViewpointFactory implements ViewpointFactory {
    @Override
    public Viewpoint create(PrimitiveQueryUpdater updater, final NodeContext context, ViewpointKey key) {
        return new LazyViewpoint(updater, context, key) {
            @Override
            public NodeContext[] children(ReadGraph graph) throws DatabaseException {
                return toContextsWithInput(graph.getObjects((Resource) context.getConstant(BuiltinKeys.INPUT), graph.getBuiltins().ConsistsOf));
            }
            @Override
            public Boolean hasChildren(ReadGraph graph) throws DatabaseException {
                return Boolean.valueOf(children(graph).length > 0);
            }
        };
    }
    @Override
    public String toString() {
        return "Structure";
    }
}

The name of the ViewpointFactory is shown for a viewpoint if viewpoint selection is available.