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); } }
コネクションを管理する為の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) { } }
コネクション用の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;
}
線の両端となるノードクラスにもコネクション用の実装を追加する。
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); }
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でコネクション追加の処理を指定する。
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()はコネクションの再接続に使用する。
→コネクションの再接続
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());
}
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); } }
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; } }
再接続の時に呼ばれるメソッドは新規接続時とは違うが、同じコマンドが使える。
ただし、接続コマンドに 古い接続を削除する処理だけ追加する必要がある。
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); } }