/******************************************************************************
* Copyright (c) 2014 Oracle
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Shenxue Zhou - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.sapphire.ui.diagram;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.ElementList;
import org.eclipse.sapphire.FilteredListener;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.services.ServiceCondition;
import org.eclipse.sapphire.services.ServiceContext;
import org.eclipse.sapphire.ui.ISapphirePart;
import org.eclipse.sapphire.ui.diagram.def.ConnectionServiceType;
import org.eclipse.sapphire.ui.diagram.def.DiagramEditorPageDef;
import org.eclipse.sapphire.ui.diagram.def.IDiagramConnectionDef;
import org.eclipse.sapphire.ui.diagram.def.IDiagramExplicitConnectionBindingDef;
import org.eclipse.sapphire.ui.diagram.def.IDiagramImplicitConnectionBindingDef;
import org.eclipse.sapphire.ui.diagram.editor.DiagramNodeEvent;
import org.eclipse.sapphire.ui.diagram.editor.DiagramNodePart;
import org.eclipse.sapphire.ui.diagram.editor.DiagramNodePostAddEvent;
import org.eclipse.sapphire.ui.diagram.editor.DiagramNodePreDeleteEvent;
import org.eclipse.sapphire.ui.diagram.editor.DiagramNodeTemplate;
import org.eclipse.sapphire.ui.diagram.editor.NodeTemplateVisibilityEvent;
import org.eclipse.sapphire.ui.diagram.editor.SapphireDiagramEditorPagePart;
import org.eclipse.sapphire.ui.diagram.internal.DiagramConnectionTemplate;
import org.eclipse.sapphire.ui.diagram.internal.DiagramConnectionTemplate.DiagramConnectionTemplateListener;
import org.eclipse.sapphire.ui.diagram.internal.DiagramEmbeddedConnectionTemplate;
import org.eclipse.sapphire.ui.diagram.internal.DiagramImplicitConnectionTemplate;
import org.eclipse.sapphire.ui.diagram.internal.DiagramImplicitConnectionTemplate.DiagramImplicitConnectionTemplateListener;
import org.eclipse.sapphire.ui.diagram.internal.StandardImplicitConnectionPart;
import org.eclipse.sapphire.util.ListFactory;
/**
* @author <a href="mailto:shenxue.zhou@oracle.com">Shenxue Zhou</a>
*/
public class StandardConnectionService extends ConnectionService
{
private SapphireDiagramEditorPagePart diagramPagePart;
private DiagramEditorPageDef diagramPageDef;
private List<IDiagramConnectionDef> connectionDefs;
private Map<DiagramNodeTemplate, DiagramEmbeddedConnectionTemplate> embeddedConnectionTemplateMap;
private List<DiagramConnectionTemplate> connectionTemplates;
private List<DiagramImplicitConnectionTemplate> implicitConnectionTemplates;
private ConnectionTemplateListener connTemplateListener;
private ImplicitConnectionTemplateListener implicitConnTemplateListener;
private Listener diagramNodeListener;
private Listener diagramNodeTemplateListener;
@Override
protected void init()
{
this.diagramPagePart = context(SapphireDiagramEditorPagePart.class);
this.diagramPageDef = (DiagramEditorPageDef)this.diagramPagePart.getPageDef();
this.connectionDefs = this.diagramPageDef.getDiagramConnectionDefs();
this.embeddedConnectionTemplateMap = new HashMap<DiagramNodeTemplate, DiagramEmbeddedConnectionTemplate>();
this.connTemplateListener = new ConnectionTemplateListener();
this.implicitConnTemplateListener = new ImplicitConnectionTemplateListener();
// Need to initialize the embedded connections after all the diagram node templates are initialized.
// For connections between "anonymous" nodes, we'd represent connections using node index based
// mechanism.
for (DiagramNodeTemplate nodeTemplate : this.diagramPagePart.getNodeTemplates())
{
nodeTemplate.initEmbeddedConnections();
if (nodeTemplate.getEmbeddedConnectionTemplate() != null)
{
nodeTemplate.getEmbeddedConnectionTemplate().addTemplateListener(this.connTemplateListener);
this.embeddedConnectionTemplateMap.put(nodeTemplate, nodeTemplate.getEmbeddedConnectionTemplate());
}
}
// Initialize connection templates
this.connectionTemplates = new ArrayList<DiagramConnectionTemplate>();
ElementList<IDiagramExplicitConnectionBindingDef> connectionBindings = this.diagramPageDef.getDiagramConnectionBindingDefs();
for (IDiagramExplicitConnectionBindingDef connBinding : connectionBindings)
{
IDiagramConnectionDef connDef = getDiagramConnectionDef(connBinding.getConnectionId().content());
DiagramConnectionTemplate connectionTemplate = new DiagramConnectionTemplate(connBinding);
connectionTemplate.init(this.diagramPagePart, this.diagramPagePart.getLocalModelElement(),
connDef, Collections.<String,String>emptyMap());
connectionTemplate.initialize();
this.connectionTemplates.add(connectionTemplate);
connectionTemplate.addTemplateListener(this.connTemplateListener);
}
// initialize implicit connections
this.implicitConnectionTemplates = new ArrayList<DiagramImplicitConnectionTemplate>();
ElementList<IDiagramImplicitConnectionBindingDef> implicitConnBindings = this.diagramPageDef.getImplicitConnectionBindingDefs();
for (IDiagramImplicitConnectionBindingDef implicitConnBinding : implicitConnBindings)
{
IDiagramConnectionDef connDef = getDiagramConnectionDef(implicitConnBinding.getConnectionId().content());
DiagramImplicitConnectionTemplate connectionTemplate = new DiagramImplicitConnectionTemplate(implicitConnBinding);
connectionTemplate.init(this.diagramPagePart, this.diagramPagePart.getLocalModelElement(),
connDef, Collections.<String,String>emptyMap());
connectionTemplate.initialize();
this.implicitConnectionTemplates.add(connectionTemplate);
connectionTemplate.addTemplateListener(this.implicitConnTemplateListener);
}
// Listen to "node about to be deleted" event to remove the connection parent element for 1 x n
// connection type
this.diagramNodeListener = new FilteredListener<DiagramNodeEvent>()
{
@Override
protected void handleTypedEvent(DiagramNodeEvent event)
{
if (event instanceof DiagramNodePreDeleteEvent)
{
handleNodeAboutToBeDeleted(event.part());
}
else if (event instanceof DiagramNodePostAddEvent)
{
refreshAttachedConnections(event.part());
}
}
};
this.diagramNodeTemplateListener = new FilteredListener<NodeTemplateVisibilityEvent>()
{
@Override
protected void handleTypedEvent(NodeTemplateVisibilityEvent event)
{
if (event.getNodeTemplate().visible())
{
showAllAttachedConnections(event.getNodeTemplate());
}
else
{
hideAllAttachedConnections(event.getNodeTemplate());
}
}
};
this.diagramPagePart.attach(this.diagramNodeListener);
this.diagramPagePart.attach(this.diagramNodeTemplateListener);
}
@Override
public boolean valid(DiagramNodePart srcNode, DiagramNodePart targetNode, String connectionType)
{
DiagramConnectionTemplate connectionTemplate = getConnectionTemplate(srcNode, connectionType);
if (connectionTemplate != null)
{
return connectionTemplate.canCreateNewConnection(srcNode, targetNode);
}
return false;
}
@Override
public DiagramConnectionPart connect(DiagramNodePart srcNode, DiagramNodePart targetNode, String connectionType)
{
DiagramConnectionTemplate connectionTemplate = getConnectionTemplate(srcNode, connectionType);
if (connectionTemplate != null)
{
DiagramConnectionPart connection = connectionTemplate.createNewDiagramConnection(srcNode, targetNode);
return connection;
}
return null;
}
@Override
public List<DiagramConnectionPart> list()
{
final ListFactory<DiagramConnectionPart> connections = ListFactory.start();
for( DiagramConnectionTemplate template : getAllConnectionTemplates() )
{
for (DiagramConnectionPart connPart : template.getDiagramConnections(null))
{
connections.add(connPart);
}
}
for (DiagramConnectionTemplate embeddedConnectionTemplate : this.embeddedConnectionTemplateMap.values())
{
for (DiagramConnectionPart connPart : embeddedConnectionTemplate.getDiagramConnections(null))
{
connections.add(connPart);
}
}
for( DiagramImplicitConnectionTemplate template : this.implicitConnectionTemplates )
{
for (StandardImplicitConnectionPart connPart : template.getImplicitConnections())
{
connections.add( connPart );
}
}
return connections.result();
}
@Override
public List<IDiagramConnectionDef> possibleConnectionDefs( DiagramNodePart srcNode )
{
List<IDiagramConnectionDef> connectionDefs = new ArrayList<IDiagramConnectionDef>();
DiagramEmbeddedConnectionTemplate embeddedConnTemplate =
this.embeddedConnectionTemplateMap.get(srcNode.getDiagramNodeTemplate());
if (embeddedConnTemplate != null)
{
connectionDefs.add(embeddedConnTemplate.getConnectionDef());
}
for (DiagramConnectionTemplate connectionTemplate2 : getAllConnectionTemplates())
{
if (connectionTemplate2.canStartNewConnection(srcNode))
{
connectionDefs.add(connectionTemplate2.getConnectionDef());
}
}
return connectionDefs;
}
private void showAllAttachedConnections(DiagramNodeTemplate nodeTemplate)
{
if (nodeTemplate != null)
{
List<DiagramConnectionTemplate> connTemplates = getAllConnectionTemplates();
for (DiagramConnectionTemplate connTemplate : connTemplates)
{
connTemplate.showAllConnectionParts(nodeTemplate);
}
DiagramEmbeddedConnectionTemplate embeddedConnTemplate = this.embeddedConnectionTemplateMap.get(nodeTemplate);
if (embeddedConnTemplate != null)
{
embeddedConnTemplate.showAllConnectionParts(nodeTemplate);
}
refreshImplicitConnections();
}
}
private void hideAllAttachedConnections(DiagramNodeTemplate nodeTemplate)
{
if (nodeTemplate != null)
{
List<DiagramConnectionTemplate> connTemplates = getAllConnectionTemplates();
for (DiagramConnectionTemplate connTemplate : connTemplates)
{
connTemplate.hideAllConnectionParts(nodeTemplate);
}
DiagramEmbeddedConnectionTemplate embeddedConnTemplate = this.embeddedConnectionTemplateMap.get(nodeTemplate);
if (embeddedConnTemplate != null)
{
embeddedConnTemplate.hideAllConnectionParts(nodeTemplate);
}
refreshImplicitConnections();
}
}
private List<DiagramConnectionTemplate> getAllConnectionTemplates()
{
return this.connectionTemplates;
}
private IDiagramConnectionDef getDiagramConnectionDef(String connId)
{
if (connId == null)
{
throw new IllegalArgumentException();
}
IDiagramConnectionDef connDef = null;
for (IDiagramConnectionDef def : this.connectionDefs)
{
String id = def.getId().content();
if (id != null && id.equalsIgnoreCase(connId))
{
connDef = def;
break;
}
}
return connDef;
}
private DiagramConnectionTemplate getConnectionTemplate(DiagramNodePart srcNode, String connectionType)
{
DiagramConnectionTemplate connectionTemplate = null;
DiagramEmbeddedConnectionTemplate embeddedConnTemplate =
this.embeddedConnectionTemplateMap.get(srcNode.getDiagramNodeTemplate());
if (embeddedConnTemplate != null &&
embeddedConnTemplate.getConnectionTypeId().equalsIgnoreCase(connectionType))
{
connectionTemplate = embeddedConnTemplate;
}
else
{
for (DiagramConnectionTemplate connectionTemplate2 : getAllConnectionTemplates())
{
if (connectionTemplate2.getConnectionTypeId().equalsIgnoreCase(connectionType))
{
connectionTemplate = connectionTemplate2;
break;
}
}
}
return connectionTemplate;
}
private void refreshImplicitConnections()
{
for( DiagramImplicitConnectionTemplate template : this.implicitConnectionTemplates )
{
template.refreshImplicitConnections();
}
}
private void handleNodeAboutToBeDeleted(DiagramNodePart nodePart)
{
Element nodeModel = nodePart.getLocalModelElement();
// Check top level connections to see whether we need to remove the connection parent element
for (DiagramConnectionTemplate connTemplate : getAllConnectionTemplates())
{
if (connTemplate.getConnectionType() == DiagramConnectionTemplate.ConnectionType.OneToMany)
{
Element connParentElement = connTemplate.getConnectionParentElement(nodeModel);
if (connParentElement != null)
{
ElementList<?> connParentList = (ElementList<?>) connParentElement.parent();
connParentList.remove(connParentElement);
}
}
}
}
/**
* In the case where the entire Sapphire model is reconstructed (revert source file in the source editor),
* connection properties may have triggered events before the node properties change events
* are sent out. So those connection parts will be created before the endpoint node
* parts are created. But those connection parts won't be displayed visually on diagram canvas
* until those corresponding endpoint nodes are created on the canvas.
*
* [Bug 376245] Revert action in StructuredTextEditor does not revert diagram nodes and connections
* in SapphireDiagramEditor
*
* @param nodePart
*/
private void refreshAttachedConnections(DiagramNodePart nodePart)
{
Element nodeElement = nodePart.getLocalModelElement();
for (DiagramConnectionPart connPart : list())
{
if (connPart.removable() &&
(connPart.getEndpoint1() == nodeElement ||
connPart.getEndpoint2() == nodeElement))
{
ConnectionAddEvent addEvent = new ConnectionAddEvent(connPart);
this.broadcast(addEvent);
}
}
}
private final class ConnectionTemplateListener extends DiagramConnectionTemplateListener
{
@Override
public void handleConnectionEndpointUpdate(final ConnectionEndpointsEvent event)
{
StandardConnectionService.this.broadcast(event);
}
@Override
public void handleConnectionAddEvent(final ConnectionAddEvent event)
{
StandardConnectionService.this.broadcast(event);
}
@Override
public void handleConnectionDeleteEvent(final ConnectionDeleteEvent event)
{
StandardConnectionService.this.broadcast(event);
}
}
private final class ImplicitConnectionTemplateListener extends DiagramImplicitConnectionTemplateListener
{
@Override
public void handleConnectionAddEvent(final ConnectionAddEvent event)
{
StandardConnectionService.this.broadcast(event);
}
@Override
public void handleConnectionDeleteEvent(final ConnectionDeleteEvent event)
{
StandardConnectionService.this.broadcast(event);
}
}
public static final class Condition extends ServiceCondition
{
@Override
public boolean applicable( final ServiceContext context )
{
ISapphirePart part = context.find(ISapphirePart.class);
if (part instanceof SapphireDiagramEditorPagePart)
{
SapphireDiagramEditorPagePart diagramPagePart = (SapphireDiagramEditorPagePart)part;
DiagramEditorPageDef pageDef = diagramPagePart.getPageDef();
if (pageDef.getConnectionServiceType().content() == ConnectionServiceType.STANDARD)
{
return true;
}
}
return false;
}
}
}