/******************************************************************************* * Copyright (c) 2007, 2020 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.modeling.ui.diagramEditor.e4; import java.awt.Point; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.osgi.util.NLS; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.request.IsLinkedTo; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.service.SerialisationSupport; import org.simantics.diagram.adapter.GraphToDiagramSynchronizer; import org.simantics.diagram.content.Change; import org.simantics.diagram.content.DiagramContentChanges; import org.simantics.diagram.content.DiagramContentTracker; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.symbollibrary.ISymbolItem; import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.diagram.ui.ElementClassTransferable; import org.simantics.diagram.ui.ElementClassTransferable.ResourceElementClassTransferData; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.DiagramUtils; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.PickContext; import org.simantics.g2d.diagram.handler.PickRequest; import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.dnd.DnDHints; import org.simantics.g2d.dnd.ElementClassDragItem; import org.simantics.g2d.dnd.IDnDContext; import org.simantics.g2d.dnd.IDragItem; import org.simantics.g2d.dnd.IDropTargetParticipant; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.participant.TransformUtil; import org.simantics.modeling.ModelingResources; import org.simantics.modeling.ui.Activator; import org.simantics.modeling.ui.diagramEditor.WSEDragItem; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.ui.dnd.LocalObjectTransfer; import org.simantics.ui.dnd.LocalObjectTransferable; import org.simantics.ui.selection.WorkbenchSelectionElement; import org.simantics.ui.workbench.e4.E4WorkbenchUtils; import org.simantics.utils.logging.TimeLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This participant populates Elements from ElementClass-resources drops */ public class PopulateElementDropParticipant extends AbstractDiagramParticipant implements IDropTargetParticipant { private static final Logger LOGGER = LoggerFactory.getLogger(PopulateElementDropParticipant.class); @Dependency PickContext pickContext; @Dependency TransformUtil transformUtil; protected GraphToDiagramSynchronizer synchronizer; protected MPart part; public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer) { this(synchronizer, null); } public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer, MPart part) { this.synchronizer = synchronizer; this.part = part; } @Override public void dragEnter(DropTargetDragEvent dtde, IDnDContext dp) { if (diagram == null) return; Transferable tr = dtde.getTransferable(); if (tr.isDataFlavorSupported(LocalObjectTransferable.FLAVOR)) { // System.out.println("joo"); Object obj = null; // This must be done to have SWT transfer set the source data try { obj = tr.getTransferData(LocalObjectTransferable.FLAVOR); // System.out.println("GOT FROM AWT: " + obj); } catch (UnsupportedFlavorException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Check SWT if (!(obj instanceof IStructuredSelection)) { obj = LocalObjectTransfer.getTransfer().getObject(); // System.out.println("GOT FROM SWT: " + obj); } if (obj instanceof IStructuredSelection) { IStructuredSelection sel = (IStructuredSelection) obj; if (!sel.isEmpty()) { for (Object elm : sel.toList()) { if (elm instanceof IAdaptable) { ElementClass ec = (ElementClass) ((IAdaptable) elm).getAdapter(ElementClass.class); if (ec != null) { dp.add(new ElementClassDragItem(ec)); } else { Resource r = (Resource) ((IAdaptable) elm).getAdapter(Resource.class); if (r != null) { if (elm instanceof ISymbolItem) { /* FIXME fix this check ISymbolItem symbol = (ISymbolItem) elm; Resource dia = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); try { if (!DiagramTypeUtils.symbolAllowedOnDiagram(synchronizer.getSession(), symbol, dia)) { // Deny dragging of this symbol continue; } } catch (DatabaseException e) { e.printStackTrace(); continue; }*/ } try { String valid = validateDrop(synchronizer.getSession(), r, diagram. getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE)); if (valid == null) { ElementClassDragItem item = new ElementClassDragItem(synchronizer.getNodeClass(r)); item.getHintContext().setHint(ElementHints.KEY_TRANSFORM, AffineTransform.getScaleInstance(1, 1)); dp.add(item); } } catch (DatabaseException e) { // Ignore node-class retrieval failures. //System.out.println("error: " + e.getMessage()); } } } } } // Let the default logic handle out how many columns to use. dp.getHints().removeHint(DnDHints.KEY_DND_GRID_COLUMNS); } } return; } if (tr.isDataFlavorSupported(ElementClassTransferable.FLAVOR)) { ResourceElementClassTransferData dada; try { dada = (ResourceElementClassTransferData) tr.getTransferData(ElementClassTransferable.FLAVOR); } catch (UnsupportedFlavorException e) { throw new Error(e); } catch (IOException e) { throw new Error(e); } Session s = synchronizer.getSession(); try { for (String rid : dada.elementClassResourceRandomAccessReference) { SerialisationSupport support = s.getService(SerialisationSupport.class); Resource r = support.getResource(Long.parseLong(rid)); dp.add(new ElementClassDragItem(synchronizer.getNodeClass(r))); } } catch (DatabaseException e) { throw new RuntimeException(e); } return; } } private String validateDrop(RequestProcessor processor, final Resource draggedResource, final Resource dropTarget) throws DatabaseException { return processor.syncRequest(new UniqueRead() { @Override public String perform(ReadGraph graph) throws DatabaseException { // System.out.println("dragged resource: " + draggedResource); // System.out.println("drop target resource: " + dropTarget); Resource sourceModel = graph.syncRequest(new PossibleIndexRoot(draggedResource)); Resource targetModel = graph.syncRequest(new PossibleIndexRoot(dropTarget)); // System.out.println("source model: " + sourceModel); // System.out.println("target model: " + targetModel); // Prevent dragging data from one source model to another. // If source is not part of any model, everything is okay. if (sourceModel != null && !graph.syncRequest(new IsLinkedTo(targetModel, sourceModel))) { // Prevent a symbol instantiating within its own configuration. // NOTE: this doesn't handle transitive cycles. return NLS.bind(Messages.PopulateElementDropParticipant_CannotInstantiate, new Object[] { NameUtils.getSafeName(graph, draggedResource), NameUtils.getURIOrSafeNameInternal(graph, targetModel), NameUtils.getURIOrSafeNameInternal(graph, sourceModel) }); } // Prevent dragging to published components ModelingResources MOD = ModelingResources.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); Resource configuration = graph.getPossibleObject(dropTarget, MOD.DiagramToComposite); if (configuration != null) { Resource componentTypeFromDiagram = graph.getPossibleObject(configuration, STR.Defines); if (componentTypeFromDiagram != null) { if (Layer0Utils.isPublished(graph, componentTypeFromDiagram)) return Messages.PopulateElementDropParticipant_CannotCreateElementIntoDiagram; } } // Prevent dragging a symbol of component type into its own configuration. Resource componentTypeFromSymbol = graph.getPossibleObject(draggedResource, MOD.SymbolToComponentType); if (componentTypeFromSymbol != null) { if (configuration != null) { Resource componentTypeFromDiagram = graph.getPossibleObject(configuration, STR.Defines); if (componentTypeFromDiagram != null && componentTypeFromSymbol.equals(componentTypeFromDiagram)) { return Messages.PopulateElementDropParticipant_CannotInstantiateUserComponent; } } } return null; } }); } @Override public void dragExit(DropTargetEvent dte, IDnDContext dp) { // System.out.println("exit"); } @Override public void dragOver(DropTargetDragEvent dtde, IDnDContext dp) { // System.out.println("over"); } private IElement tryPick(Point p) { Point2D canvas = transformUtil.controlToCanvas(p, null); assertDependencies(); PickRequest req = new PickRequest(canvas); req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; List picks = new ArrayList(); pickContext.pick(diagram, req, picks); if(picks.size() == 1) return picks.iterator().next(); return null; } @Override public void drop(DropTargetDropEvent dtde, final IDnDContext dp) { TimeLogger.resetTimeAndLog(getClass(), "drop"); //$NON-NLS-1$ final IDiagram d = getHint(DiagramHints.KEY_DIAGRAM); if (d == null) return; IElement pick = tryPick(dtde.getLocation()); if(pick != null) { final List wses = new ArrayList(); for(IDragItem i : dp.toArray()) if(i instanceof WSEDragItem) wses.add(((WSEDragItem)i).getObject()); final Resource element = (Resource)ElementUtils.getData(d, pick); if(element != null && !wses.isEmpty()) { try { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); Simantics.invokeSCLWrite(graph, element, DIA.symbolDropHandler, wses); } }); } catch (DatabaseException e) { LOGGER.error("symbolDropHandler invocation failed", e); } return; } } Runnable creator = () -> { DiagramUtils.mutateDiagram(d, m -> { IDragItem items[] = dp.toArray(); for (IDragItem i : items) { if (!(i instanceof ElementClassDragItem)) continue; ElementClassDragItem res = (ElementClassDragItem) i; ElementClass ec = res.getElementClass(); Point2D pos = dp.getItemPosition(i); // System.out.println(pos); assert (pos != null); IElement element = m.newElement(ec); element.setHints(res.getHintContext().getHints()); setupDroppedElement(element, pos); // Remove only the drag items we've processed. dp.remove(i); } }); }; selectNewDiagramContentAfter(d, part, creator); getContext().getContentContext().setDirty(); } protected void selectNewDiagramContentAfter(IDiagram d, MPart activatePart, Runnable diagramModifier) { try { Resource diagramResource = d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); final DiagramContentTracker tracker = diagramResource == null ? null : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource); diagramModifier.run(); if (tracker != null) { // Get difference of diagram contents to find out what was added. DiagramContentChanges changes = tracker.update(); Set addedElements = changes.pick(changes.elements, Change.ADDED); if (!addedElements.isEmpty()) { new DiagramSelectionUpdater(getContext()) .setNewSelection(0, addedElements) .setOneshot(true) .track(); if (activatePart != null) E4WorkbenchUtils.activatePart(activatePart); } } } catch (DatabaseException e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.PopulateElementDropParticipant_ActivatorDiagramContentTrackingFailed, e)); } } protected void setupDroppedElement(IElement element, Point2D dropPos) { // This works only for elements without parents. ISnapAdvisor snapAdvisor = getContext().getHintStack().getHint(DiagramHints.SNAP_ADVISOR); if(snapAdvisor != null) snapAdvisor.snap(dropPos); IElement parent = element.getHint(ElementHints.KEY_PARENT_ELEMENT); if (parent != null) { Point2D parentPos = ElementUtils.getPos(parent); Point2D pos = new Point2D.Double(dropPos.getX() - parentPos.getX(), dropPos.getY() - parentPos.getY()); ElementUtils.setPos(element, pos); } else { ElementUtils.setPos(element, dropPos); } } @Override public void dropActionChanged(DropTargetDragEvent dtde, IDnDContext dp) { dtde.acceptDrag(DnDConstants.ACTION_COPY); } @Override public int getAllowedOps() { return DnDConstants.ACTION_COPY; } @Override public double getPriority() { return 10.0; } }