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.








4 comments:

  1. Hi Frank, great tutorial, that's saved me a lot of time. I guess the GEF4 API has changed quite a bit since you wrote this, but I have been able to get everything working except the very last part. The DeleteOnClickPolicy never gets invoked when I click on the red X. The MVC Logo example has FXDeleteFirstAnchorageOnClickPolicy, which looks to be similar. This implements IFXOnClickPolicy, and is bound to the default role on the FXDeleteHoverHandlePart. If I do the same with DeleteOnClickPolicy , nothing happens. I tried using FXClickDragTool.ON_CLICK_POLICY_KEY as the role, but then I get an exception saying the binding is wrong. Any ideas how to fix this?

    ReplyDelete
  2. Hi CTG,
    thanks for your feedback. Unfortunately there is currently no time available for this.
    But if you could send me a github pull request, i can update the sources, to include your API updates.

    ReplyDelete
  3. I'm not on github, so can't do that at the moment.

    I've done a bit more debugging, and think I have found the issue. FXClickDragTool is not finding the click policy when processing the click event. It is only finding the HoverHandleRootPart, not the DeleteHoverHandlePart - or more precisely, it is not getting the policies from the children of the HoverHandleRootPart. I will raise a bug with the GEF team to see what's going on.

    ReplyDelete
  4. Ah, worked it out. One of the API changes was to the listener classes. AbstractLogoHoverHandlePart was adding a listener to register the child handles - this had changed so I had commented out the code that couldn't compile. I found the equivalent listener in the latest version of the MVC Logo example and put that back into AbstractLogoHoverHandlePart, and now everything works. Cheers

    ReplyDelete