1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 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 *******************************************************************************/
12 package org.simantics.diagram.handler;
14 import java.awt.Color;
15 import java.awt.event.KeyEvent;
16 import java.awt.geom.AffineTransform;
17 import java.awt.geom.Point2D;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.List;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.jface.action.IStatusLineManager;
27 import org.eclipse.swt.widgets.Display;
28 import org.eclipse.ui.IPartListener;
29 import org.eclipse.ui.IWorkbenchPart;
30 import org.eclipse.ui.IWorkbenchPartSite;
31 import org.eclipse.ui.PlatformUI;
32 import org.simantics.Simantics;
33 import org.simantics.databoard.Bindings;
34 import org.simantics.db.ReadGraph;
35 import org.simantics.db.Resource;
36 import org.simantics.db.WriteGraph;
37 import org.simantics.db.common.request.WriteRequest;
38 import org.simantics.db.common.utils.OrderedSetUtils;
39 import org.simantics.db.exception.DatabaseException;
40 import org.simantics.db.layer0.util.ClipboardUtils;
41 import org.simantics.db.layer0.util.SimanticsClipboard;
42 import org.simantics.db.layer0.util.SimanticsClipboard.Representation;
43 import org.simantics.db.layer0.util.SimanticsKeys;
44 import org.simantics.db.layer0.variable.Variable;
45 import org.simantics.db.layer0.variable.Variables;
46 import org.simantics.db.request.Read;
47 import org.simantics.diagram.content.Change;
48 import org.simantics.diagram.content.ConnectionUtil;
49 import org.simantics.diagram.content.DiagramContentChanges;
50 import org.simantics.diagram.content.DiagramContentTracker;
51 import org.simantics.diagram.internal.Activator;
52 import org.simantics.diagram.stubs.DiagramResource;
53 import org.simantics.diagram.stubs.G2DResource;
54 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
55 import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;
56 import org.simantics.diagram.ui.DiagramModelHints;
57 import org.simantics.g2d.canvas.ICanvasContext;
58 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
59 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
60 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
61 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
62 import org.simantics.g2d.connection.ConnectionEntity;
63 import org.simantics.g2d.connection.handler.ConnectionHandler;
64 import org.simantics.g2d.diagram.IDiagram;
65 import org.simantics.g2d.diagram.handler.Topology;
66 import org.simantics.g2d.diagram.handler.Topology.Connection;
67 import org.simantics.g2d.diagram.handler.Topology.Terminal;
68 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
69 import org.simantics.g2d.diagram.participant.Selection;
70 import org.simantics.g2d.element.ElementClass;
71 import org.simantics.g2d.element.ElementHints;
72 import org.simantics.g2d.element.ElementUtils;
73 import org.simantics.g2d.element.IElement;
74 import org.simantics.g2d.element.handler.BendsHandler;
75 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
76 import org.simantics.g2d.element.handler.Move;
77 import org.simantics.g2d.element.handler.TerminalTopology;
78 import org.simantics.g2d.element.handler.Transform;
79 import org.simantics.g2d.elementclass.FlagHandler;
80 import org.simantics.g2d.participant.GridPainter;
81 import org.simantics.g2d.participant.MouseUtil;
82 import org.simantics.g2d.participant.MouseUtil.MouseInfo;
83 import org.simantics.layer0.Layer0;
84 import org.simantics.modeling.ModelingResources;
85 import org.simantics.operation.Layer0X;
86 import org.simantics.project.IProject;
87 import org.simantics.scenegraph.INode;
88 import org.simantics.scenegraph.ParentNode;
89 import org.simantics.scenegraph.g2d.G2DParentNode;
90 import org.simantics.scenegraph.g2d.IG2DNode;
91 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
92 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
93 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
94 import org.simantics.scenegraph.g2d.events.MouseEvent;
95 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
96 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
97 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
98 import org.simantics.scenegraph.g2d.events.command.Command;
99 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
100 import org.simantics.scenegraph.g2d.events.command.Commands;
101 import org.simantics.scenegraph.g2d.nodes.LinkNode;
102 import org.simantics.scenegraph.g2d.nodes.LocalDelegateNode;
103 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
104 import org.simantics.scenegraph.utils.NodeMapper;
105 import org.simantics.utils.datastructures.collections.CollectionUtils;
106 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
107 import org.simantics.utils.datastructures.hints.IHintContext.Key;
108 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
109 import org.simantics.utils.datastructures.hints.IHintListener;
110 import org.simantics.utils.datastructures.hints.IHintObservable;
111 import org.simantics.utils.logging.TimeLogger;
112 import org.simantics.utils.threads.SWTThread;
113 import org.simantics.utils.threads.ThreadUtils;
114 import org.simantics.utils.ui.ErrorLogger;
115 import org.simantics.utils.ui.SWTUtils;
116 import org.slf4j.Logger;
117 import org.slf4j.LoggerFactory;
120 * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and
121 * Commands.PASTE commands for an IDiagram.
124 * The handler attempts to copy/paste the current selection for pointer 0,
125 * meaning {@link Selection#SELECTION0}.
129 * The handler logic follows the specifications at <a
130 * href="http://www.simantics.org/wiki/index.php/UC:Copy_Item" >UC:Copy Item</a>
131 * and <a href="http://www.simantics.org/wiki/index.php/UC:Cut_Item" >UC:Cut
135 * @see Selection current diagram selection source
137 * @author Tuukka Lehtonen
139 * FIXME: translucent ghosting makes rendering REALLY sluggish, add a
140 * timer that makes the ghost opaque when the user is interacting and
141 * translucent only when still for a while.
143 public class CopyPasteHandler extends AbstractDiagramParticipant {
145 private static final Logger LOGGER = LoggerFactory.getLogger(CopyPasteHandler.class);
147 public static final Key KEY_CUT_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "CUT_SELECTION_FRAME_COLOR");
148 public static final Key KEY_CUT_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "CUT_SELECTION_CONTENT_COLOR");
149 public static final Key KEY_COPIED_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "COPY_SELECTION_FRAME_COLOR");
150 public static final Key KEY_COPIED_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "COPY_SELECTION_CONTENT_COLOR");
153 * A key for storing the current selection within the currently active
154 * project for copy/paste implementation.
156 private static final Key KEY_DIAGRAM_SELECTION = DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION;
158 private static final boolean DEBUG = false;
159 private static final boolean DEBUG_SELECTION_UPDATE = false;
161 public static final int COPY_GHOSTING_PAINT_PRIORITY = 600;
163 protected static final int HIGHLIGHT_PAINT_PRIORITY = 500;
166 protected Selection sel;
168 protected MouseUtil mouseUtil;
170 protected final IStatusLineManager statusLine;
171 protected final CopyPasteStrategy strategy;
172 protected IWorkbenchPartSite site;
173 protected IWorkbenchPartSite listenedSite;
176 * Workbench part listener for {@link #listenedSite} to keep proper track of
177 * whether this part is focused or not.
179 IPartListener partListener = new IPartListener() {
181 public void partOpened(IWorkbenchPart part) {
184 public void partDeactivated(IWorkbenchPart part) {
185 if (part == site.getPart())
189 public void partClosed(IWorkbenchPart part) {
190 // Make sure this listener is removed properly in any case.
191 if (listenedSite != null) {
192 listenedSite.getPage().removePartListener(partListener);
197 public void partBroughtToTop(IWorkbenchPart part) {
200 public void partActivated(IWorkbenchPart part) {
201 if (part == site.getPart())
207 * Indicates whether CopyPasteHandler thinks that {@link #site} has focus.
209 protected boolean hasFocus = false;
211 protected AbstractCanvasParticipant highlightMode = null;
212 private IProject observedProject = null;
215 * A counter for how many times pasting has been performed without mouse and
216 * ghosting or how many times paste has been performed without moving the
217 * mouse on the diagram. This is used to offset the paste position
218 * accordingly so that copied elements don't wind up directly on top of each
221 private int pasteWithoutMovingGhostCounter = 0;
224 * An offset used when pasting without mouse/ghosting. It forces keyboard
225 * pastes to stack up on top of the latest paste performed with
228 private final Point2D pasteOffset = new Point2D.Double(0, 0);
231 * Stores the last MouseInfo for mouse 0 from the time of the previous
232 * received mouse event. Used for deciding the paste position.
234 * @see #getPastePos(DiagramSelection)
236 private MouseInfo mouseInfo;
239 * Scale to use for pasted diagram monitors from variables.
241 private double monitorScale = 0.2;
244 * For updating the diagram selection after graph changes.
246 private DiagramSelectionUpdater selectionUpdater = null;
248 public CopyPasteHandler() {
249 this(new DefaultCopyPasteStrategy());
252 public CopyPasteHandler(CopyPasteStrategy strategy) {
253 this(strategy, null);
256 public CopyPasteHandler(IStatusLineManager statusLine) {
257 this(new DefaultCopyPasteStrategy(), statusLine);
260 public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine) {
261 this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
262 this.statusLine = statusLine;
265 public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine, double monitorScale) {
266 this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
267 this.statusLine = statusLine;
268 setMonitorScale(monitorScale);
271 public CopyPasteHandler setMonitorScale(double scale) {
272 this.monitorScale = scale;
276 public CopyPasteHandler setWorkbenchSite(IWorkbenchPartSite site) {
281 protected boolean isPasteAllowed() {
282 return listenedSite == null || hasFocus;
286 public void addedToContext(ICanvasContext ctx) {
287 super.addedToContext(ctx);
288 addProjectListener(peekProject());
291 if (listenedSite != null) {
292 listenedSite.getPage().addPartListener(partListener);
297 public void removedFromContext(ICanvasContext ctx) {
298 // Remove project selection if its ours to prevent leaking memory.
299 DiagramSelection ds = getProjectSelection();
300 if (ds.getSourceCanvas() == ctx) {
301 removeProjectSelection();
304 if (listenedSite != null) {
305 listenedSite.getPage().removePartListener(partListener);
309 removeProjectListener();
310 super.removedFromContext(ctx);
314 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
315 if (oldDiagram != null) {
316 if (selectionUpdater != null) {
317 selectionUpdater.untrack();
318 selectionUpdater = null;
321 if (newDiagram != null) {
322 selectionUpdater = new DiagramSelectionUpdater(getContext(), newDiagram).track();
326 IHintListener projectDiagramSelectionListener = new HintListenerAdapter() {
328 public void hintChanged(IHintObservable sender, Key key, Object oldValue, final Object newValue) {
329 //System.out.println(this + ": " + sender + ": " + newValue);
330 ICanvasContext ctx = getContext();
331 if (ctx != null && hasHighlight()) {
332 //System.out.println(this + " HAS HIGHLIGHT");
333 if (newValue == null || ((DiagramSelection) newValue).getSourceCanvas() != ctx) {
334 //System.out.println(this + " REMOVING HIGHLIGHT");
335 ctx.getThreadAccess().asyncExec(new Runnable() {
346 private void addProjectListener(IProject observable) {
347 if (observable != null) {
348 observable.addKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
349 observedProject = observable;
353 private void removeProjectListener() {
354 if (observedProject != null) {
355 observedProject.removeKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
356 observedProject = null;
360 IProject getProject() {
361 return Simantics.getProject();
364 IProject peekProject() {
365 return Simantics.peekProject();
368 public DiagramSelection getClipboardDiagramSelection() {
369 for (Set<Representation> content : Simantics.getClipboard().getContents()) {
371 DiagramSelection sel = ClipboardUtils.accept(content, DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION);
374 } catch (DatabaseException e) {
375 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to retrieve clipboard content.", e));
378 return DiagramSelection.EMPTY;
381 protected DiagramSelection getProjectSelection() {
382 IProject p = peekProject();
384 return DiagramSelection.EMPTY;
385 DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);
386 return ds != null ? ds : DiagramSelection.EMPTY;
389 protected void setDiagramSelection(DiagramSelection selection) {
390 setProjectSelection(selection);
391 strategy.copyToClipboard(selection);
394 protected void setProjectSelection(DiagramSelection selection) {
395 assert selection != null;
396 IProject p = getProject();
398 throw new IllegalStateException("no active project for selection");
400 pasteWithoutMovingGhostCounter = 0;
401 pasteOffset.setLocation(0, 0);
402 p.setHint(KEY_DIAGRAM_SELECTION, selection);
405 protected void removeProjectSelection() {
406 setProjectSelection(DiagramSelection.EMPTY);
413 * This DELETE command handler is required to remove any diagram selections
414 * when modules are deleted. This will prevent extra highlight painting from
415 * being left over in case a module marked for copying is deleted.
417 @EventHandler(priority = 100)
418 public boolean handleDelete(CommandEvent e) {
419 if (e.command.equals( Commands.DELETE) ) {
420 if (highlightMode != null) {
422 removeProjectSelection();
429 @EventHandler(priority = 0)
430 public boolean handleKey(org.simantics.scenegraph.g2d.events.KeyEvent e) {
431 if (e.keyCode == KeyEvent.VK_CONTROL) {
432 DiagramSelection ds = getProjectSelection();
434 if (e instanceof KeyPressedEvent) {
436 message("Move selection");
438 message("Paste selection");
440 } else if (e instanceof KeyReleasedEvent) {
450 @EventHandler(priority = 0)
451 public boolean handleCommand(CommandEvent e) {
452 if (e.command.equals( Commands.CANCEL) ) {
453 DiagramSelection s = getProjectSelection();
454 if (highlightMode != null || !s.isEmpty()) {
456 removeProjectSelection();
461 if (e.command.equals( Commands.CUT ) || e.command.equals( Commands.COPY )) {
462 boolean ret = initiateCopy( e.command.equals( Commands.CUT ) );
464 removeProjectSelection();
467 // Must have focus in order to paste! If mouse has left the editor, no
468 // pastes should be performed.
469 if (isPasteAllowed() && e.command.equals( Commands.PASTE )) {
470 DiagramSelection ds = getClipboardDiagramSelection();
472 return tryPasteMonitors();
474 return paste(e.command, ds);
479 protected boolean initiateCopy(boolean cut) {
480 //System.out.println("INITIATING COPY");
483 Set<IElement> ss = sel.getSelection(selectionId);
484 Point2D copyPos = getCopyStartPos(ss);
485 if (ss.isEmpty() || copyPos == null) {
486 message("Nothing to " + (cut ? "cut" : "copy"));
490 // Validate selection, don't initiate copy if selection is invalid.
491 ElementAssortment ea = new ElementAssortment(ss);
492 String error = fixAssortment(ea, cut);
499 pruneAssortment(ea, cut);
501 message("Nothing to " + (cut ? "cut" : "copy"));
505 // Allow the possible CopyStrategy to filter the copied element
507 if (strategy instanceof CopyStrategy) {
509 System.out.println("Commencing CopyStrategy filtering with " + ea);
510 IStatus status = ((CopyStrategy) strategy).copy(new CopyOperation(getContext(), ea, cut));
511 if (!status.isOK()) {
512 switch (status.getSeverity()) {
514 case IStatus.WARNING:
515 message(status.getMessage());
518 error(status.getMessage());
526 System.out.println("Start copy with " + ea);
528 // Treat OTHER type elements as disconnected floating graphical elements
529 // that are always copied.
531 // Anything with connection parts cannot be copied
532 if (!cut && ea.containsAny(CopyPasteUtil.CONNECTION_PARTS)) {
533 error("Cannot copy connection segments nor branch points.");
536 // if (ea.contains(CopyPasteUtil.MONITORS)) {
537 // // TODO: allow copying of monitors, means fixing the component reference relations
538 // error("Monitor " + (cut ? "cut" : "copy") + " not supported yet.");
542 // Pre-validate flag selection cases
543 if (ea.contains(CopyPasteUtil.FLAGS)) {
545 // Allow cutting of single flags or cutting of any amount of
546 // flags within a single diagram.
548 // // Deny flag copy if other kinds of elements are selected.
549 // if (ea.containsAny(NOT_FLAGS)) {
552 // Only copy flags without correspondence for now.
553 if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
554 if (!CopyPasteUtil.checkFlagsCorrespondences(ea.flags, false)) {
555 error("Cannot copy flag that already has a correspondence.");
562 // Selection is valid, go ahead and initiate a copy operation.
563 Resource sourceDiagram = diagram.<Resource>getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
564 DiagramSelection ds = new DiagramSelection(getContext(), sourceDiagram, ea.getAll(), cut, copyPos);
565 setDiagramSelection(ds);
568 highlightMode = new HighlightMode(ds, selectionId, HIGHLIGHT_PAINT_PRIORITY);
569 getContext().add(highlightMode);
573 // System.out.println("INITIATED COPY: " + ds);
577 public boolean paste(Command command, DiagramSelection ds) {
583 TimeLogger.resetTimeAndLog(getClass(), "paste");
585 ElementObjectAssortment ea = ds.getAssortment();
588 System.out.println("Initiate paste with " + ea);
591 if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
592 // Do not copy if any of the flags already have a correspondence.
593 if (!CopyPasteUtil.onlyFlagsWithoutCorrespondence(Simantics.getSession(), ea))
597 normalPaste(command, ds, ea, true);
599 setDiagramSelection(DiagramSelection.EMPTY);
600 resetSourceSelection(ds);
602 normalPaste(command, ds, ea, false);
603 // There is no point in leaving the old copy selection hanging
604 // around after copying a flag since it is a one shot operation.
606 setDiagramSelection(DiagramSelection.EMPTY);
610 normalPaste(command, ds, ea, true);
612 setDiagramSelection(DiagramSelection.EMPTY);
613 resetSourceSelection(ds);
615 normalPaste(command, ds, ea, false);
617 // // This is necessary to keep the ghost diagram properly up-to-date
618 // // after paste operations.
619 // setProjectSelection(ds.remutate());
625 } catch (PasteException e) {
626 error( e.getLocalizedMessage() );
627 ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
628 } catch (DatabaseException e) {
629 error( e.getLocalizedMessage() );
630 ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
641 * In cut/paste cases, the source selection should reset (removed) after the
642 * paste operation has been performed. This method will reset the source
643 * selection of the specified DiagramSelection from the source canvas
644 * context. This will work regardless of which diagram/editor the selection
647 * @param ds the source selection to reset
649 void resetSourceSelection(DiagramSelection ds) {
650 ICanvasContext cc = ds.getSourceCanvas();
651 boolean sameDiagram = diagram == ds.getSourceDiagram();
652 if (!sameDiagram && cc != null && !cc.isDisposed()) {
653 for (Selection sourceSelection : cc.getItemsByClass(Selection.class)) {
654 Collection<IElement> empty = Collections.emptySet();
655 sourceSelection.setSelection(0, empty);
660 private void normalPaste(Command command, DiagramSelection ds, ElementObjectAssortment ea, boolean cut) throws PasteException {
661 final Point2D copyPos = ds.getCopyPos();
662 final Point2D pastePos = getPastePos(ds);
664 double dx = pastePos.getX() - copyPos.getX();
665 double dy = pastePos.getY() - copyPos.getY();
666 final Point2D pasteOffset = new Point2D.Double(dx, dy);
669 // Get diagram contents before the paste operation
670 Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
671 final DiagramContentTracker tracker =
672 diagramResource == null ?
674 : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);
676 strategy.paste(new PasteOperation(command, getContext(), ds.getSourceDiagram(), diagramResource, diagram, ea, cut, pasteOffset));
678 if (tracker != null) {
679 // Get difference of diagram contents to find out what was added.
680 DiagramContentChanges changes = tracker.update();
681 selectionUpdater.setNewSelection(0, changes.pick(changes.elements, Change.ADDED));
682 if (DEBUG_SELECTION_UPDATE)
683 System.out.println("stored diagram changes @" + System.currentTimeMillis() + ": " + selectionUpdater.getNewSelection());
686 } catch (DatabaseException e) {
687 ErrorLogger.defaultLogError(e);
691 protected String fixAssortment(ElementAssortment ea, boolean cut) {
692 Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
693 List<Connection> conns = new ArrayList<Connection>();
695 // Include flags whether they are selected or not
696 for (IElement edge : ea.edges) {
697 Connection bc = diagramTopology.getConnection(edge, EdgeEnd.Begin);
698 if (bc != null && bc.node != null) {
699 if (bc.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
700 ea.add(ElementType.Flag, bc.node);
702 Connection ec = diagramTopology.getConnection(edge, EdgeEnd.End);
703 if (ec != null && ec.node != null) {
704 if (ec.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
705 ea.add(ElementType.Flag, ec.node);
709 // Include connections for selected flags if we're not potentially
710 // making flag continuations.
711 if (!CopyPasteUtil.isFlagsOnlySelection(ea)) {
712 for (IElement flag : ea.flags) {
714 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
715 for (Connection conn : conns) {
716 IElement edge = conn.edge;
717 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
718 ea.add(ElementType.Connection, ce.getConnection());
723 // For each selected connection, make sure that all connected elements
724 // are in the selection, otherwise don't copy the connection.
725 List<IElement> connectionsToRemove = new ArrayList<IElement>(ea.connections.size());
726 for (IElement connection : ea.connections) {
727 ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);
728 Collection<Connection> connectors = ch.getTerminalConnections(connection, null);
729 boolean allConnectorsSelected = true;
730 for (Connection c : connectors) {
731 if (!(ea.nodes.contains(c.node) || ea.flags.contains(c.node) || ea.references.contains(c.node))) {
732 allConnectorsSelected = false;
736 if (!allConnectorsSelected)
737 connectionsToRemove.add(connection);
739 ea.removeAll(ElementType.Connection, connectionsToRemove);
741 // Remove external flags whose connection(s) are not included
742 List<IElement> flagsToRemove = new ArrayList<IElement>(ea.flags.size());
743 for (IElement flag : ea.flags) {
744 if (CopyPasteUtil.flagIsExternal(flag)) {
746 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
747 for (Connection conn : conns) {
748 IElement edge = conn.edge;
749 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
750 IElement connection = ce.getConnection();
751 if (!ea.connections.contains(connection)) {
752 flagsToRemove.add(flag);
757 ea.removeAll(ElementType.Flag, flagsToRemove);
760 // Issue #1874: Prevent cut/paste for connected components
761 // https://www.simulationsite.net/redmine/issues/1874
762 // Fail if any of the included nodes has connections to it that are not
763 // included in the operation.
764 Collection<Connection> connections = new ArrayList<Connection>();
765 for (IElement node : CollectionUtils.join(ea.nodes, ea.flags)) {
767 for (Connection connection : getAllConnections(node, connections)) {
768 ConnectionEntity ce = connection.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
769 IElement conn = ce.getConnection();
770 if (ea.connections.contains(conn))
773 return "Cannot cut a node without all its connections.";
778 // Remove all reference elements from the assortment whose parent elements are not in it.
780 Collection<IElement> referenceElementsToRemove = new ArrayList<IElement>();
781 for (IElement ref : ea.references) {
782 IElement parent = ref.getHint(ElementHints.KEY_PARENT_ELEMENT);
783 if (parent != null) {
784 if (!ea.all.contains(parent)) {
785 // Cannot copy reference element whose parent is not copied also.
786 referenceElementsToRemove.add(ref);
789 // OK, reference element has no parent. Free to copy/cut in any way.
792 if (!referenceElementsToRemove.isEmpty()) {
793 ea.removeAll(ElementType.Reference, referenceElementsToRemove);
795 return "Cannot copy reference elements whose parent is not copied.";
803 private Collection<Terminal> getTerminals(IElement node) {
804 ArrayList<Terminal> result = new ArrayList<Terminal>();
805 for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))
806 tt.getTerminals(node, result);
810 private Collection<Connection> getAllConnections(IElement node, Collection<Connection> result) {
811 IDiagram diagram = node.getDiagram();
812 Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
813 if (topology == null)
815 for (Terminal t : getTerminals(node))
816 topology.getConnections(node, t, result);
821 * Classifies the specified diagram selection elements into categories
822 * appointed by the <code>ElementType</code> enumeration and prunes any
823 * edges from the selection returned assortment whose both ends are not
824 * connected to nodes within the selection.
829 protected void pruneAssortment(ElementAssortment ea, boolean cut) {
830 // Edges and branch points are never copied as such.
831 // They are always included as parts of copied connections.
832 // Edges can never be transformed or modified in any way as such,
833 // therefore it is safe to do this.
834 ea.clear(ElementType.Edge);
837 ea.clear(ElementType.BranchPoint);
840 private Point2D getPastePos(DiagramSelection ds) {
841 MouseInfo mi = mouseUtil.getMouseInfo(0);
846 double xoff = mi.canvasPosition.getX() - ds.getCopyPos().getX();
847 double yoff = mi.canvasPosition.getY() - ds.getCopyPos().getY();
848 if (xoff == pasteOffset.getX() && yoff == pasteOffset.getY()) {
849 // The mouse has not moved since last paste so let's offset the
850 // paste down and right.
851 double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
852 return new Point2D.Double(
853 mi.canvasPosition.getX() + counterOffset,
854 mi.canvasPosition.getY() + counterOffset);
856 pasteWithoutMovingGhostCounter = 0;
857 pasteOffset.setLocation(xoff, yoff);
858 return mi.canvasPosition;
860 //return ds.getCopyPos();
861 Point2D p = ds.getCopyPos();
862 double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
863 return new Point2D.Double(
864 p.getX() + pasteOffset.getX() + counterOffset,
865 p.getY() + pasteOffset.getY() + counterOffset);
869 private double getOffsetGridSize() {
870 Double grid = getHint(GridPainter.KEY_GRID_SIZE);
871 return (grid == null || grid == 0) ? 1.0 : grid;
878 protected static boolean isConnectionOrEdge(IElement e) {
879 ElementClass ec = e.getElementClass();
880 return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);
887 protected static boolean isMoveable(IElement e) {
888 ElementClass ec = e.getElementClass();
889 return ec.containsClass(Move.class) && ec.containsClass(Transform.class);
894 * @return <code>null</code> if a point of reference cannot be determined
895 * for the specified selection.
897 protected Point2D getCopyStartPos(Set<IElement> ss) {
898 // MouseInfo mi = mouseUtil.getMouseInfo(0);
900 // return (Point2D) mi.canvasPosition.clone();
903 // Find bounding rectangle top left corner
904 double mx = Double.MAX_VALUE;
905 double my = Double.MAX_VALUE;
906 for (IElement e : ss) {
907 if (isConnectionOrEdge(e) || !isMoveable(e))
910 //Point2D pos = ElementUtils.getPos(e);
911 Point2D pos = ElementUtils.getAbsolutePos(e);
918 // Find element nearest to the top left corner
919 Point2D nearest = null;
920 double dist = Double.MAX_VALUE;
921 for (IElement e : ss) {
922 if (isConnectionOrEdge(e) || !isMoveable(e))
925 Point2D pos = ElementUtils.getAbsolutePos(e);
926 double dx = pos.getX() - mx;
927 double dy = pos.getY() - my;
928 double d = dx*dx + dy*dy;
938 private void moveGhostElements(DiagramSelection ds, Point2D pastePos) {
939 Point2D copyPos = ds.getCopyPos();
940 double dx = (pastePos.getX() - copyPos.getX());
941 double dy = (pastePos.getY() - copyPos.getY());
944 Point2D snap = CopyPasteUtil.snap(getContext(), new Point2D.Double(dx, dy));
946 ghostNode.setTransform(AffineTransform.getTranslateInstance(snap.getX(), snap.getY()));
947 //System.out.println("ghost node: " + ghostNode);
950 protected SingleElementNode ghostNode = null;
951 protected NodeMapper ghostNodeMapper = new NodeMapper();
954 public void initSG(G2DParentNode parent) {
955 ghostNode = parent.addNode("cut/copy ghost", SingleElementNode.class);
956 ghostNode.setZIndex(COPY_GHOSTING_PAINT_PRIORITY);
957 //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.40f));
958 ghostNode.setVisible(Boolean.FALSE);
962 public void cleanupSG() {
967 ghostNode.removeNodes();
968 ghostNode.setVisible(Boolean.FALSE);
969 ghostNodeMapper.clear();
974 * @return <code>true</code> if the ghost nodes were hidden and a refresh is
977 boolean hideSG(DiagramSelection selection) {
978 if (ghostNode.isVisible()) {
979 // Make sure there's no leftover graphics.
980 ghostNode.removeNodes();
981 ghostNode.setVisible(Boolean.FALSE);
987 protected void scheduleActivateOwnerPart() {
990 SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {
994 site.getPage().activate(site.getPart());
999 @EventHandler(priority = 0)
1000 public boolean handleMouse(MouseExitEvent e) {
1001 DiagramSelection ds = getProjectSelection();
1002 if (!ds.isEmpty()) {
1007 // The part might no longer have focus.
1008 // [Tuukka] commented out to fix Apros #3678
1014 @EventHandler(priority = 0)
1015 public boolean handleMouse(MouseEnterEvent e) {
1016 DiagramSelection ds = getProjectSelection();
1017 if (!ds.isEmpty()) {
1019 if (inPasteMode(e)) {
1020 scheduleActivateOwnerPart();
1027 @EventHandler(priority = 0)
1028 public boolean handleMouse(MouseMovedEvent e) {
1029 DiagramSelection ds = getProjectSelection();
1030 if (!ds.isEmpty()) {
1031 MouseInfo mi = mouseUtil.getMouseInfo(0);
1032 //System.out.println("LAST MOUSE INFO: " + mi);
1036 if (inPasteMode(e)) {
1037 // Make sure that this owner part is active now.
1039 scheduleActivateOwnerPart();
1051 void updateSG(DiagramSelection selection) {
1052 MouseInfo mi = mouseUtil.getMouseInfo(0);
1056 //ghostNode.setComposite(AlphaComposite.SrcAtop.derive(0.40f));
1057 //ghostNode.setComposite(null);
1059 moveGhostElements(selection, mi.canvasPosition);
1060 if (selection.getSourceCanvas() != getContext()) {
1061 for (IElement e : selection.getOriginalElements()) {
1062 INode node = e.getHint(ElementHints.KEY_SG_NODE);
1063 //System.out.println("ghost element: " + e + ", node=" + node);
1064 if (node instanceof IG2DNode) {
1065 LocalDelegateNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e),
1066 LocalDelegateNode.class);
1067 delegate.setDelegate( (IG2DNode) node );
1071 for (IElement e : selection.getOriginalElements()) {
1072 //System.out.println("ghost element: " + e);
1073 INode node = e.getHint(ElementHints.KEY_SG_NODE);
1075 //System.out.println("ghost node: " + node);
1076 ghostNodeMapper.add(node);
1077 String nodeId = ghostNodeMapper.getId(node);
1078 //System.out.println("ghost node id: " + nodeId);
1079 LinkNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e), LinkNode.class);
1080 delegate.setDelegateId( nodeId );
1085 ghostNode.setVisible(true);
1088 private <T extends INode> T getOrCreateNode(ParentNode<?> parentNode, String id, Class<T> clazz) {
1089 INode n = ghostNode.getNode(id);
1090 if (clazz.isInstance(n))
1091 return clazz.cast(n);
1092 ghostNode.removeNode(id);
1093 return ghostNode.addNode(id, clazz);
1096 protected boolean hasHighlight() {
1097 return highlightMode != null;
1100 protected void removeHighlight() {
1103 assert getContext().getThreadAccess().currentThreadAccess();
1104 if (highlightMode != null) {
1105 if (!highlightMode.isRemoved()) {
1106 highlightMode.remove();
1109 highlightMode = null;
1113 private boolean inPasteMode(MouseEvent e) {
1114 return (e.stateMask & MouseEvent.CTRL_MASK) != 0;
1117 protected void selectedMessage(DiagramSelection ds) {
1118 int size = ds.getOriginalElements().size();
1119 StringBuilder sb = new StringBuilder();
1121 sb.append("No elements to ");
1130 sb.append("Copied ");
1132 sb.append(" element");
1136 message(sb.toString());
1139 protected void message(final String message) {
1140 if (statusLine == null)
1142 swtExec(new Runnable() {
1145 statusLine.setMessage(message);
1146 statusLine.setErrorMessage(null);
1151 protected void error(final String message) {
1152 if (statusLine == null)
1154 swtExec(new Runnable() {
1157 statusLine.setErrorMessage(message);
1162 protected void swtExec(Runnable r) {
1163 ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
1166 // MONITOR PASTE SUPPORT
1168 private boolean tryPasteMonitors() {
1169 SimanticsClipboard clipboard = Simantics.getClipboard();
1170 for (Set<Representation> content : clipboard.getContents()) {
1172 final Variable var_ = ClipboardUtils.accept(content, SimanticsKeys.KEY_VARIABLE);
1174 final Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
1175 final Resource runtime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
1176 final Resource elementResource = Simantics.getSession().syncRequest(new Read<Resource>() {
1178 public Resource perform(ReadGraph graph) throws DatabaseException {
1179 DiagramResource DIA = DiagramResource.getInstance(graph);
1181 String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
1182 if (diagramVariable == null)
1185 Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
1189 Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
1193 Variable component = Variables.getChild(graph, diaVar, var);
1194 if (component == null)
1197 Resource componentResource = component.getPossibleRepresents(graph);
1198 if (componentResource == null)
1201 return graph.getPossibleObject(componentResource, ModelingResources.getInstance(graph).ComponentToElement);
1205 if (elementResource == null)
1208 final AffineTransform monitorTransform = Simantics.getSession().syncRequest(new Read<AffineTransform>() {
1210 public AffineTransform perform(ReadGraph graph) throws DatabaseException {
1211 AffineTransform at = null;
1213 if (graph.isInstanceOf(elementResource, DiagramResource.getInstance(graph).Connection)) {
1214 Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, elementResource);
1215 if (tailNode != null) {
1216 at = DiagramGraphUtil.getAffineTransform(graph, tailNode);
1220 at = DiagramGraphUtil.getAffineTransform(graph, elementResource);
1226 MouseInfo mi = mouseUtil.getMouseInfo(0);
1229 final double dx = mi.canvasPosition.getX() - monitorTransform.getTranslateX();
1230 final double dy = mi.canvasPosition.getY() - monitorTransform.getTranslateY();
1232 Simantics.getSession().asyncRequest(new WriteRequest() {
1235 public void perform(WriteGraph graph) throws DatabaseException {
1236 Layer0 L0 = Layer0.getInstance(graph);
1237 Layer0X L0X = Layer0X.getInstance(graph);
1238 DiagramResource DIA = DiagramResource.getInstance(graph);
1239 G2DResource G2D = G2DResource.getInstance(graph);
1241 String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
1242 if (diagramVariable == null)
1245 Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
1249 Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
1253 Variable component = Variables.getChild(graph, diaVar, var);
1254 if (component == null)
1257 Resource componentResource = component.getPossibleRepresents(graph);
1258 if (componentResource == null)
1261 String suffix = Variables.getRVI(graph, component, var);
1263 Resource resource = graph.newResource();
1264 graph.claim(resource, L0.InstanceOf, null, DIA.Monitor);
1266 final double scale = monitorScale;
1268 DiagramGraphUtil.setTransform(graph, resource, new AffineTransform(scale, 0, 0, scale, dx, dy));
1270 OrderedSetUtils.add(graph, diagramResource, resource);
1272 // 5.1. Give running name to element and increment the counter attached to the diagram.
1273 Long l = graph.getPossibleRelatedValue(diagramResource, DIA.HasModCount, Bindings.LONG);
1275 l = Long.valueOf(0L);
1276 graph.claimLiteral(resource, L0.HasName, l.toString(), Bindings.STRING);
1277 graph.claimLiteral(diagramResource, DIA.HasModCount, ++l, Bindings.LONG);
1279 // 5.2. Make the diagram consist of the new element
1280 graph.claim(diagramResource, L0.ConsistsOf, resource);
1282 graph.claim(resource, G2D.HasHorizontalAlignment, null, G2D.Alignment_Leading);
1283 graph.claimLiteral(resource, DIA.HasDirection, 0.0);
1285 graph.claim(resource, DIA.HasMonitorComponent, componentResource);
1286 graph.claimLiteral(resource, DIA.HasMonitorSuffix, suffix);
1288 Resource model = Variables.getModel(graph, diaVar);
1289 if (model != null) {
1290 Resource template = graph.getPossibleObject(model, DIA.HasDefaultMonitorTemplate);
1291 if (template != null) {
1292 graph.claim(resource, L0X.ObtainsProperty1, null, template);
1299 } catch (DatabaseException e1) {
1300 LOGGER.error("Monitor paste failed", e1);