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 enum EditorElement
{
    CONNECTOR,
    NODE,
    JOINT;
}
 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 DefaultGraphEditor implements GraphEditor
{

    private final GraphEditorProperties mProperties;
    private final GraphEditorView mView;
    private final ConnectionEventManager mConnectionEventManager = new ConnectionEventManager();
    private final GraphEditorSkinManager mSkinManager;
    private final GraphEditorController<DefaultGraphEditor> mController;

    private final ObjectProperty<GModel> mModelProperty = new ObjectPropertyBase<GModel>()
    {

        @Override
        public Object getBean()
        {
            return DefaultGraphEditor.this;
        }

        @Override
        public String getName()
        {
            return "model";
        }

    };

    /**
     * Creates a new default implementation of the {@link GraphEditor}.
     */
    public DefaultGraphEditor()
    {
        this(null);
    }

    /**
     * Creates a new default implementation of the {@link GraphEditor}.
     */
    public DefaultGraphEditor(final GraphEditorProperties pProperties)
    {
        mProperties = pProperties == null ? new GraphEditorProperties() : pProperties;
        mView = new GraphEditorView(mProperties);
        mSkinManager = new GraphEditorSkinManager(this, mView);
        mController = new GraphEditorController<>(this, mSkinManager, mView, mConnectionEventManager, mProperties);

        final ConnectionLayouter connectionLayouter = mController.getConnectionLayouter();
        mView.setConnectionLayouter(connectionLayouter);
        mSkinManager.setConnectionLayouter(connectionLayouter);
    }

    @Override
    public void setNodeSkinFactory(final Callback<GNode, GNodeSkin> pSkinFactory)
    {
        mSkinManager.setNodeSkinFactory(pSkinFactory);
    }

    @Override
    public void setConnectorSkinFactory(final Callback<GConnector, GConnectorSkin> pConnectorSkinFactory)
    {
        mSkinManager.setConnectorSkinFactory(pConnectorSkinFactory);
    }

    @Override
    public void setConnectionSkinFactory(final Callback<GConnection, GConnectionSkin> pConnectionSkinFactory)
    {
        mSkinManager.setConnectionSkinFactory(pConnectionSkinFactory);
    }

    @Override
    public void setJointSkinFactory(final Callback<GJoint, GJointSkin> pJointSkinFactory)
    {
        mSkinManager.setJointSkinFactory(pJointSkinFactory);
    }

    @Override
    public void setTailSkinFactory(final Callback<GConnector, GTailSkin> pTailSkinFactory)
    {
        mSkinManager.setTailSkinFactory(pTailSkinFactory);
    }

    @Override
    public void setConnectorValidator(final GConnectorValidator pValidator)
    {
        mController.setConnectorValidator(pValidator);
    }

    @Override
    public void setModel(final GModel pModel)
    {
        mModelProperty.set(pModel);
    }

    @Override
    public GModel getModel()
    {
        return mModelProperty.get();
    }

    @Override
    public void reload()
    {
        mController.process();
    }

    @Override
    public ObjectProperty<GModel> modelProperty()
    {
        return mModelProperty;
    }

    @Override
    public Region getView()
    {
        return mView;
    }

    @Override
    public GraphEditorProperties getProperties()
    {
        return mProperties;
    }

    @Override
    public SkinLookup getSkinLookup()
    {
        return mSkinManager;
    }

    @Override
    public SelectionManager getSelectionManager()
    {
        return mController.getSelectionManager();
    }

    @Override
    public void setOnConnectionCreated(final Function<GConnection, Command> pConsumer)
    {
        mConnectionEventManager.setOnConnectionCreated(pConsumer);
    }

    @Override
    public void setOnConnectionRemoved(final BiFunction<RemoveContext, GConnection, Command> pOnConnectionRemoved)
    {
        mConnectionEventManager.setOnConnectionRemoved(pOnConnectionRemoved);
        getModelEditingManager().setOnConnectionRemoved(pOnConnectionRemoved);
    }

    @Override
    public void setOnNodeRemoved(final BiFunction<RemoveContext, GNode, Command> pOnNodeRemoved)
    {
        getModelEditingManager().setOnNodeRemoved(pOnNodeRemoved);
    }

    @Override
    public void delete(Collection<EObject> pItems)
    {
        getModelEditingManager().remove(pItems);
    }

    private ModelEditingManager getModelEditingManager()
    {
        return mController.getModelEditingManager();
    }
} public class SimpleConnectionSkin extends GConnectionSkin {

    /**
     * Property key to show detours at intersections.
     *
     * <p>
     * By default small gaps are drawn points where the connection passes <b>under</b> other connections. However it is
     * also possible to draw detours (small semicircles) at points where the connection passes <b>over</b> others.
     * </p>
     *
     * <p>
     * To activate this functionality, add this key to the graph editor's custom properties with the value "true". Do
     * <b>NOT</b> mix 'detoured' and 'gapped' connection skins in the same graph, it will look bad.
     * </p>
     */
    public static final String SHOW_DETOURS_KEY = "default-connection-skin-show-detours";

    protected final Group root = new Group();
    protected final Path path = new Path();
    protected final Path backgroundPath = new Path();

    protected final List<ConnectionSegment> connectionSegments = new ArrayList<>();

    private static final String STYLE_CLASS = "default-connection";
    private static final String STYLE_CLASS_BACKGROUND = "default-connection-background";

    private List<GJointSkin> jointSkins;

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

        super(connection);

        root.setManaged(false);

        // Background path is invisible and used only to capture hover events.
        root.getChildren().add(backgroundPath);
        root.getChildren().add(path);

        path.setMouseTransparent(true);

        backgroundPath.getStyleClass().setAll(STYLE_CLASS_BACKGROUND);
        path.getStyleClass().setAll(STYLE_CLASS);
    }

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

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

        if (this.jointSkins != null) {
            removeOldRectangularConstraints();
        }

        this.jointSkins = jointSkins;

        addRectangularConstraints();
    }

    @Override
    public Point2D[] update()
    {
        final Point2D[] points = super.update();
        checkFirstAndLastJoints(points);
        return points;
    }

    @Override
    public void draw(final Map<GConnectionSkin, Point2D[]> allPoints)
    {
        super.draw(allPoints);

        // If we are showing detours, get all intersections with connections *behind* this one. Otherwise in front.
        final double[][] intersections = IntersectionFinder.find(this, allPoints, checkShowDetours());

        final Point2D[] points = allPoints == null ? null : allPoints.get(this);
        if (points != null)
        {
            drawAllSegments(points, intersections);
        }
        else
        {
            connectionSegments.clear();
            path.getElements().clear();
        }
    }

    /**
     * Removes the old rectangular constraints on the connection's list of joint skins.
     */
    private void removeOldRectangularConstraints()
    {
        for (int i = 0; i < jointSkins.size() - 1; i++)
        {
            final DraggableBox thisJoint = jointSkins.get(i).getRoot();
            final DraggableBox nextJoint = jointSkins.get(i + 1).getRoot();

            if (RectangularConnections.isSegmentHorizontal(getItem(), i))
            {
                thisJoint.bindLayoutX(null);
                nextJoint.bindLayoutX(null);
            }
            else
            {
                thisJoint.bindLayoutY(null);
                nextJoint.bindLayoutY(null);
            }
        }
    }

    /**
     * Adds constraints to the connection's joints in order to keep the connection rectangular in shape.
     */
    private void addRectangularConstraints()
    {
        // Our rectangular connection logic assumes an even number of joints.
        for (int i = 0; i < jointSkins.size() - 1; i++)
        {
            final DraggableBox thisJoint = jointSkins.get(i).getRoot();
            final DraggableBox nextJoint = jointSkins.get(i + 1).getRoot();

            if (RectangularConnections.isSegmentHorizontal(getItem(), i))
            {
                thisJoint.bindLayoutX(nextJoint);
                nextJoint.bindLayoutX(thisJoint);
            }
            else
            {
                thisJoint.bindLayoutY(nextJoint);
                nextJoint.bindLayoutY(thisJoint);
            }
        }
    }

    /**
     * Checks the position of the first and last joints and makes sure they are aligned with their adjacent connectors.
     *
     * @param points all points that the connection should pass through (both connector and joint positions)
     */
    private void checkFirstAndLastJoints(final Point2D[] points)
    {
        alignJoint(points, RectangularConnections.isSegmentHorizontal(getItem(), 0), true);
        alignJoint(points, RectangularConnections.isSegmentHorizontal(getItem(), points.length - 2), false);
    }

    /**
     * Aligns the first or last joint to have the same vertical or horizontal position as the start or end point.
     *
     * @param points the list of points in this connection
     * @param vertical {@code true} to align in the vertical (y) direction, {@code false} for horizontal (x)
     * @param start {@code true} to align the first joint to the start, {@code false} for the last joint to the end
     */
    private void alignJoint(final Point2D[] points, final boolean vertical, final boolean start)
    {
        final int targetPositionIndex = start ? 0 : points.length - 1;
        final int jointPositionIndex = start ? 1 : points.length - 2;
        final GJointSkin jointSkin = jointSkins.get(start ? 0 : jointSkins.size() - 1);

        if (vertical)
        {
            final double newJointY = points[targetPositionIndex].getY();
            final double newJointLayoutY = GeometryUtils.moveOnPixel(newJointY - jointSkin.getHeight() / 2);
            jointSkin.getRoot().setLayoutY(newJointLayoutY);

            final double currentX = points[jointPositionIndex].getX();
            points[jointPositionIndex] = new Point2D(currentX, newJointY);
        }
        else
        {
            final double newJointX = points[targetPositionIndex].getX();
            final double newJointLayoutX = GeometryUtils.moveOnPixel(newJointX - jointSkin.getWidth() / 2);
            jointSkin.getRoot().setLayoutX(newJointLayoutX);

            final double currentY = points[jointPositionIndex].getY();
            points[jointPositionIndex] = new Point2D(newJointX, currentY);
        }
    }

    /**
     * Draws all segments of the connection.
     *
     * @param points all points that the connection should pass through (both connector and joint positions)
     * @param intersections all intersection-points of this connection with other connections
     */
    private void drawAllSegments(final Point2D[] points, final double[][] intersections)
    {
        final double startX = points[0].getX();
        final double startY = points[0].getY();

        final MoveTo moveTo = new MoveTo(GeometryUtils.moveOffPixel(startX), GeometryUtils.moveOffPixel(startY));

        connectionSegments.clear();
        path.getElements().clear();
        path.getElements().add(moveTo);

        for (int i = 0; i < points.length - 1; i++)
        {
            final Point2D start = points[i];
            final Point2D end = points[i + 1];

            final double[] segmentIntersections = intersections != null ? intersections[i] : null;
            final ConnectionSegment segment;

            if (checkShowDetours())
            {
                segment = new DetouredConnectionSegment(start, end, segmentIntersections);
            }
            else
            {
                segment = new GappedConnectionSegment(start, end, segmentIntersections);
            }

            segment.draw();

            connectionSegments.add(segment);
            path.getElements().addAll(segment.getPathElements());
        }

        backgroundPath.getElements().clear();
        backgroundPath.getElements().addAll(path.getElements());
    }

    /**
     * Checks whether the custom property has been set to show detours instead of gaps when connections intersect.
     *
     * @return {@code true} if the custom property to show detours has been set
     */
    private boolean checkShowDetours()
    {
        boolean showDetours = false;

        final String value = getGraphEditor().getProperties().getCustomProperties().get(SHOW_DETOURS_KEY);
        if (Boolean.toString(true).equals(value))
        {
            showDetours = true;
        }

        return showDetours;
    }

    @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 DefaultSkinController implements SkinController {

    protected static final int NODE_INITIAL_X = 19;
    protected static final int NODE_INITIAL_Y = 19;

    protected final GraphEditor graphEditor;
    protected final GraphEditorContainer graphEditorContainer;

    private static final int MAX_CONNECTOR_COUNT = 5;

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

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

    @Override
    public void activate()
    {
        graphEditorContainer.getMinimap().setConnectionFilter(c -> true);
    }

    @Override
    public void addNode(final double currentZoomFactor) {

        final double windowXOffset = graphEditorContainer.getContentX() / currentZoomFactor;
        final double windowYOffset = graphEditorContainer.getContentY() / currentZoomFactor;

        final GNode node = GraphFactory.eINSTANCE.createGNode();
        node.setY(NODE_INITIAL_Y + windowYOffset);

        final GConnector rightOutput = GraphFactory.eINSTANCE.createGConnector();
        node.getConnectors().add(rightOutput);

        final GConnector leftInput = GraphFactory.eINSTANCE.createGConnector();
        node.getConnectors().add(leftInput);

        node.setX(NODE_INITIAL_X + windowXOffset);

        rightOutput.setType(DefaultConnectorTypes.RIGHT_OUTPUT);
        leftInput.setType(DefaultConnectorTypes.LEFT_INPUT);

        Commands.addNode(graphEditor.getModel(), node);
    }

    /**
     * Adds a connector of the given type to all nodes that are currently selected.
     *
     * @param position the position of the new connector
     * @param input {@code true} for input, {@code false} for output
     */
    @Override
    public void addConnector(final Side position, final boolean input) {

        final String type = getType(position, input);

        final GModel model = graphEditor.getModel();
        final SkinLookup skinLookup = graphEditor.getSkinLookup();
        final CompoundCommand command = new CompoundCommand();
        final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(model);

        for (final GNode node : model.getNodes()) {

            if (skinLookup.lookupNode(node).isSelected()) {
                if (countConnectors(node, position) < MAX_CONNECTOR_COUNT) {

                    final GConnector connector = GraphFactory.eINSTANCE.createGConnector();
                    connector.setType(type);

                    final EReference connectors = GraphPackage.Literals.GNODE__CONNECTORS;
                    command.append(AddCommand.create(editingDomain, node, connectors, connector));
                }
            }
        }

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

    @Override
    public void clearConnectors() {
        Commands.clearConnectors(graphEditor.getModel(), graphEditor.getSelectionManager().getSelectedNodes());
    }

    @Override
    public void handlePaste(final SelectionCopier selectionCopier) {
    	selectionCopier.paste(null);
    }

    @Override
    public void handleSelectAll() {
    	graphEditor.getSelectionManager().selectAll();
    }

    /**
     * Counts the number of connectors the given node currently has of the given type.
     *
     * @param node a {@link GNode} instance
     * @param side the {@link Side} the connector is on
     * @return the number of connectors this node has on the given side
     */
    private int countConnectors(final GNode node, final Side side) {

        int count = 0;

        for (final GConnector connector : node.getConnectors()) {
            if (side.equals(DefaultConnectorTypes.getSide(connector.getType()))) {
                count++;
            }
        }

        return count;
    }

    /**
     * Gets the connector type string corresponding to the given position and input values.
     *
     * @param position a {@link Side} value
     * @param input {@code true} for input, {@code false} for output
     * @return the connector type corresponding to these values
     */
    private String getType(final Side position, final boolean input)
    {
        switch (position)
        {
            case TOP:
                if (input)
                {
                    return DefaultConnectorTypes.TOP_INPUT;
                }
                return DefaultConnectorTypes.TOP_OUTPUT;
            case RIGHT:
                if (input)
                {
                    return DefaultConnectorTypes.RIGHT_INPUT;
                }
                return DefaultConnectorTypes.RIGHT_OUTPUT;
            case BOTTOM:
                if (input)
                {
                    return DefaultConnectorTypes.BOTTOM_INPUT;
                }
                return DefaultConnectorTypes.BOTTOM_OUTPUT;
            case LEFT:
                if (input)
                {
                    return DefaultConnectorTypes.LEFT_INPUT;
                }
                return DefaultConnectorTypes.LEFT_OUTPUT;
        }
        return null;
    }
} public interface SkinController {

    /**
     * Adds a node to the graph.
     *
     * @param currentZoomFactor the current zoom factor (1 for 100%)
     */
    void addNode(final double currentZoomFactor);

    /**
     * activates this skin
     */
    void activate();

    /**
     * Adds a connector of the given type to all selected nodes.
     *
     * @param position the currently selected connector position
     * @param input {@code true} for input, {@code false} for output
     */
    void addConnector(Side position, boolean input);

    /**
     * Clears all connectors from all selected nodes.
     */
    void clearConnectors();

    /**
     * Handles the paste operation.
     * @param selectionCopier {@link SelectionCopier}
     */
    void handlePaste(SelectionCopier selectionCopier);

    /**
     * Handles the select-all operation.
     */
    void handleSelectAll();
} public class TitledSkinController extends DefaultSkinController {

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

    @Override
    public void activate() {
        super.activate();
        graphEditor.setNodeSkinFactory(this::createSkin);
        graphEditor.setConnectorSkinFactory(this::createSkin);
        graphEditor.setTailSkinFactory(this::createTailSkin);
    }

    private GNodeSkin createSkin(final GNode node) {
        return TitledSkinConstants.TITLED_NODE.equals(node.getType()) ? new TitledNodeSkin(node) : new DefaultNodeSkin(node);
    }

    private GConnectorSkin createSkin(final GConnector connector) {
        return TitledSkinConstants.TITLED_INPUT_CONNECTOR.equals(connector.getType()) || TitledSkinConstants.TITLED_OUTPUT_CONNECTOR.equals(connector.getType()) ?
                new TitledConnectorSkin(connector) : new DefaultConnectorSkin(connector);
    }

    private GTailSkin createTailSkin(final GConnector connector) {
        return TitledSkinConstants.TITLED_INPUT_CONNECTOR.equals(connector.getType()) || TitledSkinConstants.TITLED_INPUT_CONNECTOR.equals(connector.getType()) ?
                new TitledTailSkin(connector) : new DefaultTailSkin(connector);
    }

    @Override
    public void addNode(final double currentZoomFactor) {

        final double windowXOffset = graphEditorContainer.getContentX() / currentZoomFactor;
        final double windowYOffset = graphEditorContainer.getContentY() / currentZoomFactor;

        final GNode node = GraphFactory.eINSTANCE.createGNode();
        node.setY(NODE_INITIAL_Y + windowYOffset);

        node.setType(TitledSkinConstants.TITLED_NODE);
        node.setX(NODE_INITIAL_X + windowXOffset);
        node.setId(allocateNewId());

        final GConnector input = GraphFactory.eINSTANCE.createGConnector();
        node.getConnectors().add(input);
        input.setType(TitledSkinConstants.TITLED_INPUT_CONNECTOR);

        final GConnector output = GraphFactory.eINSTANCE.createGConnector();
        node.getConnectors().add(output);
        output.setType(TitledSkinConstants.TITLED_OUTPUT_CONNECTOR);

        Commands.addNode(graphEditor.getModel(), node);
    }

    @Override
    public void handlePaste(final SelectionCopier selectionCopier) {
        selectionCopier.paste((nodes, command) -> allocateIds(nodes, command));
    }

    /**
     * Allocates ID's to recently pasted nodes.
     *
     * @param nodes the recently pasted nodes
     * @param command the command responsible for adding the nodes
     */
    private void allocateIds(final List<GNode> nodes, final CompoundCommand command) {

        final EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(graphEditor.getModel());
        final EAttribute feature = GraphPackage.Literals.GNODE__ID;

        for (final GNode node : nodes) {

            if (checkNeedsNewId(node, nodes)) {

                final String id = allocateNewId();
                final Command setCommand = SetCommand.create(domain, node, feature, id);

                if (setCommand.canExecute()) {
                    command.appendAndExecute(setCommand);
                }

                graphEditor.getSkinLookup().lookupNode(node).initialize();
            }
        }
    }

    /**
     * Check the given node needs a new ID, i.e. that it's not already in use.
     *
     * @param node the nodes to check
     * @param pastedNodes the recently-pasted nodes
     */
    private boolean checkNeedsNewId(final GNode node, final List<GNode> pastedNodes) {

        final List<GNode> nodes = new ArrayList<>(graphEditor.getModel().getNodes());
        nodes.removeAll(pastedNodes);

        return nodes.stream().anyMatch(other -> other.getId().equals(node.getId()));
    }

    /**
     * Allocates a new ID corresponding to the largest existing ID + 1.
     *
     * @return the new ID
     */
    private String allocateNewId() {

        final List<GNode> nodes = graphEditor.getModel().getNodes();
        final OptionalInt max = nodes.stream().mapToInt(node -> Integer.parseInt(node.getId())).max();

        if (max.isPresent()) {
            return Integer.toString(max.getAsInt() + 1);
        }
        // ELSE:
        return "1"; //$NON-NLS-1$
    }
} 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);
    }

    @Override
    public void addNode(final double currentZoomFactor) {

        final double windowXOffset = graphEditorContainer.getContentX() / currentZoomFactor;
        final double windowYOffset = graphEditorContainer.getContentY() / currentZoomFactor;

        final GNode node = GraphFactory.eINSTANCE.createGNode();
        node.setY(TREE_NODE_INITIAL_Y + windowYOffset);

        final GConnector output = GraphFactory.eINSTANCE.createGConnector();
        node.getConnectors().add(output);

        final double initialX = graphEditorContainer.getWidth() / (2 * currentZoomFactor) - node.getWidth() / 2;
        node.setX(Math.floor(initialX) + windowXOffset);

        node.setType(TreeSkinConstants.TREE_NODE);
        output.setType(TreeSkinConstants.TREE_OUTPUT_CONNECTOR);

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

        Commands.addNode(graphEditor.getModel(), node);
    }

    @Override
    public void addConnector(final Side position, final boolean input) {
        // Not implemented for tree nodes.
    }

    @Override
    public void clearConnectors() {
        // Not implemented for tree nodes.
    }

    @Override
    public void handlePaste(final SelectionCopier selectionCopier) {
        selectionCopier.paste((nodes, command) -> selectReferencedConnections(nodes));
    }

    @Override
    public void handleSelectAll() {
    	graphEditor.getSelectionManager().selectAll();
    }

    /**
     * Selects all connections that are referenced (i.e. connected to) the given nodes.
     *
     * @param nodes a list of graph nodes
     */
	private void selectReferencedConnections(final List<GNode> nodes) {

		nodes.stream()
			.flatMap(node -> node.getConnectors().stream())
			.flatMap(connector -> connector.getConnections().stream())
			.forEach(graphEditor.getSelectionManager()::select);
	}
} public class TitledSkinConstants {

    public static final String TITLED_NODE = "titled-node"; //$NON-NLS-1$
    public static final String TITLED_INPUT_CONNECTOR = "titled-left-input"; //$NON-NLS-1$
    public static final String TITLED_OUTPUT_CONNECTOR = "titled-right-output"; //$NON-NLS-1$
} 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 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 enum AwesomeIcon {

    /**
     * A plus icon.
     */
    PLUS(0xf067),

    /**
     * A times / cross icon.
     */
    TIMES(0xf00d),

    /**
     * A map icon.
     */
    MAP(0xf03e);

    private static final String STYLE_CLASS = "icon"; //$NON-NLS-1$
    private static final String FONT_AWESOME = "FontAwesome"; //$NON-NLS-1$
    private int unicode;

    /**
     * Creates a new awesome icon for the given unicode value.
     *
     * @param pUnicode the unicode value as an integer
     */
    private AwesomeIcon(final int pUnicode) {
        this.unicode = pUnicode;
    }

    /**
     * Returns a new {@link Node} containing the icon.
     *
     * @return a new node containing the icon
     */
    public Node node() {

        final Text text = new Text(String.valueOf((char) unicode));
        text.getStyleClass().setAll(STYLE_CLASS);
        text.setFont(Font.font(FONT_AWESOME));

        return text;
    }
}
 public interface GModel extends EObject {
	/**
	 * Returns the value of the '<em><b>Nodes</b></em>' containment reference list.
	 * The list contents are of type {@link de.tesis.dynaware.grapheditor.model.GNode}.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Nodes</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>Nodes</em>' containment reference list.
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGModel_Nodes()
	 * @model containment="true"
	 * @generated
	 */
	EList<GNode> getNodes();

	/**
	 * Returns the value of the '<em><b>Connections</b></em>' containment 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>' 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>Connections</em>' containment reference list.
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGModel_Connections()
	 * @model containment="true"
	 * @generated
	 */
	EList<GConnection> getConnections();

	/**
	 * 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#getGModel_Type()
	 * @model
	 * @generated
	 */
	String getType();

	/**
	 * Sets the value of the '{@link de.tesis.dynaware.grapheditor.model.GModel#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>Content Width</b></em>' attribute.
	 * The default value is <code>"3000"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Content 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>Content Width</em>' attribute.
	 * @see #setContentWidth(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGModel_ContentWidth()
	 * @model default="3000" required="true"
	 * @generated
	 */
	double getContentWidth();

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

	/**
	 * Returns the value of the '<em><b>Content Height</b></em>' attribute.
	 * The default value is <code>"2250"</code>.
	 * <!-- begin-user-doc -->
	 * <p>
	 * If the meaning of the '<em>Content 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>Content Height</em>' attribute.
	 * @see #setContentHeight(double)
	 * @see de.tesis.dynaware.grapheditor.model.GraphPackage#getGModel_ContentHeight()
	 * @model default="2250" required="true"
	 * @generated
	 */
	double getContentHeight();

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

} // GModel 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.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import de.tesis.dynaware.grapheditor.Commands;
import de.tesis.dynaware.grapheditor.EditorElement;
import de.tesis.dynaware.grapheditor.GraphEditor;
import de.tesis.dynaware.grapheditor.core.DefaultGraphEditor;
import de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin;
import de.tesis.dynaware.grapheditor.core.view.GraphEditorContainer;
import de.tesis.dynaware.grapheditor.demo.customskins.DefaultSkinController;
import de.tesis.dynaware.grapheditor.demo.customskins.SkinController;
import de.tesis.dynaware.grapheditor.demo.customskins.TitledSkinController;
import de.tesis.dynaware.grapheditor.demo.customskins.TreeSkinController;
import de.tesis.dynaware.grapheditor.demo.customskins.titled.TitledSkinConstants;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeConnectorValidator;
import de.tesis.dynaware.grapheditor.demo.customskins.tree.TreeSkinConstants;
import de.tesis.dynaware.grapheditor.demo.selections.SelectionCopier;
import de.tesis.dynaware.grapheditor.demo.utils.AwesomeIcon;
import de.tesis.dynaware.grapheditor.model.GModel;
import de.tesis.dynaware.grapheditor.model.GNode;
import de.tesis.dynaware.grapheditor.model.GraphFactory;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.SetChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
/*
 * Copyright (C) 2005 - 2014 by TESIS DYNAware GmbH
 */
