How to reliably remove Connection figures, whose EditParts are orphans, on removal of respective model object?

StackOverflow https://stackoverflow.com/questions/15295509

Question

Assume the following simplified EMF model structure:

   Graph
   /   \
Node   Edge

In my GEF editor, the EditParts are organized as follows.

GraphEditPart (Figure = FreeformLayer) excerpt

@Override
protected List<EObject> getModelChildren() {
  List<EObject> childrenList = new ArrayList<EObject>();
  Graph graph = (Graph) getModel();
  childrenList.addAll(graph.getNodes());
  return childrenList;
}

NodeEditPart (Figure = extending Figure) excerpt (also showing how Edges for which a Nodeis source or target are got)

@Override
protected List<Edge> getModelSourceConnections() {
  Node node = (Node) getModel();
  Graph graph = node.getGraph();
  String nodeId = node.getId();
  List<Edge> sourceList = new ArrayList<Edge>();
  if (graph != null)
  sourceList.addAll(graph.getOutEdges(nodeId));
  return sourceList;
}

@Override
protected List<Edge> getModelTargetConnections() {
  // Same principle as getModelSourceConnections
}

Editor class excerpt (in case it matters)

@Override
protected void initializeGraphicalViewer() {
  super.initializeGraphicalViewer();
  GraphicalViewer viewer = getGraphicalViewer();
  viewer.setContents(graph);
  ScalableFreeformRootEditPart root = (ScalableFreeformRootEditPart) viewer.getRootEditPart();
  ConnectionLayer connLayer = (ConnectionLayer) root.getLayer(LayerConstants.CONNECTION_LAYER);
  GraphicalEditPart contentEditPart = (GraphicalEditPart) root.getContents();
  ShortestPathConnectionRouter shortestPathConnectionRouter = new ShortestPathConnectionRouter(contentEditPart.getFigure());
  connLayer.setConnectionRouter(shortestPathConnectionRouter);
}

All EditParts have their own adapter (extending org.eclipse.emf.ecore.util.EContentAdapter or implementing org.eclipse.emf.common.notify.Adapter).

This results in an EditPart structure, where NodeEditParts are children of the GraphEditPart, and EdgeEditParts are orphans, i.e., they have no parent. Consequentially, I've had difficulties refreshing figures whenever Edges got added or deleted.

I've managed to make the update work when I added an Edge by doing an expensive iteration in the GraphAdapter (which is notified as a newly created Edge must be registered on the Graph (newEdge.setGraph(graph)):

if (notification.getOldValue() == null && notification.getNewValue() instanceof Edge) {     
  for (Object ep : getChildren()) {
    if (ep instanceof NodeEditPart) { // There are in fact other EditParts as well
      Graph graph = (Graph) getModel();
      NodeEditPart nep = (NodeEditPart) ep;
      Node n = (Node) nep.getModel();
    if (graph.getOutEdges(n.getSId()).contains(notification.getNewValue()) || graph.getInEdges(n.getSId()).contains(notification.getNewValue()))
      nep.refresh();
}

[Note: If - by the way - you can think of any better way of doing this, feel free to hit me up with your solution!]

Problem

I am unable to reliably remove the Edge figure from the editor when deleting the Edge model object! Sometimes it works, sometimes it doesn't.

I guess the unreliability might have to do with the fact that (a) my real life model has three layers of abstraction, and (b) that different EMF Adapters don't always recognize changes in the same temporal order (?).

execute() from EdgeDeleteCommand simply calls edge.setGraph(null), which triggers EMF to clean up after itself (i.e., model elements that aren't connected to the graph are removed from the model).

How can I reliably remove Edges' figures when deleting the respective model object, when the respective EditPart is an orphan?

Was it helpful?

Solution

Simply let the source and target model objects know that something about them has changed, and let their Adapter handle the refresh. Assume the following EdgeDeleteCommand's execute() method.

@Override
public void execute() {
  graph = edge.getGraph();
  source = edge.getSource();
  target = edge.getTarget();

  edge.setGraph(null);
  source.eNotify(new NotificationImpl(Notification.REMOVE, edge, null));
  target.eNotify(new NotificationImpl(Notification.REMOVE, edge, null));
}

The last two lines of code notify the source and target model elements that the object edge has been removed "from them". This is in fact a fake notification message, as edge was never a child of source or target. However, the Adapters attached to the the source and target NodeEditPart will register this, and can react as follows.

@Override
public void notifyChanged(Notification notification) {
  switch (notification.getEventType()) {
  case Notification.REMOVE:
    refresh();
    break;

  default:
    break;
  }
}

The NodeEditPart.refresh() method will call getModelSourceConnections() and getModelTargetConnections() "on its way", which will return a list of Edges each, with the deleted edge not in them anymore.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top