1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 * Semantum Oy - gitlab #215
12 *******************************************************************************/
13 package org.simantics.modeling.ui.diagramEditor;
15 import java.awt.Point;
16 import java.awt.datatransfer.Transferable;
17 import java.awt.datatransfer.UnsupportedFlavorException;
18 import java.awt.dnd.DnDConstants;
19 import java.awt.dnd.DropTargetDragEvent;
20 import java.awt.dnd.DropTargetDropEvent;
21 import java.awt.dnd.DropTargetEvent;
22 import java.awt.geom.AffineTransform;
23 import java.awt.geom.Point2D;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
29 import java.util.stream.Collectors;
31 import org.eclipse.core.runtime.IAdaptable;
32 import org.eclipse.core.runtime.IStatus;
33 import org.eclipse.core.runtime.Status;
34 import org.eclipse.jface.viewers.IStructuredSelection;
35 import org.eclipse.osgi.util.NLS;
36 import org.eclipse.swt.widgets.Shell;
37 import org.eclipse.ui.IWorkbenchPartSite;
38 import org.simantics.Simantics;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.RequestProcessor;
41 import org.simantics.db.Resource;
42 import org.simantics.db.Session;
43 import org.simantics.db.common.request.PossibleIndexRoot;
44 import org.simantics.db.common.request.ResourceRead2;
45 import org.simantics.db.common.utils.NameUtils;
46 import org.simantics.db.exception.DatabaseException;
47 import org.simantics.db.layer0.request.IsLinkedTo;
48 import org.simantics.db.layer0.util.Layer0Utils;
49 import org.simantics.db.layer0.variable.Variable;
50 import org.simantics.db.layer0.variable.Variables;
51 import org.simantics.db.request.Read;
52 import org.simantics.db.request.Write;
53 import org.simantics.db.service.SerialisationSupport;
54 import org.simantics.diagram.adapter.GraphToDiagramSynchronizer;
55 import org.simantics.diagram.content.Change;
56 import org.simantics.diagram.content.DiagramContentChanges;
57 import org.simantics.diagram.content.DiagramContentTracker;
58 import org.simantics.diagram.stubs.DiagramResource;
59 import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;
60 import org.simantics.diagram.ui.DiagramModelHints;
61 import org.simantics.diagram.ui.ElementClassTransferable;
62 import org.simantics.diagram.ui.ElementClassTransferable.ResourceElementClassTransferData;
63 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
64 import org.simantics.g2d.diagram.DiagramHints;
65 import org.simantics.g2d.diagram.DiagramUtils;
66 import org.simantics.g2d.diagram.IDiagram;
67 import org.simantics.g2d.diagram.handler.PickContext;
68 import org.simantics.g2d.diagram.handler.PickRequest;
69 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
70 import org.simantics.g2d.dnd.DnDHints;
71 import org.simantics.g2d.dnd.ElementClassDragItem;
72 import org.simantics.g2d.dnd.IDnDContext;
73 import org.simantics.g2d.dnd.IDragItem;
74 import org.simantics.g2d.dnd.IDropTargetParticipant;
75 import org.simantics.g2d.element.ElementClass;
76 import org.simantics.g2d.element.ElementHints;
77 import org.simantics.g2d.element.ElementUtils;
78 import org.simantics.g2d.element.IElement;
79 import org.simantics.g2d.participant.TransformUtil;
80 import org.simantics.modeling.ModelingResources;
81 import org.simantics.modeling.ui.Activator;
82 import org.simantics.modeling.ui.diagramEditor.dnd.DropSuggestion;
83 import org.simantics.modeling.ui.diagramEditor.dnd.DropSuggestions;
84 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
85 import org.simantics.structural.stubs.StructuralResource2;
86 import org.simantics.ui.dnd.LocalObjectTransfer;
87 import org.simantics.ui.dnd.LocalObjectTransferable;
88 import org.simantics.ui.selection.WorkbenchSelectionElement;
89 import org.simantics.utils.datastructures.hints.IHintContext;
90 import org.simantics.utils.datastructures.hints.IHintContext.Key;
91 import org.simantics.utils.logging.TimeLogger;
92 import org.simantics.utils.strings.EString;
93 import org.simantics.utils.ui.SWTUtils;
94 import org.simantics.utils.ui.dialogs.ShowError;
95 import org.simantics.utils.ui.workbench.WorkbenchUtils;
96 import org.slf4j.Logger;
97 import org.slf4j.LoggerFactory;
100 * This participant populates Elements from ElementClass-resources drops
102 public class PopulateElementDropParticipant extends AbstractDiagramParticipant implements IDropTargetParticipant {
104 private static final Logger LOGGER = LoggerFactory.getLogger(PopulateElementDropParticipant.class);
107 * List of {@link DropSuggestion} instances that need to be applied to be model
108 * before the drop can commence. Used with the hint context from
109 * {@link IDnDContext#getHints()}.
111 private static final Key KEY_SUGGESTIONS = new IHintContext.KeyOf(List.class);
113 @Dependency PickContext pickContext;
114 @Dependency TransformUtil transformUtil;
116 protected GraphToDiagramSynchronizer synchronizer;
117 protected IWorkbenchPartSite partSite;
119 public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer) {
120 this(synchronizer, null);
123 public PopulateElementDropParticipant(GraphToDiagramSynchronizer synchronizer, IWorkbenchPartSite partSite) {
124 this.synchronizer = synchronizer;
125 this.partSite = partSite;
129 public void dragEnter(DropTargetDragEvent dtde, IDnDContext dp) {
133 Transferable tr = dtde.getTransferable();
134 if (tr.isDataFlavorSupported(LocalObjectTransferable.FLAVOR)) {
137 // This must be done to have SWT transfer set the source data
139 obj = tr.getTransferData(LocalObjectTransferable.FLAVOR);
140 // System.out.println("GOT FROM AWT: " + obj);
141 } catch (UnsupportedFlavorException | IOException e) {
142 LOGGER.error("Could not get AWT transferable data", e); //$NON-NLS-1$
146 if (!(obj instanceof IStructuredSelection)) {
147 obj = LocalObjectTransfer.getTransfer().getObject();
148 // System.out.println("GOT FROM SWT: " + obj);
151 if (obj instanceof IStructuredSelection) {
152 IStructuredSelection sel = (IStructuredSelection) obj;
153 if (!sel.isEmpty()) {
154 for (Object elm : sel.toList()) {
155 if (elm instanceof IAdaptable) {
156 ElementClass ec = (ElementClass) ((IAdaptable) elm).getAdapter(ElementClass.class);
158 dp.add(new ElementClassDragItem(ec));
160 Resource r = (Resource) ((IAdaptable) elm).getAdapter(Resource.class);
163 Object errorOrSymbolResource = validateDrag(synchronizer.getSession(), r,
164 diagram.<Resource> getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE),
166 if (errorOrSymbolResource instanceof Resource) {
167 Resource symbol = (Resource) errorOrSymbolResource;
168 ElementClassDragItem item = new ElementClassDragItem(synchronizer.getNodeClass(symbol));
169 item.getHintContext().setHint(ElementHints.KEY_TRANSFORM, AffineTransform.getScaleInstance(1, 1));
170 item.getHintContext().setHint(ElementHints.KEY_OBJECT, symbol);
173 } catch (DatabaseException e) {
174 // Ignore node-class retrieval failures, so only log as debug
175 LOGGER.debug("Could not retrieve node class for dropped symbol", e); //$NON-NLS-1$
182 // Let the default logic handle out how many columns to use.
183 dp.getHints().removeHint(DnDHints.KEY_DND_GRID_COLUMNS);
190 if (tr.isDataFlavorSupported(ElementClassTransferable.FLAVOR)) {
191 ResourceElementClassTransferData dada;
193 dada = (ResourceElementClassTransferData) tr.getTransferData(ElementClassTransferable.FLAVOR);
194 } catch (UnsupportedFlavorException e) {
196 } catch (IOException e) {
199 Session s = synchronizer.getSession();
201 for (String rid : dada.elementClassResourceRandomAccessReference) {
202 SerialisationSupport support = s.getService(SerialisationSupport.class);
203 Resource r = support.getResource(Long.parseLong(rid));
204 dp.add(new ElementClassDragItem(synchronizer.getNodeClass(r)));
206 } catch (DatabaseException e) {
207 throw new RuntimeException(e);
214 private Object validateDrag(RequestProcessor processor, final Resource draggedResource, final Resource dropTarget, IHintContext dndHints) throws DatabaseException {
215 return processor.syncRequest((Read<Object>) graph -> {
216 List<DropSuggestion> suggestions = dndHints.getHint(KEY_SUGGESTIONS);
217 if (suggestions == null) {
218 suggestions = new ArrayList<>();
219 dndHints.setHint(KEY_SUGGESTIONS, suggestions);
222 //System.out.println("dragged resource: " + draggedResource);
223 //System.out.println("drop target resource: " + dropTarget);
224 Resource sourceRoot = graph.syncRequest(new PossibleIndexRoot(draggedResource));
225 Resource targetRoot = graph.syncRequest(new PossibleIndexRoot(dropTarget));
226 //System.out.println("source model: " + sourceRoot);
227 //System.out.println("target model: " + targetRoot);
229 // Prevent dragging data from one source model to another.
230 // If source is not part of any model, everything is okay.
231 if (sourceRoot != null && !graph.syncRequest(new IsLinkedTo(targetRoot, sourceRoot))) {
232 // Prevent instantiation from source roots that are already dependent on the target root.
233 // This would form a dependency cycle.
234 if (graph.syncRequest(new IsLinkedTo(sourceRoot, targetRoot))) {
235 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$
237 NameUtils.getSafeName(graph, draggedResource),
238 NameUtils.getURIOrSafeNameInternal(graph, targetRoot),
239 NameUtils.getURIOrSafeNameInternal(graph, sourceRoot)
243 // It is OK to continue for now, even though the target root is not linked to the source root.
244 // The question of whether to link the target root to the source root will asked at drop time.
245 suggestions.add(DropSuggestions.linkToLibrary(graph, targetRoot, sourceRoot));
248 ModelingResources MOD = ModelingResources.getInstance(graph);
249 StructuralResource2 STR = StructuralResource2.getInstance(graph);
251 Resource configurationComposite = graph.getPossibleObject(dropTarget, MOD.DiagramToComposite);
252 Resource componentTypeFromDiagram = configurationComposite != null ? graph.getPossibleObject(configurationComposite, STR.Defines) : null;
254 // Prevent dragging to published components
255 if (componentTypeFromDiagram != null && Layer0Utils.isPublished(graph, componentTypeFromDiagram))
256 return "Cannot create elements into a diagram that belongs to a published user component."; //$NON-NLS-1$
258 // Check if dragged object is symbol or component type and determine other
259 Resource componentType;
260 Resource symbol = graph.getPossibleObject(draggedResource, MOD.ComponentTypeToSymbol);
262 componentType = draggedResource;
264 componentType = graph.getPossibleObject(draggedResource, MOD.SymbolToComponentType);
265 symbol = draggedResource;
268 // Prevent dragging a symbol of component type into its own configuration.
269 if (componentType != null
270 && configurationComposite != null
271 && componentTypeFromDiagram != null
272 && componentType.equals(componentTypeFromDiagram)) {
273 return "Cannot instantiate user component within its own configuration."; //$NON-NLS-1$
281 public void dragExit(DropTargetEvent dte, IDnDContext dp) {
282 // System.out.println("exit");
286 public void dragOver(DropTargetDragEvent dtde, IDnDContext dp) {
287 // System.out.println("over");
290 private IElement tryPick(Point p) {
291 Point2D canvas = transformUtil.controlToCanvas(p, null);
293 assertDependencies();
295 PickRequest req = new PickRequest(canvas);
296 req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
297 List<IElement> picks = new ArrayList<>();
298 pickContext.pick(diagram, req, picks);
300 if(picks.size() == 1) return picks.iterator().next();
306 public void drop(DropTargetDropEvent dtde, final IDnDContext dp) {
307 TimeLogger.resetTimeAndLog(getClass(), "drop"); //$NON-NLS-1$
309 final Point loc = dtde.getLocation();
310 final IDiagram d = diagram;
315 validateDrop(d, dp, () -> performDrop(d, loc, dp));
316 } catch (DatabaseException e) {
317 LOGGER.error("Element drop validation failed", e); //$NON-NLS-1$
321 private void validateDrop(IDiagram diagram, IDnDContext dp, Runnable dropFunction)
322 throws DatabaseException {
323 List<DropSuggestion> reqs = dp.getHints().getHint(KEY_SUGGESTIONS);
324 if (reqs != null && !reqs.isEmpty()) {
325 // Ask user if suggestions should be ran before dropping.
327 Shell parentShell = partSite.getWorkbenchWindow().getShell();
328 SWTUtils.asyncExec(parentShell, () -> {
329 if (parentShell.isDisposed())
331 if (!DropSuggestions.askSuggestions(parentShell, reqs))
335 Simantics.getSession().syncRequest(DropSuggestions.performSuggestionsRequest(reqs));
337 getThread().asyncExec(() -> {
342 } catch (DatabaseException e) {
343 String format = Messages.PopulateElementDropParticipant_PreDropFixesFailed;
344 String formattedSuggestions = EString.implode(reqs);
345 LOGGER.error(format, formattedSuggestions, e);
346 ShowError.showError(Messages.PopulateElementDropParticipant_PreDropFixesFailed_Title, NLS.bind(format, formattedSuggestions), e);
354 private void performDrop(IDiagram d, Point loc, IDnDContext dp) {
355 IElement pick = tryPick(loc);
357 List<WorkbenchSelectionElement> wses = Arrays.stream(dp.toArray())
358 .filter(WSEDragItem.class::isInstance)
359 .map(di -> ((WSEDragItem) di).getObject())
360 .collect(Collectors.toList());
362 final Resource element = (Resource) ElementUtils.getData(d, pick);
363 if (element != null && !wses.isEmpty()) {
365 Session db = Simantics.getSession();
366 DiagramResource DIA = DiagramResource.getInstance(db);
367 Variable function = db.syncRequest(new PossibleVariableProperty(element, DIA.symbolDropHandler));
368 if (function != null) {
369 db.syncRequest((Write) graph -> Simantics.invokeSCLWrite(graph, function, wses));
372 } catch (DatabaseException e) {
373 Activator.getDefault().getLog()
374 .log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
375 "Invocation to custom symbolDropHandler for element " //$NON-NLS-1$
376 + element + " failed.", //$NON-NLS-1$
383 Runnable creator = () -> {
384 DiagramUtils.mutateDiagram(d, m -> {
385 IDragItem items[] = dp.toArray();
387 for (IDragItem i : items) {
388 if (!(i instanceof ElementClassDragItem))
391 ElementClassDragItem res = (ElementClassDragItem) i;
392 ElementClass ec = res.getElementClass();
394 Point2D pos = dp.getItemPosition(i);
395 // System.out.println(pos);
396 assert (pos != null);
398 IElement element = m.newElement(ec);
399 element.setHints(res.getHintContext().getHints());
401 setupDroppedElement(element, pos);
403 // Remove only the drag items we've processed.
409 selectNewDiagramContentAfter(d, partSite, creator);
411 getContext().getContentContext().setDirty();
414 private static class PossibleVariableProperty extends ResourceRead2<Variable> {
416 public PossibleVariableProperty(Resource entity, Resource property) {
417 super(entity, property);
421 public Variable perform(ReadGraph graph) throws DatabaseException {
422 return Variables.tryGetProperty(graph, resource, resource2);
427 protected void selectNewDiagramContentAfter(IDiagram d, IWorkbenchPartSite activateSite, Runnable diagramModifier) {
429 Resource diagramResource = d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
430 final DiagramContentTracker tracker = diagramResource == null ? null
431 : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);
433 diagramModifier.run();
435 if (tracker != null) {
436 // Get difference of diagram contents to find out what was added.
437 DiagramContentChanges changes = tracker.update();
438 Set<Resource> addedElements = changes.pick(changes.elements, Change.ADDED);
439 if (!addedElements.isEmpty()) {
440 new DiagramSelectionUpdater(getContext())
441 .setNewSelection(0, addedElements)
444 if (activateSite != null)
445 WorkbenchUtils.activatePart(activateSite);
448 } catch (DatabaseException e) {
449 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Diagram content change tracking failed.", e)); //$NON-NLS-1$
453 protected void setupDroppedElement(IElement element, Point2D dropPos) {
454 // This works only for elements without parents.
455 ISnapAdvisor snapAdvisor = getContext().getHintStack().getHint(DiagramHints.SNAP_ADVISOR);
456 if(snapAdvisor != null)
457 snapAdvisor.snap(dropPos);
459 IElement parent = element.getHint(ElementHints.KEY_PARENT_ELEMENT);
460 if (parent != null) {
461 Point2D parentPos = ElementUtils.getPos(parent);
462 Point2D pos = new Point2D.Double(dropPos.getX() - parentPos.getX(), dropPos.getY() - parentPos.getY());
463 ElementUtils.setPos(element, pos);
465 ElementUtils.setPos(element, dropPos);
470 public void dropActionChanged(DropTargetDragEvent dtde, IDnDContext dp) {
471 dtde.acceptDrag(DnDConstants.ACTION_COPY);
475 public int getAllowedOps() {
476 return DnDConstants.ACTION_COPY;
480 public double getPriority() {