org.drools.workbench.screens.guided.dtree.client.widget.GuidedDecisionTreeWidget.java Source code

Java tutorial

Introduction

Here is the source code for org.drools.workbench.screens.guided.dtree.client.widget.GuidedDecisionTreeWidget.java

Source

/*
* Copyright 2014 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.workbench.screens.guided.dtree.client.widget;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

import com.emitrom.lienzo.client.core.animation.AnimationProperties;
import com.emitrom.lienzo.client.core.animation.AnimationTweener;
import com.emitrom.lienzo.client.core.animation.IAnimation;
import com.emitrom.lienzo.client.core.animation.IAnimationCallback;
import com.emitrom.lienzo.client.core.animation.IAnimationHandle;
import com.emitrom.lienzo.client.core.shape.Group;
import com.emitrom.lienzo.client.core.shape.Rectangle;
import com.emitrom.lienzo.client.core.shape.Text;
import com.emitrom.lienzo.client.core.types.Point2D;
import com.emitrom.lienzo.shared.core.types.TextAlign;
import com.emitrom.lienzo.shared.core.types.TextBaseLine;
import com.google.gwt.user.client.Window;
import org.drools.workbench.models.guided.dtree.shared.model.GuidedDecisionTree;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.ActionInsertNode;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.ActionRetractNode;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.ActionUpdateNode;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.ConstraintNode;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.Node;
import org.drools.workbench.models.guided.dtree.shared.model.nodes.TypeNode;
import org.drools.workbench.screens.guided.dtree.client.editor.GuidedDecisionTreeEditorPresenter;
import org.drools.workbench.screens.guided.dtree.client.resources.i18n.GuidedDecisionTreeConstants;
import org.drools.workbench.screens.guided.dtree.client.widget.factories.ActionInsertNodeFactory;
import org.drools.workbench.screens.guided.dtree.client.widget.factories.ActionRetractNodeFactory;
import org.drools.workbench.screens.guided.dtree.client.widget.factories.ActionUpdateNodeFactory;
import org.drools.workbench.screens.guided.dtree.client.widget.factories.ConstraintNodeFactory;
import org.drools.workbench.screens.guided.dtree.client.widget.factories.TypeNodeFactory;
import org.drools.workbench.screens.guided.dtree.client.widget.shapes.BaseGuidedDecisionTreeShape;
import org.drools.workbench.screens.guided.dtree.client.widget.shapes.TypeShape;
import org.uberfire.client.mvp.UberView;
import org.uberfire.commons.data.Pair;
import org.uberfire.ext.wires.core.api.events.ClearEvent;
import org.uberfire.ext.wires.core.api.events.ShapeAddedEvent;
import org.uberfire.ext.wires.core.api.events.ShapeDeletedEvent;
import org.uberfire.ext.wires.core.api.events.ShapeDragCompleteEvent;
import org.uberfire.ext.wires.core.api.events.ShapeDragPreviewEvent;
import org.uberfire.ext.wires.core.api.events.ShapeSelectedEvent;
import org.uberfire.ext.wires.core.api.layout.LayoutManager;
import org.uberfire.ext.wires.core.api.layout.RequiresLayoutManager;
import org.uberfire.ext.wires.core.api.shapes.WiresBaseShape;
import org.uberfire.ext.wires.core.client.canvas.WiresCanvas;
import org.uberfire.ext.wires.core.client.util.ShapeFactoryUtil;
import org.uberfire.ext.wires.core.trees.client.canvas.WiresTreeNodeConnector;
import org.uberfire.ext.wires.core.trees.client.layout.WiresLayoutUtilities;
import org.uberfire.ext.wires.core.trees.client.layout.treelayout.Rectangle2D;
import org.uberfire.ext.wires.core.trees.client.shapes.WiresBaseTreeNode;

public class GuidedDecisionTreeWidget extends WiresCanvas implements UberView<GuidedDecisionTreeEditorPresenter> {

    private static final int MAX_PROXIMITY = 200;

    private static final int ANIMATION_DURATION = 250;

    @Inject
    private Event<ClearEvent> clearEvent;

    @Inject
    private Event<ShapeSelectedEvent> shapeSelectedEvent;

    @Inject
    private Event<ShapeAddedEvent> shapeAddedEvent;

    @Inject
    private Event<ShapeDeletedEvent> shapeDeletedEvent;

    @Inject
    private LayoutManager layoutManager;

    @Inject
    private TypeNodeFactory typeNodeFactory;

    @Inject
    private ConstraintNodeFactory constraintNodeFactory;

    @Inject
    private ActionInsertNodeFactory actionInsertNodeFactory;

    @Inject
    private ActionUpdateNodeFactory actionUpdateNodeFactory;

    @Inject
    private ActionRetractNodeFactory actionRetractNodeFactory;

    private GuidedDecisionTreeDropContext dropContext = new GuidedDecisionTreeDropContext();

    private WiresTreeNodeConnector connector = null;

    private WiresBaseTreeNode uiRoot;
    private GuidedDecisionTree model;

    private GuidedDecisionTreeEditorPresenter presenter;

    private Group hint = null;
    private boolean isGettingStartedHintVisible = false;

    @Override
    public void init(final GuidedDecisionTreeEditorPresenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public void selectShape(final WiresBaseShape shape) {
        shapeSelectedEvent.fire(new ShapeSelectedEvent(shape));
    }

    public void onShapeSelected(@Observes ShapeSelectedEvent event) {
        final WiresBaseShape shape = event.getShape();
        super.selectShape(shape);
    }

    @Override
    public void deselectShape(final WiresBaseShape shape) {
        super.deselectShape(shape);
    }

    public void onDragPreviewHandler(@Observes ShapeDragPreviewEvent shapeDragPreviewEvent) {
        //We can only connect WiresTreeNodes to each other
        if (!(shapeDragPreviewEvent.getShape() instanceof BaseGuidedDecisionTreeShape)) {
            dropContext.setContext(null);
            return;
        }

        //Find a Parent Node to attach the Shape to
        final double cx = getX(shapeDragPreviewEvent.getX());
        final double cy = getY(shapeDragPreviewEvent.getY());
        final BaseGuidedDecisionTreeShape uiChild = (BaseGuidedDecisionTreeShape) shapeDragPreviewEvent.getShape();
        final BaseGuidedDecisionTreeShape uiProspectiveParent = getParentNode(uiChild, cx, cy);

        //If there is a prospective parent show the line between child and parent
        if (uiProspectiveParent != null) {
            if (connector == null) {
                connector = new WiresTreeNodeConnector();
                canvasLayer.add(connector);
                connector.moveToBottom();
            }
            connector.getPoints().getPoint(0).set(uiProspectiveParent.getLocation());
            connector.getPoints().getPoint(1).set(new Point2D(cx, cy));
        } else if (connector != null) {
            canvasLayer.remove(connector);
            connector = null;
        }

        dropContext.setContext(uiProspectiveParent);
        canvasLayer.draw();
    }

    public void onDragCompleteHandler(@Observes ShapeDragCompleteEvent shapeDragCompleteEvent) {
        final WiresBaseShape wiresShape = shapeDragCompleteEvent.getShape();

        //Hide the temporary connector
        if (connector != null) {
            canvasLayer.remove(connector);
            canvasLayer.draw();
            connector = null;
        }

        //If there's no Shape to add then exit
        if (wiresShape == null) {
            dropContext.setContext(null);
            return;
        }

        //If the Shape is not intended for the Guided Decision Tree widget then exit
        if (!(wiresShape instanceof BaseGuidedDecisionTreeShape)) {
            dropContext.setContext(null);
            return;
        }
        final BaseGuidedDecisionTreeShape uiChild = (BaseGuidedDecisionTreeShape) wiresShape;

        //Get Shape's co-ordinates relative to the Canvas
        final double cx = getX(shapeDragCompleteEvent.getX());
        final double cy = getY(shapeDragCompleteEvent.getY());

        //If the Shape was dropped outside the bounds of the Canvas then exit
        if (cx < 0 || cy < 0) {
            dropContext.setContext(null);
            return;
        }

        final int scrollWidth = getElement().getScrollWidth();
        final int scrollHeight = getElement().getScrollHeight();
        if (cx > scrollWidth || cy > scrollHeight) {
            dropContext.setContext(null);
            return;
        }

        //Add the new Node to it's parent (unless this is the first node)
        final BaseGuidedDecisionTreeShape uiParent = dropContext.getContext();
        boolean addShape = ((getShapesInCanvas().size() == 0 && (uiChild instanceof TypeShape))
                || (getShapesInCanvas().size() > 0 && uiParent != null));
        boolean addChildToParent = uiParent != null;

        if (addShape) {
            uiChild.setX(cx);
            uiChild.setY(cy);

            if (addChildToParent) {
                uiParent.addChildNode(uiChild);
                uiParent.getModelNode().addChild(uiChild.getModelNode());

            } else if (uiChild instanceof TypeShape) {
                uiRoot = uiChild;
                model.setRoot(((TypeShape) uiChild).getModelNode());
            }

            addShape(uiChild);

            //Notify other Panels of a Shape being added
            shapeAddedEvent.fire(new ShapeAddedEvent(uiChild));
        }
    }

    private double getX(double xShapeEvent) {
        return xShapeEvent - getAbsoluteLeft();
    }

    private double getY(double yShapeEvent) {
        return yShapeEvent - getAbsoluteTop();
    }

    @Override
    public void clear() {
        if (Window.confirm(GuidedDecisionTreeConstants.INSTANCE.confirmDeleteDecisionTree())) {
            super.clear();
            clearEvent.fire(new ClearEvent());
            uiRoot = null;
        }
    }

    @Override
    public void deleteShape(final WiresBaseShape shape) {
        if (Window.confirm(GuidedDecisionTreeConstants.INSTANCE.confirmDeleteDecisionTreeNode())) {

            if (uiRoot != null && uiRoot.equals(shape)) {
                uiRoot = null;
                model.setRoot(null);

            } else if (shape instanceof BaseGuidedDecisionTreeShape) {
                final BaseGuidedDecisionTreeShape uiChild = (BaseGuidedDecisionTreeShape) shape;
                if (uiChild.getParentNode() instanceof BaseGuidedDecisionTreeShape) {
                    final BaseGuidedDecisionTreeShape uiParent = (BaseGuidedDecisionTreeShape) uiChild
                            .getParentNode();
                    uiParent.getModelNode().removeChild(uiChild.getModelNode());
                }
            }

            shapeDeletedEvent.fire(new ShapeDeletedEvent(shape));

            layout();
        }
    }

    @Override
    public void forceDeleteShape(final WiresBaseShape shape) {
        shapeDeletedEvent.fire(new ShapeDeletedEvent(shape));
    }

    public void onShapeDeleted(@Observes ShapeDeletedEvent event) {
        super.deleteShape(event.getShape());
        if (getShapesInCanvas().isEmpty()) {
            showGettingStartedHint();
        }
    }

    @Override
    public void addShape(final WiresBaseShape shape) {
        super.addShape(shape);

        //Attach relevant handlers
        if (shape instanceof RequiresLayoutManager) {
            ((RequiresLayoutManager) shape).setLayoutManager(layoutManager);
        }
        if (shape instanceof BaseGuidedDecisionTreeShape) {
            ((BaseGuidedDecisionTreeShape) shape).setPresenter(presenter);
        }

        if (!getShapesInCanvas().isEmpty()) {
            hideGettingStartedHint();
        }

        layout();
    }

    public void setModel(final GuidedDecisionTree model, final boolean isReadOnly) {
        this.uiRoot = null;
        this.model = model;

        //Clear existing state
        super.clear();
        clearEvent.fire(new ClearEvent());

        //Walk model creating UIModel
        final TypeNode root = model.getRoot();
        if (root != null) {
            final WiresBaseTreeNode uiRoot = typeNodeFactory.getShape(root, isReadOnly);
            this.uiRoot = uiRoot;

            processChildren(root, uiRoot, isReadOnly);

            final Map<WiresBaseShape, Point2D> layout = layoutManager.getLayoutInformation(uiRoot);
            final Rectangle2D canvasBounds = WiresLayoutUtilities.alignLayoutInCanvas(layout);
            for (Map.Entry<WiresBaseShape, Point2D> e : layout.entrySet()) {
                final Point2D destination = new Point2D(e.getValue().getX(), e.getValue().getY());

                e.getKey().setLocation(destination);
            }

            WiresLayoutUtilities.resizeViewPort(canvasBounds, canvasLayer.getViewport());
        }

        if (shapesInCanvas.isEmpty()) {
            showGettingStartedHint();
        }

        canvasLayer.draw();
    }

    private void processChildren(final Node node, final WiresBaseTreeNode uiNode, final boolean isReadOnly) {
        uiNode.setSelectionManager(this);
        uiNode.setShapesManager(this);
        uiNode.setLayoutManager(layoutManager);
        if (uiNode instanceof BaseGuidedDecisionTreeShape) {
            ((BaseGuidedDecisionTreeShape) uiNode).setPresenter(presenter);
        }
        canvasLayer.add(uiNode);
        shapesInCanvas.add(uiNode);

        final Iterator<Node> itr = node.iterator();
        while (itr.hasNext()) {
            final Node child = itr.next();
            WiresBaseTreeNode uiChildNode = null;
            if (child instanceof TypeNode) {
                uiChildNode = typeNodeFactory.getShape((TypeNode) child, isReadOnly);

            } else if (child instanceof ConstraintNode) {
                uiChildNode = constraintNodeFactory.getShape((ConstraintNode) child, isReadOnly);

            } else if (child instanceof ActionInsertNode) {
                uiChildNode = actionInsertNodeFactory.getShape((ActionInsertNode) child, isReadOnly);

            } else if (child instanceof ActionUpdateNode) {
                uiChildNode = actionUpdateNodeFactory.getShape((ActionUpdateNode) child, isReadOnly);

            } else if (child instanceof ActionRetractNode) {
                uiChildNode = actionRetractNodeFactory.getShape((ActionRetractNode) child, isReadOnly);
            }

            if (uiChildNode != null) {
                uiNode.addChildNode(uiChildNode);
                processChildren(child, uiChildNode, isReadOnly);
            }
        }
    }

    protected BaseGuidedDecisionTreeShape getParentNode(final BaseGuidedDecisionTreeShape uiChild, final double cx,
            final double cy) {
        BaseGuidedDecisionTreeShape uiProspectiveParent = null;
        double finalDistance = Double.MAX_VALUE;
        for (WiresBaseShape ws : getShapesInCanvas()) {
            if (ws.isVisible()) {
                if (ws instanceof BaseGuidedDecisionTreeShape) {
                    final BaseGuidedDecisionTreeShape uiNode = (BaseGuidedDecisionTreeShape) ws;
                    if (uiNode.acceptChildNode(uiChild) && !uiNode.hasCollapsedChildren()) {
                        double deltaX = cx - uiNode.getX();
                        double deltaY = cy - uiNode.getY();
                        double distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));

                        if (finalDistance > distance) {
                            finalDistance = distance;
                            uiProspectiveParent = uiNode;
                        }
                    }
                }
            }
        }

        //If we're too far away from a parent we might as well not have a parent
        if (finalDistance > MAX_PROXIMITY) {
            uiProspectiveParent = null;
        }
        return uiProspectiveParent;
    }

    private void layout() {
        //Get layout information
        final Map<WiresBaseShape, Point2D> layout = layoutManager.getLayoutInformation(uiRoot);
        final Rectangle2D canvasBounds = WiresLayoutUtilities.alignLayoutInCanvas(layout);

        //Run an animation to move WiresBaseTreeNodes from their current position to the target position
        uiRoot.animate(AnimationTweener.EASE_OUT, new AnimationProperties(), ANIMATION_DURATION,
                new IAnimationCallback() {

                    private final Map<WiresBaseShape, Pair<Point2D, Point2D>> transformations = new HashMap<WiresBaseShape, Pair<Point2D, Point2D>>();

                    @Override
                    public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Reposition nodes. First we store the WiresBaseTreeNode together with its current position and target position
                        transformations.clear();
                        for (Map.Entry<WiresBaseShape, Point2D> e : layout.entrySet()) {
                            final Point2D origin = e.getKey().getLocation();
                            final Point2D destination = new Point2D(e.getValue().getX(), e.getValue().getY());
                            transformations.put(e.getKey(), new Pair<Point2D, Point2D>(origin, destination));
                        }
                        WiresLayoutUtilities.resizeViewPort(canvasBounds, canvasLayer.getViewport());
                    }

                    @Override
                    public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Lienzo's IAnimation.getPercent() passes values > 1.0
                        final double pct = iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent();

                        //Move each descendant along the line between its origin and the target destination
                        for (Map.Entry<WiresBaseShape, Pair<Point2D, Point2D>> e : transformations.entrySet()) {
                            final Point2D descendantOrigin = e.getValue().getK1();
                            final Point2D descendantTarget = e.getValue().getK2();
                            final double dx = (descendantTarget.getX() - descendantOrigin.getX()) * pct;
                            final double dy = (descendantTarget.getY() - descendantOrigin.getY()) * pct;
                            e.getKey().setX(descendantOrigin.getX() + dx);
                            e.getKey().setY(descendantOrigin.getY() + dy);
                        }

                        //Without this call Lienzo doesn't update the Canvas for sub-classes of WiresBaseTreeNode
                        uiRoot.getLayer().draw();
                    }

                    @Override
                    public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Nothing to do
                    }
                });

        canvasLayer.draw();
    }

    private void showGettingStartedHint() {
        if (isGettingStartedHintVisible) {
            return;
        }
        if (hint == null) {
            hint = new Group();
            final Rectangle hintRectangle = new Rectangle(600, 225, 15);
            hintRectangle.setStrokeWidth(2.0);
            hintRectangle.setStrokeColor("#6495ED");
            hintRectangle.setFillColor("#AFEEEE");
            hintRectangle.setAlpha(0.75);

            final Text hintText = new Text(GuidedDecisionTreeConstants.INSTANCE.gettingStartedHint(),
                    ShapeFactoryUtil.FONT_FAMILY_DESCRIPTION, 18);
            hintText.setTextAlign(TextAlign.CENTER);
            hintText.setTextBaseLine(TextBaseLine.MIDDLE);
            hintText.setFillColor("#6495ED");
            hintText.setX(hintRectangle.getWidth() / 2);
            hintText.setY(hintRectangle.getHeight() / 2);

            hint.setX((canvasLayer.getWidth() - hintRectangle.getWidth()) / 2);
            hint.setY((canvasLayer.getHeight() / 3) - (hintRectangle.getHeight() / 2));
            hint.add(hintRectangle);
            hint.add(hintText);
        }

        hint.animate(AnimationTweener.LINEAR, new AnimationProperties(), ANIMATION_DURATION,
                new IAnimationCallback() {

                    @Override
                    public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        hint.setAlpha(0.0);
                        canvasLayer.add(hint);
                        isGettingStartedHintVisible = true;
                    }

                    @Override
                    public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Lienzo's IAnimation.getPercent() passes values > 1.0
                        final double pct = iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent();
                        hint.setAlpha(pct);
                        hint.getLayer().draw();
                    }

                    @Override
                    public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Nothing to do
                    }
                });
    }

    private void hideGettingStartedHint() {
        if (!isGettingStartedHintVisible) {
            return;
        }
        hint.animate(AnimationTweener.LINEAR, new AnimationProperties(), ANIMATION_DURATION,
                new IAnimationCallback() {

                    @Override
                    public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Nothing to do
                    }

                    @Override
                    public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        //Lienzo's IAnimation.getPercent() passes values > 1.0
                        final double pct = iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent();
                        hint.setAlpha(1 - pct);
                        hint.getLayer().draw();
                    }

                    @Override
                    public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) {
                        canvasLayer.remove(hint);
                        isGettingStartedHintVisible = false;
                    }
                });

    }

}