tesis-dynaware / graph-editor

Eclipse Public License 1.0
132 stars 42 forks source link

How to connect two default nodes programmatically #37

Open danielemaddaluno opened 3 years ago

danielemaddaluno commented 3 years ago

I didn't manage to connect two simple nodes together programmatically. In the demo it is done with the tree skin, but what if I just want to do it with the default skin? I would add this to the wiki documentation too.

danielemaddaluno commented 3 years ago

This is what I'm trying to do:

import java.util.Arrays;

import de.tesis.dynaware.grapheditor.Commands;
import de.tesis.dynaware.grapheditor.GraphEditor;
import de.tesis.dynaware.grapheditor.core.DefaultGraphEditor;
import de.tesis.dynaware.grapheditor.core.connections.ConnectionCommands;
import de.tesis.dynaware.grapheditor.model.GConnector;
import de.tesis.dynaware.grapheditor.model.GModel;
import de.tesis.dynaware.grapheditor.model.GNode;
import de.tesis.dynaware.grapheditor.model.GraphFactory;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

@SuppressWarnings("restriction")
public class GraphEditorTutorial extends Application {

    GraphEditor graphEditor;

    public static void main(final String[] args) {
        launch(args);
    }

    @Override
    public void start(final Stage primaryStage) throws Exception {

        graphEditor = new DefaultGraphEditor();

        Scene scene = new Scene(graphEditor.getView(), 800, 600);

        GModel model = GraphFactory.eINSTANCE.createGModel();
        graphEditor.setModel(model);
        addNodes(model);

        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private GNode createNode() {
        GNode node = GraphFactory.eINSTANCE.createGNode();

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

        input.setType("left-input");
        output.setType("right-output");

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

        return node;
    }

    private void addNodes(GModel model) {
        GNode n1 = createNode();
        GNode n2 = createNode();

        n1.setX(150);
        n1.setY(150);

        n2.setX(400);
        n2.setY(200);
        n2.setWidth(200);
        n2.setHeight(150);

        Commands.addNode(model, n1);
        Commands.addNode(model, n2);

        GConnector source = n1.getConnectors().get(1);
        GConnector target = n2.getConnectors().get(0);

        // ERROR
        ConnectionCommands.addConnection(model, source, target, null, Arrays.asList());

        // WORKS:
        //new TreeSkinController(graphEditor, null);
        //ConnectionCommands.addConnection(model, source, target, "tree-connection", Arrays.asList());
    }

}

And this is the error that I get:

org.eclipse.emf.common.util.WrappedException: An exception was ignored during command execution
    at org.eclipse.emf.common.command.BasicCommandStack.handleError(BasicCommandStack.java:281)
    at org.eclipse.emf.common.command.BasicCommandStack.execute(BasicCommandStack.java:112)
    at de.tesis.dynaware.grapheditor.core.connections.ConnectionCommands.addConnection(ConnectionCommands.java:80)
    at com.graph.tests.demo.tests.GraphEditorTutorial.addNodes(GraphEditorTutorial.java:77)
    at com.graph.tests.demo.tests.GraphEditorTutorial.start(GraphEditorTutorial.java:35)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.get(ArrayList.java:433)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.restrictFirstAndLastJoints(SimpleConnectionSkin.java:198)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.addRectangularConstraints(SimpleConnectionSkin.java:186)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.setJointSkins(SimpleConnectionSkin.java:112)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultConnectionSkin.setJointSkins(DefaultConnectionSkin.java:70)
    at de.tesis.dynaware.grapheditor.core.skins.SkinManager.addJoints(SkinManager.java:358)
    at de.tesis.dynaware.grapheditor.core.skins.SkinManager.addConnections(SkinManager.java:204)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.updateSkinManager(GraphEditorController.java:254)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.reloadView(GraphEditorController.java:196)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.initializeAll(GraphEditorController.java:155)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.lambda$createCommandStackListener$41(GraphEditorController.java:183)
    at org.eclipse.emf.common.command.BasicCommandStack.notifyListeners(BasicCommandStack.java:270)
    at org.eclipse.emf.common.command.BasicCommandStack.execute(BasicCommandStack.java:104)
    ... 9 more
Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$1(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.get(ArrayList.java:433)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.restrictFirstAndLastJoints(SimpleConnectionSkin.java:198)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.addRectangularConstraints(SimpleConnectionSkin.java:186)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.connection.SimpleConnectionSkin.setJointSkins(SimpleConnectionSkin.java:112)
    at de.tesis.dynaware.grapheditor.core.skins.defaults.DefaultConnectionSkin.setJointSkins(DefaultConnectionSkin.java:70)
    at de.tesis.dynaware.grapheditor.core.skins.SkinManager.addJoints(SkinManager.java:252)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.updateSkinManager(GraphEditorController.java:262)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.reloadView(GraphEditorController.java:196)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.initializeAll(GraphEditorController.java:155)
    at de.tesis.dynaware.grapheditor.core.GraphEditorController.lambda$createCommandStackListener$41(GraphEditorController.java:183)
    at org.eclipse.emf.common.command.BasicCommandStack.notifyListeners(BasicCommandStack.java:270)
    at org.eclipse.emf.common.command.BasicCommandStack.execute(BasicCommandStack.java:115)
    at de.tesis.dynaware.grapheditor.core.connections.ConnectionCommands.addConnection(ConnectionCommands.java:80)
    at com.graph.tests.demo.tests.GraphEditorTutorial.addNodes(GraphEditorTutorial.java:77)
    at com.graph.tests.demo.tests.GraphEditorTutorial.start(GraphEditorTutorial.java:35)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
Exception running application com.graph.tests.demo.tests.GraphEditorTutorial
nadrian4 commented 3 years ago

Hi,

if you are still facing this issue, I think i've got a solution. While dragging from one connector to another to create connection, the TailManager::snapPosition method is called, which calls GTailSkin::draw method. In DefaultTailSkin, this method adds some points (joitns) to the connection.

When you release your mouse while hovering over target connector, the releaseHandler for Connector is called, which, if the connection is valid, calls private ConnectorDragManager::addConnection method. It looks like this:

    private void addConnection(final GConnector source, final GConnector target) {

        final String connectionType = validator.createConnectionType(source, target);
        final String jointType = validator.createJointType(source, target);
        final List<Point2D> jointPositions = skinLookup.lookupTail(source).allocateJointPositions();

        final List<GJoint> joints = new ArrayList<>();

        for (final Point2D position : jointPositions) {

            final GJoint joint = GraphFactory.eINSTANCE.createGJoint();
            joint.setX(position.getX());
            joint.setY(position.getY());
            joint.setType(jointType);

            joints.add(joint);
        }

        final CompoundCommand command = ConnectionCommands.addConnection(model, source, target, connectionType, joints);

        // Notify the event manager so additional commands may be appended to this compound command.
        final GConnection addedConnection = model.getConnections().get(model.getConnections().size() - 1);
        connectionEventManager.notifyConnectionAdded(addedConnection, command);
    }

As you can see, this method collects joints and passes it as an argument to ConnectionCommands::addConnection. Your main problem was that you did not create any joints hence the method resulted in throwing IndexOutOfBoundsException.

So, if you want to add a connection programatically, you should call first:

graphEditor.getSkinLookup().lookupTail(source).draw(
                new Point2D(source.getX(), source.getX()),
                new Point2D(target.getX(), source.getY()),
                target,
                true
        );

and then:

final List<Point2D> jointPositions = graphEditor.getSkinLookup().lookupTail(source).allocateJointPositions();
final List<GJoint> joints = new ArrayList<>();
for (final Point2D position : jointPositions) {
    final GJoint joint = GraphFactory.eINSTANCE.createGJoint();
    joint.setX(position.getX());
    joint.setY(position.getY());
    joint.setType(null);
    joints.add(joint);
}
ConnectionCommands.addConnection(model, source, target, null, joints);

Which should result in adding the connection in the way you expected.

The connection path does not look perfectly, but you could try to deal with joints coordinates - i suppose it should help.

Hope you find it helpful.