S-JIS[2013-04-07] 変更履歴

Eclipse GEF コネクション

Eclipseプラグイン開発GEFのConnectionについて。


概要

図形と図形をつなぐ線がコネクション。

GEFでは、パレットの「コネクション作成」ツールを選択するとコネクション作成モードになり、
図面上の図形をクリックして次の図形をクリックすると線が引ける。


コネクション関連の実装例。

パレット

パレットに「コネクション作成」ツールを追加する。

import org.eclipse.gef.palette.ConnectionCreationToolEntry;
import org.eclipse.gef.requests.SimpleFactory;
public class MyPalette extends PaletteRoot {
	public MyPalette() {
		PaletteGroup group = new PaletteGroup("選択ツール");
		group.add(new PanningSelectionToolEntry());
		group.add(new MarqueeToolEntry());
		group.add(new ConnectionCreationToolEntry("Connection", "connection", new SimpleFactory(MyConnection.class), null, null));
		this.add(group);
	}

コネクション

コネクション(線のデータ)を表すクラスを新設する。

下記に出てくるAbstractModelは、図形を表す共通クラス。
NodeElementは、線をつなぐ対象となる図形を表すクラス。

public class MyConnection extends AbstractModel {
	public static final String PROP_SOURCE = "source";
	public static final String PROP_TARGET = "target";

	private NodeElement source;
	private NodeElement target;
	public NodeElement getSource() {
		return source;
	}

	public void setSource(NodeElement source) {
		NodeElement old = this.source;
		this.source = source;
		firePropertyChange(PROP_SOURCE, old, source);
	}
	public NodeElement getTarget() {
		return target;
	}

	public void setTarget(NodeElement target) {
		NodeElement old = this.target;
		this.target = target;
		firePropertyChange(PROP_TARGET, old, target);
	}
	@Override
	public String toString() {
		return String.format("Connection{ %s->%s }", source, target);
	}
}

ConnectionEditPart

コネクションを管理する為のEditPartを新設する。

コネクションは線なので、専用のFigureクラスは用意しない。
EditPartの中で線の定義を行う。

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.editparts.AbstractConnectionEditPart;
import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
public class MyConnectionEditPart extends AbstractConnectionEditPart implements PropertyChangeListener {
	@Override
	protected IFigure createFigure() {
		PolylineConnection polyline = new PolylineConnection();

		PolygonDecoration decoration = new PolygonDecoration();
		polyline.setTargetDecoration(decoration);

		return polyline;
	}
	@Override
	public void activate() {
		super.activate();

		AbstractModel model = (AbstractModel) getModel();
		model.addPropertyChangeListener(this);
	}
	@Override
	public void deactivate() {
		super.deactivate();

		AbstractModel model = (AbstractModel) getModel();
		model.removePropertyChangeListener(this);
	}
	@Override
	protected void createEditPolicies() {
		installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
	}
	@Override
	public void propertyChange(PropertyChangeEvent event) {
	}
}

EditPartFactory

コネクション用のEditPartを新設したので、EditPartFactoryに登録しておく。

public class MyEditPartFactory implements EditPartFactory {
	@Override
	public EditPart createEditPart(EditPart editpart, Object model) {
		if (model instanceof Diagram) {
			editPart = new DiagramEditPart();
〜
		} else if (model instanceof MyConnection) {
			editPart = new MyConnectionEditPart();
		} else {
			throw new UnsupportedOperationException(MessageFormat.format("model={0}", model));
		}

		editPart.setModel(model);
		return editPart;
	}

NodeElement

線の両端となるノードクラスにもコネクション用の実装を追加する。

public abstract class NodeElement extends AbstractModel {
〜
	static final String PROP_INCOMINGS = "incomings";
	static final String PROP_OUTGOINGS = "outgoings";

	private List<Connection> incomings;
	private List<Connection> outgoings;
	public List<Connection> getIncomings() {
		if (incomings == null) {
			return Collections.emptyList();
		}
		return incomings;
	}

	public void addIncoming(Connection connection) {
		if (incomings == null) {
			incomings = new ArrayList<Connection>();
		}
		incomings.add(connection);
		firePropertyChange(PROP_INCOMINGS, null, null);
	}

	public void removeIncoming(Connection connection) {
		if (incomings != null) {
			incomings.remove(connection);
		}
		firePropertyChange(PROP_INCOMINGS, null, null);
	}
	public List<Connection> getOutgoings() {
		if (outgoings == null) {
			return Collections.emptyList();
		}
		return outgoings;
	}

