edu.kit.trufflehog.view.jung.visualization.FXVisualizationViewer.java Source code

Java tutorial

Introduction

Here is the source code for edu.kit.trufflehog.view.jung.visualization.FXVisualizationViewer.java

Source

/*
 * This file is part of TruffleHog.
 *
 * TruffleHog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * TruffleHog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with TruffleHog.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.kit.trufflehog.view.jung.visualization;

import edu.kit.trufflehog.command.usercommand.IUserCommand;
import edu.kit.trufflehog.command.usercommand.SelectionContextMenuCommand;
import edu.kit.trufflehog.interaction.GraphInteraction;
import edu.kit.trufflehog.model.jung.layout.ObservableLayout;
import edu.kit.trufflehog.model.network.INetworkViewPort;
import edu.kit.trufflehog.model.network.graph.IConnection;
import edu.kit.trufflehog.model.network.graph.INode;
import edu.kit.trufflehog.model.network.graph.components.IRenderer;
import edu.kit.trufflehog.model.network.graph.components.ViewComponent;
import edu.kit.trufflehog.model.network.graph.components.edge.EdgeStatisticsComponent;
import edu.kit.trufflehog.model.network.graph.components.edge.IEdgeRenderer;
import edu.kit.trufflehog.model.network.graph.components.node.NodeInfoComponent;
import edu.kit.trufflehog.model.network.graph.components.node.NodeStatisticsComponent;
import edu.kit.trufflehog.util.IListener;
import edu.kit.trufflehog.util.INotifier;
import edu.kit.trufflehog.util.Notifier;
import edu.kit.trufflehog.util.bindings.MyBindings;
import edu.kit.trufflehog.view.controllers.IViewController;
import edu.uci.ics.jung.algorithms.layout.FRLayout2;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.event.GraphEvent;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationModel;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.RenderingHints;
import java.awt.event.ItemListener;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * \brief
 * \details
 * \date 21.03.16
 * \copyright GNU Public License
 *
 * @author Jan Hermes
 * @version 0.0.1
 */
