Friday, November 6, 2015

GEF4 Tutorial - Part 4 - Dragging and store to model

In step 4 of this tutorial, the text nodes can be dragged around with the mouse. The new positions are stored into the model object.
The model as whole is restored and persisted at application start and end.

For the source of this tutorial step see github - gef4.mvc.tutorial4.

Note: parts of this tutorial are copied from other examples or from forum postings.

Restoring and persisting the Model


For mapping the model, here JAXB is use.

@XmlRootElement
public class Model {
    @XmlElement
    LinkedList<TextNode> nodes = new LinkedList<>();

Those annotation tell JAXB how to map content to XML and reverse.
This code loads an XML file and create the model with all child objects.

jaxbContext = JAXBContext.newInstance(Model.class, TextNode.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
model = (Model) jaxbUnmarshaller.unmarshal(new File("model.xml"));

This code persists the model to a XML file:

Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal( model, new File("model.xml"));

Adding this code into the applications start and stop methods, automates the reload and store.

The Adapter pattern used in GEF4


Eclipse support the Adapter pattern, see this article:

In summary, it means, objects in Eclipse context that implement the IAdaptable interface, can give an implementation of a requested interface.

In GEF4, this pattern was enhanced. 
See this article by Alexander Ny├čen:

In addition to the Eclipse adapters, in GEF4, adapters can be configured at runtime, can exists for the same interface type in different roles, can have a reference to the adapted object.

Making the nodes selectable


So in GEF4, the configuration of the Guice module is one of the important control points of a application.

To make the nodes in the tutorial selectable, the following code was taken from the Logo example.

@Override
protected void bindAbstractContentPartAdapters( MapBinder<AdapterKey<?>, Object> adapterMapBinder) {
    super.bindAbstractContentPartAdapters(adapterMapBinder);
    // register (default) interaction policies (which are based on viewer
    // models and do not depend on transaction policies)
    adapterMapBinder
        .addBinding(AdapterKey.get(FXClickDragTool.CLICK_TOOL_POLICY_KEY))
        .to(FXFocusAndSelectOnClickPolicy.class);

    adapterMapBinder
        .addBinding(AdapterKey.get(FXHoverTool.TOOL_POLICY_KEY))
        .to(FXHoverOnHoverPolicy.class);
    
    // geometry provider for selection feedback
    adapterMapBinder
        .addBinding(AdapterKey.get(
            new TypeToken<Provider<IGeometry>>(){},
            FXDefaultFeedbackPartFactory.SELECTION_FEEDBACK_GEOMETRY_PROVIDER))
        .to(VisualBoundsGeometryProvider.class);

    // geometry provider for hover feedback
    adapterMapBinder
        .addBinding(AdapterKey.get(
            new TypeToken<Provider<IGeometry>>(){},
            FXDefaultFeedbackPartFactory.HOVER_FEEDBACK_GEOMETRY_PROVIDER))
        .to(VisualBoundsGeometryProvider.class);
}

Normally shown node:

The mouse hoovering over the node, creates a surrounding box marker.

Clicking makes the box darker, so it is shown as selected.


Making the node dragable


In the Guice module configure:

bindTextNodePartAdapters(AdapterMaps.getAdapterMapBinder(binder(), TextNodePart.class));

The implementation:

protected void bindTextNodePartAdapters( MapBinder<AdapterKey<?>, Object> adapterMapBinder) {
    // register resize/transform policies (writing changes also to model)
    adapterMapBinder
        .addBinding(AdapterKey.get(FXTransformPolicy.class))
        .to(FxTransformPolicy.class);
    // interaction policies to relocate on drag
    adapterMapBinder
        .addBinding( AdapterKey.get(FXClickDragTool.DRAG_TOOL_POLICY_KEY))
        .to(FXTranslateSelectedOnDragPolicy.class);
}

This uses the standard components to make items dragable.
It is surprising that this works, as there is yet no linkage to the model. 
Try it out!
It even works if you press the button to update the model (vary the values).
The dragging information is stored in the visuals as a transformation. The model and part can continue to work with the original coordinates.

Updating the model


To give the whole a sense, the position of the TextNode shall be stored to the model. Then it can be persisted and restored.

For this, the ItemTransformPolicy is extended from FXTransformPolicy.

public class ItemTransformPolicy extends FXTransformPolicy {

    @Override
    public ITransactionalOperation commit() {
        ITransactionalOperation visualOperation = super.commit();
        ITransactionalOperation modelOperation = createUpdateModelOperation();
        ForwardUndoCompositeOperation commit = new ForwardUndoCompositeOperation("Translate()");
        if (visualOperation != null) commit.add(visualOperation);
        if (modelOperation != null) commit.add(modelOperation);
        return commit.unwrap(true);
    }

    private ITransactionalOperation createUpdateModelOperation() {
        return new ChangeTextNodePositionOperation(getHost());
    }
}

The ItemTransformPolicy combines the original FXTranformPolicy with a new opereration, the ChangeTextNodePositionOperation. The new operation shall remove the tranformation from the visuals, and store the information into the mode.

public class ChangeTextNodePositionOperation extends AbstractOperation implements ITransactionalOperation {

    TextNodePart part;
    public ChangeTextNodePositionOperation(IVisualPart<Node, ? extends Node> part) {
        super( "" );
        Assert.isLegal(part instanceof TextNodePart, "Only TestNodePart supported for ChangeItemPositionOperation");
        this.part = (TextNodePart) part;
    }

    @Override
    public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        Affine transform = part.getAdapter(FXTransformPolicy.TRANSFORM_PROVIDER_KEY).get();
        // tell the part, which updates the model, will also trigger a doRefreshVisuals
        part.translate(transform.getTx(), transform.getTy());
        // reset the transformation
        transform.setTx(0);
        transform.setTy(0);
        return Status.OK_STATUS;
    }

    @Override
    public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        return null;
    }

    @Override
    public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        return null;
    }

    @Override
    public boolean isNoOp() {
        return false;
    }

}

Last step is to configure the ItemTranformPolicy to be used as implementation for FXTransformPolicy.

    adapterMapBinder
        .addBinding(AdapterKey.get(FXTransformPolicy.class))
        .to(ItemTransformPolicy.class);
















No comments:

Post a Comment