	public void addOutgoing(Connection connection) {
		if (outgoings == null) {
			outgoings = new ArrayList<Connection>();
		}
		outgoings.add(connection);
		firePropertyChange(PROP_OUTGOINGS, null, null);
	}

	public void removeOutgoing(Connection connection) {
		if (outgoings != null) {
			outgoings.remove(connection);
		}
		firePropertyChange(PROP_OUTGOINGS, null, null);
	}

NodeElementEditPart

NodeElementのEditPartにもコネクションを接続する為の実装を追加する。

コネクション接続可能なノードにする為にはNodeEditPartインターフェースを実装する必要がある。

import org.eclipse.draw2d.ChopboxAnchor;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.NodeEditPart;
public abstract class NodeElementEditPart extends AbstractModelEditPart implements NodeEditPart {
〜
// NodeEditPartのメソッド
	@Override
	public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connectioneditpart) {
		return new ChopboxAnchor(getFigure());
	}

	@Override
	public ConnectionAnchor getSourceConnectionAnchor(Request request) {
		return new ChopboxAnchor(getFigure());
	}

	@Override
	public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connectioneditpart) {
		return new ChopboxAnchor(getFigure());
	}

	@Override
	public ConnectionAnchor getTargetConnectionAnchor(Request request) {
		return new ChopboxAnchor(getFigure());
	}

ConnectionAnchorを返すメソッドを実装する。
コネクションアンカーは、自分の図形のどの位置に接続されるかを決定するクラス。
ChopboxAnchorは四角形の中心に向かいつつ、辺の上に接続されるアンカー。
例えば円形のFigureの場合は円周上に接続される方が見栄えがいい。そういう場合はEllipseAnchorを返すようにする。

	@Override
	public void propertyChange(PropertyChangeEvent event) {
		String name = event.getPropertyName();
		if (NodeElement.PROP_X.equals(name) || NodeElement.PROP_Y.equals(name)
				|| NodeElement.PROP_WIDTH.equals(name) || NodeElement.PROP_HEIGHT.equals(name)) {
			refreshVisuals();
		} else if (NodeElement.PROP_INCOMINGS.equals(name)) {
			refreshTargetConnections();
		} else if (NodeElement.PROP_OUTGOINGS.equals(name)) {
			refreshSourceConnections();
		}
	}
	@Override
	protected void createEditPolicies() {
		installEditPolicy(EditPolicy.COMPONENT_ROLE, new ElementComponentEditPolicy());
		installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new ElementGraphicalNodeEditPolicy());
	}

GraphicalNodeEditPolicyでコネクション追加の処理を指定する。

ElementGraphicalNodeEditPolicy

import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
public class ElementGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {
	@Override
	protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
		Connection connection = (Connection) request.getNewObject();
		NodeElement element = (NodeElement) request.getTargetEditPart().getModel();

		CreateConnectionCommand command = new CreateConnectionCommand(connection);
		command.setSource(element);

		request.setStartCommand(command);
		return command;
	}
	@Override
	protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
		CreateConnectionCommand command = (CreateConnectionCommand) request.getStartCommand();

		NodeElement element = (NodeElement) request.getTargetEditPart().getModel();
		command.setTarget(element);

		return command;
	}

線を引くに当たり、最初の図形がクリックされるとgetConnectionCreateCommand()が呼ばれる。
ここで線を引く為のコマンドインスタンスを生成し、setStartCommand()でrequestに保存する。
次の図形がクリックされると、getConnectionCompleteCommand()が呼ばれる。
この引数であるrequestは最初の図形を選択したときに渡されたreqeustと同じ。なので、ここから最初に保持したコマンドインスタンスが取得できる。

	@Override
	protected Command getReconnectTargetCommand(ReconnectRequest request) {
		return null;
	}
	@Override
	protected Command getReconnectSourceCommand(ReconnectRequest request) {
		return null;
	}
}

getReconnectTargetCommand()・getReconnectSourceCommand()はコネクションの再接続に使用する。
コネクションの再接続

CreateConnectionCommand

import java.util.List;

import org.eclipse.gef.commands.Command;
public class CreateConnectionCommand extends Command {

	private MyConnection connection;
	private NodeElement source;
	private NodeElement target;
	public CreateConnectionCommand(MyConnection connection) {
		this.connection = connection;
	}
	public void setSource(NodeElement source) {
		this.source = source;
	}
	public void setTarget(NodeElement target) {
		this.target = target;
	}
	@Override
	public boolean canExecute() {
		if (source == null || target == null) {
			return false;
		}
		if (source.equals(target)) {
			return false;
		}

		List<MyConnection> list = target.getIncomings();
		for (MyConnection c : list) {
			if (source.equals(c.getSource())) {
				return false;
			}
		}

		return true;
	}
	@Override
	public void execute() {
		source.addOutgoing(connection);
		target.addIncoming(connection);
		connection.setSource(source);
		connection.setTarget(target);
	}
	@Override
	public void undo() {
		source.removeOutgoing(connection);
		target.removeIncoming(connection);
		connection.setSource(null);
		connection.setTarget(null);
	}
}