package de.tesis.dynaware.grapheditor.demo;




/**
 * Controller for the {@link GraphEditorDemo} application.
 */
public class GraphEditorDemoController {

    private static final String STYLE_CLASS_TITLED_SKINS = "titled-skins"; //$NON-NLS-1$

    @FXML
    private AnchorPane root;
    @FXML
    private MenuBar menuBar;
    @FXML
    private MenuItem addConnectorButton;
    @FXML
    private MenuItem clearConnectorsButton;
    @FXML
    private Menu connectorTypeMenu;
    @FXML
    private Menu connectorPositionMenu;
    @FXML
    private RadioMenuItem inputConnectorTypeButton;
    @FXML
    private RadioMenuItem outputConnectorTypeButton;
    @FXML
    private RadioMenuItem leftConnectorPositionButton;
    @FXML
    private RadioMenuItem rightConnectorPositionButton;
    @FXML
    private RadioMenuItem topConnectorPositionButton;
    @FXML
    private RadioMenuItem bottomConnectorPositionButton;
    @FXML
    private RadioMenuItem showGridButton;
    @FXML
    private RadioMenuItem snapToGridButton;
    @FXML
    private Menu readOnlyMenu;
    @FXML
    private RadioMenuItem defaultSkinButton;
    @FXML
    private RadioMenuItem treeSkinButton;
    @FXML
    private RadioMenuItem titledSkinButton;
    @FXML
    private Menu intersectionStyle;
    @FXML
    private RadioMenuItem gappedStyleButton;
    @FXML
    private RadioMenuItem detouredStyleButton;
    @FXML
    private ToggleButton minimapButton;
    @FXML
    private GraphEditorContainer graphEditorContainer;

    private final GraphEditor graphEditor = new DefaultGraphEditor();
	private final SelectionCopier selectionCopier = new SelectionCopier(graphEditor.getSkinLookup(),
			graphEditor.getSelectionManager());
    private final GraphEditorPersistence graphEditorPersistence = new GraphEditorPersistence();

    private DefaultSkinController defaultSkinController;
    private TreeSkinController treeSkinController;
    private TitledSkinController titledSkinController;

    private final ObjectProperty<SkinController> activeSkinController = new SimpleObjectProperty<>()