/******************************************************************************
* Copyright (c) 2016 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
* Konstantin Komissarchik - [342897] Integrate with properties view
* Konstantin Komissarchik - [348813] Generalize Sapphire.Diagram.Drop action
* Ling Hao - [44319] Image specification for diagram parts inconsistent with the rest of sdef
* Konstantin Komissarchik - [378756] Convert ModelElementListener and ModelPropertyListener to common listener infrastructure
* Konstantin Komissarchik - [381794] Cleanup needed in presentation code for diagram context menu
* Ling Hao - [383924] Flexible diagram node shapes
******************************************************************************/
package org.eclipse.sapphire.ui.diagram.editor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.ElementList;
import org.eclipse.sapphire.ElementType;
import org.eclipse.sapphire.FilteredListener;
import org.eclipse.sapphire.ImageData;
import org.eclipse.sapphire.ListProperty;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.PropertyDef;
import org.eclipse.sapphire.PropertyEvent;
import org.eclipse.sapphire.ValueProperty;
import org.eclipse.sapphire.java.JavaType;
import org.eclipse.sapphire.modeling.CapitalizationType;
import org.eclipse.sapphire.modeling.el.FunctionResult;
import org.eclipse.sapphire.ui.SapphireActionSystem;
import org.eclipse.sapphire.ui.SapphirePart;
import org.eclipse.sapphire.ui.diagram.def.IDiagramConnectionDef;
import org.eclipse.sapphire.ui.diagram.def.IDiagramExplicitConnectionBindingDef;
import org.eclipse.sapphire.ui.diagram.def.IDiagramNodeDef;
import org.eclipse.sapphire.ui.diagram.def.ToolPaletteImageDef;
import org.eclipse.sapphire.ui.diagram.internal.DiagramEmbeddedConnectionTemplate;
import org.eclipse.sapphire.util.CollectionsUtil;
/**
* @author <a href="mailto:shenxue.zhou@oracle.com">Shenxue Zhou</a>
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
* @author <a href="mailto:ling.hao@oracle.com">Ling Hao</a>
*/
public final class DiagramNodeTemplate extends SapphirePart
{
public static abstract class DiagramNodeTemplateListener
{
public void handleNodeAdd(final DiagramNodePart nodePart)
{
}
public void handlePostNodeAdd(final DiagramNodePart nodePart)
{
}
public void handleNodeDelete(final DiagramNodePart nodePart)
{
}
public void handlePreNodeDelete(final DiagramNodePart nodePart)
{
}
public void handleNodeMove(final DiagramNodeMoveEvent event)
{
}
}
private SapphireDiagramEditorPagePart diagramEditor;
private IDiagramNodeDef definition;
private Element modelElement;
private String propertyName;
private ListProperty modelProperty;
private JavaType modelElementType;
private String toolPaletteLabel;
private String toolPaletteDesc;
private List<FunctionResult> toolPaletteImageFunctionResults;
private DiagramEmbeddedConnectionTemplate embeddedConnTemplate;
private Listener modelPropertyListener;
private Listener nodePartListener;
private Set<DiagramNodeTemplateListener> listeners;
private List<DiagramNodePart> diagramNodes;
@Override
public void init()
{
this.diagramEditor = (SapphireDiagramEditorPagePart)parent();
this.modelElement = getModelElement();
this.definition = (IDiagramNodeDef)super.definition;
if (this.definition.getToolPaletteLabel().content() != null)
{
ValueProperty tpLabelProperty = IDiagramNodeDef.PROP_TOOL_PALETTE_LABEL;
this.toolPaletteLabel = tpLabelProperty.getLocalizationService().text(
this.definition.getToolPaletteLabel().content(), CapitalizationType.TITLE_STYLE, false);
}
this.toolPaletteDesc = this.definition.getToolPaletteDescription().content();
this.diagramNodes = new ArrayList<DiagramNodePart>();
this.listeners = new CopyOnWriteArraySet<DiagramNodeTemplateListener>();
this.propertyName = this.definition.getProperty().content();
this.modelProperty = (ListProperty)resolve(this.modelElement, this.propertyName);
this.modelElementType = this.definition.getElementType().target();
initNodePartListener();
ElementList<?> list = this.modelElement.property(this.modelProperty);
for( Element listEntryModelElement : list )
{
if (this.modelElementType == null)
{
createNewNodePart(listEntryModelElement);
}
else
{
final Class<?> cl = (Class<?>) this.modelElementType.artifact();
if( cl == null || cl.isAssignableFrom( listEntryModelElement.getClass() ) )
{
createNewNodePart(listEntryModelElement);
}
}
}
this.toolPaletteImageFunctionResults = new ArrayList<FunctionResult>();
List<ToolPaletteImageDef> imageDefs = this.definition.getToolPaletteImages();
for (ToolPaletteImageDef imageDef : imageDefs)
{
FunctionResult fr = initExpression
(
imageDef.getToolPaletteImage().content(),
ImageData.class,
null,
new Runnable()
{
public void run()
{
broadcast( new ImageChangedEvent( DiagramNodeTemplate.this ) );
}
}
);
this.toolPaletteImageFunctionResults.add(fr);
}
// Add model property listener
this.modelPropertyListener = new FilteredListener<PropertyEvent>()
{
@Override
protected void handleTypedEvent( final PropertyEvent event )
{
handleModelPropertyChange( event );
}
};
addModelListener();
}
private void initNodePartListener() {
this.nodePartListener = new FilteredListener<DiagramNodeMoveEvent>() {
@Override
public void handleTypedEvent(final DiagramNodeMoveEvent event) {
notifyNodeMoveEvent(event);
}
};
}
/*
* We need to initialize all the node parts before we can initialize embedded connections.
* Connections between "anonymous" nodes are represented using node index based mechanisms.
*/
public void initEmbeddedConnections()
{
// handle embedded connections
if (!this.definition.getEmbeddedConnections().isEmpty())
{
IDiagramExplicitConnectionBindingDef embeddedConnDef =
this.definition.getEmbeddedConnections().get( 0 );
this.embeddedConnTemplate = new DiagramEmbeddedConnectionTemplate(embeddedConnDef);
IDiagramConnectionDef connDef = this.diagramEditor.getDiagramConnectionDef(embeddedConnDef.getConnectionId().content());
this.embeddedConnTemplate.init(this, this.modelElement, connDef, Collections.<String,String>emptyMap());
this.embeddedConnTemplate.initialize();
}
}
public IDiagramNodeDef definition()
{
return this.definition;
}
public List<DiagramNodePart> getDiagramNodes()
{
return this.diagramNodes;
}
public String getToolPaletteLabel()
{
return this.toolPaletteLabel;
}
public String getToolPaletteDesc()
{
return this.toolPaletteDesc;
}
public List<ImageData> getToolPaletteImages()
{
List<ImageData> imageDatas = new ArrayList<ImageData>();
for (FunctionResult fr : this.toolPaletteImageFunctionResults)
{
imageDatas.add((ImageData)fr.value());
}
return imageDatas;
}
public String getNodeTypeId()
{
return this.definition.getId().content();
}
@Override
public Set<String> getActionContexts()
{
Set<String> ret = new HashSet<String>();
ret.add(SapphireActionSystem.CONTEXT_DIAGRAM_NODE);
ret.add(SapphireActionSystem.CONTEXT_DIAGRAM);
return ret;
}
@SuppressWarnings( "unchecked" )
public DiagramNodePart createNewDiagramNode()
{
Element newElement = null;
ElementList<Element> list = this.modelElement.property(this.modelProperty);
if (this.modelElementType == null)
{
newElement = list.insert();
}
else
{
final Class<Element> cl = (Class<Element>) this.modelElementType.artifact();
if (cl != null)
{
newElement = list.insert(cl);
}
else
{
newElement = list.insert();
}
}
DiagramNodePart newNodePart = getNodePart(newElement);
return newNodePart;
}
public void deleteNode(DiagramNodePart nodePart)
{
notifyNodeAboutToBeDeleted(nodePart);
Element nodeModel = nodePart.getLocalModelElement();
ElementList<?> list = (ElementList<?>) nodeModel.parent();
list.remove(nodeModel);
}
public PropertyDef getModelProperty()
{
return this.modelProperty;
}
public ElementType getNodeType()
{
if (this.modelElementType == null)
{
return this.modelProperty.getType();
}
else
{
final Class<?> cl = (Class<?>) this.modelElementType.artifact();
return ElementType.read(cl);
}
}
public DiagramEmbeddedConnectionTemplate getEmbeddedConnectionTemplate()
{
return this.embeddedConnTemplate;
}
public void addModelListener()
{
this.modelElement.attach(this.modelPropertyListener, this.propertyName);
}
public void removeModelLister()
{
this.modelElement.detach(this.modelPropertyListener, this.propertyName);
}
public void addTemplateListener( final DiagramNodeTemplateListener listener )
{
this.listeners.add( listener );
}
public void removeTemplateListener( final DiagramNodeTemplateListener listener )
{
this.listeners.remove( listener );
}
private void handleModelPropertyChange(final PropertyEvent event)
{
ElementList<?> tempList = (ElementList<?>) event.property();
// filter the list property with specified element type
List<Element> newList = new ArrayList<Element>();
for (Element ele : tempList)
{
if (this.modelElementType == null)
{
newList.add(ele);
}
else
{
final Class<?> cl = (Class<?>) this.modelElementType.artifact();
if( cl == null || cl.isAssignableFrom( ele.getClass()))
{
newList.add(ele);
}
}
}
List<DiagramNodePart> nodeParts = getDiagramNodes();
List<Element> oldList = new ArrayList<Element>(nodeParts.size());
for (DiagramNodePart nodePart : nodeParts)
{
oldList.add(nodePart.getLocalModelElement());
}
List<Element> deletedNodes = CollectionsUtil.removedBasedOnEntryIdentity(oldList, newList);
List<Element> newNodes = CollectionsUtil.removedBasedOnEntryIdentity(newList, oldList);
for (Element deletedNode : deletedNodes)
{
DiagramNodePart nodePart = getNodePart(deletedNode);
if (nodePart != null)
{
notifyNodeDelete(nodePart);
nodePart.dispose();
nodePart.detach(this.nodePartListener);
this.diagramNodes.remove(nodePart);
if (this.embeddedConnTemplate != null)
{
// remove embedded connection parts that are attached to this node
this.embeddedConnTemplate.removeConnectionParts(deletedNode);
}
}
}
for (Element newNode : newNodes)
{
DiagramNodePart nodePart = createNewNodePart(newNode);
if (visible())
{
notifyNodeAdd(nodePart);
}
if (this.embeddedConnTemplate != null)
{
this.embeddedConnTemplate.refreshConnections(newNode);
}
if (visible())
{
notifyNodeAdded(nodePart);
}
}
}
public DiagramNodePart getNodePart(Element element)
{
List<DiagramNodePart> nodeParts = getDiagramNodes();
for (DiagramNodePart nodePart : nodeParts)
{
if (nodePart.getLocalModelElement() == element)
{
return nodePart;
}
}
return null;
}
private DiagramNodePart createNewNodePart(Element element)
{
DiagramNodePart newNode = new DiagramNodePart();
newNode.init(this, element, this.definition,
Collections.<String,String>emptyMap());
newNode.initialize();
newNode.attach(this.nodePartListener);
this.diagramNodes.add(newNode);
if (this.embeddedConnTemplate != null)
{
this.embeddedConnTemplate.addModelListener(element);
}
return newNode;
}
public void hideAllNodeParts()
{
List<DiagramNodePart> nodeParts = getDiagramNodes();
for (DiagramNodePart nodePart : nodeParts)
{
notifyNodeDelete(nodePart);
}
}
public void showAllNodeParts()
{
List<DiagramNodePart> nodeParts = getDiagramNodes();
for (DiagramNodePart nodePart : nodeParts)
{
notifyNodeAdd(nodePart);
}
}
@Override
public void dispose()
{
removeModelLister();
List<DiagramNodePart> nodeParts = getDiagramNodes();
for (DiagramNodePart nodePart : nodeParts)
{
notifyNodeDelete(nodePart);
nodePart.dispose();
}
this.diagramNodes.clear();
if (this.embeddedConnTemplate != null)
{
this.embeddedConnTemplate.dispose();
}
for (FunctionResult fr : this.toolPaletteImageFunctionResults )
{
fr.dispose();
}
}
public SapphireDiagramEditorPagePart getDiagramEditorPart()
{
return this.diagramEditor;
}
private void notifyNodeAdd(DiagramNodePart nodePart)
{
for( DiagramNodeTemplateListener listener : this.listeners )
{
listener.handleNodeAdd(nodePart);
}
}
private void notifyNodeAdded(DiagramNodePart nodePart)
{
for( DiagramNodeTemplateListener listener : this.listeners )
{
listener.handlePostNodeAdd(nodePart);
}
}
private void notifyNodeAboutToBeDeleted(DiagramNodePart nodePart)
{
for( DiagramNodeTemplateListener listener : this.listeners )
{
listener.handlePreNodeDelete(nodePart);
}
}
private void notifyNodeDelete(DiagramNodePart nodePart)
{
for( DiagramNodeTemplateListener listener : this.listeners )
{
listener.handleNodeDelete(nodePart);
}
}
private void notifyNodeMoveEvent(DiagramNodeMoveEvent event)
{
for( DiagramNodeTemplateListener listener : this.listeners )
{
listener.handleNodeMove(event);
}
}
}