public class FXVisualizationViewer extends Pane
        implements VisualizationServer<INode, IConnection>, IViewController<GraphInteraction> {

    private static final Logger logger = LogManager.getLogger();

    private final PannableCanvas canvas;
    private final NodeGestures nodeGestures;
    private final EdgeGestures edgeGestures = new EdgeGestures();

    //private boolean wasRightClicked = false;

    private final SelectionModel selectionModel = new SelectionModel();

    private final PickedState<INode> pickedNodes = new MyPickedState<>(selectionModel.selectedVertices);

    private final INotifier<IUserCommand> viewControllerNotifier = new Notifier<IUserCommand>() {
        @Override
        public boolean addListener(IListener<IUserCommand> listener) {
            return super.addListener(listener);
        }
    };

    /** The commands that are mapped to their interactions. **/
    private final Map<GraphInteraction, IUserCommand> interactionMap = new EnumMap<>(GraphInteraction.class);

    private ObservableLayout<INode, IConnection> layout;

    private INetworkViewPort port;

    public FXVisualizationViewer(INetworkViewPort port) {

        this.port = port;
        // create canvas
        this.setStyle("-fx-background-color: #213245");

        for (double divide = .25; divide < 1; divide += .25) {
            final Line line = new Line(0, 200, this.getWidth(), this.getHeight());
            line.setFill(null);
            line.setStroke(Color.web("0x385172"));
            line.setStrokeWidth(1.3);
            line.getStrokeDashArray().addAll(5d, 10d);

            line.startYProperty().bind(this.heightProperty().multiply(divide));

            line.endXProperty().bind(this.widthProperty());
            line.endYProperty().bind(this.heightProperty().multiply(divide));

            this.getChildren().add(line);
        }

        for (double divide = .25; divide < 1; divide += .25) {
            final Line line = new Line(0, 0, this.getWidth(), this.getHeight());
            line.setFill(null);
            line.setStroke(Color.web("0x385172"));
            line.setStrokeWidth(1.3);
            line.getStrokeDashArray().addAll(5d, 10d);

            line.startXProperty().bind(this.widthProperty().multiply(divide));

            line.endXProperty().bind(this.widthProperty().multiply(divide));
            line.endYProperty().bind(this.heightProperty());

            this.getChildren().add(line);
        }

        //StackPane spane = new StackPane();
        //spane.setBackground(new Background(new BackgroundFill(Color.BEIGE, null, null)));

        Pane ghost = new Pane();
        canvas = new PannableCanvas(ghost);

        Pane parent = new Pane();
        parent.getChildren().add(ghost);
        parent.getChildren().add(canvas);

        SceneGestures sceneGestures = new SceneGestures(parent, canvas);

        addEventFilter(MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
        addEventFilter(MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
        addEventFilter(ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());

        new RubberBandSelection(this);

        // TODO make canvas transparent
        //canvas.setStyle("-fx-background-color: #1d1d1d");

        // we don't want the canvas on the top/left in this example => just
        // translate it a bit
        //canvas.setTranslateX(100);
        //canvas.setTranslateY(100);

        // create sample nodes which can be dragged
        nodeGestures = new NodeGestures();
        //this.getChildren().add(spane);
        this.getChildren().add(parent);

        this.layout = port.getDelegate();

        //this.layout.getGraph().getVertices().forEach(v -> Platform.runLater(() -> this.initVertex(v)));
        //this.layout.getGraph().getEdges().forEach(e -> Platform.runLater(() -> this.initEdge(e)));

        this.layout.getObservableGraph().addGraphEventListener(e -> {

            //Platform.runLater(() -> {

            switch (e.getType()) {
            case VERTEX_ADDED:
                final INode node = ((GraphEvent.Vertex<INode, IConnection>) e).getVertex();
                Platform.runLater(() -> initVertex(node));
                break;

            case EDGE_ADDED:
                final IConnection edge = ((GraphEvent.Edge<INode, IConnection>) e).getEdge();
                Platform.runLater(() -> initEdge(edge));
                break;

            case VERTEX_CHANGED:
                break;

            case EDGE_CHANGED:
                final IConnection changedEdge = ((GraphEvent.Edge<INode, IConnection>) e).getEdge();
                Platform.runLater(() -> changedEdge.getComponent(ViewComponent.class).getRenderer().animate());
                break;
            }
            //});
        });

    }

    // TODO check if synch is needed
    synchronized private void initEdge(IConnection edge) {

        final Pair<INode> pair = this.layout.getGraph().getEndpoints(edge);

        if (pair.getSecond().getAddress().isMulticast()) {

            // TODO check or do something with this (generify the getShape command?)
            final Circle destCircle = (Circle) pair.getFirst().getComponent(ViewComponent.class).getRenderer()
                    .getShape();
            final Shape shape = edge.getComponent(ViewComponent.class).getRenderer().getShape();
            shape.layoutXProperty().bind(destCircle.layoutXProperty());
            shape.layoutYProperty().bind(destCircle.layoutYProperty());
            canvas.getChildren().add(shape);
            shape.setPickOnBounds(false);
            shape.setMouseTransparent(true);
            return;
        }

        // source and destination shape
        final Shape srcShape = pair.getFirst().getComponent(ViewComponent.class).getRenderer().getShape();
        final Shape destShape = pair.getSecond().getComponent(ViewComponent.class).getRenderer().getShape();

        // the positions of the shapes
        final DoubleBinding srcX = srcShape.layoutXProperty().add(srcShape.translateXProperty());
        final DoubleBinding srcY = srcShape.layoutYProperty().add(srcShape.translateYProperty());
        final DoubleBinding destX = destShape.layoutXProperty().add(destShape.translateXProperty());
        final DoubleBinding destY = destShape.layoutYProperty().add(destShape.translateYProperty());

        // the direction vector from source to destination (deltaX, deltaY)
        final DoubleBinding deltaX = destX.subtract(srcX);
        final DoubleBinding deltaY = destY.subtract(srcY);

        // the length of the direction vector
        final DoubleBinding length = MyBindings
                .sqrt(Bindings.add(MyBindings.pow2(deltaX), MyBindings.pow2(deltaY)));

        // the normalized direction vector
        final DoubleBinding normalX = deltaX.divide(length);
        final DoubleBinding normalY = deltaY.divide(length);

        // cast the shapes to circles (because right now i know they are circles) //TODO make this for arbitrary shapes
        final Circle destCircle = (Circle) destShape;
        final Circle srcCircle = (Circle) srcShape;

        // get the real source by multiplying the normal with the radius and the scale of the shape
        final DoubleBinding realSoureX = srcX
                .add(normalX.multiply(srcCircle.radiusProperty().multiply(srcShape.scaleXProperty())));
        final DoubleBinding realSoureY = srcY
                .add(normalY.multiply(srcCircle.radiusProperty().multiply(srcShape.scaleYProperty())));

        // get the real destination by multipling the normal vector with the length minus the radius and scale of the destination shape
        final DoubleBinding realDestX = srcX.add(normalX
                .multiply(length.subtract(destCircle.radiusProperty().multiply(destShape.scaleXProperty()))));
        final DoubleBinding realDestY = srcY.add(normalY
                .multiply(length.subtract(destCircle.radiusProperty().multiply(destShape.scaleYProperty()))));

        final IEdgeRenderer edgeRenderer = (IEdgeRenderer) edge.getComponent(ViewComponent.class).getRenderer();

        edgeRenderer.getArrowShape().layoutXProperty().bind(realDestX);
        edgeRenderer.getArrowShape().layoutYProperty().bind(realDestY);

        final QuadCurve curve = edgeRenderer.getLine();
        curve.setCacheHint(CacheHint.SPEED);

        curve.addEventFilter(MouseEvent.MOUSE_PRESSED, edgeGestures.getOnMousePressedEventHandler(edge));
        curve.addEventFilter(MouseEvent.MOUSE_RELEASED, edgeGestures.getOnMouseReleasedEventHandler(edge));
        edgeRenderer.getArrowShape().addEventFilter(MouseEvent.MOUSE_PRESSED,
                edgeGestures.getOnMousePressedEventHandler(edge));
        edgeRenderer.getArrowShape().addEventFilter(MouseEvent.MOUSE_RELEASED,
                edgeGestures.getOnMouseReleasedEventHandler(edge));

        /*        // make the edge clickable
                curve.setOnMouseClicked(e -> {
            
        if (e.getButton() != MouseButton.PRIMARY) {
            return;
        }
            
        logger.debug(edge.getSrc().getComponent(NodeInfoComponent.class).toString() + " --[" + edge.getComponent(EdgeStatisticsComponent.class).getTraffic() +
                "]-->" + edge.getDest().getComponent(NodeInfoComponent.class).toString());
            
        //            edge.getComponent(ViewComponent.class).getRenderer().togglePicked();
                });
            
                edgeRenderer.getArrowShape().setOnMouseClicked(e -> {
            
        if (e.getButton() != MouseButton.PRIMARY) {
            return;
        }
            
        logger.debug(edge.getSrc().getComponent(NodeInfoComponent.class).toString() + " --[" + edge.getComponent(EdgeStatisticsComponent.class).getTraffic() +
        "]-->" + edge.getDest().getComponent(NodeInfoComponent.class).toString());
            
        //            edge.getComponent(ViewComponent.class).getRenderer().togglePicked();
            
            
                });*/

        //get the edge size binding by dividing the total traffic with the local traffic
        DoubleBinding edgeSize = MyBindings
                .divideIntToDouble(edge.getComponent(EdgeStatisticsComponent.class).getTrafficProperty(),
                        port.getMaxConnectionSizeProperty())
                .multiply(8).add(2);
        curve.strokeWidthProperty().bind(edgeSize.multiply(edgeRenderer.edgeWidthMultiplierProperty()));

        // bind the ending to the real destionaion point
        curve.endXProperty().bind(realDestX);
        curve.endYProperty().bind(realDestY);

        // bind the source to the real source point
        curve.startXProperty().bind(realSoureX);
        curve.startYProperty().bind(realSoureY);

        NumberBinding normalVectorX = Bindings.subtract(realDestY, realSoureY).negate();
        NumberBinding normalVectorY = Bindings.subtract(realDestX, realSoureX);

        NumberBinding centerPointX = Bindings.divide(Bindings.add(curve.endXProperty(), curve.startXProperty()), 2);
        NumberBinding centerPointY = Bindings.divide(Bindings.add(curve.endYProperty(), curve.startYProperty()), 2);

        NumberBinding normalLength = MyBindings
                .sqrt(Bindings.add(normalVectorX.multiply(normalVectorX), normalVectorY.multiply(normalVectorY)));

        NumberBinding normalizedNVX = normalVectorX.divide(normalLength);
        NumberBinding normalizedNVY = normalVectorY.divide(normalLength);

        NumberBinding bezierPointOffset = length.multiply(.1);

        curve.controlXProperty().bind(Bindings.add(centerPointX, normalizedNVX.multiply(bezierPointOffset)));
        curve.controlYProperty().bind(Bindings.add(centerPointY, normalizedNVY.multiply(bezierPointOffset)));

        // TODO do this in component
        curve.setFill(null);

        canvas.getChildren().add(edgeRenderer.getArrowShape());
        // add the edge to the canvas
        canvas.getChildren().add(curve);
    }

    synchronized private void initVertex(INode vertex) {

        if (vertex.getAddress().isMulticast()) {
            return;
        }

        final IRenderer renderer = vertex.getComponent(ViewComponent.class).getRenderer();
        final Shape nodeShape = renderer.getShape();
        //nodeShape.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
        //nodeShape.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());

        //nodeShape.setCache(false);
        //nodeShape.setCacheHint(CacheHint.);

        final NodeStatisticsComponent nsc = vertex.getComponent(NodeStatisticsComponent.class);

        final DoubleBinding nodeSize = MyBindings
                .divideIntToDouble(nsc.getCommunicationCountProperty(), port.getMaxThroughputProperty()).add(1);

        nodeShape.scaleXProperty().bind(nodeSize);
        nodeShape.scaleYProperty().bind(nodeSize);

        nodeShape.setLayoutX(layout.transform(vertex).getX());
        nodeShape.setLayoutY(layout.transform(vertex).getY());

        ///////////
        // LABEL //
        ///////////

        Label nodeLabel = new Label();

        // cast the shapes to circles (because right now i know they are circles) //TODO make this for arbitrary shapes
        final Circle nodeCircle = (Circle) nodeShape;

        /*        final DoubleProperty labelX = new SimpleDoubleProperty();
                final DoubleProperty labelY = new SimpleDoubleProperty();*/

        //labelX.bind(nodeShape.layoutXProperty().add(nodeCircle.radiusProperty().multiply(nodeShape.scaleXProperty())));
        //labelY.bind(nodeShape.layoutYProperty().add(nodeCircle.radiusProperty().multiply(nodeShape.scaleYProperty())));

        //nodeLabel.layoutXProperty().bindBidirectionalWithOffset(labelX);
        //nodeLabel.layoutYProperty().bindBidirectionalWithOffset(labelY);

        //nodeLabel.layoutXProperty().bind(nodeShape.layoutXProperty().add(nodeCircle.radiusProperty().multiply(nodeShape.scaleXProperty())));

        nodeLabel.textFillProperty().bind(new SimpleObjectProperty<>(Color.WHITE));

        //MyBindings.bindBidirectionalWithOffset(nodeLabel.layoutXProperty(), nodeShape.layoutXProperty(), nodeCircle.radiusProperty().multiply(nodeShape.scaleXProperty()));
        //MyBindings.bindBidirectionalWithOffset(nodeLabel.layoutYProperty(), nodeShape.layoutYProperty(), nodeCircle.radiusProperty().multiply(nodeShape.scaleYProperty()));

        nodeLabel.layoutXProperty().bind(nodeShape.layoutXProperty().add(nodeShape.translateXProperty())
                .add(nodeCircle.radiusProperty().multiply(nodeShape.scaleXProperty())));
        nodeLabel.layoutYProperty().bind(nodeShape.layoutYProperty().add(nodeShape.translateYProperty())
                .add(nodeCircle.radiusProperty().multiply(nodeShape.scaleYProperty())));

        NodeInfoComponent nic = vertex.getComponent(NodeInfoComponent.class);
        if (nic != null) {
            nodeLabel.textProperty().bind(nic.toStringBinding());
        }

        nodeLabel.scaleXProperty().bind(Bindings.divide(1, canvas.scaleXProperty()));
        nodeLabel.scaleYProperty().bind(Bindings.divide(1, canvas.scaleYProperty()));

        nodeLabel.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler(vertex));
        nodeLabel.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler(vertex));
        nodeLabel.addEventFilter(MouseEvent.MOUSE_RELEASED, nodeGestures.getOnMouseReleasedEventHandler(vertex));

        nodeShape.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler(vertex));
        nodeShape.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler(vertex));
        nodeShape.addEventFilter(MouseEvent.MOUSE_RELEASED, nodeGestures.getOnMouseReleasedEventHandler(vertex));
        nodeShape.addEventFilter(MouseEvent.MOUSE_CLICKED, nodeGestures.getOnMouseClickedEventHandler(vertex));

        canvas.getChildren().addAll(nodeLabel, nodeShape);
    }

    synchronized public void refreshLayout() {

        //  logger.debug("refresh");
        final FRLayout2<INode, IConnection> l = new FRLayout2<>(this.layout.getObservableGraph());
        l.setMaxIterations(layout.getGraph().getEdgeCount() * (int) (this.getWidth() / canvas.getScale()));
        // l.setMaxIterations(700);
        this.layout = new ObservableLayout<>(l);
        //TODO make the dimension changeable from settings menu?

        // logger.debug(canvas.getScale() + " " + this.getWidth() + " " + this.getHeight());
        layout.setSize(new Dimension((int) (this.getWidth() / (2 * canvas.getScale())),
                (int) (this.getHeight() / (2 * canvas.getScale()))));

        //layout.set

        final Executor layouter = Executors.newSingleThreadExecutor();

        layouter.execute(() -> {

            while (!layout.done()) {
                layout.step();
                Platform.runLater(this::repaint);
            }

        });

    }

    @Override
    public void setDoubleBuffered(boolean b) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public boolean isDoubleBuffered() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public VisualizationModel<INode, IConnection> getModel() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setModel(VisualizationModel<INode, IConnection> visualizationModel) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void stateChanged(ChangeEvent changeEvent) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setRenderer(Renderer<INode, IConnection> renderer) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public Renderer<INode, IConnection> getRenderer() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setGraphLayout(Layout<INode, IConnection> layout) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public Layout<INode, IConnection> getGraphLayout() {
        return layout;
    }

    @Override
    public Map<RenderingHints.Key, Object> getRenderingHints() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setRenderingHints(Map<RenderingHints.Key, Object> map) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void addPreRenderPaintable(Paintable paintable) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void removePreRenderPaintable(Paintable paintable) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void addPostRenderPaintable(Paintable paintable) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void removePostRenderPaintable(Paintable paintable) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void addChangeListener(ChangeListener changeListener) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void removeChangeListener(ChangeListener changeListener) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public ChangeListener[] getChangeListeners() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void fireStateChanged() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public PickedState<INode> getPickedVertexState() {
        return pickedNodes;
    }

    @Override
    public PickedState<IConnection> getPickedEdgeState() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setPickedVertexState(PickedState<INode> pickedState) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setPickedEdgeState(PickedState<IConnection> pickedState) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public GraphElementAccessor<INode, IConnection> getPickSupport() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setPickSupport(GraphElementAccessor<INode, IConnection> graphElementAccessor) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public Point2D getCenter() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public RenderContext<INode, IConnection> getRenderContext() {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    public void setRenderContext(RenderContext<INode, IConnection> renderContext) {
        throw new UnsupportedOperationException("Operation not implemented yet");
    }

    @Override
    synchronized public void repaint() {

        this.layout.getGraph().getVertices().forEach(v -> {

            //this.layout = new FRLayout<>(observableGraph);

            final ViewComponent vc = v.getComponent(ViewComponent.class);

            //System.out.println(layout.transform(v));

            vc.getRenderer().getShape().setLayoutX(layout.transform(v).getX());
            vc.getRenderer().getShape().setLayoutY(layout.transform(v).getY());

        });

    }

    public PannableCanvas getCanvas() {
        return canvas;
    }

    private void onSelectionChanged() {

        final IUserCommand selectionCommand = interactionMap.get(GraphInteraction.SELECTION);

        if (selectionCommand == null) {
            logger.warn("There is no command registered to: " + GraphInteraction.SELECTION);
            return;
        }

        selectionCommand.setSelection(new ImmutablePair<>(new HashSet<>(selectionModel.getSelectedVertices()),
                new HashSet<>(selectionModel.getSelectedEdges())));

        notifyListeners(selectionCommand);

    }

    private void onSelectionContextMenu(double posX, double posY) {
        //FIXME: make this adequate
        final SelectionContextMenuCommand cmCommand = (SelectionContextMenuCommand) interactionMap
                .get(GraphInteraction.SELECTION_CONTEXTMENU);

        if (cmCommand == null) {
            logger.warn("There is no command registered to: " + GraphInteraction.SELECTION_CONTEXTMENU);
            return;
        }
        cmCommand.setSelection(new ImmutablePair<>(new HashSet<>(selectionModel.getSelectedVertices()),
                new HashSet<>(selectionModel.getSelectedEdges())));
        cmCommand.setPosX(posX);
        cmCommand.setPosY(posY);

        notifyListeners(cmCommand);
    }

    @Override
    public final boolean addListener(final IListener<IUserCommand> listener) {

        return viewControllerNotifier.addListener(listener);
    }

    @Override
    public final boolean removeListener(final IListener<IUserCommand> listener) {
        return viewControllerNotifier.removeListener(listener);
    }

    @Override
    public final void notifyListeners(final IUserCommand message) {
        viewControllerNotifier.notifyListeners(message);
    }

    @Override
    public void addCommand(GraphInteraction interaction, IUserCommand command) {
        interactionMap.put(interaction, command);
    }

    public void selectAllNodes() {

        getGraphLayout().getGraph().getVertices().stream().forEach(vertex -> {

            /*final IRenderer renderer = vertex.getComponent(ViewComponent.class).getRenderer();
                
            if (!renderer.picked()) {
            selectedNodes.add(vertex);
            Platform.runLater(() -> {
                renderer.isPicked(true);
            });
            }*/
        });
        onSelectionChanged();
    }

    private static class MyPickedState<T> implements PickedState<T> {

        private final Set<T> pickedNodes;

        public MyPickedState(Set<T> selectedNodes) {

            this.pickedNodes = selectedNodes;
        }

        @Override
        public boolean pick(T v, boolean b) {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }

        @Override
        public void clear() {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }

        @Override
        public Set<T> getPicked() {
            return pickedNodes;
        }

        @Override
        public boolean isPicked(T v) {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }

        @Override
        public Object[] getSelectedObjects() {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }

        @Override
        public void addItemListener(ItemListener l) {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }

        @Override
        public void removeItemListener(ItemListener l) {
            //TODO implement this method
            throw new UnsupportedOperationException("Not implemented yet");
        }
    }

    /*    private class Selectable extends Region {
        
    ImageView view;
        
    public Selectable( double width, double height) {
        
        view = new ImageView( image);
        view.setFitWidth(width);
        view.setFitHeight(height);
        
        getChildren().add( view);
        
        this.setPrefSize(width, height);
    }
        
        }*/

    private class SelectionModel {

        private final Set<Node> selection = new HashSet<>();

        private final Set<INode> selectedVertices = new HashSet<>();

        private final Set<IConnection> selectedEdges = new HashSet<>();

        //private final Set<Node> lineSelection = new HashSet<>();

        public Set<INode> getSelectedVertices() {

            return selectedVertices;
        }

        public Set<IConnection> getSelectedEdges() {
            return selectedEdges;
        }

        public Set<Node> getSelectedNodes() {

            return selection;
        }

        public void add(IConnection c) {

            IEdgeRenderer renderer = (IEdgeRenderer) c.getComponent(ViewComponent.class).getRenderer();

            Platform.runLater(() -> {

                renderer.setCurrentFill(renderer.getArrowFillPicked());
            });
            //renderer.getArrowShape().setFill(renderer.getArrowFillPicked());
            //renderer.getLine().setFill(renderer.getArrowFillPicked());

            selectedEdges.add(c);

            onSelectionChanged();
        }

        public void add(INode v) {

            final IRenderer renderer = v.getComponent(ViewComponent.class).getRenderer();
            Platform.runLater(() -> {

                renderer.getShape().setStroke(Color.WHITE);
            });

            //renderer.getShape().setStrokeWidth(3);

            //renderer.getShape().setStyle("fx-stroke: WHITE;");

            //if (!renderer.getShape().getStyleClass().contains("picked")) {
            //   renderer.getShape().getStyleClass().add("picked");
            //}

            selection.add(renderer.getShape());
            selectedVertices.add(v);

            onSelectionChanged();

        }

        public void remove(INode v) {

            final IRenderer renderer = v.getComponent(ViewComponent.class).getRenderer();

            //renderer.getShape().setStyle("fx-stroke: null;");

            Platform.runLater(() -> {

                renderer.getShape().setStroke(null);
            });
            //renderer.getShape().getStyleClass().remove("picked");
            selection.remove(renderer.getShape());
            selectedVertices.remove(v);

            onSelectionChanged();

        }

        public void remove(IConnection c) {

            final IEdgeRenderer renderer = (IEdgeRenderer) c.getComponent(ViewComponent.class).getRenderer();

            Platform.runLater(() -> {

                renderer.setCurrentFill(renderer.getArrowFillUnpicked());
            });

            // renderer.getLine().setFill(renderer.getArrowFillUnpicked());

            selectedEdges.remove(c);

            onSelectionChanged();
        }

        public void clear() {
            clearVertices();
            clearEdges();
        }

        public void clearVertices() {

            while (!selectedVertices.isEmpty()) {
                remove(selectedVertices.iterator().next());
            }
        }

        public void clearEdges() {
            while (!selectedEdges.isEmpty()) {
                remove(selectedEdges.iterator().next());
            }
        }

        public boolean contains(Node node) {
            return selection.contains(node);
        }

        public boolean contains(INode v) {
            return selectedVertices.contains(v);
        }

        public boolean contains(IConnection c) {
            return selectedEdges.contains(c);
        }

        public int size() {
            return selectedVertices.size();
        }

        public void log() {
            System.out.println("Items in model: " + Arrays.asList(selectedVertices.toArray()));
        }

    }

    private class RubberBandSelection {

        final DragContext dragContext = new DragContext();
        Rectangle rect;

        Pane group;
        boolean enabled = false;

        boolean wasPrimary = false;

        public RubberBandSelection(Pane group) {

            this.group = group;

            rect = new Rectangle(0, 0, 0, 0);
            rect.setStroke(Color.web("0x4089BF"));
            rect.setStrokeWidth(1);
            rect.setStrokeLineCap(StrokeLineCap.ROUND);
            rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));

            group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
        }

        EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                if (!event.isPrimaryButtonDown()) {
                    return;
                }

                wasPrimary = true;

                // simple flag to prevent multiple handling of this event or we'd get an exception because rect is already on the scene
                // eg if you drag with left mouse button and while doing that click the right mouse button
                if (enabled) {
                    return;
                }
                dragContext.mouseAnchorX = event.getSceneX();
                dragContext.mouseAnchorY = event.getSceneY();

                Platform.runLater(() -> {

                    rect.setX(dragContext.mouseAnchorX);
                    rect.setY(dragContext.mouseAnchorY);
                    rect.setWidth(0);
                    rect.setHeight(0);
                    group.getChildren().add(rect);
                });

                event.consume();
                enabled = true;
            }
        };

        EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                if (!wasPrimary) {
                    return;
                }

                if (!event.isShiftDown() && !event.isControlDown()) {
                    selectionModel.clear();
                    onSelectionContextMenu(0, 0);
                }
                getGraphLayout().getGraph().getVertices().stream().forEach(vertex -> {

                    final IRenderer renderer = vertex.getComponent(ViewComponent.class).getRenderer();

                    if (rect.localToScene(rect.getBoundsInLocal())
                            .intersects(renderer.getShape().localToScene(renderer.getShape().getBoundsInLocal()))) {

                        selectionModel.add(vertex);
                    }
                    /*
                                        if (renderer.getShape().getBoundsInParent().intersects(rect.getBoundsInParent())) {
                        
                    selectionModel.add(vertex);
                                        }*/
                });
                /*getGraphLayout().getGraph().getVertices().stream().forEach(vertex -> {
                    
                });
                onSelectionChanged();
                    
                for( Node node: group.getChildren()) {
                    
                    if( node.getBoundsInParent().intersects( rect.getBoundsInParent())) {
                    
                        if( event.isShiftDown()) {
                    
                            selectionModel.add( node);
                    
                        } else if( event.isControlDown()) {
                    
                            if( selectionModel.contains( node)) {
                                selectionModel.remove( node);
                            } else {
                                selectionModel.add( node);
                            }
                        } else {
                            selectionModel.add( node);
                        }
                    
                    }
                    
                    
                }*/

                //selectionModel.log();

                Platform.runLater(() -> {

                    rect.setX(0);
                    rect.setY(0);
                    rect.setWidth(0);
                    rect.setHeight(0);

                    group.getChildren().remove(rect);

                });

                event.consume();

                enabled = false;
                wasPrimary = false;
            }
        };

        EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                final double parentScaleX = group.localToSceneTransformProperty().getValue().getMxx();
                final double parentScaleY = group.localToSceneTransformProperty().getValue().getMyy();

                dragContext.translateAnchorX = event.getSceneX();
                dragContext.translateAnchorY = event.getSceneY();

                dragContext.mouseAnchorX = Math.max(dragContext.mouseAnchorX, 0);
                dragContext.mouseAnchorY = Math.max(dragContext.mouseAnchorY, 0);

                dragContext.translateAnchorX = Math.max(dragContext.translateAnchorX, 0);
                dragContext.translateAnchorY = Math.max(dragContext.translateAnchorY, 0);

                double x = Math.min(dragContext.mouseAnchorX, dragContext.translateAnchorX);
                double y = Math.min(dragContext.mouseAnchorY, dragContext.translateAnchorY);

                double width = Math.abs(dragContext.translateAnchorX - dragContext.mouseAnchorX);
                double height = Math.abs(dragContext.translateAnchorY - dragContext.mouseAnchorY);

                Platform.runLater(() -> {

                    rect.setX(x);
                    rect.setY(y);
                    rect.setWidth(width);
                    rect.setHeight(height);
                });

                event.consume();
            }
        };

        //        private final class DragContext {
        //
        //            public double mouseAnchorX;
        //            public double mouseAnchorY;
        //
        //
        //        }
    }

    private class EdgeGestures {

        private boolean enabled = false;

        public EdgeGestures() {

        }

        public EventHandler<MouseEvent> getOnMousePressedEventHandler(IConnection c) {

            return event -> {

                // left mouse button => dragging
                if (!event.isPrimaryButtonDown())
                    return;

                // don't do anything if the user is in the process of adding to the selection model
                if (event.isControlDown() || event.isShiftDown()) {

                    if (selectionModel.contains(c)) {
                        selectionModel.remove(c);
                    } else {

                        selectionModel.add(c);
                    }
                    return;
                }
                final Node node = (Node) event.getSource();

                // clearVertices the model if the current node isn't in the selection => new selection
                if (!selectionModel.contains(c)) {
                    selectionModel.clearEdges();
                    selectionModel.add(c);
                }
                // flag that the mouse released handler should consume the event, so it won't bubble up to the pane which has a rubberband selection mouse released handler
                enabled = true;

                // prevent rubberband selection handler
                event.consume();
            };
        }

        public EventHandler<MouseEvent> getOnMouseReleasedEventHandler(IConnection n) {

            return event -> {

                // prevent rubberband selection handler
                if (enabled) {

                    enabled = false;
                    event.consume();
                }
            };
        }

    }

    private class NodeGestures {

        private DragContext dragContext = new DragContext();

        private boolean enabled = false;

        private boolean wasRightClick = false;

        public NodeGestures() {

        }

        public EventHandler<MouseEvent> getOnMousePressedEventHandler(INode n) {

            return event -> {

                if (event.isSecondaryButtonDown()) {
                    wasRightClick = true;
                    return;
                }

                wasRightClick = false;

                // left mouse button => dragging
                if (event.isPrimaryButtonDown()) {
                    // don't do anything if the user is in the process of adding to the selection model
                    if (event.isControlDown() || event.isShiftDown()) {

                        if (selectionModel.contains(n)) {
                            selectionModel.remove(n);
                        } else {

                            selectionModel.add(n);
                        }
                        return;
                    }
                    final Node node = (Node) event.getSource();

                    dragContext.startX = event.getSceneX();
                    dragContext.startY = event.getSceneY();

                    // clearVertices the model if the current node isn't in the selection => new selection
                    if (!selectionModel.contains(n)) {
                        selectionModel.clearVertices();
                        selectionModel.add(n);
                    }
                    // flag that the mouse released handler should consume the event, so it won't bubble up to the pane which has a rubberband selection mouse released handler
                    enabled = true;
                }
                // prevent rubberband selection handler
                event.consume();
            };
        }

        public EventHandler<MouseEvent> getOnMouseDraggedEventHandler(INode n) {

            return event -> {

                wasRightClick = false;

                // left mouse button => dragging
                if (!event.isPrimaryButtonDown())
                    return;

                if (!enabled) {
                    return;
                }
                // all in selection
                for (Node node : selectionModel.getSelectedNodes()) {

                    Platform.runLater(() -> {

                        node.setTranslateX((event.getSceneX() - dragContext.startX) / canvas.getScale());
                        node.setTranslateY((event.getSceneY() - dragContext.startY) / canvas.getScale());
                    });
                }
            };
        }

        public EventHandler<MouseEvent> getOnMouseClickedEventHandler(INode n) {

            return event -> {

                if (wasRightClick) {

                    if (selectionModel.contains(n)) {

                        onSelectionContextMenu(event.getScreenX(), event.getScreenY());
                    }

                    logger.debug("was right");
                }

                wasRightClick = false;

            };

        }

        public EventHandler<MouseEvent> getOnMouseReleasedEventHandler(INode n) {

            return event -> {

                // prevent rubberband selection handler
                if (enabled) {

                    // set node's layout position to current position,remove translate coordinates
                    //selectionModel.selection.forEach(this::fixPosition);

                    selectionModel.getSelectedNodes().stream().forEach(node -> {

                        Platform.runLater(() -> {

                            node.setLayoutX(node.getLayoutX() + node.getTranslateX());
                            node.setLayoutY(node.getLayoutY() + node.getTranslateY());
                            node.setTranslateX(0);
                            node.setTranslateY(0);
                        });
                    });
                    enabled = false;

                }
                /*                } else {
                if (enabled) enabled = false;
                //selectionModel.clear();
                if (!selectionModel.contains(n)) selectionModel.add(n);
                    
                onSelectionContextMenu(event.getScreenX(), event.getScreenY());
                    
                wasRightClicked = false;
                                }*/
                event.consume();
            };
        }
    }
}