Sunday, November 15, 2015

GEF4 Tutorial - Part 7 - Add / Remove

In step 7 of this tutorial, the nodes can be added and removed.
Adding is done via mouse right-click. A palette is opened (with one item only) that will be added when pressed.
Removed of nodes can be done in two ways. Either hover over the node, a handle tool with red cross is shown. Or select the node and press DEL key.

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

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


Creating a palette on right-click

The new palette to add items, shall show up, when clicked on the canvas. For this, we bind a policy to the root part adapter map.

adapterMapBinder
    .addBinding(
        AdapterKey.get(FXClickDragTool.CLICK_TOOL_POLICY_KEY, "FXCreationMenuOnClick"))
    .to(FXCreationMenuOnClickPolicy.class);

Then we need to test if the click is really on this canvas. And it is needed to store the coordinates in 2 ways
1. in screen coordinates, to position the palette as popup.
2. in the canvas coordinates, which might be even different in scaling, if the viewer is currently zoomed.

FXCreationMenuOnClickPolicy extends AbstractFXOnClickPolicy. The click() method looks like this:

@Override
public void click(MouseEvent e) {
    // open menu on right click
    if (MouseButton.SECONDARY.equals(e.getButton())) {
        initialMousePositionInScreen = new Point2D(e.getScreenX(), e.getScreenY());
        // use the viewer to transform into local coordinates
        // this works even if the viewer is scrolled and/or zoomed.
        InfiniteCanvas infiniteCanvas = getViewer().getCanvas();
        initialMousePositionInScene = infiniteCanvas.getContentGroup().screenToLocal(initialMousePositionInScreen);
        // only open if the even was on the visible canvas
        if( infiniteCanvas.getBoundsInLocal().contains(initialMousePositionInScene) ){
            openMenu(e);
        }
    }
}

As palette, a Java FX popup is used. The image is set as graphics into a button instance in the popup. So we automatically have some hover behavior from the button. 

The popup is set to have a border line and a shadow, to look like a context menu.



Adding a new TextNode

The action now is again done via an operation, so it can be undone.

GEF4 provides a ready to use policy for this, the CreationPolicy.

CreationPolicy<Node> creationPolicy = root.getAdapter(CreationPolicy.class);
creationPolicy.init();
creationPolicy.create(
    textNode,
    contentPartModel,
    HashMultimap.create());

The textNode is the newly create object for the model. The contentPartModel is the ModelPart instance, that shall add the textNode.

The CreatePolicy delegates the work to the ModelPart#doAddContentChild(), which must be overridden, otherwise the base class implementation will throw an exception.

Deleting via short cut

Deletion of parts is just a matter of registering the TextNodePart:

adapterMapBinder.addBinding(AdapterKey.get(FXTypeTool.TOOL_POLICY_KEY))
    .to(FXDeleteSelectedOnTypePolicy.class);

It delegates the model changes to the parents part doAddContentChild() and doRemoveContentChild().

Delete with the handle tool

This implementation is completely taken from the Logo example.

This deletion is implemented as an icon getting visible when hovering over the TextNodePart.





HoverHandleRootPart is a parent for optionally more than one such handle action. Now we have only one action, the deletion, but there may be more to add. So HoverHandleRootPart manages the position at the upper left corner of the TextNodePart, and adds child handle parts in a VBox vertically downwards.

HandlePartFactory creates the handle parts. It is registered in the overriden bindIHandlePartFactory() of the GuiceModule. When requested to give the handle parts for TextNodePart, it constructs the HoverHandleRootPart with one child, the DeleteHoverHandlePart.



The DeleteHoverHandlePart gets a policy DeleteOnClickPolicy attached, that implements the action to be done. In this case, it delegates to the DeletionPolicy, which is part of GEF4.








Saturday, November 7, 2015

GEF4 Tutorial - Part 6 - Undo / Redo

In step 6 of this tutorial, the changes that can be done to the text nodes are undoable and redoable.

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

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

At the moment there are 2 kind of changes that can be done to TextNodes.
1. Drag the node and change its location.
2. Edit the text

To be able to make use of the undo/redo mechanism that Eclipse implements.
To read more about this, you can start here:


Every action that applies a change that shall be undoable, must be represented by an implementation of IUndoableOperation.

Example is the text change.
Before it looked like this, just changing the text in the editModeEnd().

getContent().setText(newText);

Now an operation is needed, that look like this:

public class ChangeTextNodeTextOperation extends AbstractOperation implements ITransactionalOperation {

