Java tutorial
/* * 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; } }); } }