/******************************************************************************* * Copyright (c) 2007, 2018 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 * Semantum Oy - gitlab #215 *******************************************************************************/ package org.simantics.modeling.ui.diagramEditor; 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.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPartSite; 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.common.request.PossibleIndexRoot; import org.simantics.db.common.request.ResourceRead2; 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.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.request.Read; import org.simantics.db.request.Write; 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.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.dnd.DropSuggestion; import org.simantics.modeling.ui.diagramEditor.dnd.DropSuggestions; 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.utils.datastructures.hints.IHintContext; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.logging.TimeLogger; import org.simantics.utils.strings.EString; import org.simantics.utils.ui.SWTUtils; import org.simantics.utils.ui.dialogs.ShowError; import org.simantics.utils.ui.workbench.WorkbenchUtils; 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); /** * List of {@link DropSuggestion} instances that need to be applied to be model * before the drop can commence. Used with the hint context from * {@link IDnDContext#getHints()}. */ private static final Key KEY_SUGGESTIONS = new IHintContext.KeyOf(List.class); @Dependency PickContext pickContext; @Dependency TransformUtil transformUtil; protected GraphToDiagramSynchronizer synchronizer; protected IWorkbenchPartSite partSite; public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer) { this(synchronizer, null); } public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer, IWorkbenchPartSite partSite) { this.synchronizer = synchronizer; this.partSite = partSite; } @Override public void dragEnter(DropTargetDragEvent dtde, IDnDContext dp) { if (diagram == null) return; Transferable tr = dtde.getTransferable(); if (tr.isDataFlavorSupported(LocalObjectTransferable.FLAVOR)) { 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 | IOException e) { LOGGER.error("Could not get AWT transferable data", e); //$NON-NLS-1$ } // 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) { try { Object errorOrSymbolResource = validateDrag(synchronizer.getSession(), r, diagram. getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), dp.getHints()); if (errorOrSymbolResource instanceof Resource) { Resource symbol = (Resource) errorOrSymbolResource; ElementClassDragItem item = new ElementClassDragItem(synchronizer.getNodeClass(symbol)); item.getHintContext().setHint(ElementHints.KEY_TRANSFORM, AffineTransform.getScaleInstance(1, 1)); item.getHintContext().setHint(ElementHints.KEY_OBJECT, symbol); dp.add(item); } } catch (DatabaseException e) { // Ignore node-class retrieval failures, so only log as debug LOGGER.debug("Could not retrieve node class for dropped symbol", e); //$NON-NLS-1$ } } } } } // 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 Object validateDrag(RequestProcessor processor, final Resource draggedResource, final Resource dropTarget, IHintContext dndHints) throws DatabaseException { return processor.syncRequest((Read) graph -> { List suggestions = dndHints.getHint(KEY_SUGGESTIONS); if (suggestions == null) { suggestions = new ArrayList<>(); dndHints.setHint(KEY_SUGGESTIONS, suggestions); } //System.out.println("dragged resource: " + draggedResource); //System.out.println("drop target resource: " + dropTarget); Resource sourceRoot = graph.syncRequest(new PossibleIndexRoot(draggedResource)); Resource targetRoot = graph.syncRequest(new PossibleIndexRoot(dropTarget)); //System.out.println("source model: " + sourceRoot); //System.out.println("target model: " + targetRoot); // Prevent dragging data from one source model to another. // If source is not part of any model, everything is okay. if (sourceRoot != null && !graph.syncRequest(new IsLinkedTo(targetRoot, sourceRoot))) { // Prevent instantiation from source roots that are already dependent on the target root. // This would form a dependency cycle. if (graph.syncRequest(new IsLinkedTo(sourceRoot, targetRoot))) { return NLS.bind("Cannot instantiate {0} into namespace {1}. The source namespace ({2}) is already linked to the target namespace. Linking the target to the source would form a dependency cycle.", //$NON-NLS-1$ new Object[] { NameUtils.getSafeName(graph, draggedResource), NameUtils.getURIOrSafeNameInternal(graph, targetRoot), NameUtils.getURIOrSafeNameInternal(graph, sourceRoot) }); } // It is OK to continue for now, even though the target root is not linked to the source root. // The question of whether to link the target root to the source root will asked at drop time. suggestions.add(DropSuggestions.linkToLibrary(graph, targetRoot, sourceRoot)); } ModelingResources MOD = ModelingResources.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); Resource configurationComposite = graph.getPossibleObject(dropTarget, MOD.DiagramToComposite); Resource componentTypeFromDiagram = configurationComposite != null ? graph.getPossibleObject(configurationComposite, STR.Defines) : null; // Prevent dragging to published components if (componentTypeFromDiagram != null && Layer0Utils.isPublished(graph, componentTypeFromDiagram)) return "Cannot create elements into a diagram that belongs to a published user component."; //$NON-NLS-1$ // Check if dragged object is symbol or component type and determine other Resource componentType; Resource symbol = graph.getPossibleObject(draggedResource, MOD.ComponentTypeToSymbol); if (symbol != null) componentType = draggedResource; else { componentType = graph.getPossibleObject(draggedResource, MOD.SymbolToComponentType); symbol = draggedResource; } // Prevent dragging a symbol of component type into its own configuration. if (componentType != null && configurationComposite != null && componentTypeFromDiagram != null && componentType.equals(componentTypeFromDiagram)) { return "Cannot instantiate user component within its own configuration."; //$NON-NLS-1$ } return symbol; }); } @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 Point loc = dtde.getLocation(); final IDiagram d = diagram; if (d == null) return; try { validateDrop(d, dp, () -> performDrop(d, loc, dp)); } catch (DatabaseException e) { LOGGER.error("Element drop validation failed", e); //$NON-NLS-1$ } } private void validateDrop(IDiagram diagram, IDnDContext dp, Runnable dropFunction) throws DatabaseException { List reqs = dp.getHints().getHint(KEY_SUGGESTIONS); if (reqs != null && !reqs.isEmpty()) { // Ask user if suggestions should be ran before dropping. // If not, cancel. Shell parentShell = partSite.getWorkbenchWindow().getShell(); SWTUtils.asyncExec(parentShell, () -> { if (parentShell.isDisposed()) return; if (!DropSuggestions.askSuggestions(parentShell, reqs)) return; try { Simantics.getSession().syncRequest(DropSuggestions.performSuggestionsRequest(reqs)); getThread().asyncExec(() -> { if (isRemoved()) return; dropFunction.run(); }); } catch (DatabaseException e) { String format = Messages.PopulateElementDropParticipant_PreDropFixesFailed; String formattedSuggestions = EString.implode(reqs); LOGGER.error(format, formattedSuggestions, e); ShowError.showError(Messages.PopulateElementDropParticipant_PreDropFixesFailed_Title, NLS.bind(format, formattedSuggestions), e); } }); } else { dropFunction.run(); } } private void performDrop(IDiagram d, Point loc, IDnDContext dp) { IElement pick = tryPick(loc); if (pick != null) { List wses = Arrays.stream(dp.toArray()) .filter(WSEDragItem.class::isInstance) .map(di -> ((WSEDragItem) di).getObject()) .collect(Collectors.toList()); final Resource element = (Resource) ElementUtils.getData(d, pick); if (element != null && !wses.isEmpty()) { try { Session db = Simantics.getSession(); DiagramResource DIA = DiagramResource.getInstance(db); Variable function = db.syncRequest(new PossibleVariableProperty(element, DIA.symbolDropHandler)); if (function != null) { db.syncRequest((Write) graph -> Simantics.invokeSCLWrite(graph, function, wses)); return; } } catch (DatabaseException e) { Activator.getDefault().getLog() .log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Invocation to custom symbolDropHandler for element " //$NON-NLS-1$ + element + " failed.", //$NON-NLS-1$ 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, partSite, creator); getContext().getContentContext().setDirty(); } private static class PossibleVariableProperty extends ResourceRead2 { public PossibleVariableProperty(Resource entity, Resource property) { super(entity, property); } @Override public Variable perform(ReadGraph graph) throws DatabaseException { return Variables.tryGetProperty(graph, resource, resource2); } } protected void selectNewDiagramContentAfter(IDiagram d, IWorkbenchPartSite activateSite, 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 (activateSite != null) WorkbenchUtils.activatePart(activateSite); } } } catch (DatabaseException e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Diagram content change tracking failed.", e)); //$NON-NLS-1$ } } 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; } }