    TextNodePart part;
    private String oldText;
    private String newText;
    public ChangeTextNodeTextOperation(TextNodePart part, String oldText, String newText ) {
        super( "Change Text in TextNode" );
        this.part = part;
        this.oldText = oldText;
        this.newText = newText;
    }

    @Override
    public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        part.getContent().setText(newText);
        return Status.OK_STATUS;
    }

    @Override
    public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        execute(monitor, info);
        return Status.OK_STATUS;
    }

    @Override
    public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        part.getContent().setText(oldText);
        return Status.OK_STATUS;
    }

    @Override
    public boolean isNoOp() {
        return oldText.equals(newText);
    }
}

1. Get all information at creation time, because several operation might be executed, it is not reliable in which state the other objects are at that time.
2. Store as well the old state, so you can implement the undo.
3. If possible implement the redo with delegating to execute
4. Implement the isNoOp, so the operation with no effect are sorted out of the undo history.

To execute this operation you code like this:

getViewer().getDomain().execute(
    new ChangeTextNodeTextOperation(this, getContent().getText(), newText));

The Viewer has a reference to the domain. 
The domain's execute() method, delegates to the operation history. 
In case of working in a Eclipse plugin, the operation history is bound to the one from Eclipse, org.eclipse.gef4.mvc.ui.MvcUiModule. If you want to bind your own, you can overwrite the bindIOperationHistory in the Guice module.

The same pattern is applied to implement the ChangeTextNodePositionOperation.



Keyboard shortcuts

To have the keyboard shortcuts work for undo Ctrl-Z and redo Ctrl-Y, again a Policy is installed. But on which object. The keyboard input is send to the part which is selected, or the RootPart.
Guice can be used to add the policy to all TextNodeParts and the RootPart, but using only a single instance.

binder()
    .bind(GlobalOnTypePolicy.class )
    .in(Scopes.SINGLETON);

Configure the RootPartAdapters:

@Override
protected void bindAbstractRootPartAdapters(MapBinder<AdapterKey<?>, Object> adapterMapBinder) {
    super.bindAbstractRootPartAdapters(adapterMapBinder);
    adapterMapBinder
        .addBinding( AdapterKey.get( FXTypeTool.TOOL_POLICY_KEY, "GlobalOnTypePolicy"))
        .to( GlobalOnTypePolicy.class);
}

And then add the same mapping to the TextNodePart adapters.




GEF4 Tutorial - Part 5 - Editing text

In step 5 of this tutorial, the text nodes can be edited in the UI directly.

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


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

The text shall be editable when double clicked, or if the node is selected and ENTER is hit. The text is single line only. 
The text if ENTER is hit while editing. In case of ESC or if the focus is changed the edited test is discarded.













After the text is accepted the node size is adjusted, by keeping the position (left upper corner) stable.

To implement this the visual of the node is changed to use the StackPane as top level, having a RoundedRectangle, Text and TextField. The StackPane adjusts all widgets to be placed around the same center point. By switching on of the visibility and the managed state, it can show only the Text (normal mode) or the TestField (editing mode).

For this behavior, it is needed to react on mouse double click and on keyboard typing.
The classes TextNodeOnDoubleClickPolicy and TextNodeOnTypePolicy are added to the adapters. The given string for the role, is added to make those adapter not replace existing ones. So the value of the strings is in this case not important, it just must be different to what might be used elsewhere.

adapterMapBinder
    .addBinding( AdapterKey.get(
        FXClickDragTool.CLICK_TOOL_POLICY_KEY,
        "TextNodeOnDoubleClickPolicy"))
    .to( TextNodeOnDoubleClickPolicy.class);

adapterMapBinder
    .addBinding( AdapterKey.get(
        FXTypeTool.TOOL_POLICY_KEY,
        "TextNodeOnTypePolicy"))
    .to( TextNodeOnTypePolicy.class);

The double click policy checks if the click count is 2 and it was the primary button, then call to the part (TextNodePart) the new editModeStart() methods.

The type policy handles more cases in the «pressed» method:
F2 : start editing
ESC : cancel editing
ENTER if not yet in editing: start editing
ENTER if in editing: stop editing and take the value to the model

In case the key event was detected as action, the «event.consume()» shall be called, otherwise I had the case where the event was reported a second time.

The last case to handle is when the user clicks on another node and changes the focus.
For this, the TextNodePart registers/unregisters a listener for the focus model in it's doActivate/doDeactivate methods like this:

getViewer()
    .getAdapter(FocusModel.class)
    .addPropertyChangeListener( this::handleFocusModelUpdate );

If a change is detected and the current selected part is not this TextNodePart, the editing is cancelled.


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);