コネクションの削除

コネクションを削除できるようにする例。

ConnectionEditPartにコネクション削除用のポリシーを追加する。

public class MyConnectionEditPart extends AbstractConnectionEditPart implements PropertyChangeListener {
	@Override
	protected void createEditPolicies() {
		installEditPolicy(EditPolicy.COMPONENT_ROLE, new MyConnectionComponentEditPolicy());
		installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
	}

ConnectionComponentEditPolicy

import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.ComponentEditPolicy;
import org.eclipse.gef.requests.GroupRequest;
public class MyConnectionComponentEditPolicy extends ComponentEditPolicy {
	@Override
	protected Command createDeleteCommand(GroupRequest request) {
		MyConnection connection = (MyConnection) getHost().getModel();
		return new DeleteConnectionCommand(connection);
	}
}

DeleteConnectionCommand

import org.eclipse.gef.commands.Command;
public class DeleteConnectionCommand extends Command {

	private MyConnection connection;
	private NodeElement old_source;
	private NodeElement old_target;
	public DeleteConnectionCommand(MyConnection connection) {
		this.connection = connection;
	}
	@Override
	public void execute() {
		old_source = connection.getSource();
		old_target = connection.getTarget();

		if (old_source != null) {
			old_source.removeOutgoing(connection);
		}
		if (old_target != null) {
			old_target.removeIncoming(connection);
		}
		connection.setSource(null);
		connection.setTarget(null);
	}
	@Override
	public void undo() {
		if (old_source != null) {
			old_source.addOutgoing(connection);
		}
		if (old_target != null) {
			old_target.addIncoming(connection);
		}
		connection.setSource(old_source);
		connection.setTarget(old_target);
	}
}

コネクションの再接続

既に接続されているコネクションを別のノードへ接続し直す例。

public class ElementGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {
	// 接続先ノードがドラッグされる時に呼ばれる
	@Override
	protected Command getReconnectTargetCommand(ReconnectRequest request) {
		MyConnection connection = (MyConnection) request.getConnectionEditPart().getModel();
		CreateConnectionCommand command = new CreateConnectionCommand(connection);

		command.setSource(connection.getSource()); //接続元はそのまま残す

		NodeElementEditPart part = (NodeElementEditPart) request.getTarget();
		command.setTarget(part.getModel());

		return command;
	}
	// 接続元ノードがドラッグされる時に呼ばれる
	@Override
	protected Command getReconnectSourceCommand(ReconnectRequest request) {
		MyConnection connection = (MyConnection) request.getConnectionEditPart().getModel();
		CreateConnectionCommand command = new CreateConnectionCommand(connection);

		NodeElementEditPart part = (NodeElementEditPart) request.getTarget();
		command.setSource(part.getModel());

		command.setTarget(connection.getTarget()); //接続先はそのまま残す

		return command;
	}
}

再接続の時に呼ばれるメソッドは新規接続時とは違うが、同じコマンドが使える。
ただし、接続コマンドに 古い接続を削除する処理だけ追加する必要がある。

CreateConnectionCommand

import java.util.List;

import org.eclipse.gef.commands.Command;
public class CreateConnectionCommand extends Command {

	private MyConnection connection;
	private NodeElement source, old_source;
	private NodeElement target, old_target;
	public CreateConnectionCommand(MyConnection connection) {
		this.connection = connection;
		old_source = connection.getSource();
		old_target = connection.getTarget();
	}
〜
	@Override
	public void execute() {
		if (old_source != null) {
			old_source.removeOutgoing(connection);
		}
		if (old_target != null) {
			old_target.removeIncoming(connection);
		}
		source.addOutgoing(connection);
		target.addIncoming(connection);
		connection.setSource(source);
		connection.setTarget(target);
	}
	@Override
	public void undo() {
		source.removeOutgoing(connection);
		target.removeIncoming(connection);
		if (old_source != null) {
			old_source.addOutgoing(connection);
		}
		if (old_target != null) {
			old_target.addIncoming(connection);
		}
		connection.setSource(old_source);
		connection.setTarget(old_target);
	}
}

GEFへ戻る / Eclipseプラグインへ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま