public class Commands
{

    private static final Logger LOGGER = LoggerFactory.getLogger(Commands.class);

    private static final EReference NODES = GraphPackage.Literals.GMODEL__NODES;
    private static final EReference CONNECTIONS = GraphPackage.Literals.GMODEL__CONNECTIONS;

    private static final EAttribute NODE_X = GraphPackage.Literals.GNODE__X;
    private static final EAttribute NODE_Y = GraphPackage.Literals.GNODE__Y;
    private static final EAttribute NODE_WIDTH = GraphPackage.Literals.GNODE__WIDTH;
    private static final EAttribute NODE_HEIGHT = GraphPackage.Literals.GNODE__HEIGHT;
    private static final EReference NODE_CONNECTORS = GraphPackage.Literals.GNODE__CONNECTORS;

    private static final EReference CONNECTOR_CONNECTIONS = GraphPackage.Literals.GCONNECTOR__CONNECTIONS;

    private static final EAttribute JOINT_X = GraphPackage.Literals.GJOINT__X;
    private static final EAttribute JOINT_Y = GraphPackage.Literals.GJOINT__Y;

    /**
     * Static class, not to be instantiated.
     */
    private Commands()
    {
    }

    /**
     * Adds a node to the model.
     *
     * <p>
     * The node's x, y, width, and height values should be set before calling this method.
     * </p>
     *
     * @param model the {@link GModel} to which the node should be added
     * @param node the {@link GNode} to add to the model
     */
    public static void addNode(final GModel model, final GNode node)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null)
        {
            final Command command = AddCommand.create(editingDomain, model, NODES, node);

            if (command.canExecute())
            {
                editingDomain.getCommandStack().execute(command);
            }
        }
    }

    /**
     * Removes a node from the model.
     *
     * <p>
     * Also removes any connections that were attached to the node.
     * </p>
     *
     * @param model the {@link GModel} from which the node should be removed
     * @param node the {@link GNode} to remove from the model
     */
    public static void removeNode(final GModel model, final GNode node)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null)
        {
            final CompoundCommand command = new CompoundCommand();
            command.append(RemoveCommand.create(editingDomain, model, NODES, node));

            final List<GConnection> connectionsToDelete = new ArrayList<>();

            for (final GConnector connector : node.getConnectors())
            {
                for (final GConnection connection : connector.getConnections())
                {
                    if (connection != null && !connectionsToDelete.contains(connection))
                    {
                        connectionsToDelete.add(connection);
                    }
                }
            }

            for (final GConnection connection : connectionsToDelete)
            {
                command.append(RemoveCommand.create(editingDomain, model, CONNECTIONS, connection));

                final GConnector source = connection.getSource();
                final GConnector target = connection.getTarget();

                if (!node.equals(source.getParent()))
                {
                    command.append(RemoveCommand.create(editingDomain, source, CONNECTOR_CONNECTIONS, connection));
                }

                if (!node.equals(target.getParent()))
                {
                    command.append(RemoveCommand.create(editingDomain, target, CONNECTOR_CONNECTIONS, connection));
                }
            }

            if (command.canExecute())
            {
                editingDomain.getCommandStack().execute(command);
            }
        }
    }

    /**
     * Clears everything in the given model.
     *
     * @param model the {@link GModel} to be cleared
     */
    public static void clear(final GModel model)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null)
        {
            final CompoundCommand command = new CompoundCommand();

            command.append(RemoveCommand.create(editingDomain, model, CONNECTIONS, model.getConnections()));
            command.append(RemoveCommand.create(editingDomain, model, NODES, model.getNodes()));

            if (command.canExecute())
            {
                editingDomain.getCommandStack().execute(command);
            }
        }
    }

    /**
     * Removes all connectors from the given nodes, and all connections attached to them.
     *
     * @param model the {@link GModel} being edited
     * @param nodes a list of {@link GNode} instances whose connectors should be removed
     */
    public static void clearConnectors(final GModel model, final List<GNode> nodes)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null)
        {
            final CompoundCommand command = new CompoundCommand();

            final Set<GConnection> connectionsToRemove = new HashSet<>();
            final Set<GConnector> connectorsToRemove = new HashSet<>();

            for (final GNode node : nodes)
            {
                command.append(RemoveCommand.create(editingDomain, node, NODE_CONNECTORS, node.getConnectors()));
                connectorsToRemove.addAll(node.getConnectors());

                for (final GConnector connector : node.getConnectors())
                {
                    connectionsToRemove.addAll(connector.getConnections());
                }
            }

            for (final GConnection connection : connectionsToRemove)
            {
                final GConnector source = connection.getSource();
                final GConnector target = connection.getTarget();

                if (!connectorsToRemove.contains(source))
                {
                    command.append(RemoveCommand.create(editingDomain, source, CONNECTOR_CONNECTIONS, connection));
                }

                if (!connectorsToRemove.contains(target))
                {
                    command.append(RemoveCommand.create(editingDomain, target, CONNECTOR_CONNECTIONS, connection));
                }
            }

            command.append(RemoveCommand.create(editingDomain, model, CONNECTIONS, connectionsToRemove));

            if (command.canExecute())
            {
                editingDomain.getCommandStack().execute(command);
            }
        }
    }

    /**
     * Updates the model's layout values to match those in the skin instances.
     *
     * <p>
     * This method adds set operations to the given compound command but does <b>not</b> execute it.
     * </p>
     *
     * @param command a {@link CompoundCommand} to which the set commands will be added
     * @param model the {@link GModel} whose layout values should be updated
     * @param skinLookup the {@link SkinLookup} in use for this graph editor instance
     */
    public static void updateLayoutValues(final CompoundCommand command, final GModel model,
            final SkinLookup skinLookup)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null)
        {
            for (final GNode node : model.getNodes())
            {
                final GNodeSkin nodeSkin = skinLookup.lookupNode(node);
                if (nodeSkin != null && checkNodeChanged(node, nodeSkin))
                {
                    final Region nodeRegion = nodeSkin.getRoot();
                    command.append(SetCommand.create(editingDomain, node, NODE_X, nodeRegion.getLayoutX()));
                    command.append(SetCommand.create(editingDomain, node, NODE_Y, nodeRegion.getLayoutY()));
                    command.append(SetCommand.create(editingDomain, node, NODE_WIDTH, nodeRegion.getWidth()));
                    command.append(SetCommand.create(editingDomain, node, NODE_HEIGHT, nodeRegion.getHeight()));
                }
            }

            for (final GConnection connection : model.getConnections())
            {
                updateConnector(connection.getSource(), command, editingDomain, skinLookup);
                updateConnector(connection.getTarget(), command, editingDomain, skinLookup);

                for (final GJoint joint : connection.getJoints())
                {
                    final GJointSkin jointSkin = skinLookup.lookupJoint(joint);
                    if (jointSkin != null && checkJointChanged(joint, jointSkin))
                    {
                        final Region jointRegion = jointSkin.getRoot();
                        final double x = jointRegion.getLayoutX() + jointSkin.getWidth() / 2;
                        final double y = jointRegion.getLayoutY() + jointSkin.getHeight() / 2;

                        command.append(SetCommand.create(editingDomain, joint, JOINT_X, x));
                        command.append(SetCommand.create(editingDomain, joint, JOINT_Y, y));
                    }
                }
            }
        }
    }

    private static void updateConnector(final GConnector connector, final CompoundCommand command,
            final EditingDomain editingDomain, final SkinLookup skinLookup)
    {
        final GNode node = connector.getParent();
        final GConnectorSkin connectorSkin = skinLookup.lookupConnector(connector);
        final GNodeSkin nodeSkin = skinLookup.lookupNode(node);
        if (nodeSkin != null && connectorSkin != null)
        {
            final Point2D connectorPosition = nodeSkin.getConnectorPosition(connectorSkin);
            if (checkConnectorChanged(connector, connectorPosition))
            {
                command.append(SetCommand.create(editingDomain, connector, GraphPackage.Literals.GCONNECTOR__X,
                        connectorPosition.getX()));
                command.append(SetCommand.create(editingDomain, connector, GraphPackage.Literals.GCONNECTOR__Y,
                        connectorPosition.getY()));
            }
        }
    }

    /**
     * Checks if a connector's JavaFX region has different layout values than those
     * currently stored in the model.
     *
     * @param connector
     *            the model instance for the connector
     *
     * @return {@code true} if any layout value has changed, {@code false if not}
     */
    private static boolean checkConnectorChanged(final GConnector connector, final Point2D connectorPosition)
    {
        if (connectorPosition.getX() != connector.getX())
        {
            return true;
        }
        else if (connectorPosition.getY() != connector.getY())
        {
            return true;
        }
        return false;
    }

    /**
     * Checks if a node's JavaFX region has different layout values than those
     * currently stored in the model.
     *
     * @param node
     *            the model instance for the node
     *
     * @return {@code true} if any layout value has changed,
     *         {@code false if not}
     */
    private static boolean checkNodeChanged(final GNode node, final GNodeSkin nodeSkin)
    {
        final Region nodeRegion = nodeSkin.getRoot();

        if (nodeRegion.getLayoutX() != node.getX())
        {
            return true;
        }
        else if (nodeRegion.getLayoutY() != node.getY())
        {
            return true;
        }
        else if (nodeRegion.getWidth() != node.getWidth())
        {
            return true;
        }
        else if (nodeRegion.getHeight() != node.getHeight())
        {
            return true;
        }
        return false;
    }

    /**
     * Checks if a joint's JavaFX region has different layout values than those
     * currently stored in the model.
     *
     * @param joint
     *            the model instance for the joint
     *
     * @return {@code true} if any layout value has changed,
     *         {@code false if not}
     */
    private static boolean checkJointChanged(final GJoint joint, final GJointSkin jointSkin)
    {
        final Region jointRegion = jointSkin.getRoot();

        final double jointRegionX = jointRegion.getLayoutX() + jointSkin.getWidth() / 2;
        final double jointRegionY = jointRegion.getLayoutY() + jointSkin.getHeight() / 2;

        if (jointRegionX != joint.getX())
        {
            return true;
        }
        else if (jointRegionY != joint.getY())
        {
            return true;
        }
        return false;
    }

    /**
     * Attempts to undo the given model to its previous state.
     *
     * @param model the {@link GModel} to undo
     */
    public static void undo(final GModel model)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null && editingDomain.getCommandStack().canUndo())
        {
            editingDomain.getCommandStack().undo();
        }
    }

    /**
     * Attempts to redo the given model to its next state.
     *
     * @param model the {@link GModel} to redo
     */
    public static void redo(final GModel model)
    {
        final EditingDomain editingDomain = getEditingDomain(model);

        if (editingDomain != null && editingDomain.getCommandStack().canRedo())
        {
            editingDomain.getCommandStack().redo();
        }
    }

    /**
     * Gets the editing domain associated to the model.
     *
     * <p>
     * Logs an error if none is found.
     * </p>
     *
     * @param model a {@link GModel} instance
     * @return the {@link EditingDomain} associated to this model instance
     */
    private static EditingDomain getEditingDomain(final GModel model)
    {
        final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(model);

        if (editingDomain == null)
        {
            LOGGER.error("No editing domain found for this model. Maybe it hasn't been set inside a graph editor?"); //$NON-NLS-1$
        }

        return editingDomain;
    }
} public abstract class GConnectionSkin extends GSkin<GConnection> {

    /**
     * Cache the index of this connection skin inside the list of children of the
     * connection layer. As the graph editor grows the indexOf() lookup calls take
     * up a considerable amount of time.
     */
    private int mConnectionIndex;

    /**
     * Creates a new {@link GConnectionSkin}.
     *
     * @param connection the {@link GConnection} represented by the skin
     */
    public GConnectionSkin(final GConnection connection) {
        super(connection);
    }

    /**
     * Sets the skin objects for all joints inside the connection.
     *
     * <p>
     * This will be called as the connection skin is created. The connection skin can manipulate its joint skins if it
     * chooses. For example a 'rectangular' connection skin may restrict the movement of the first and last joints to
     * the x direction only.
     * </p>
     *
     * @param jointSkins the list of all {@link GJointSkin} instances associated to the connection
     */
    public abstract void setJointSkins(final List<GJointSkin> jointSkins);

    /**
     * Draws the connection skin. This is called every time the connection's
     * position could change, for example if one of its connectors is moved,
     * after {@link #update()}.
     *
     * <p>
     * A simple connection skin may ignore the given parameter. This parameter
     * can for example be used to display a 'rerouting' effect when the
     * connection passes over another connection.
     * </p>
     *
     * @param allConnections
     *            the lists of points for all connections (can be ignored in a
     *            simple skin)
     */
    public void draw(@SuppressWarnings("unused") final Map<GConnectionSkin, Point2D[]> allConnections)
    {
        if (getRoot() != null && getRoot().getParent() != null)
        {
            mConnectionIndex = getRoot().getParent().getChildrenUnmodifiable().indexOf(getRoot());
        }
        else
        {
            mConnectionIndex = -1;
        }
    }

    /**
     * Update and return the points of this connection. This is called every
     * time the connection's position could change, for example if one of its
     * connectors is moved before {@link #draw(Map)}.
     * <p>
     * The order of the points is as follows:
     *
     * <ol>
     * <li>Source position.
     * <li>Joint positions in same order the joints appear in their
     * {@link GConnection}.
     * <li>Target position.
     * </ol>
     *
     * </p>
     *
     * <p>
     * This method is called on <b>all</b> connection skins <b>before</b> the
     * draw method is called on any connection skin. It can safely be ignored by
     * simple skin implementations.
     * </p>
     *
     * <p>
     * Overriding this method allows the connection skin to apply constraints to
     * its set of points, and these constraints will be taken into account
     * during the draw methods of other connections, even if they are drawn
     * before this connection.
     * </p>
     *
     * @return points
     */
    public Point2D[] update()
    {
        final GConnection item = getItem();
        final SkinLookup skinLookup = getGraphEditor() == null ? null : getGraphEditor().getSkinLookup();
        if (item == null || skinLookup == null)
        {
            return null;
        }
        else if (item.getJoints().isEmpty())
        {
            final Point2D[] points = new Point2D[2];

            // Start: Source position
            points[0] = GeometryUtils.getConnectorPosition(item.getSource(), skinLookup);

            // End: Target position
            points[1] = GeometryUtils.getConnectorPosition(item.getTarget(), skinLookup);

            return points;
        }
        else
        {
            final int len = item.getJoints().size() + 2;
            final Point2D[] points = new Point2D[len];

            // Middle: joint positions
            GeometryUtils.fillJointPositions(item, skinLookup, points);

            // Start: Source position
            points[0] = GeometryUtils.getConnectorPosition(item.getSource(), skinLookup);

            // End: Target position
            points[len - 1] = GeometryUtils.getConnectorPosition(item.getTarget(), skinLookup);

            return points;
        }
    }

    /**
     * @return cached position (index) of this connection skin inside the
     *         child-list of the parent connection layer.
     */
    public int getParentIndex()
    {
        return mConnectionIndex;
    }
} public abstract class GConnectorSkin extends GSkin<GConnector> {

    /**
     * Creates a new {@link GConnectorSkin}.
     *
     * @param connector the {@link GConnector} represented by the skin
     */
    public GConnectorSkin(final GConnector connector) {
        super(connector);
    }

    /**
     * Gets the width of the connector skin.
     *
     * @return the width of the connector skin
     */
    public abstract double getWidth();

    /**
     * Gets the height of the connector skin.
     *
     * @return the height of the connector skin
     */
    public abstract double getHeight();

    /**
     * Applys the specified style to the connector.
     *
     * <p>
     * This is called by the library during various mouse events. For example when a connector is dragged over another
     * connector in an attempt to create a new connection.
     * </p>
     *
     * @param style the {@link GConnectorStyle} to apply
     */
    public abstract void applyStyle(GConnectorStyle style);
} public abstract class GNodeSkin extends GSkin<GNode> {

    private final DraggableBox root;

    /**
     * Creates a new {@link GNodeSkin}.
     *
     * @param node the {@link GNode} represented by the skin
     */
    public GNodeSkin(final GNode node) {
        super(node);
        root = createContainer();
    }

    /**
     * Gets the root JavaFX node of the skin.
     *
     * @return a {@link ResizableBox} containing the skin's root JavaFX node
     */
    @Override
    public DraggableBox getRoot()
    {
        return root;
    }

    /**
     * Initializes the node skin.
     *
     * <p>
     * The skin's layout values, e.g. its x and y position, are loaded from the {@link GNode} at this point.
     * </p>
     */
    public void initialize() {

        getRoot().setLayoutX(getItem().getX());
        getRoot().setLayoutY(getItem().getY());

        getRoot().resize(getItem().getWidth(), getItem().getHeight());
    }

    /**
     * Sets the node's connector skins.
     *
     * <p>
     * This will be called as the node is created, or if a connector is added or removed. The connector skin's regions
     * should be added to the scene graph.
     * </p>
     *
     * @param connectorSkins a list of {@link GConnectorSkin} objects for each of the node's connectors
     */
    public abstract void setConnectorSkins(List<GConnectorSkin> connectorSkins);

    /**
     * Lays out the node's connectors.
     */
    public abstract void layoutConnectors();

    /**
     * Gets the position of the <b>center</b> of a connector relative to the node region.
     *
     * <p>
     * This will be the point where a connection will attach to.
     * </p>
     *
     * @param connectorSkin a {@link GConnectorSkin} instance
     *
     * @return the x and y coordinates of the connector
     */
    public abstract Point2D getConnectorPosition(GConnectorSkin connectorSkin);

    /**
     * Creates and returns the {@link DraggableBox} that serves as the root for
     * this node skin.<br>
     * By default a {@link ResizableBox} will be created and return as most
     * nodes will be both draggable and resizable.
     *
     * @return {@link DraggableBox}
     */
    protected DraggableBox createContainer()
    {
        return new ResizableBox(EditorElement.NODE)
        {

            @Override
            protected void layoutChildren()
            {
                super.layoutChildren();
                layoutConnectors();
            }

            @Override
            public void positionMoved()
            {
                super.positionMoved();
                GNodeSkin.this.impl_positionMoved();
            }
        };
    }
} public abstract class GTailSkin extends GSkin<GConnector> {

    /**
     * Creates a new {@link GTailSkin}.
     *
     * @param connector the {@link GConnector} that the tail will extend from
     */
    public GTailSkin(final GConnector connector) {
        super(connector);
    }

    /**
     * Updates the position of the tail according to the specified start and end points.
     *
     * <p>
     * This method will be called when a 'fresh' tail is created from an unoccupied connector.
     * </p>
     *
     * @param start a {@link Point2D} containing the start x and y values
     * @param end a {@link Point2D} containing the end x and y values
     */
    public abstract void draw(Point2D start, Point2D end);

    /**
     * Updates the position of the tail according to the specified start and end points.
     *
     * <p>
     * This method will be called when a tail is snapped to the target connector that the mouse is hovering over.
     * </p>
     *
     * @param start a {@link Point2D} containing the start x and y values
     * @param end a {@link Point2D} containing the end x and y values
     * @param target the target connector that the tail is snapping to
     * @param valid {@code true} if the connection is valid, {@code false} if invalid
     */
    public abstract void draw(Point2D start, Point2D end, GConnector target, boolean valid);

    /**
     * Updates the position of the tail according to the specified start points, end points, and joint positions.
     *
     * <p>
     * This method will be called when an existing connection is repositioned. The tail skin may use the position of the
     * old connection to decide how to position itself, or it may ignore this information.
     * </p>
     *
     * @param start a {@link Point2D} containing the start x and y values
     * @param end a {@link Point2D} containing the end x and y values
     * @param jointPositions the positions of the joints at the time the connection was removed
     */
    public abstract void draw(Point2D start, Point2D end, List<Point2D> jointPositions);

    /**
     * Updates the position of the tail according to the specified start points, end points, and joint positions.
     *
     * <p>
     * This method will be called when an existing connection is repositioned and the stail is snapped to a target
     * connector.
     * </p>
     *
     * @param start a {@link Point2D} containing the start x and y values
     * @param end a {@link Point2D} containing the end x and y values
     * @param jointPositions 
     * @param target the target connector that the tail is snapping to
     * @param valid {@code true} if the connection is valid, {@code false} if invalid
     */
    public abstract void draw(Point2D start, Point2D end, List<Point2D> jointPositions, GConnector target, boolean valid);

    /**
     * Allocates a list of joint positions for a new connection.
     *
     * <p>
     * When the tail is 'converted' into a connection during a successful drag-drop gesture, this method will be called
     * so that the new connection's joint positions can be based on the final position of the tail.
     * </p>
     *
     * @return a list of {@code Point2D} objects containing x and y values for a newly-created connection
     */
    public abstract List<Point2D> allocateJointPositions();
} public interface GraphEditor extends GraphEditorSkins {

    /**
     * Sets a custom connector validator.
     *
     * <p>
     * This will be used to decide which connections are allowed / forbidden during drag and drop events in the editor.
     * </p>
     *
     * @param validator a custom validator implements {@link GConnectorValidator}, or null to use the default validator
     */
    void setConnectorValidator(final GConnectorValidator validator);

    /**
     * Sets the graph model to be edited.
     *
     * @param model the {@link GModel} to be edited
     */
    void setModel(final GModel model);

    /**
     * Gets the graph model that is currently being edited.
     *
     * @return the {@link GModel} being edited, or {@code null} if no model was ever set
     */
    GModel getModel();

    /**
     * Reloads the graph model currently being edited.
     *
     * <p>
     * <b>Note: </b><br>
     * If the model is updated via EMF commands, as is recommended, it should rarely be necessary to call this method.
     * The model will be reloaded automatically via a command-stack listener.
     * </p>
     */
    void reload();

    /**
     * The property containing the graph model being edited.
     *
     * @return a property containing the {@link GModel} being edited
     */
    ObjectProperty<GModel> modelProperty();

    /**
     * Gets the view where the graph is displayed and edited.
     *
     * <p>
     * The view is a JavaFX {@link Region}. It should be added to the scene
     * graph in the usual way.
     * </p>
     *
     * @return the {@link Region} where the graph is displayed and edited
     */
    Region getView();

    /**
     * Gets the properties of the editor.
     *
     * <p>
     * This provides access to global properties such as:
     *
     * <ul>
     * <li>Show/hide alignment grid.</li>
     * <li>Toggle snap-to-grid on/off.</li>
     * <li>Toggle editor bounds on/off.</li>
     * </ul>
     *
     * </p>
     *
     * @return an {@link GraphEditorProperties} instance containing the properties of the editor
     */
    GraphEditorProperties getProperties();

    /**
     * Gets the skin lookup.
     *
     * <p>
     * The skin lookup is used to get any skin instance associated to a model element instance.
     * </p>
     *
     * @return a {@link SkinLookup} used to lookup skins
     */
    SkinLookup getSkinLookup();

    /**
     * Gets the selection manager.
     *
     * <p>
     * The selection manager keeps track of the selected nodes, connections,
     * etc.
     * </p>
     *
     * @return the {@link SelectionManager}
     */
    SelectionManager getSelectionManager();

    /**
     * Sets a method to be called when a connection is created in the editor.
     *
     * <p>
     * This can be used to append additional commands to the one that created the connection.
     * </p>
     *
     * @param consumer a consumer to append additional commands
     */
    void setOnConnectionCreated(Function<GConnection, Command> consumer);

    /**
     * Sets a method to be called when a connection is removed in the editor.
     *
     * <p>
     * This can be used to create additional commands to the one that removed
     * the connection.
     * </p>
     *
     * @param pOnConnectionRemoved
     *            a {@link BiFunction} creating the additional command
     */
    void setOnConnectionRemoved(BiFunction<RemoveContext, GConnection, Command> pOnConnectionRemoved);

    /**
     * Sets a method to be called when a node is removed in the editor.
     *
     * <p>
     * This can be used to create additional commands to the one that removed
     * the node.
     * </p>
     *
     * @param pOnNodeRemoved
     *            a {@link BiFunction} creating the additional command
     */
    void setOnNodeRemoved(BiFunction<RemoveContext, GNode, Command> pOnNodeRemoved);

    /**
     * Deletes all elements that are currently selected.
     *
     * @param pItems
     *            the items to remove from the graph
     */
    void delete(Collection<EObject> pItems);
} public class DefaultConnectionSkin extends SimpleConnectionSkin {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnectionSkin.class);

    private final JointCreator jointCreator;
    private final JointCleaner jointCleaner;
    private final JointAlignmentManager jointAlignmentManager;
    private final CursorOffsetCalculator cursorOffsetCalculator;

    /**
     * Creates a new default connection skin instance.
     *
     * @param connection the {@link GConnection} the skin is being created for
     */
    public DefaultConnectionSkin(final GConnection connection) {

        super(connection);

        performChecks();

        cursorOffsetCalculator = new CursorOffsetCalculator(connection, path, backgroundPath, connectionSegments);
        jointCreator = new JointCreator(connection, cursorOffsetCalculator);
        jointCleaner = new JointCleaner(connection);
        jointAlignmentManager = new JointAlignmentManager(connection);

        jointCreator.addJointCreationHandler(root);
    }

    @Override
    public void setGraphEditor(final GraphEditor graphEditor) {

        super.setGraphEditor(graphEditor);

        jointCreator.setGraphEditor(graphEditor);
        jointCleaner.setGraphEditor(graphEditor);
        jointAlignmentManager.setSkinLookup(graphEditor.getSkinLookup());
    }

    @Override
    public void setJointSkins(final List<GJointSkin> jointSkins) {

        super.setJointSkins(jointSkins);

        jointCleaner.addCleaningHandlers(jointSkins);
        jointAlignmentManager.addAlignmentHandlers(jointSkins);
    }

    /**
     * Checks that the connection has the correct values to be displayed using
     * this skin.
     */
    private void performChecks()
    {
        if (!RectangularConnections.checkJointCount(getItem()))
        {
            LOGGER.error("Joint count not compatible with source and target connector types.");
        }
    }
} public class DefaultConnectorSkin extends GConnectorSkin {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnectorSkin.class);

    private static final String STYLE_CLASS_BASE = "default-connector";

    private static final PseudoClass PSEUDO_CLASS_ALLOWED = PseudoClass.getPseudoClass("allowed");
    private static final PseudoClass PSEUDO_CLASS_FORBIDDEN = PseudoClass.getPseudoClass("forbidden");

    private static final String ALLOWED = "-animated-color-allowed";
    private static final String FORBIDDEN = "-animated-color-forbidden";

    private static final double SIZE = 25;

    private final Pane root = new Pane();
    private final Polygon polygon = new Polygon();

    private final AnimatedColor animatedColorAllowed;
    private final AnimatedColor animatedColorForbidden;

    /**
     * Creates a new default connector skin instance.
     *
     * @param connector the {@link GConnector} the skin is being created for
     */
    public DefaultConnectorSkin(final GConnector connector) {

        super(connector);

        performChecks();

        root.setManaged(false);
        root.resize(SIZE, SIZE);
        root.setPickOnBounds(false);

        polygon.setManaged(false);
        polygon.getStyleClass().addAll(STYLE_CLASS_BASE, connector.getType());

        drawTriangleConnector(connector.getType(), polygon);

        root.getChildren().add(polygon);

        animatedColorAllowed = new AnimatedColor(ALLOWED, Color.WHITE, Color.MEDIUMSEAGREEN, Duration.millis(500));
        animatedColorForbidden = new AnimatedColor(FORBIDDEN, Color.WHITE, Color.TOMATO, Duration.millis(500));
    }

    @Override
    public Node getRoot() {
        return root;
    }

    @Override
    public double getWidth() {
        return SIZE;
    }

    @Override
    public double getHeight() {
        return SIZE;
    }

    @Override
    public void applyStyle(final GConnectorStyle style) {

        switch (style) {

        case DEFAULT:
            ColorAnimationUtils.removeAnimation(polygon);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, false);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, false);
            break;

        case DRAG_OVER_ALLOWED:
            ColorAnimationUtils.animateColor(polygon, animatedColorAllowed);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, false);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, true);
            break;

        case DRAG_OVER_FORBIDDEN:
            ColorAnimationUtils.animateColor(polygon, animatedColorForbidden);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, true);
            polygon.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, false);
            break;
        }
    }

    /**
     * Draws the given polygon to have a triangular shape.
     *
     * @param type the connector type
     * @param polygon the polygon to be drawn
     */
    public static void drawTriangleConnector(final String type, final Polygon polygon) {

        switch (type) {

        case DefaultConnectorTypes.TOP_INPUT:
            drawVertical(false, polygon);
            break;

        case DefaultConnectorTypes.TOP_OUTPUT:
            drawVertical(true, polygon);
            break;

        case DefaultConnectorTypes.RIGHT_INPUT:
            drawHorizontal(false, polygon);
            break;

        case DefaultConnectorTypes.RIGHT_OUTPUT:
            drawHorizontal(true, polygon);
            break;

        case DefaultConnectorTypes.BOTTOM_INPUT:
            drawVertical(true, polygon);
            break;

        case DefaultConnectorTypes.BOTTOM_OUTPUT:
            drawVertical(false, polygon);
            break;

        case DefaultConnectorTypes.LEFT_INPUT:
            drawHorizontal(true, polygon);
            break;

        case DefaultConnectorTypes.LEFT_OUTPUT:
            drawHorizontal(false, polygon);
            break;
        }
    }

    /**
     * Draws the polygon for a horizontal orientation, pointing right or left.
     *
     * @param pointingRight {@code true} to point right, {@code false} to point left
     * @param polygon the polygon to be drawn
     */
    private static void drawHorizontal(final boolean pointingRight, final Polygon polygon) {

        if (pointingRight) {
            polygon.getPoints().addAll(new Double[] { 0D, 0D, SIZE, SIZE / 2, 0D, SIZE });
        } else {
            polygon.getPoints().addAll(new Double[] { SIZE, 0D, SIZE, SIZE, 0D, SIZE / 2 });
        }
    }

    /**
     * Draws the polygon for a vertical orientation, pointing up or down.
     *
     * @param pointingUp {@code true} to point up, {@code false} to point down
     * @param polygon the polygon to be drawn
     */
    private static void drawVertical(final boolean pointingUp, final Polygon polygon) {

        if (pointingUp) {
            polygon.getPoints().addAll(new Double[] { SIZE / 2, 0D, SIZE, SIZE, 0D, SIZE });
        } else {
            polygon.getPoints().addAll(new Double[] { 0D, 0D, SIZE, 0D, SIZE / 2, SIZE });
        }
    }

    /**
     * Checks that the connector has the correct values to be displayed using this skin.
     */
    private void performChecks()
    {
        if (!DefaultConnectorTypes.isValid(getItem().getType()))
        {
            LOGGER.error("Connector type '{}' not recognized, setting to 'left-input'.", getItem().getType());
            getItem().setType(DefaultConnectorTypes.LEFT_INPUT);
        }
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        // Not implemented
    }
} public class DefaultNodeSkin extends GNodeSkin {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNodeSkin.class);

    private static final String STYLE_CLASS_BORDER = "default-node-border";
    private static final String STYLE_CLASS_BACKGROUND = "default-node-background";
    private static final String STYLE_CLASS_SELECTION_HALO = "default-node-selection-halo";

    private static final PseudoClass PSEUDO_CLASS_SELECTED = PseudoClass.getPseudoClass("selected");

    private static final double HALO_OFFSET = 5;
    private static final double HALO_CORNER_SIZE = 10;

    private static final double MINOR_POSITIVE_OFFSET = 2;
    private static final double MINOR_NEGATIVE_OFFSET = -3;

    private static final double MIN_WIDTH = 41;
    private static final double MIN_HEIGHT = 41;

    private final Rectangle selectionHalo = new Rectangle();

    private final List<GConnectorSkin> topConnectorSkins = new ArrayList<>();
    private final List<GConnectorSkin> rightConnectorSkins = new ArrayList<>();
    private final List<GConnectorSkin> bottomConnectorSkins = new ArrayList<>();
    private final List<GConnectorSkin> leftConnectorSkins = new ArrayList<>();

    // Border and background are separated into 2 rectangles so they can have different effects applied to them.
    private final Rectangle border = new Rectangle();
    private final Rectangle background = new Rectangle();

    /**
     * Creates a new default node skin instance.
     *
     * @param node the {@link GNode} the skin is being created for
     */
    public DefaultNodeSkin(final GNode node) {

        super(node);

        performChecks();

        background.widthProperty().bind(border.widthProperty().subtract(border.strokeWidthProperty().multiply(2)));
        background.heightProperty().bind(border.heightProperty().subtract(border.strokeWidthProperty().multiply(2)));

        border.widthProperty().bind(getRoot().widthProperty());
        border.heightProperty().bind(getRoot().heightProperty());

        border.getStyleClass().setAll(STYLE_CLASS_BORDER);
        background.getStyleClass().setAll(STYLE_CLASS_BACKGROUND);

        getRoot().getChildren().addAll(border, background);
        getRoot().setMinSize(MIN_WIDTH, MIN_HEIGHT);

        background.addEventFilter(MouseEvent.MOUSE_DRAGGED, this::filterMouseDragged);

        addSelectionHalo();
    }

    @Override
    public void setConnectorSkins(final List<GConnectorSkin> connectorSkins) {

        removeAllConnectors();

        topConnectorSkins.clear();
        rightConnectorSkins.clear();
        bottomConnectorSkins.clear();
        leftConnectorSkins.clear();

        if (connectorSkins != null) {
            for (final GConnectorSkin connectorSkin : connectorSkins) {

                final String type = connectorSkin.getItem().getType();

                if (DefaultConnectorTypes.isTop(type)) {
                    topConnectorSkins.add(connectorSkin);
                } else if (DefaultConnectorTypes.isRight(type)) {
                    rightConnectorSkins.add(connectorSkin);
                } else if (DefaultConnectorTypes.isBottom(type)) {
                    bottomConnectorSkins.add(connectorSkin);
                } else if (DefaultConnectorTypes.isLeft(type)) {
                    leftConnectorSkins.add(connectorSkin);
                }

                getRoot().getChildren().add(connectorSkin.getRoot());
            }
        }

        layoutConnectors();
    }

    @Override
    public void layoutConnectors() {
        layoutAllConnectors();
        layoutSelectionHalo();
    }

    @Override
    public Point2D getConnectorPosition(final GConnectorSkin connectorSkin) {

        final Node connectorRoot = connectorSkin.getRoot();

        final Side side = DefaultConnectorTypes.getSide(connectorSkin.getItem().getType());

        // The following logic is required because the connectors are offset slightly from the node edges.
        final double x, y;
        if (side.equals(Side.LEFT)) {
            x = 0;
            y = connectorRoot.getLayoutY() + connectorSkin.getHeight() / 2;
        } else if (side.equals(Side.RIGHT)) {
            x = getRoot().getWidth();
            y = connectorRoot.getLayoutY() + connectorSkin.getHeight() / 2;
        } else if (side.equals(Side.TOP)) {
            x = connectorRoot.getLayoutX() + connectorSkin.getWidth() / 2;
            y = 0;
        } else {
            x = connectorRoot.getLayoutX() + connectorSkin.getWidth() / 2;
            y = getRoot().getHeight();
        }

        return new Point2D(x, y);
    }

    /**
     * Checks that the node and its connectors have the correct values to be displayed using this skin.
     */
    private void performChecks()
    {
        for (final GConnector connector : getItem().getConnectors())
        {
            if (!DefaultConnectorTypes.isValid(connector.getType()))
            {
                LOGGER.error("Connector type '{}' not recognized, setting to 'left-input'.", connector.getType());
                connector.setType(DefaultConnectorTypes.LEFT_INPUT);
            }
        }
    }

    /**
     * Lays out the node's connectors.
     */
    private void layoutAllConnectors() {

        layoutConnectors(topConnectorSkins, false, 0);
        layoutConnectors(rightConnectorSkins, true, getRoot().getWidth());
        layoutConnectors(bottomConnectorSkins, false, getRoot().getHeight());
        layoutConnectors(leftConnectorSkins, true, 0);
    }

    /**
     * Lays out the given connector skins in a horizontal or vertical direction at the given offset.
     *
     * @param connectorSkins the skins to lay out
     * @param vertical {@code true} to lay out vertically, {@code false} to lay out horizontally
     * @param offset the offset in the other dimension that the skins are layed out in
     */
    private void layoutConnectors(final List<GConnectorSkin> connectorSkins, final boolean vertical, final double offset) {

        final int count = connectorSkins.size();

        for (int i = 0; i < count; i++) {

            final GConnectorSkin skin = connectorSkins.get(i);
            final Node root = skin.getRoot();

            if (vertical) {

                final double offsetY = getRoot().getHeight() / (count + 1);
                final double offsetX = getMinorOffsetX(skin.getItem());

                root.setLayoutX(GeometryUtils.moveOnPixel(offset - skin.getWidth() / 2 + offsetX));
                root.setLayoutY(GeometryUtils.moveOnPixel((i + 1) * offsetY - skin.getHeight() / 2));

            } else {

                final double offsetX = getRoot().getWidth() / (count + 1);
                final double offsetY = getMinorOffsetY(skin.getItem());

                root.setLayoutX(GeometryUtils.moveOnPixel((i + 1) * offsetX - skin.getWidth() / 2));
                root.setLayoutY(GeometryUtils.moveOnPixel(offset - skin.getHeight() / 2 + offsetY));
            }
        }
    }

    /**
     * Adds the selection halo and initializes some of its values.
     */
    private void addSelectionHalo() {

        getRoot().getChildren().add(selectionHalo);

        selectionHalo.setManaged(false);
        selectionHalo.setMouseTransparent(false);
        selectionHalo.setVisible(false);

        selectionHalo.setLayoutX(-HALO_OFFSET);
        selectionHalo.setLayoutY(-HALO_OFFSET);

        selectionHalo.getStyleClass().add(STYLE_CLASS_SELECTION_HALO);
    }

    /**
     * Lays out the selection halo based on the current width and height of the node skin region.
     */
    private void layoutSelectionHalo() {

        if (selectionHalo.isVisible()) {

            selectionHalo.setWidth(border.getWidth() + 2 * HALO_OFFSET);
            selectionHalo.setHeight(border.getHeight() + 2 * HALO_OFFSET);

            final double cornerLength = 2 * HALO_CORNER_SIZE;
            final double xGap = border.getWidth() - 2 * HALO_CORNER_SIZE + 2 * HALO_OFFSET;
            final double yGap = border.getHeight() - 2 * HALO_CORNER_SIZE + 2 * HALO_OFFSET;

            selectionHalo.setStrokeDashOffset(HALO_CORNER_SIZE);
            selectionHalo.getStrokeDashArray().setAll(cornerLength, yGap, cornerLength, xGap);
        }
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        if (isSelected) {
            selectionHalo.setVisible(true);
            layoutSelectionHalo();
            background.pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, true);
            getRoot().toFront();
        } else {
            selectionHalo.setVisible(false);
            background.pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, false);
        }
    }

    /**
     * Removes all connectors from the list of children.
     */
    private void removeAllConnectors() {

        topConnectorSkins.stream().forEach(skin -> getRoot().getChildren().remove(skin.getRoot()));
        rightConnectorSkins.stream().forEach(skin -> getRoot().getChildren().remove(skin.getRoot()));
        bottomConnectorSkins.stream().forEach(skin -> getRoot().getChildren().remove(skin.getRoot()));
        leftConnectorSkins.stream().forEach(skin -> getRoot().getChildren().remove(skin.getRoot()));
    }

    /**
     * Gets a minor x-offset of a few pixels in order that the connector's area is distributed more evenly on either
     * side of the node border.
     *
     * @param connector the connector to be positioned
     * @return an x-offset of a few pixels
     */
    private double getMinorOffsetX(final GConnector connector) {

        final String type = connector.getType();

        if (type.equals(DefaultConnectorTypes.LEFT_INPUT) || type.equals(DefaultConnectorTypes.RIGHT_OUTPUT)) {
            return MINOR_POSITIVE_OFFSET;
        } else {
            return MINOR_NEGATIVE_OFFSET;
        }
    }

    /**
     * Gets a minor y-offset of a few pixels in order that the connector's area is distributed more evenly on either
     * side of the node border.
     *
     * @param connector the connector to be positioned
     * @return a y-offset of a few pixels
     */
    private double getMinorOffsetY(final GConnector connector) {

        final String type = connector.getType();

        if (type.equals(DefaultConnectorTypes.TOP_INPUT) || type.equals(DefaultConnectorTypes.BOTTOM_OUTPUT)) {
            return MINOR_POSITIVE_OFFSET;
        } else {
            return MINOR_NEGATIVE_OFFSET;
        }
    }

    /**
     * Stops the node being dragged if it isn't selected.
     *
     * @param event a mouse-dragged event on the node
     */
    private void filterMouseDragged(final MouseEvent event) {
        if (event.isPrimaryButtonDown() && !isSelected()) {
            event.consume();
        }
    }
} public class DefaultTailSkin extends GTailSkin {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTailSkin.class);

    private static final double ENDPOINT_SIZE = 25;

    private static final String STYLE_CLASS = "default-tail";
    private static final String STYLE_CLASS_ENDPOINT = "default-tail-endpoint";

    protected final Polyline line = new Polyline();
    protected final Polygon endpoint = new Polygon();
    protected final Group group = new Group(line, endpoint);

    /**
     * Creates a new default tail skin instance.
     *
     * @param connector the {@link GConnector} the skin is being created for
     */
    public DefaultTailSkin(final GConnector connector) {

        super(connector);

        performChecks();

        DefaultConnectorSkin.drawTriangleConnector(connector.getType(), endpoint);

        endpoint.getStyleClass().addAll(STYLE_CLASS_ENDPOINT, connector.getType());
        line.getStyleClass().setAll(STYLE_CLASS);
        group.setManaged(false);
    }

    @Override
    public Node getRoot() {
        return group;
    }

    @Override
    public void draw(final Point2D start, final Point2D end) {

        endpoint.setVisible(true);
        layoutEndpoint(end);
        drawStupid(start, end);
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final GConnector target, final boolean valid) {

        endpoint.setVisible(false);
        if (valid) {
            drawSmart(start, end, target);
        } else {
            drawStupid(start, end);
        }
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final List<Point2D> jointPositions) {
        draw(start, end);
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final List<Point2D> jointPositions,
            final GConnector target, final boolean valid) {
        draw(start, end, target, valid);
    }

    @Override
    public List<Point2D> allocateJointPositions() {

        final List<Point2D> jointPositions = new ArrayList<>();

        for (int i = 2; i < line.getPoints().size() - 2; i = i + 2) {

            final double x = GeometryUtils.moveOnPixel(line.getPoints().get(i));
            final double y = GeometryUtils.moveOnPixel(line.getPoints().get(i + 1));

            jointPositions.add(new Point2D(x, y));
        }

        return jointPositions;
    }

    /**
     * Sets layout values of the endpoint based on the new cursor position.
     *
     * @param position the new cursor position
     */
    protected void layoutEndpoint(final Point2D position) {
        endpoint.setLayoutX(GeometryUtils.moveOnPixel(position.getX() - ENDPOINT_SIZE / 2));
        endpoint.setLayoutY(GeometryUtils.moveOnPixel(position.getY() - ENDPOINT_SIZE / 2));
    }

    /**
     * Checks that the connector has the correct values to use this skin.
     */
    private void performChecks()
    {
        if (!DefaultConnectorTypes.isValid(getItem().getType()))
        {
            LOGGER.error("Connector type '{}' not recognized, setting to 'left-input'.", getItem().getType());
            getItem().setType(DefaultConnectorTypes.LEFT_INPUT);
        }
    }

    /**
     * Draws the tail simply from the start position to the end.
     *
     * @param start the start position of the tail
     * @param end the end position of the tail
     */
    private void drawStupid(final Point2D start, final Point2D end) {

        clearPoints();
        addPoint(start);

        if (DefaultConnectorTypes.getSide(getItem().getType()).isVertical()) {
            addPoint((start.getX() + end.getX()) / 2, start.getY());
            addPoint((start.getX() + end.getX()) / 2, end.getY());
        } else {
            addPoint(start.getX(), (start.getY() + end.getY()) / 2);
            addPoint(end.getX(), (start.getY() + end.getY()) / 2);
        }

        addPoint(end);
    }

    /**
     * Draws the tail based additionally on the sides of the nodes it starts and ends at.
     *
     * @param start the start position of the tail
     * @param end the end position of the tail
     * @param target the connector the tail is attaching to
     */
    private void drawSmart(final Point2D start, final Point2D end, final GConnector target) {

        clearPoints();
        addPoint(start);

        final Side startSide = DefaultConnectorTypes.getSide(getItem().getType());
        final Side endSide = DefaultConnectorTypes.getSide(target.getType());

        final List<Point2D> points = RectangularPathCreator.createPath(start, end, startSide, endSide);
        points.stream().forEachOrdered(point -> addPoint(point));

        addPoint(end);
    }

    /**
     * Clears all the points from the tail path.
     */
    private void clearPoints() {
        line.getPoints().clear();
    }

    /**
     * Adds the given point to the tail path.
     *
     * @param point the x & y coordinates of the point
     */
    private void addPoint(final Point2D point) {
        addPoint(point.getX(), point.getY());
    }

    /**
     * Adds the given point to the tail path.
     *
     * @param x the x coordinate of the point
     * @param y the y coordinate of the point
     */
    private void addPoint(final double x, final double y) {
        line.getPoints().addAll(GeometryUtils.moveOffPixel(x), GeometryUtils.moveOffPixel(y));
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        // Not implemented
    }

} public class GraphEditorContainer extends AutoScrollingWindow
{

    /**
     * default view stylesheet
     */
    private static final String STYLESHEET_VIEW = GraphEditorContainer.class.getResource("defaults.css").toExternalForm(); //$NON-NLS-1$

    private static final double MINIMAP_INDENT = 10;

    private final GraphEditorMinimap minimap = new GraphEditorMinimap();

    private GraphEditor graphEditor;
    private final ChangeListener<GModel> modelChangeListener = (observable, oldValue, newValue) -> modelChanged(newValue);

    /**
     * Creates a new {@link GraphEditorContainer}.
     */
    public GraphEditorContainer()
    {
        getChildren().add(minimap);

        minimap.setWindow(this);
        minimap.setVisible(false);
    }

    @Override
    public String getUserAgentStylesheet()
    {
        return STYLESHEET_VIEW;
    }

    private void modelChanged(final GModel newValue)
    {
        if (newValue != null)
        {
            graphEditor.getView().resize(newValue.getContentWidth(), newValue.getContentHeight());
        }
        checkWindowBounds();
        minimap.setModel(newValue);
    }

    /**
     * Sets the graph editor to be displayed in this container.
     *
     * @param pGraphEditor
     *            a {@link GraphEditor} instance
     */
    public void setGraphEditor(final GraphEditor pGraphEditor)
    {
        final GraphEditor previous = graphEditor;
        if (previous != null)
        {
            previous.modelProperty().removeListener(modelChangeListener);
            setEditorProperties(null);
        }

        graphEditor = pGraphEditor;

        if (pGraphEditor != null)
        {
            pGraphEditor.modelProperty().addListener(modelChangeListener);

            final Region view = pGraphEditor.getView();
            final GModel model = pGraphEditor.getModel();

            if (model != null)
            {
                view.resize(model.getContentWidth(), model.getContentHeight());
            }

            setContent(view);
            minimap.setContent(view);
            minimap.setModel(model);
            minimap.setSelectionManager(pGraphEditor.getSelectionManager());

            view.toBack();

            setEditorProperties(pGraphEditor.getProperties());
        }
        else
        {
            setEditorProperties(null);
            minimap.setContent(null);
            minimap.setModel(null);
        }
    }

    /**
     * Returns the {@link GraphEditorMinimap}
     *
     * @return the graph editor minimap
     */
    public GraphEditorMinimap getMinimap()
    {
        return minimap;
    }

    @Override
    protected void layoutChildren()
    {
        super.layoutChildren();

        if (getChildren().contains(minimap))
        {
            minimap.relocate(getWidth() - (minimap.getWidth() + MINIMAP_INDENT), MINIMAP_INDENT);
        }
    }
} public class TreeConnectionSkin extends GConnectionSkin {

    private static final String STYLE_CLASS = "tree-connection"; //$NON-NLS-1$
    private static final String STYLE_CLASS_BACKGROUND = "tree-connection-background"; //$NON-NLS-1$
    private static final String STYLE_CLASS_SELECTION_HALO = "tree-connection-selection-halo"; //$NON-NLS-1$

    private static final double OFFSET_FROM_CONNECTOR = 15;
    private static final double HALO_BREADTH_OFFSET = 5;
    private static final double HALO_LENGTH_OFFSET_START = 1;
    private static final double HALO_LENGTH_OFFSET_END = 12;

    private final Arrow arrow = new Arrow();
    private final Arrow background = new Arrow();
    private final Line haloFirstSide = new Line();
    private final Line haloSecondSide = new Line();
    private final Group selectionHalo = new Group(haloFirstSide, haloSecondSide);
    private final Group root = new Group(background, selectionHalo, arrow);

    /**
     * Creates a new {@link TreeConnectionSkin} instance.
     *
     * @param connection the {@link GConnection} that this skin is representing
     */
    public TreeConnectionSkin(final GConnection connection) {

        super(connection);

        arrow.setManaged(false);
        arrow.getStyleClass().setAll(STYLE_CLASS);

        background.setManaged(false);
        background.getStyleClass().setAll(STYLE_CLASS_BACKGROUND);

        root.setOnMousePressed(this::handleMousePressed);
        root.setOnMouseDragged(this::handleMouseDragged);

        haloFirstSide.getStyleClass().add(STYLE_CLASS_SELECTION_HALO);
        haloSecondSide.getStyleClass().add(STYLE_CLASS_SELECTION_HALO);
        selectionHalo.setVisible(false);
    }

    @Override
    public Node getRoot() {
        return root;
    }

    @Override
    public void setJointSkins(final List<GJointSkin> jointSkins) {
        // This skin is not intended to show joints.
    }

    @Override
    public void draw(final Map<GConnectionSkin, Point2D[]> allPoints)
    {
        final Point2D[] points = allPoints == null ? null : allPoints.get(this);
        if (points != null && points.length == 2)
        {
            final Point2D start = points[0];
            final Point2D end = points[1];

            if (getItem().getSource().getType().equals(TreeSkinConstants.TREE_OUTPUT_CONNECTOR))
            {
                ArrowUtils.draw(arrow, start, end, OFFSET_FROM_CONNECTOR);
                ArrowUtils.draw(background, start, end, OFFSET_FROM_CONNECTOR);
            }
            else
            {
                ArrowUtils.draw(arrow, end, start, OFFSET_FROM_CONNECTOR);
                ArrowUtils.draw(background, start, end, OFFSET_FROM_CONNECTOR);
            }
        }

        if (isSelected())
        {
            drawSelectionHalo();
        }
    }

    /**
     * Handles mouse-pressed events on the connection skin to select / de-select the connection.
     *
     * @param event the mouse-pressed event
     */
	private void handleMousePressed(final MouseEvent event) {

		final GraphEditor editor = getGraphEditor();
		if (editor == null) {
			return;
		}

		if (event.isShortcutDown()) {
			if (isSelected()) {
				editor.getSelectionManager().clearSelection(getItem());
			} else {
				editor.getSelectionManager().select(getItem());
			}
		} else if (!isSelected()) {
			getGraphEditor().getSelectionManager().clearSelection();
			editor.getSelectionManager().select(getItem());
		}

		event.consume();
	}

    /**
     * Handles mouse-dragged events on the connection skin. Consumes the event so it doesn't reach the view.
     *
     * @param event the mouse-dragged event
     */
    private void handleMouseDragged(final MouseEvent event) {
        event.consume();
    }


    @Override
    protected void selectionChanged(final boolean selected) {
        selectionHalo.setVisible(selected);
        if (selected) {
            drawSelectionHalo();
        }
    }

    /**
     * Draws the 'selection halo' that indicates that the connection is selected.
     */
    private void drawSelectionHalo() {

        final Point2D arrowStart = arrow.getStart();
        final Point2D arrowEnd = arrow.getEnd();

        final double deltaX = arrowEnd.getX() - arrowStart.getX();
        final double deltaY = arrowEnd.getY() - arrowStart.getY();

        final double angle = Math.atan2(deltaX, deltaY);

        final double breadthOffsetX = HALO_BREADTH_OFFSET * Math.cos(angle);
        final double breadthOffsetY = HALO_BREADTH_OFFSET * Math.sin(angle);

        final double lengthOffsetStartX = HALO_LENGTH_OFFSET_START * Math.sin(angle);
        final double lengthOffsetStartY = HALO_LENGTH_OFFSET_START * Math.cos(angle);

        final double lengthOffsetEndX = HALO_LENGTH_OFFSET_END * Math.sin(angle);
        final double lengthOffsetEndY = HALO_LENGTH_OFFSET_END * Math.cos(angle);

        haloFirstSide.setStartX(GeometryUtils.moveOffPixel(arrowStart.getX() - breadthOffsetX + lengthOffsetStartX));
        haloFirstSide.setStartY(GeometryUtils.moveOffPixel(arrowStart.getY() + breadthOffsetY + lengthOffsetStartY));

        haloSecondSide.setStartX(GeometryUtils.moveOffPixel(arrowStart.getX() + breadthOffsetX + lengthOffsetStartX));
        haloSecondSide.setStartY(GeometryUtils.moveOffPixel(arrowStart.getY() - breadthOffsetY + lengthOffsetStartY));

        haloFirstSide.setEndX(GeometryUtils.moveOffPixel(arrowEnd.getX() - breadthOffsetX - lengthOffsetEndX));
        haloFirstSide.setEndY(GeometryUtils.moveOffPixel(arrowEnd.getY() + breadthOffsetY - lengthOffsetEndY));

        haloSecondSide.setEndX(GeometryUtils.moveOffPixel(arrowEnd.getX() + breadthOffsetX - lengthOffsetEndX));
        haloSecondSide.setEndY(GeometryUtils.moveOffPixel(arrowEnd.getY() - breadthOffsetY - lengthOffsetEndY));
    }
} public class TreeConnectorSkin extends GConnectorSkin {

    private static final String STYLE_CLASS_INPUT = "tree-input-connector"; //$NON-NLS-1$
    private static final String STYLE_CLASS_OUTPUT = "tree-output-connector"; //$NON-NLS-1$

    private static final PseudoClass PSEUDO_CLASS_ALLOWED = PseudoClass.getPseudoClass("allowed"); //$NON-NLS-1$
    private static final PseudoClass PSEUDO_CLASS_FORBIDDEN = PseudoClass.getPseudoClass("forbidden"); //$NON-NLS-1$

    private static final double RADIUS = 8;

    private final Pane root = new Pane();
    private final Circle circle = new Circle(RADIUS);

    /**
     * Creates a new {@link TreeConnectorSkin} instance.
     *
     * @param connector the {@link GConnector} that this skin is representing
     */
    public TreeConnectorSkin(final GConnector connector) {

        super(connector);

        root.setMinSize(2 * RADIUS, 2 * RADIUS);
        root.setPrefSize(2 * RADIUS, 2 * RADIUS);
        root.setMaxSize(2 * RADIUS, 2 * RADIUS);

        root.setPickOnBounds(false);

        circle.setManaged(false);
        circle.resizeRelocate(0, 0, 2 * RADIUS, 2 * RADIUS);

        if (TreeSkinConstants.TREE_INPUT_CONNECTOR.equals(connector.getType())) {
            circle.getStyleClass().setAll(STYLE_CLASS_INPUT);
        } else {
            circle.getStyleClass().setAll(STYLE_CLASS_OUTPUT);
        }

        root.getChildren().add(circle);
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        // Not implemented
    }

    @Override
    public Node getRoot() {
        return root;
    }

    @Override
    public double getWidth() {
        return 2 * RADIUS;
    }

    @Override
    public double getHeight() {
        return 2 * RADIUS;
    }

    @Override
    public void applyStyle(final GConnectorStyle style) {

        switch (style) {

        case DEFAULT:
            circle.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, false);
            circle.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, false);
            break;

        case DRAG_OVER_ALLOWED:
            circle.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, false);
            circle.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, true);
            break;

        case DRAG_OVER_FORBIDDEN:
            circle.pseudoClassStateChanged(PSEUDO_CLASS_FORBIDDEN, true);
            circle.pseudoClassStateChanged(PSEUDO_CLASS_ALLOWED, false);
            break;
        }
    }
} public class TreeNodeSkin extends GNodeSkin {

    private static final String STYLE_CLASS_BORDER = "tree-node-border"; //$NON-NLS-1$
    private static final String STYLE_CLASS_BACKGROUND = "tree-node-background"; //$NON-NLS-1$
    private static final String STYLE_CLASS_SELECTION_HALO = "tree-node-selection-halo"; //$NON-NLS-1$
    private static final String STYLE_CLASS_BUTTON = "tree-node-button"; //$NON-NLS-1$

    private static final PseudoClass PSEUDO_CLASS_SELECTED = PseudoClass.getPseudoClass("selected"); //$NON-NLS-1$

    private static final double HALO_OFFSET = 5;
    private static final double HALO_CORNER_SIZE = 10;

    private static final double MIN_WIDTH = 81;
    private static final double MIN_HEIGHT = 61;

    // Child nodes will be added this far below their parent.
    private static final double CHILD_Y_OFFSET = 80;

    private static final EReference NODES = GraphPackage.Literals.GMODEL__NODES;
    private static final EReference CONNECTIONS = GraphPackage.Literals.GMODEL__CONNECTIONS;
    private static final EReference CONNECTOR_CONNECTIONS = GraphPackage.Literals.GCONNECTOR__CONNECTIONS;

    private static final double VIEW_PADDING = 15;

    private final Rectangle selectionHalo = new Rectangle();
    private final Button addChildButton = new Button();

    private GConnectorSkin inputConnectorSkin;
    private GConnectorSkin outputConnectorSkin;

    // Border and background are separated into 2 rectangles so they can have different effects applied to them.
    private final Rectangle border = new Rectangle();
    private final Rectangle background = new Rectangle();

    /**
     * Creates a new {@link TreeNodeSkin} instance.
     *
     * @param node the {link GNode} this skin is representing
     */
    public TreeNodeSkin(final GNode node) {

        super(node);

        background.widthProperty().bind(border.widthProperty().subtract(border.strokeWidthProperty().multiply(2)));
        background.heightProperty().bind(border.heightProperty().subtract(border.strokeWidthProperty().multiply(2)));

        border.widthProperty().bind(getRoot().widthProperty());
        border.heightProperty().bind(getRoot().heightProperty());

        border.getStyleClass().setAll(STYLE_CLASS_BORDER);
        background.getStyleClass().setAll(STYLE_CLASS_BACKGROUND);

        getRoot().getChildren().addAll(border, background);
        getRoot().setMinSize(MIN_WIDTH, MIN_HEIGHT);

        addSelectionHalo();
        addButton();

        background.addEventFilter(MouseEvent.MOUSE_DRAGGED, this::filterMouseDragged);
    }

    @Override
    public void setConnectorSkins(final List<GConnectorSkin> connectorSkins) {

        removeConnectors();

        if (connectorSkins == null || connectorSkins.isEmpty() || connectorSkins.size() > 2) {
            return;
        }

        for (final GConnectorSkin skin : connectorSkins) {
            if (TreeSkinConstants.TREE_OUTPUT_CONNECTOR.equals(skin.getItem().getType())) {
                outputConnectorSkin = skin;
                getRoot().getChildren().add(skin.getRoot());
            } else if (TreeSkinConstants.TREE_INPUT_CONNECTOR.equals(skin.getItem().getType())) {
                inputConnectorSkin = skin;
                getRoot().getChildren().add(skin.getRoot());
            }
        }
    }

    @Override
    public void layoutConnectors() {
        layoutTopAndBottomConnectors();
        layoutSelectionHalo();
    }

    @Override
    public Point2D getConnectorPosition(final GConnectorSkin connectorSkin) {

        final Node connectorRoot = connectorSkin.getRoot();

        final double x = connectorRoot.getLayoutX() + connectorSkin.getWidth() / 2;
        final double y = connectorRoot.getLayoutY() + connectorSkin.getHeight() / 2;

        return new Point2D(x, y);
    }

    /**
     * Lays out the connectors. Inputs on top, outputs on the bottom.
     */
    private void layoutTopAndBottomConnectors() {

        if (inputConnectorSkin != null) {

            final double inputX = (getRoot().getWidth() - inputConnectorSkin.getWidth()) / 2;
            final double inputY = -inputConnectorSkin.getHeight() / 2;

            inputConnectorSkin.getRoot().setLayoutX(inputX);
            inputConnectorSkin.getRoot().setLayoutY(inputY);
        }

        if (outputConnectorSkin != null) {

            final double outputX = (getRoot().getWidth() - outputConnectorSkin.getWidth()) / 2;
            final double outputY = getRoot().getHeight() - outputConnectorSkin.getHeight() / 2;

            outputConnectorSkin.getRoot().setLayoutX(outputX);
            outputConnectorSkin.getRoot().setLayoutY(outputY);
        }
    }

    /**
     * Adds the selection halo and initializes some of its values.
     */
    private void addSelectionHalo() {

        getRoot().getChildren().add(selectionHalo);

        selectionHalo.setManaged(false);
        selectionHalo.setMouseTransparent(false);
        selectionHalo.setVisible(false);

        selectionHalo.setLayoutX(-HALO_OFFSET);
        selectionHalo.setLayoutY(-HALO_OFFSET);

        selectionHalo.getStyleClass().add(STYLE_CLASS_SELECTION_HALO);
    }

    /**
     * Lays out the selection halo based on the current width and height of the node skin region.
     */
    private void layoutSelectionHalo() {

        if (selectionHalo.isVisible()) {

            selectionHalo.setWidth(border.getWidth() + 2 * HALO_OFFSET);
            selectionHalo.setHeight(border.getHeight() + 2 * HALO_OFFSET);

            final double cornerLength = 2 * HALO_CORNER_SIZE;
            final double xGap = border.getWidth() - 2 * HALO_CORNER_SIZE + 2 * HALO_OFFSET;
            final double yGap = border.getHeight() - 2 * HALO_CORNER_SIZE + 2 * HALO_OFFSET;

            selectionHalo.setStrokeDashOffset(HALO_CORNER_SIZE);
            selectionHalo.getStrokeDashArray().setAll(cornerLength, yGap, cornerLength, xGap);
        }
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        if (isSelected) {
            background.pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, true);
            selectionHalo.setVisible(true);
            layoutSelectionHalo();
            getRoot().toFront();
        } else {
            background.pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, false);
            selectionHalo.setVisible(false);
        }
    }

    /**
     * Removes any input and output connectors from the list of children, if they exist.
     */
    private void removeConnectors() {

        if (inputConnectorSkin != null) {
            getRoot().getChildren().remove(inputConnectorSkin.getRoot());
        }

        if (outputConnectorSkin != null) {
            getRoot().getChildren().remove(outputConnectorSkin.getRoot());
        }
    }

    /**
     * Adds a button to the node skin that will add a child node when pressed.
     */
    private void addButton() {

        StackPane.setAlignment(addChildButton, Pos.BOTTOM_RIGHT);

        addChildButton.getStyleClass().setAll(STYLE_CLASS_BUTTON);
        addChildButton.setCursor(Cursor.DEFAULT);
        addChildButton.setPickOnBounds(false);

        addChildButton.setGraphic(AwesomeIcon.PLUS.node());
        addChildButton.setOnAction(event -> addChildNode());

        getRoot().getChildren().add(addChildButton);
    }

    /**
     * Adds a child node with one input and one output connector, placed directly underneath its parent.
     */
    private void addChildNode() {

        final GNode childNode = GraphFactory.eINSTANCE.createGNode();

        childNode.setType(TreeSkinConstants.TREE_NODE);
        childNode.setX(getItem().getX() + (getItem().getWidth() - childNode.getWidth()) / 2);
        childNode.setY(getItem().getY() + getItem().getHeight() + CHILD_Y_OFFSET);

        final GModel model = getGraphEditor().getModel();
        final double maxAllowedY = model.getContentHeight() - VIEW_PADDING;

        if (childNode.getY() + childNode.getHeight() > maxAllowedY) {
            childNode.setY(maxAllowedY - childNode.getHeight());
        }

        final GConnector input = GraphFactory.eINSTANCE.createGConnector();
        final GConnector output = GraphFactory.eINSTANCE.createGConnector();

        input.setType(TreeSkinConstants.TREE_INPUT_CONNECTOR);
        output.setType(TreeSkinConstants.TREE_OUTPUT_CONNECTOR);

        childNode.getConnectors().add(input);
        childNode.getConnectors().add(output);

        // This allows multiple connections to be created from the output.
        output.setConnectionDetachedOnDrag(false);

        final GConnector parentOutput = findOutput();
        final GConnection connection = GraphFactory.eINSTANCE.createGConnection();

        connection.setType(TreeSkinConstants.TREE_CONNECTION);
        connection.setSource(parentOutput);
        connection.setTarget(input);

        input.getConnections().add(connection);

        // Set the rest of the values via EMF commands because they touch the currently-edited model.
        final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(model);
        final CompoundCommand command = new CompoundCommand();

        command.append(AddCommand.create(editingDomain, model, NODES, childNode));
        command.append(AddCommand.create(editingDomain, model, CONNECTIONS, connection));
        command.append(AddCommand.create(editingDomain, parentOutput, CONNECTOR_CONNECTIONS, connection));

        if (command.canExecute()) {
            editingDomain.getCommandStack().execute(command);
        }
    }

    /**
     * Finds the output connector of this skin's node.
     *
     * <p>
     * Assumes the node has 1 or 2 connectors, and if there are 2 connectors the second is the output. Bit dodgy but
     * only used in the demo.
     * </p>
     */
    private GConnector findOutput() {

        if (getItem().getConnectors().size() == 1) {
            return getItem().getConnectors().get(0);
        } else if (getItem().getConnectors().size() == 2) {
            return getItem().getConnectors().get(1);
        } else {
            return null;
        }
    }

    /**
     * Stops the node being dragged if it isn't selected.
     *
     * @param event a mouse-dragged event on the node
     */
    private void filterMouseDragged(final MouseEvent event) {
        if (event.isPrimaryButtonDown() && !isSelected()) {
            event.consume();
        }
    }
} public class TreeSkinConstants {

    public static final String TREE_NODE = "tree-node"; //$NON-NLS-1$
    public static final String TREE_INPUT_CONNECTOR = "tree-input"; //$NON-NLS-1$
    public static final String TREE_OUTPUT_CONNECTOR = "tree-output"; //$NON-NLS-1$
    public static final String TREE_CONNECTION = "tree-connection"; //$NON-NLS-1$
} public class TreeTailSkin extends GTailSkin {

    private static final String STYLE_CLASS = "tree-tail"; //$NON-NLS-1$
    private static final double OFFSET_DISTANCE = 15;

    private final Arrow arrow = new Arrow();

    public TreeTailSkin(final GConnector connector) {

        super(connector);

        arrow.getStyleClass().add(STYLE_CLASS);
    }

    @Override
    public Node getRoot() {
        return arrow;
    }

    @Override
    public void draw(final Point2D start, final Point2D end) {
        drawArrow(start, end);
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final List<Point2D> jointPositions) {
        drawArrow(start, end);
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final GConnector target, final boolean valid) {
        drawArrow(start, end);
    }

    @Override
    public void draw(final Point2D start, final Point2D end, final List<Point2D> jointPositions,
            final GConnector target, final boolean valid) {
        drawArrow(start, end);
    }

    @Override
    public List<Point2D> allocateJointPositions() {
        return new ArrayList<>();
    }

    @Override
    protected void selectionChanged(boolean isSelected) {
        // Not implemented
    }

    /**
     * Draws an arrow from the start to end point.
     *
     * @param start the start point of the arrow
     * @param end the end point (tip) of the arrow
     */
    private void drawArrow(final Point2D start, final Point2D end) {

        if (getItem().getType().equals(TreeSkinConstants.TREE_OUTPUT_CONNECTOR)) {
            ArrowUtils.draw(arrow, start, end, OFFSET_DISTANCE);
        } else {
            ArrowUtils.draw(arrow, end, start, OFFSET_DISTANCE);
        }
    }
} public class SelectionCopier {

    private static final EReference NODES = GraphPackage.Literals.GMODEL__NODES;
    private static final EReference CONNECTIONS = GraphPackage.Literals.GMODEL__CONNECTIONS;

    private static final double BASE_PASTE_OFFSET = 20;

    private final SkinLookup skinLookup;
    private final SelectionManager selectionManager;

    private final List<GNode> copiedNodes = new ArrayList<>();
    private final List<GConnection> copiedConnections = new ArrayList<>();

    private Parent parentAtTimeOfCopy;
    private double parentSceneXAtTimeOfCopy;
    private double parentSceneYAtTimeOfCopy;

    private GModel model;

    /**
     * Creates a new {@link SelectionCopier} instance.
     *
     * @param skinLookup
     *            the {@link SkinLookup} instance for the graph editor
     * @param selectionManager
     *            the {@link SelectionManager} instance for the graph editor
     */
	public SelectionCopier(final SkinLookup skinLookup, final SelectionManager selectionManager) {

        this.skinLookup = skinLookup;
        this.selectionManager = selectionManager;
    }

    /**
     * Initializes the selection copier for the current model.
     *
     * @param model the {@link GModel} currently being edited
     */
    public void initialize(final GModel model) {
        this.model = model;
    }

    /**
     * Copies the current selection and stores it in memory.
     */
    public void copy() {

        if (selectionManager.getSelectedItems().isEmpty()) {
            return;
        }

        copiedNodes.clear();
        copiedConnections.clear();

        final Map<GNode, GNode> copyStorage = new HashMap<>();

        // Don't iterate directly over selectionTracker.getSelectedNodes() because that will not preserve ordering.
        for (final GNode node : model.getNodes()) {
            if (selectionManager.isSelected(node)) {

                final GNode copiedNode = EcoreUtil.copy(node);
                copiedNodes.add(copiedNode);
                copyStorage.put(node, copiedNode);
            }
        }

        copiedConnections.addAll(ConnectionCopier.copyConnections(copyStorage));
        saveParentPositionInScene();
    }

    /**
     * Pastes the most-recently-copied selection.
     *
     * <p>
     * After the paste operation, the newly-pasted elements will be selected.
     * </p>
     *
     * @param consumer a consumer to allow custom commands to be appended to the paste command
     * @return the list of new {@link GNode} instances created by the paste operation
     */
    public List<GNode> paste(final BiConsumer<List<GNode>, CompoundCommand> consumer) {

    	selectionManager.clearSelection();

        final List<GNode> pastedNodes = new ArrayList<>();
        final List<GConnection> pastedConnections = new ArrayList<>();

        preparePastedElements(pastedNodes, pastedConnections);
        addPasteOffset(pastedNodes, pastedConnections);
        checkWithinBounds(pastedNodes, pastedConnections);
        addPastedElements(pastedNodes, pastedConnections, consumer);

        for (final GNode pastedNode : pastedNodes) {
        	selectionManager.select(pastedNode);
        }

        for (final GConnection pastedConnection : pastedConnections) {
            for (final GJoint pastedJoint : pastedConnection.getJoints()) {
            	selectionManager.select(pastedJoint);
            }
        }

        return pastedNodes;
    }

    /**
     * Clears the memory of what was cut / copied. Future paste operations will do nothing.
     */
    public void clearMemory() {
        copiedNodes.clear();
        copiedConnections.clear();
    }

    /**
     * Prepares the lists of pasted nodes and connections.
     *
     * @param pastedNodes an empty list to be filled with pasted nodes
     * @param pastedConnections an empty list to be filled with pasted connections
     */
    private void preparePastedElements(final List<GNode> pastedNodes, final List<GConnection> pastedConnections) {

        final Map<GNode, GNode> pasteStorage = new HashMap<>();

        for (final GNode copiedNode : copiedNodes) {

            final GNode pastedNode = EcoreUtil.copy(copiedNode);
            pastedNodes.add(pastedNode);

            pasteStorage.put(copiedNode, pastedNode);
        }

        pastedConnections.addAll(ConnectionCopier.copyConnections(pasteStorage));
    }

    /**
     * Adds an x and y offset to all nodes and connections that are about to be pasted.
     *
     * @param pastedNodes the nodes that are going to be pasted
     * @param pastedConnections the connections that are going to be pasted
     */
    private void addPasteOffset(final List<GNode> pastedNodes, final List<GConnection> pastedConnections) {

        final Point2D pasteOffset = determinePasteOffset();

        for (final GNode node : pastedNodes) {
            node.setX(node.getX() + pasteOffset.getX());
            node.setY(node.getY() + pasteOffset.getY());
        }

        for (final GConnection connection : pastedConnections) {
            for (final GJoint joint : connection.getJoints()) {
                joint.setX(joint.getX() + pasteOffset.getX());
                joint.setY(joint.getY() + pasteOffset.getY());
            }
        }
    }

    /**
     * Checks that the pasted node and joints will be inside the bounds of their parent.
     *
     * <p>
     * Corrects the x and y positions accordingly if they will be outside the bounds.
     * </p>
     *
     * @param pastedNodes the nodes that are going to be pasted
     * @param pastedConnections the connections that are going to be pasted
     */
    private void checkWithinBounds(final List<GNode> pastedNodes, final List<GConnection> pastedConnections) {

        if (parentAtTimeOfCopy instanceof Region) {

            final Region parentRegion = (Region) parentAtTimeOfCopy;

            final Bounds parentBounds = getBounds(parentRegion);
            final Bounds contentBounds = getContentBounds(pastedNodes, pastedConnections);

            double xCorrection = 0;
            double yCorrection = 0;

            if (contentBounds.startX < parentBounds.startX) {
                xCorrection = parentBounds.startX - contentBounds.startX;
            } else if (contentBounds.endX > parentBounds.endX) {
                xCorrection = parentBounds.endX - contentBounds.endX;
            }

            if (contentBounds.startY < parentBounds.startY) {
                yCorrection = parentBounds.startY - contentBounds.startY;
            } else if (contentBounds.endY > parentBounds.endY) {
                yCorrection = parentBounds.endY - contentBounds.endY;
            }

            if (xCorrection != 0 || yCorrection != 0) {

                for (final GNode node : pastedNodes) {
                    node.setX(node.getX() + xCorrection);
                    node.setY(node.getY() + yCorrection);
                }

                for (final GConnection connection : pastedConnections) {
                    for (final GJoint joint : connection.getJoints()) {
                        joint.setX(joint.getX() + xCorrection);
                        joint.setY(joint.getY() + yCorrection);
                    }
                }
            }
        }
    }

    /**
     * Adds the pasted elements to the graph editor via a single EMF command.
     *
     * @param pastedNodes the pasted nodes to be added
     * @param pastedConnections the pasted connections to be added
     * @param consumer a consumer to allow custom commands to be appended to the paste command
     */
    private void addPastedElements(final List<GNode> pastedNodes, final List<GConnection> pastedConnections,
            final BiConsumer<List<GNode>, CompoundCommand> consumer) {

        final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(model);
        final CompoundCommand command = new CompoundCommand();

        for (final GNode pastedNode : pastedNodes) {
            command.append(AddCommand.create(editingDomain, model, NODES, pastedNode));
        }

        for (final GConnection pastedConnection : pastedConnections) {
            command.append(AddCommand.create(editingDomain, model, CONNECTIONS, pastedConnection));
        }

        if (command.canExecute()) {
            editingDomain.getCommandStack().execute(command);
        }

        if (consumer != null) {
            consumer.accept(pastedNodes, command);
        }

    }

    /**
     * Saves the position in the scene of the JavaFX {@link Parent} of the node skins.
     */
    private void saveParentPositionInScene() {

        if (!selectionManager.getSelectedItems().isEmpty()) {

            final GNode firstSelectedNode = selectionManager.getSelectedNodes().get(0);
            final GNodeSkin firstSelectedNodeSkin = skinLookup.lookupNode(firstSelectedNode);

            final Node root = firstSelectedNodeSkin.getRoot();
            final Parent parent = root.getParent();

            if (parent != null) {

                parentAtTimeOfCopy = parent;

                final Point2D localToScene = parent.localToScene(0, 0);

                parentSceneXAtTimeOfCopy = localToScene.getX();
                parentSceneYAtTimeOfCopy = localToScene.getY();
            }
        }
    }

    /**
     * Determines the offset by which the new nodes and joints should be positioned relative to the nodes and joints
     * they were copied from.
     *
     * <p>
     * The aim here is to paste the new nodes and joints into a <b>visible</b> area on the screen, even if the user has
     * panned around in the graph editor container since the copy-action took place.
     * </p>
     *
     * @return a {@link Point2D} containing the x and y offsets
     */
    private Point2D determinePasteOffset() {

        double offsetX = BASE_PASTE_OFFSET;
        double offsetY = BASE_PASTE_OFFSET;

        if (parentAtTimeOfCopy != null) {

            final Point2D localToScene = parentAtTimeOfCopy.localToScene(0, 0);

            final double parentSceneXAtTimeOfPaste = localToScene.getX();
            final double parentSceneYAtTimeOfPaste = localToScene.getY();

            offsetX += parentSceneXAtTimeOfCopy - parentSceneXAtTimeOfPaste;
            offsetY += parentSceneYAtTimeOfCopy - parentSceneYAtTimeOfPaste;
        }

        return new Point2D(offsetX, offsetY);
    }

    /**
     * Gets the start and end x- and y-positions of the given region (including insets).
     *
     * @param region a {@link Region}
     * @return the bounds of the given region
     */
    private Bounds getBounds(final Region region) {

        final Bounds bounds = new Bounds();

        bounds.startX = region.getInsets().getLeft();
        bounds.startY = region.getInsets().getTop();

        bounds.endX = region.getWidth() - region.getInsets().getRight();
        bounds.endY = region.getHeight() - region.getInsets().getBottom();

        return bounds;
    }

    /**
     * Gets the start and end x- and y-positions of the given group of nodes and joints.
     *
     * @param nodes a list of nodes
     * @param connections a list of connections
     * @return the start and end x- and y-positions of the given nodes and joints.
     */
    private Bounds getContentBounds(final List<GNode> nodes, final List<GConnection> connections) {

        final Bounds contentBounds = new Bounds();

        contentBounds.startX = Double.MAX_VALUE;
        contentBounds.startY = Double.MAX_VALUE;

        contentBounds.endX = 0;
        contentBounds.endY = 0;

        for (final GNode node : nodes) {

            if (node.getX() < contentBounds.startX) {
                contentBounds.startX = node.getX();
            }
            if (node.getY() < contentBounds.startY) {
                contentBounds.startY = node.getY();
            }
            if (node.getX() + node.getWidth() > contentBounds.endX) {
                contentBounds.endX = node.getX() + node.getWidth();
            }
            if (node.getY() + node.getHeight() > contentBounds.endY) {
                contentBounds.endY = node.getY() + node.getHeight();
            }
        }

        for (final GConnection connection : connections) {
            for (final GJoint joint : connection.getJoints()) {

                if (joint.getX() < contentBounds.startX) {
                    contentBounds.startX = joint.getX();
                }
                if (joint.getY() < contentBounds.startY) {
                    contentBounds.startY = joint.getY();
                }
                if (joint.getX() > contentBounds.endX) {
                    contentBounds.endX = joint.getX();
                }
                if (joint.getY() > contentBounds.endY) {
                    contentBounds.endY = joint.getY();
                }
            }
        }

        return contentBounds;
    }

    /**
     * Stores start and end x- and y-positions of a rectangular object.
     */
    private class Bounds {

        public double startX;
        public double startY;
        public double endX;
        public double endY;
    }
} public interface GConnection extends EObject {
	/**
	 * Returns the value of the '<em><b>Id</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Id</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Id</em>' attribute.
	 * @see #setId(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnection_Id()
	 * @model id="true"
	 * @generated
	 */
	String getId();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnection#getId <em>Id</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Id</em>' attribute.
	 * @see #getId()
	 * @generated
	 */
	void setId(String value);

	/**
	 * Returns the value of the '<em><b>Type</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Type</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Type</em>' attribute.
	 * @see #setType(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnection_Type()
	 * @model
	 * @generated
	 */
	String getType();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnection#getType <em>Type</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Type</em>' attribute.
	 * @see #getType()
	 * @generated
	 */
	void setType(String value);

	/**
	 * Returns the value of the '<em><b>Source</b></em>' reference.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Source</em>' reference isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Source</em>' reference.
	 * @see #setSource(GConnector)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnection_Source()
	 * @model required="true"
	 * @generated
	 */
	GConnector getSource();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnection#getSource <em>Source</em>}' reference.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Source</em>' reference.
	 * @see #getSource()
	 * @generated
	 */
	void setSource(GConnector value);

	/**
	 * Returns the value of the '<em><b>Target</b></em>' reference.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Target</em>' reference isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Target</em>' reference.
	 * @see #setTarget(GConnector)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnection_Target()
	 * @model required="true"
	 * @generated
	 */
	GConnector getTarget();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnection#getTarget <em>Target</em>}' reference.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Target</em>' reference.
	 * @see #getTarget()
	 * @generated
	 */
	void setTarget(GConnector value);

	/**
	 * Returns the value of the '<em><b>Joints</b></em>' containment reference list.
	 * The list contents are of type {@link de.tesis.dynaware.grapheditor.model.GJoint}.
	 * It is bidirectional and its opposite is '{@link de.tesis.dynaware.grapheditor.model.GJoint#getConnection <em>Connection</em>}'.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Joints</em>' containment reference list isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Joints</em>' containment reference list.
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnection_Joints()
	 * @see de.tesis.dynaware.grapheditor.model.GJoint#getConnection
	 * @model opposite="connection" containment="true"
	 * @generated
	 */
	EList<GJoint> getJoints();

} // GConnection public interface GConnector extends EObject {
	/**
	 * Returns the value of the '<em><b>Id</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Id</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Id</em>' attribute.
	 * @see #setId(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_Id()
	 * @model id="true"
	 * @generated
	 */
	String getId();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#getId <em>Id</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Id</em>' attribute.
	 * @see #getId()
	 * @generated
	 */
	void setId(String value);

	/**
	 * Returns the value of the '<em><b>Type</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Type</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Type</em>' attribute.
	 * @see #setType(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_Type()
	 * @model
	 * @generated
	 */
	String getType();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#getType <em>Type</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Type</em>' attribute.
	 * @see #getType()
	 * @generated
	 */
	void setType(String value);

	/**
	 * Returns the value of the '<em><b>Parent</b></em>' container reference.
	 * It is bidirectional and its opposite is '{@link de.tesis.dynaware.grapheditor.model.GNode#getConnectors <em>Connectors</em>}'.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Parent</em>' container reference isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Parent</em>' container reference.
	 * @see #setParent(GNode)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_Parent()
	 * @see de.tesis.dynaware.grapheditor.model.GNode#getConnectors
	 * @model opposite="connectors" required="true" transient="false"
	 * @generated
	 */
	GNode getParent();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#getParent <em>Parent</em>}' container reference.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Parent</em>' container reference.
	 * @see #getParent()
	 * @generated
	 */
	void setParent(GNode value);

	/**
	 * Returns the value of the '<em><b>Connections</b></em>' reference list.
	 * The list contents are of type {@link de.tesis.dynaware.grapheditor.model.GConnection}.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Connections</em>' reference list isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Connections</em>' reference list.
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_Connections()
	 * @model
	 * @generated
	 */
	EList<GConnection> getConnections();

	/**
	 * Returns the value of the '<em><b>X</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>X</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>X</em>' attribute.
	 * @see #setX(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_X()
	 * @model
	 * @generated
	 */
	double getX();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#getX <em>X</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>X</em>' attribute.
	 * @see #getX()
	 * @generated
	 */
	void setX(double value);

	/**
	 * Returns the value of the '<em><b>Y</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Y</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Y</em>' attribute.
	 * @see #setY(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_Y()
	 * @model
	 * @generated
	 */
	double getY();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#getY <em>Y</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Y</em>' attribute.
	 * @see #getY()
	 * @generated
	 */
	void setY(double value);

	/**
	 * Returns the value of the '<em><b>Connection Detached On Drag</b></em>' attribute.
	 * The default value is <code>"true"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Connection Detached On Drag</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Connection Detached On Drag</em>' attribute.
	 * @see #setConnectionDetachedOnDrag(boolean)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGConnector_ConnectionDetachedOnDrag()
	 * @model default="true" required="true"
	 * @generated
	 */
	boolean isConnectionDetachedOnDrag();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GConnector#isConnectionDetachedOnDrag <em>Connection Detached On Drag</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Connection Detached On Drag</em>' attribute.
	 * @see #isConnectionDetachedOnDrag()
	 * @generated
	 */
	void setConnectionDetachedOnDrag(boolean value);

} // GConnector public interface GNode extends EObject {
	/**
	 * Returns the value of the '<em><b>Id</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Id</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Id</em>' attribute.
	 * @see #setId(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Id()
	 * @model id="true"
	 * @generated
	 */
	String getId();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getId <em>Id</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Id</em>' attribute.
	 * @see #getId()
	 * @generated
	 */
	void setId(String value);

	/**
	 * Returns the value of the '<em><b>Type</b></em>' attribute.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Type</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Type</em>' attribute.
	 * @see #setType(String)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Type()
	 * @model
	 * @generated
	 */
	String getType();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getType <em>Type</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Type</em>' attribute.
	 * @see #getType()
	 * @generated
	 */
	void setType(String value);

	/**
	 * Returns the value of the '<em><b>X</b></em>' attribute.
	 * The default value is <code>"0"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>X</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>X</em>' attribute.
	 * @see #setX(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_X()
	 * @model default="0" required="true"
	 * @generated
	 */
	double getX();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getX <em>X</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>X</em>' attribute.
	 * @see #getX()
	 * @generated
	 */
	void setX(double value);

	/**
	 * Returns the value of the '<em><b>Y</b></em>' attribute.
	 * The default value is <code>"0"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Y</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Y</em>' attribute.
	 * @see #setY(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Y()
	 * @model default="0" required="true"
	 * @generated
	 */
	double getY();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getY <em>Y</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Y</em>' attribute.
	 * @see #getY()
	 * @generated
	 */
	void setY(double value);

	/**
	 * Returns the value of the '<em><b>Width</b></em>' attribute.
	 * The default value is <code>"151"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Width</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Width</em>' attribute.
	 * @see #setWidth(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Width()
	 * @model default="151" required="true"
	 * @generated
	 */
	double getWidth();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getWidth <em>Width</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Width</em>' attribute.
	 * @see #getWidth()
	 * @generated
	 */
	void setWidth(double value);

	/**
	 * Returns the value of the '<em><b>Height</b></em>' attribute.
	 * The default value is <code>"101"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Height</em>' attribute isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Height</em>' attribute.
	 * @see #setHeight(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Height()
	 * @model default="101" required="true"
	 * @generated
	 */
	double getHeight();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GNode#getHeight <em>Height</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Height</em>' attribute.
	 * @see #getHeight()
	 * @generated
	 */
	void setHeight(double value);

	/**
	 * Returns the value of the '<em><b>Connectors</b></em>' containment reference list.
	 * The list contents are of type {@link de.tesis.dynaware.grapheditor.model.GConnector}.
	 * It is bidirectional and its opposite is '{@link de.tesis.dynaware.grapheditor.model.GConnector#getParent <em>Parent</em>}'.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Connectors</em>' containment reference list isn't clear,
	 * there really should be more of a description here...
	 * </p>
	 * <!-- end-user-doc -->
	 * @return the value of the '<em>Connectors</em>' containment reference list.
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGNode_Connectors()
	 * @see de.tesis.dynaware.grapheditor.model.GConnector#getParent
	 * @model opposite="parent" containment="true"
	 * @generated
	 */
	EList<GConnector> getConnectors();

} // GNode public interface GraphFactory extends EFactory {
	/**
	 * The singleton instance of the factory.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	GraphFactory eINSTANCE = de.tesis.dynaware.grapheditor.model.impl.GraphFactoryImpl.init();

	/**
	 * Returns a new object of class '<em>GModel</em>'.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return a new object of class '<em>GModel</em>'.
	 * @generated
	 */
	GModel createGModel();

	/**
	 * Returns a new object of class '<em>GNode</em>'.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return a new object of class '<em>GNode</em>'.
	 * @generated
	 */
	GNode createGNode();

	/**
	 * Returns a new object of class '<em>GConnection</em>'.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return a new object of class '<em>GConnection</em>'.
	 * @generated
	 */
	GConnection createGConnection();

	/**
	 * Returns a new object of class '<em>GConnector</em>'.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return a new object of class '<em>GConnector</em>'.
	 * @generated
	 */
	GConnector createGConnector();

	/**
	 * Returns a new object of class '<em>GJoint</em>'.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return a new object of class '<em>GJoint</em>'.
	 * @generated
	 */
	GJoint createGJoint();

	/**
	 * Returns the package supported by this factory.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @return the package supported by this factory.
	 * @generated
	 */
	GraphPackage getGraphPackage();

} //GraphFactory
import java.util.List;
import de.tesis.dynaware.grapheditor.Commands;
import de.tesis.dynaware.grapheditor.GConnectionSkin;
import de.tesis.dynaware.grapheditor.GConnectorSkin;
import de.tesis.dynaware.grapheditor.GNodeSkin;
import de.tesis.dynaware.grapheditor.GTailSkin;
import de.tesis.dynaware.grapheditor.GraphEditor;
import de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultConnectionSkin;
import de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultConnectorSkin;
import de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultNodeSkin;
import de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultTailSkin;
import de.tesis.dynaware.grapheditor.core.view.GraphEditorContainer;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeConnectionSkin;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeConnectorSkin;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeNodeSkin;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeSkinConstants;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeTailSkin;
import de.tesis.dynaware.grapheditor.demo.selections.SelectionCopier;
import de.tesis.dynaware.grapheditor.model.GConnection;
import de.tesis.dynaware.grapheditor.model.GConnector;
import de.tesis.dynaware.grapheditor.model.GNode;
import de.tesis.dynaware.grapheditor.model.GraphFactory;
import javafx.geometry.Side;
package de.tesis.dynaware.grapheditor.demo.customskins;



/**
 * Responsible for tree-skin specific logic in the graph editor demo.
 */
public class TreeSkinController implements SkinController {

    protected static final int TREE_NODE_INITIAL_Y = 19;

    private final GraphEditor graphEditor;
    private final GraphEditorContainer graphEditorContainer;

    /**
     * Creates a new {@link TreeSkinController} instance.
     *
     * @param graphEditor the graph editor on display in this demo
     * @param graphEditorContainer the graph editor container on display in this demo
     */
    public TreeSkinController(final GraphEditor graphEditor, final GraphEditorContainer graphEditorContainer) {

        this.graphEditor = graphEditor;
        this.graphEditorContainer = graphEditorContainer;
    }

    @Override
    public void activate()
    {
        graphEditor.setNodeSkinFactory(this::createSkin);
        graphEditor.setConnectorSkinFactory(this::createSkin);
        graphEditor.setConnectionSkinFactory(this::createSkin);
        graphEditor.setTailSkinFactory(this::createTailSkin);
        graphEditorContainer.getMinimap().setConnectionFilter(c -> false);
    }

    private GNodeSkin createSkin(final GNode node) {
        return TreeSkinConstants.TREE_NODE.equals(node.getType()) ? new TreeNodeSkin(node) : new DefaultNodeSkin(node);
    }

    private GConnectionSkin createSkin(final GConnection connection) {
        return TreeSkinConstants.TREE_CONNECTION.equals(connection.getType()) ? new TreeConnectionSkin(connection) : new DefaultConnectionSkin(connection);
    }

    private GConnectorSkin createSkin(final GConnector connector) {
        return TreeSkinConstants.TREE_INPUT_CONNECTOR.equals(connector.getType()) || TreeSkinConstants.TREE_OUTPUT_CONNECTOR.equals(connector.getType()) ?
                new TreeConnectorSkin(connector) : new DefaultConnectorSkin(connector);
    }

    private GTailSkin createTailSkin(final GConnector connector) {
        return TreeSkinConstants.TREE_INPUT_CONNECTOR.equals(connector.getType()) || TreeSkinConstants.TREE_OUTPUT_CONNECTOR.equals(connector.getType()) ?
                new TreeTailSkin(connector) : new DefaultTailSkin(connector);