]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteHandler.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / CopyPasteHandler.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.diagram.handler;\r
13 \r
14 import java.awt.Color;\r
15 import java.awt.event.KeyEvent;\r
16 import java.awt.geom.AffineTransform;\r
17 import java.awt.geom.Point2D;\r
18 import java.util.ArrayList;\r
19 import java.util.Collection;\r
20 import java.util.Collections;\r
21 import java.util.List;\r
22 import java.util.Set;\r
23 \r
24 import org.eclipse.core.runtime.IStatus;\r
25 import org.eclipse.core.runtime.Status;\r
26 import org.eclipse.jface.action.IStatusLineManager;\r
27 import org.eclipse.swt.widgets.Display;\r
28 import org.eclipse.ui.IPartListener;\r
29 import org.eclipse.ui.IWorkbenchPart;\r
30 import org.eclipse.ui.IWorkbenchPartSite;\r
31 import org.eclipse.ui.PlatformUI;\r
32 import org.simantics.Simantics;\r
33 import org.simantics.databoard.Bindings;\r
34 import org.simantics.db.ReadGraph;\r
35 import org.simantics.db.Resource;\r
36 import org.simantics.db.WriteGraph;\r
37 import org.simantics.db.common.request.WriteRequest;\r
38 import org.simantics.db.common.utils.OrderedSetUtils;\r
39 import org.simantics.db.exception.DatabaseException;\r
40 import org.simantics.db.layer0.util.ClipboardUtils;\r
41 import org.simantics.db.layer0.util.SimanticsClipboard;\r
42 import org.simantics.db.layer0.util.SimanticsClipboard.Representation;\r
43 import org.simantics.db.layer0.util.SimanticsKeys;\r
44 import org.simantics.db.layer0.variable.Variable;\r
45 import org.simantics.db.layer0.variable.Variables;\r
46 import org.simantics.db.request.Read;\r
47 import org.simantics.diagram.Logger;\r
48 import org.simantics.diagram.content.Change;\r
49 import org.simantics.diagram.content.ConnectionUtil;\r
50 import org.simantics.diagram.content.DiagramContentChanges;\r
51 import org.simantics.diagram.content.DiagramContentTracker;\r
52 import org.simantics.diagram.internal.Activator;\r
53 import org.simantics.diagram.stubs.DiagramResource;\r
54 import org.simantics.diagram.stubs.G2DResource;\r
55 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
56 import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;\r
57 import org.simantics.diagram.ui.DiagramModelHints;\r
58 import org.simantics.g2d.canvas.ICanvasContext;\r
59 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
60 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
61 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
62 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
63 import org.simantics.g2d.connection.ConnectionEntity;\r
64 import org.simantics.g2d.connection.handler.ConnectionHandler;\r
65 import org.simantics.g2d.diagram.IDiagram;\r
66 import org.simantics.g2d.diagram.handler.Topology;\r
67 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
68 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
69 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
70 import org.simantics.g2d.diagram.participant.Selection;\r
71 import org.simantics.g2d.element.ElementClass;\r
72 import org.simantics.g2d.element.ElementHints;\r
73 import org.simantics.g2d.element.ElementUtils;\r
74 import org.simantics.g2d.element.IElement;\r
75 import org.simantics.g2d.element.handler.BendsHandler;\r
76 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
77 import org.simantics.g2d.element.handler.Move;\r
78 import org.simantics.g2d.element.handler.TerminalTopology;\r
79 import org.simantics.g2d.element.handler.Transform;\r
80 import org.simantics.g2d.elementclass.FlagHandler;\r
81 import org.simantics.g2d.participant.GridPainter;\r
82 import org.simantics.g2d.participant.MouseUtil;\r
83 import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
84 import org.simantics.layer0.Layer0;\r
85 import org.simantics.modeling.ModelingResources;\r
86 import org.simantics.operation.Layer0X;\r
87 import org.simantics.project.IProject;\r
88 import org.simantics.scenegraph.INode;\r
89 import org.simantics.scenegraph.ParentNode;\r
90 import org.simantics.scenegraph.g2d.G2DParentNode;\r
91 import org.simantics.scenegraph.g2d.IG2DNode;\r
92 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
93 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
94 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
95 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
96 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;\r
97 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;\r
98 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
99 import org.simantics.scenegraph.g2d.events.command.Command;\r
100 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
101 import org.simantics.scenegraph.g2d.events.command.Commands;\r
102 import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
103 import org.simantics.scenegraph.g2d.nodes.LocalDelegateNode;\r
104 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
105 import org.simantics.scenegraph.utils.NodeMapper;\r
106 import org.simantics.utils.datastructures.collections.CollectionUtils;\r
107 import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
108 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
109 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
110 import org.simantics.utils.datastructures.hints.IHintListener;\r
111 import org.simantics.utils.datastructures.hints.IHintObservable;\r
112 import org.simantics.utils.logging.TimeLogger;\r
113 import org.simantics.utils.threads.SWTThread;\r
114 import org.simantics.utils.threads.ThreadUtils;\r
115 import org.simantics.utils.ui.ErrorLogger;\r
116 import org.simantics.utils.ui.SWTUtils;\r
117 \r
118 /**\r
119  * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and\r
120  * Commands.PASTE commands for an IDiagram.\r
121  * \r
122  * <p>\r
123  * The handler attempts to copy/paste the current selection for pointer 0,\r
124  * meaning {@link Selection#SELECTION0}.\r
125  * </p>\r
126  * \r
127  * <p>\r
128  * The handler logic follows the specifications at <a\r
129  * href="http://www.simantics.org/wiki/index.php/UC:Copy_Item" >UC:Copy Item</a>\r
130  * and <a href="http://www.simantics.org/wiki/index.php/UC:Cut_Item" >UC:Cut\r
131  * Item</a>.\r
132  * </p>\r
133  * \r
134  * @see Selection current diagram selection source\r
135  * \r
136  * @author Tuukka Lehtonen\r
137  * \r
138  *         FIXME: translucent ghosting makes rendering REALLY sluggish, add a\r
139  *         timer that makes the ghost opaque when the user is interacting and\r
140  *         translucent only when still for a while.\r
141  */\r
142 public class CopyPasteHandler extends AbstractDiagramParticipant {\r
143 \r
144     public static final Key KEY_CUT_SELECTION_FRAME_COLOR      = new KeyOf(Color.class, "CUT_SELECTION_FRAME_COLOR");\r
145     public static final Key KEY_CUT_SELECTION_CONTENT_COLOR    = new KeyOf(Color.class, "CUT_SELECTION_CONTENT_COLOR");\r
146     public static final Key KEY_COPIED_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "COPY_SELECTION_FRAME_COLOR");\r
147     public static final Key KEY_COPIED_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "COPY_SELECTION_CONTENT_COLOR");\r
148 \r
149     /**\r
150      * A key for storing the current selection within the currently active\r
151      * project for copy/paste implementation.\r
152      */\r
153     private static final Key               KEY_DIAGRAM_SELECTION              = DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION;\r
154 \r
155     private static final boolean          DEBUG                              = false;\r
156     private static final boolean          DEBUG_SELECTION_UPDATE             = false;\r
157 \r
158     public static final int               COPY_GHOSTING_PAINT_PRIORITY       = 600;\r
159 \r
160     private static final int              HIGHLIGHT_PAINT_PRIORITY           = 500;\r
161 \r
162     @Dependency\r
163     private Selection                     sel;\r
164     @Dependency\r
165     private MouseUtil                     mouseUtil;\r
166 \r
167     protected final IStatusLineManager    statusLine;\r
168     protected final CopyPasteStrategy     strategy;\r
169     protected IWorkbenchPartSite          site;\r
170     protected IWorkbenchPartSite          listenedSite;\r
171 \r
172     /**\r
173      * Workbench part listener for {@link #listenedSite} to keep proper track of\r
174      * whether this part is focused or not.\r
175      */\r
176     IPartListener partListener = new IPartListener() {\r
177         @Override\r
178         public void partOpened(IWorkbenchPart part) {\r
179         }\r
180         @Override\r
181         public void partDeactivated(IWorkbenchPart part) {\r
182             if (part == site.getPart())\r
183                 hasFocus = false;\r
184         }\r
185         @Override\r
186         public void partClosed(IWorkbenchPart part) {\r
187             // Make sure this listener is removed properly in any case.\r
188             if (listenedSite != null) {\r
189                 listenedSite.getPage().removePartListener(partListener);\r
190                 listenedSite = null;\r
191             }\r
192         }\r
193         @Override\r
194         public void partBroughtToTop(IWorkbenchPart part) {\r
195         }\r
196         @Override\r
197         public void partActivated(IWorkbenchPart part) {\r
198             if (part == site.getPart())\r
199                 hasFocus = true;\r
200         }\r
201     };\r
202 \r
203     /**\r
204      * Indicates whether CopyPasteHandler thinks that {@link #site} has focus. \r
205      */\r
206     protected boolean                     hasFocus                           = false;\r
207 \r
208     private AbstractCanvasParticipant     highlightMode                      = null;\r
209     private IProject                      observedProject                    = null;\r
210 \r
211     /**\r
212      * A counter for how many times pasting has been performed without mouse and\r
213      * ghosting or how many times paste has been performed without moving the\r
214      * mouse on the diagram. This is used to offset the paste position\r
215      * accordingly so that copied elements don't wind up directly on top of each\r
216      * other.\r
217      */\r
218     private int                           pasteWithoutMovingGhostCounter     = 0;\r
219 \r
220     /**\r
221      * An offset used when pasting without mouse/ghosting. It forces keyboard\r
222      * pastes to stack up on top of the latest paste performed with\r
223      * mouse/ghosting.\r
224      */\r
225     private final Point2D                 pasteOffset                        = new Point2D.Double(0, 0);\r
226 \r
227     /**\r
228      * Stores the last MouseInfo for mouse 0 from the time of the previous\r
229      * received mouse event. Used for deciding the paste position.\r
230      * \r
231      * @see #getPastePos(DiagramSelection)\r
232      */\r
233     private MouseInfo                     mouseInfo;\r
234 \r
235     /**\r
236      * Scale to use for pasted diagram monitors from variables.\r
237      */\r
238     private double                        monitorScale = 0.2;\r
239 \r
240     /**\r
241      * For updating the diagram selection after graph changes.\r
242      */\r
243     private DiagramSelectionUpdater       selectionUpdater                   = null;\r
244 \r
245     public CopyPasteHandler() {\r
246         this(new DefaultCopyPasteStrategy());\r
247     }\r
248 \r
249     public CopyPasteHandler(CopyPasteStrategy strategy) {\r
250         this(strategy, null);\r
251     }\r
252 \r
253     public CopyPasteHandler(IStatusLineManager statusLine) {\r
254         this(new DefaultCopyPasteStrategy(), statusLine);\r
255     }\r
256 \r
257     public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine) {\r
258         this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();\r
259         this.statusLine = statusLine;\r
260     }\r
261 \r
262     public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine, double monitorScale) {\r
263         this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();\r
264         this.statusLine = statusLine;\r
265         setMonitorScale(monitorScale);\r
266     }\r
267 \r
268     public CopyPasteHandler setMonitorScale(double scale) {\r
269         this.monitorScale = scale;\r
270         return this;\r
271     }\r
272 \r
273     public CopyPasteHandler setWorkbenchSite(IWorkbenchPartSite site) {\r
274         this.site = site;\r
275         return this;\r
276     }\r
277 \r
278     protected boolean isPasteAllowed() {\r
279         return listenedSite == null || hasFocus;\r
280     }\r
281 \r
282     @Override\r
283     public void addedToContext(ICanvasContext ctx) {\r
284         super.addedToContext(ctx);\r
285         addProjectListener(peekProject());\r
286 \r
287         listenedSite = site;\r
288         if (listenedSite != null) {\r
289             listenedSite.getPage().addPartListener(partListener);\r
290         }\r
291     }\r
292 \r
293     @Override\r
294     public void removedFromContext(ICanvasContext ctx) {\r
295         // Remove project selection if its ours to prevent leaking memory.\r
296         DiagramSelection ds = getProjectSelection();\r
297         if (ds.getSourceCanvas() == ctx) {\r
298             removeProjectSelection();\r
299         }\r
300 \r
301         if (listenedSite != null) {\r
302             listenedSite.getPage().removePartListener(partListener);\r
303             listenedSite = null;\r
304         }\r
305 \r
306         removeProjectListener();\r
307         super.removedFromContext(ctx);\r
308     }\r
309 \r
310     @Override\r
311     protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
312         if (oldDiagram != null) {\r
313             if (selectionUpdater != null) {\r
314                 selectionUpdater.untrack();\r
315                 selectionUpdater = null;\r
316             }\r
317         }\r
318         if (newDiagram != null) {\r
319             selectionUpdater = new DiagramSelectionUpdater(getContext(), newDiagram).track();\r
320         }\r
321     }\r
322 \r
323     IHintListener projectDiagramSelectionListener = new HintListenerAdapter() {\r
324         @Override\r
325         public void hintChanged(IHintObservable sender, Key key, Object oldValue, final Object newValue) {\r
326             //System.out.println(this + ": " + sender + ": " + newValue);\r
327             ICanvasContext ctx = getContext();\r
328             if (ctx != null && hasHighlight()) {\r
329                 //System.out.println(this + " HAS HIGHLIGHT");\r
330                 if (newValue == null || ((DiagramSelection) newValue).getSourceCanvas() != ctx) {\r
331                     //System.out.println(this + " REMOVING HIGHLIGHT");\r
332                     ctx.getThreadAccess().asyncExec(new Runnable() {\r
333                         @Override\r
334                         public void run() {\r
335                             removeHighlight();\r
336                         }\r
337                     });\r
338                 }\r
339             }\r
340         }\r
341     };\r
342 \r
343     private void addProjectListener(IProject observable) {\r
344         if (observable != null) {\r
345             observable.addKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);\r
346             observedProject = observable;\r
347         }\r
348     }\r
349 \r
350     private void removeProjectListener() {\r
351         if (observedProject != null) {\r
352             observedProject.removeKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);\r
353             observedProject = null;\r
354         }\r
355     }\r
356 \r
357     IProject getProject() {\r
358         return Simantics.getProject();\r
359     }\r
360 \r
361     IProject peekProject() {\r
362         return Simantics.peekProject();\r
363     }\r
364 \r
365     public DiagramSelection getClipboardDiagramSelection() {\r
366         for (Set<Representation> content : Simantics.getClipboard().getContents()) {\r
367             try {\r
368                 DiagramSelection sel = ClipboardUtils.accept(content, DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION);\r
369                 if (sel != null)\r
370                     return sel;\r
371             } catch (DatabaseException e) {\r
372                 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to retrieve clipboard content.", e));\r
373             }\r
374         }\r
375         return DiagramSelection.EMPTY;\r
376     }\r
377 \r
378     private DiagramSelection getProjectSelection() {\r
379         IProject p = peekProject();\r
380         if (p == null)\r
381             return DiagramSelection.EMPTY;\r
382         DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);\r
383         return ds != null ? ds : DiagramSelection.EMPTY;\r
384     }\r
385 \r
386     void setDiagramSelection(DiagramSelection selection) {\r
387         setProjectSelection(selection);\r
388         strategy.copyToClipboard(selection);\r
389     }\r
390 \r
391     void setProjectSelection(DiagramSelection selection) {\r
392         assert selection != null;\r
393         IProject p = getProject();\r
394         if (p == null)\r
395             throw new IllegalStateException("no active project for selection");\r
396         clearSG();\r
397         pasteWithoutMovingGhostCounter = 0;\r
398         pasteOffset.setLocation(0, 0);\r
399         p.setHint(KEY_DIAGRAM_SELECTION, selection);\r
400     }\r
401 \r
402     private void removeProjectSelection() {\r
403         setProjectSelection(DiagramSelection.EMPTY);\r
404         removeHighlight();\r
405         clearSG();\r
406         setDirty();\r
407     }\r
408 \r
409     /**\r
410      * This DELETE command handler is required to remove any diagram selections\r
411      * when modules are deleted. This will prevent extra highlight painting from\r
412      * being left over in case a module marked for copying is deleted.\r
413      */\r
414     @EventHandler(priority = 100)\r
415     public boolean handleDelete(CommandEvent e) {\r
416         if (e.command.equals( Commands.DELETE) ) {\r
417             if (highlightMode != null) {\r
418                 message(null);\r
419                 removeProjectSelection();\r
420                 return false;\r
421             }\r
422         }\r
423         return false;\r
424     }\r
425 \r
426     @EventHandler(priority = 0)\r
427     public boolean handleKey(org.simantics.scenegraph.g2d.events.KeyEvent e) {\r
428         if (e.keyCode == KeyEvent.VK_CONTROL) {\r
429             DiagramSelection ds = getProjectSelection();\r
430             if (!ds.isEmpty()) {\r
431                 if (e instanceof KeyPressedEvent) {\r
432                     if (ds.isCut())\r
433                         message("Move selection");\r
434                     else\r
435                         message("Paste selection");\r
436                     updateSG(ds);\r
437                 } else if (e instanceof KeyReleasedEvent) {\r
438                     selectedMessage(ds);\r
439                     hideSG(ds);\r
440                 }\r
441                 setDirty();\r
442             }\r
443         }\r
444         return false;\r
445     }\r
446 \r
447     @EventHandler(priority = 0)\r
448     public boolean handleCommand(CommandEvent e) {\r
449         if (e.command.equals( Commands.CANCEL) ) {\r
450             DiagramSelection s = getProjectSelection();\r
451             if (highlightMode != null || !s.isEmpty()) {\r
452                 message(null);\r
453                 removeProjectSelection();\r
454                 return true;\r
455             }\r
456             return false;\r
457         }\r
458         if (e.command.equals( Commands.CUT ) || e.command.equals( Commands.COPY )) {\r
459             boolean ret = initiateCopy( e.command.equals( Commands.CUT ) );\r
460             if (!ret)\r
461                 removeProjectSelection();\r
462             return ret;\r
463         }\r
464         // Must have focus in order to paste! If mouse has left the editor, no\r
465         // pastes should be performed.\r
466         if (isPasteAllowed() && e.command.equals( Commands.PASTE )) {\r
467             DiagramSelection ds = getClipboardDiagramSelection();\r
468             if (ds.isEmpty()) {\r
469                 return tryPasteMonitors();\r
470             }\r
471             return paste(e.command, ds);\r
472         }\r
473         return false;\r
474     }\r
475 \r
476     boolean initiateCopy(boolean cut) {\r
477         //System.out.println("INITIATING COPY");\r
478         int selectionId = 0;\r
479 \r
480         Set<IElement> ss = sel.getSelection(selectionId);\r
481         Point2D copyPos = getCopyStartPos(ss);\r
482         if (ss.isEmpty() || copyPos == null) {\r
483             message("Nothing to " + (cut ? "cut" : "copy"));\r
484             return false;\r
485         }\r
486 \r
487         // Validate selection, don't initiate copy if selection is invalid.\r
488         ElementAssortment ea = new ElementAssortment(ss);\r
489         String error = fixAssortment(ea, cut);\r
490 \r
491         if (error != null) {\r
492             message(error);\r
493             return false;\r
494         }\r
495 \r
496         pruneAssortment(ea, cut);\r
497         if (ea.isEmpty()) {\r
498             message("Nothing to " + (cut ? "cut" : "copy"));\r
499             return false;\r
500         }\r
501 \r
502         // Allow the possible CopyStrategy to filter the copied element\r
503         // assortment also.\r
504         if (strategy instanceof CopyStrategy) {\r
505             if (DEBUG)\r
506                 System.out.println("Commencing CopyStrategy filtering with " + ea);\r
507             IStatus status = ((CopyStrategy) strategy).copy(new CopyOperation(getContext(), ea, cut));\r
508             if (!status.isOK()) {\r
509                 switch (status.getSeverity()) {\r
510                 case IStatus.CANCEL:\r
511                 case IStatus.WARNING:\r
512                     message(status.getMessage());\r
513                     break;\r
514                 case IStatus.ERROR:\r
515                     error(status.getMessage());\r
516                     break;\r
517                 }\r
518                 return false;\r
519             }\r
520         }\r
521 \r
522         if (DEBUG)\r
523             System.out.println("Start copy with " + ea);\r
524 \r
525         // Treat OTHER type elements as disconnected floating graphical elements\r
526         // that are always copied.\r
527 \r
528         // Anything with connection parts cannot be copied\r
529         if (!cut && ea.containsAny(CopyPasteUtil.CONNECTION_PARTS)) {\r
530             error("Cannot copy connection segments nor branch points.");\r
531             return false;\r
532         }\r
533 //        if (ea.contains(CopyPasteUtil.MONITORS)) {\r
534 //            // TODO: allow copying of monitors, means fixing the component reference relations\r
535 //            error("Monitor " + (cut ? "cut" : "copy") + " not supported yet.");\r
536 //            return false;\r
537 //        }\r
538 \r
539         // Pre-validate flag selection cases\r
540         if (ea.contains(CopyPasteUtil.FLAGS)) {\r
541             if (cut) {\r
542                 // Allow cutting of single flags or cutting of any amount of\r
543                 // flags within a single diagram.\r
544             } else {\r
545 //                // Deny flag copy if other kinds of elements are selected.\r
546 //                if (ea.containsAny(NOT_FLAGS)) {\r
547 //                    return false;\r
548 //                }\r
549                 // Only copy flags without correspondence for now.\r
550                 if (CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
551                     if (!CopyPasteUtil.checkFlagsCorrespondences(ea.flags, false)) {\r
552                         error("Cannot copy flag that already has a correspondence.");\r
553                         return false;\r
554                     }\r
555                 }\r
556             }\r
557         }\r
558 \r
559         // Selection is valid, go ahead and initiate a copy operation.\r
560         Resource sourceDiagram = diagram.<Resource>getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
561         DiagramSelection ds = new DiagramSelection(getContext(), sourceDiagram, ea.getAll(), cut, copyPos);\r
562         setDiagramSelection(ds);\r
563 \r
564         removeHighlight();\r
565         highlightMode = new HighlightMode(ds, selectionId, HIGHLIGHT_PAINT_PRIORITY);\r
566         getContext().add(highlightMode);\r
567 \r
568         selectedMessage(ds);\r
569 \r
570 //        System.out.println("INITIATED COPY: " + ds);\r
571         return true;\r
572     }\r
573 \r
574     public boolean paste(Command command, DiagramSelection ds) {\r
575         if (ds.isEmpty()) {\r
576             message(null);\r
577             return false;\r
578         }\r
579         \r
580         TimeLogger.resetTimeAndLog(getClass(), "paste");\r
581 \r
582         ElementObjectAssortment ea = ds.getAssortment();\r
583 \r
584         if (DEBUG)\r
585             System.out.println("Initiate paste with " + ea);\r
586 \r
587         try {\r
588             if (CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
589                 // Do not copy if any of the flags already have a correspondence.\r
590                 if (!CopyPasteUtil.onlyFlagsWithoutCorrespondence(Simantics.getSession(), ea))\r
591                     return true;\r
592 \r
593                 if (ds.isCut()) {\r
594                     normalPaste(command, ds, ea, true);\r
595                     removeHighlight();\r
596                     setDiagramSelection(DiagramSelection.EMPTY);\r
597                     resetSourceSelection(ds);\r
598                 } else {\r
599                     normalPaste(command, ds, ea, false);\r
600                     // There is no point in leaving the old copy selection hanging\r
601                     // around after copying a flag since it is a one shot operation.\r
602                     removeHighlight();\r
603                     setDiagramSelection(DiagramSelection.EMPTY);\r
604                 }\r
605             } else {\r
606                 if (ds.isCut()) {\r
607                     normalPaste(command, ds, ea, true);\r
608                     removeHighlight();\r
609                     setDiagramSelection(DiagramSelection.EMPTY);\r
610                     resetSourceSelection(ds);\r
611                 } else {\r
612                     normalPaste(command, ds, ea, false);\r
613 \r
614 //                // This is necessary to keep the ghost diagram properly up-to-date\r
615 //                // after paste operations.\r
616 //                setProjectSelection(ds.remutate());\r
617                 }\r
618             }\r
619 \r
620             message(null);\r
621 \r
622         } catch (PasteException e) {\r
623             error( e.getLocalizedMessage() );\r
624             ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );\r
625         } catch (DatabaseException e) {\r
626             error( e.getLocalizedMessage() );\r
627             ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );\r
628         }\r
629 \r
630         // Clear ghosting\r
631         clearSG();\r
632         setDirty();\r
633 \r
634         return true;\r
635     }\r
636 \r
637     /**\r
638      * In cut/paste cases, the source selection should reset (removed) after the\r
639      * paste operation has been performed. This method will reset the source\r
640      * selection of the specified DiagramSelection from the source canvas\r
641      * context. This will work regardless of which diagram/editor the selection\r
642      * originates from.\r
643      * \r
644      * @param ds the source selection to reset\r
645      */\r
646     void resetSourceSelection(DiagramSelection ds) {\r
647         ICanvasContext cc = ds.getSourceCanvas();\r
648         boolean sameDiagram = diagram == ds.getSourceDiagram();\r
649         if (!sameDiagram && cc != null && !cc.isDisposed()) {\r
650             for (Selection sourceSelection : cc.getItemsByClass(Selection.class)) {\r
651                 Collection<IElement> empty = Collections.emptySet();\r
652                 sourceSelection.setSelection(0, empty);\r
653             }\r
654         }\r
655     }\r
656 \r
657     private void normalPaste(Command command, DiagramSelection ds, ElementObjectAssortment ea, boolean cut) throws PasteException {\r
658         final Point2D copyPos = ds.getCopyPos();\r
659         final Point2D pastePos = getPastePos(ds);\r
660 \r
661         double dx = pastePos.getX() - copyPos.getX();\r
662         double dy = pastePos.getY() - copyPos.getY();\r
663         final Point2D pasteOffset = new Point2D.Double(dx, dy);\r
664 \r
665         try {\r
666             // Get diagram contents before the paste operation\r
667             Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
668             final DiagramContentTracker tracker =\r
669                 diagramResource == null ?\r
670                         null\r
671                         : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);\r
672 \r
673             strategy.paste(new PasteOperation(command, getContext(), ds.getSourceDiagram(), diagramResource, diagram, ea, cut, pasteOffset));\r
674 \r
675             if (tracker != null) {\r
676                 // Get difference of diagram contents to find out what was added.\r
677                 DiagramContentChanges changes = tracker.update();\r
678                 selectionUpdater.setNewSelection(0, changes.pick(changes.elements, Change.ADDED));\r
679                 if (DEBUG_SELECTION_UPDATE)\r
680                     System.out.println("stored diagram changes @" + System.currentTimeMillis() + ": " + selectionUpdater.getNewSelection());\r
681             }\r
682 \r
683         } catch (DatabaseException e) {\r
684             ErrorLogger.defaultLogError(e);\r
685         }\r
686     }\r
687 \r
688     private String fixAssortment(ElementAssortment ea, boolean cut) {\r
689         Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
690         List<Connection> conns = new ArrayList<Connection>();\r
691 \r
692         // Include flags whether they are selected or not\r
693         for (IElement edge : ea.edges) {\r
694             Connection bc = diagramTopology.getConnection(edge, EdgeEnd.Begin);\r
695             if (bc != null && bc.node != null) {\r
696                 if (bc.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)\r
697                     ea.add(ElementType.Flag, bc.node);\r
698             }\r
699             Connection ec = diagramTopology.getConnection(edge, EdgeEnd.End);\r
700             if (ec != null && ec.node != null) {\r
701                 if (ec.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)\r
702                     ea.add(ElementType.Flag, ec.node);\r
703             }\r
704         }\r
705 \r
706         // Include connections for selected flags if we're not potentially\r
707         // making flag continuations.\r
708         if (!CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
709             for (IElement flag : ea.flags) {\r
710                 conns.clear();\r
711                 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);\r
712                 for (Connection conn : conns) {\r
713                     IElement edge = conn.edge;\r
714                     ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
715                     ea.add(ElementType.Connection, ce.getConnection());\r
716                 }\r
717             }\r
718         }\r
719 \r
720         // For each selected connection, make sure that all connected elements\r
721         // are in the selection, otherwise don't copy the connection.\r
722         List<IElement> connectionsToRemove = new ArrayList<IElement>(ea.connections.size());\r
723         for (IElement connection : ea.connections) {\r
724             ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);\r
725             Collection<Connection> connectors = ch.getTerminalConnections(connection, null);\r
726             boolean allConnectorsSelected = true;\r
727             for (Connection c : connectors) {\r
728                 if (!(ea.nodes.contains(c.node) || ea.flags.contains(c.node) || ea.references.contains(c.node))) {\r
729                     allConnectorsSelected = false;\r
730                     break;\r
731                 }\r
732             }\r
733             if (!allConnectorsSelected)\r
734                 connectionsToRemove.add(connection);\r
735         }\r
736         ea.removeAll(ElementType.Connection, connectionsToRemove);\r
737 \r
738         // Remove external flags whose connection(s) are not included\r
739         List<IElement> flagsToRemove = new ArrayList<IElement>(ea.flags.size());\r
740         for (IElement flag : ea.flags) {\r
741             if (CopyPasteUtil.flagIsExternal(flag)) {\r
742                 conns.clear();\r
743                 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);\r
744                 for (Connection conn : conns) {\r
745                     IElement edge = conn.edge;\r
746                     ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
747                     IElement connection = ce.getConnection();\r
748                     if (!ea.connections.contains(connection)) {\r
749                         flagsToRemove.add(flag);\r
750                     }\r
751                 }\r
752             }\r
753         }\r
754         ea.removeAll(ElementType.Flag, flagsToRemove);\r
755 \r
756         if (cut) {\r
757             // Issue #1874: Prevent cut/paste for connected components\r
758             // https://www.simulationsite.net/redmine/issues/1874\r
759             // Fail if any of the included nodes has connections to it that are not\r
760             // included in the operation.\r
761             Collection<Connection> connections = new ArrayList<Connection>();\r
762             for (IElement node : CollectionUtils.join(ea.nodes, ea.flags)) {\r
763                 connections.clear();\r
764                 for (Connection connection : getAllConnections(node, connections)) {\r
765                     ConnectionEntity ce = connection.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
766                     IElement conn = ce.getConnection();\r
767                     if (ea.connections.contains(conn))\r
768                         continue;\r
769 \r
770                     return "Cannot cut a node without all its connections.";\r
771                 }\r
772             }\r
773         }\r
774 \r
775         // Remove all reference elements from the assortment whose parent elements are not in it.\r
776         if (!cut) {\r
777             Collection<IElement> referenceElementsToRemove = new ArrayList<IElement>();\r
778             for (IElement ref : ea.references) {\r
779                 IElement parent = ref.getHint(ElementHints.KEY_PARENT_ELEMENT);\r
780                 if (parent != null) {\r
781                     if (!ea.all.contains(parent)) {\r
782                         // Cannot copy reference element whose parent is not copied also.\r
783                         referenceElementsToRemove.add(ref);\r
784                     }\r
785                 } else {\r
786                     // OK, reference element has no parent. Free to copy/cut in any way.\r
787                 }\r
788             }\r
789             if (!referenceElementsToRemove.isEmpty()) {\r
790                 ea.removeAll(ElementType.Reference, referenceElementsToRemove);\r
791                 if (ea.isEmpty()) {\r
792                     return "Cannot copy reference elements whose parent is not copied.";\r
793                 }\r
794             }\r
795         }\r
796 \r
797         return null;\r
798     }\r
799 \r
800     private Collection<Terminal> getTerminals(IElement node) {\r
801         ArrayList<Terminal> result = new ArrayList<Terminal>();\r
802         for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))\r
803             tt.getTerminals(node, result);\r
804         return result;\r
805     }\r
806 \r
807     private Collection<Connection> getAllConnections(IElement node, Collection<Connection> result) {\r
808         IDiagram diagram = node.getDiagram();\r
809         Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
810         if (topology == null)\r
811             return result;\r
812         for (Terminal t : getTerminals(node))\r
813             topology.getConnections(node, t, result);\r
814         return result;\r
815     }\r
816 \r
817     /**\r
818      * Classifies the specified diagram selection elements into categories\r
819      * appointed by the <code>ElementType</code> enumeration and prunes any\r
820      * edges from the selection returned assortment whose both ends are not\r
821      * connected to nodes within the selection.\r
822      *\r
823      * @param ea\r
824      * @return\r
825      */\r
826     private void pruneAssortment(ElementAssortment ea, boolean cut) {\r
827         // Edges and branch points are never copied as such.\r
828         // They are always included as parts of copied connections.\r
829         // Edges can never be transformed or modified in any way as such,\r
830         // therefore it is safe to do this.\r
831         ea.clear(ElementType.Edge);\r
832 \r
833         if (!cut)\r
834             ea.clear(ElementType.BranchPoint);\r
835     }\r
836 \r
837     private Point2D getPastePos(DiagramSelection ds) {\r
838         MouseInfo mi = mouseUtil.getMouseInfo(0);\r
839         if (mi == null)\r
840             mi = mouseInfo;\r
841 \r
842         if (mi != null) {\r
843             double xoff = mi.canvasPosition.getX() - ds.getCopyPos().getX();\r
844             double yoff = mi.canvasPosition.getY() - ds.getCopyPos().getY();\r
845             if (xoff == pasteOffset.getX() && yoff == pasteOffset.getY()) {\r
846                 // The mouse has not moved since last paste so let's offset the\r
847                 // paste down and right.\r
848                 double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);\r
849                 return new Point2D.Double(\r
850                         mi.canvasPosition.getX() + counterOffset,\r
851                         mi.canvasPosition.getY() + counterOffset);\r
852             }\r
853             pasteWithoutMovingGhostCounter = 0;\r
854             pasteOffset.setLocation(xoff, yoff);\r
855             return mi.canvasPosition;\r
856         } else {\r
857             //return ds.getCopyPos();\r
858             Point2D p = ds.getCopyPos();\r
859             double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);\r
860             return new Point2D.Double(\r
861                     p.getX() + pasteOffset.getX() + counterOffset,\r
862                     p.getY() + pasteOffset.getY() + counterOffset);\r
863         }\r
864     }\r
865 \r
866     private double getOffsetGridSize() {\r
867         Double grid = getHint(GridPainter.KEY_GRID_SIZE);\r
868         return (grid == null || grid == 0) ? 1.0 : grid;\r
869     }\r
870 \r
871     /**\r
872      * @param e\r
873      * @return\r
874      */\r
875     private static boolean isConnectionOrEdge(IElement e) {\r
876         ElementClass ec = e.getElementClass();\r
877         return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);\r
878     }\r
879 \r
880     /**\r
881      * @param e\r
882      * @return\r
883      */\r
884     private static boolean isMoveable(IElement e) {\r
885         ElementClass ec = e.getElementClass();\r
886         return ec.containsClass(Move.class) && ec.containsClass(Transform.class);\r
887     }\r
888 \r
889     /**\r
890      * @param ss\r
891      * @return <code>null</code> if a point of reference cannot be determined\r
892      *         for the specified selection.\r
893      */\r
894     private Point2D getCopyStartPos(Set<IElement> ss) {\r
895 //        MouseInfo mi = mouseUtil.getMouseInfo(0);\r
896 //        if (mi != null) {\r
897 //            return (Point2D) mi.canvasPosition.clone();\r
898 //        }\r
899 \r
900         // Find bounding rectangle top left corner\r
901         double mx = Double.MAX_VALUE;\r
902         double my = Double.MAX_VALUE;\r
903         for (IElement e : ss) {\r
904             if (isConnectionOrEdge(e) || !isMoveable(e))\r
905                 continue;\r
906 \r
907             //Point2D pos = ElementUtils.getPos(e);\r
908             Point2D pos = ElementUtils.getAbsolutePos(e);\r
909             if (pos.getX() < mx)\r
910                 mx = pos.getX();\r
911             if (pos.getY() < my)\r
912                 my = pos.getY();\r
913         }\r
914 \r
915         // Find element nearest to the top left corner\r
916         Point2D nearest = null;\r
917         double dist = Double.MAX_VALUE;\r
918         for (IElement e : ss) {\r
919             if (isConnectionOrEdge(e) || !isMoveable(e))\r
920                 continue;\r
921 \r
922             Point2D pos = ElementUtils.getAbsolutePos(e);\r
923             double dx = pos.getX() - mx;\r
924             double dy = pos.getY() - my;\r
925             double d = dx*dx + dy*dy;\r
926             if (d < dist) {\r
927                 dist = d;\r
928                 nearest = pos;\r
929             }\r
930         }\r
931 \r
932         return nearest;\r
933     }\r
934 \r
935     private void moveGhostElements(DiagramSelection ds, Point2D pastePos) {\r
936         Point2D copyPos = ds.getCopyPos();\r
937         double dx = (pastePos.getX() - copyPos.getX());\r
938         double dy = (pastePos.getY() - copyPos.getY());\r
939 \r
940         // Snap delta\r
941         Point2D snap = CopyPasteUtil.snap(getContext(), new Point2D.Double(dx, dy));\r
942 \r
943         ghostNode.setTransform(AffineTransform.getTranslateInstance(snap.getX(), snap.getY()));\r
944         //System.out.println("ghost node: " + ghostNode);\r
945     }\r
946 \r
947     protected SingleElementNode ghostNode = null;\r
948     protected NodeMapper ghostNodeMapper = new NodeMapper();\r
949 \r
950     @SGInit\r
951     public void initSG(G2DParentNode parent) {\r
952         ghostNode = parent.addNode("cut/copy ghost", SingleElementNode.class);\r
953         ghostNode.setZIndex(COPY_GHOSTING_PAINT_PRIORITY);\r
954         //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.40f));\r
955         ghostNode.setVisible(Boolean.FALSE);\r
956     }\r
957 \r
958     @SGCleanup\r
959     public void cleanupSG() {\r
960         ghostNode.remove();\r
961     }\r
962 \r
963     void clearSG() {\r
964         ghostNode.removeNodes();\r
965         ghostNode.setVisible(Boolean.FALSE);\r
966         ghostNodeMapper.clear();\r
967     }\r
968 \r
969     /**\r
970      * @param selection\r
971      * @return <code>true</code> if the ghost nodes were hidden and a refresh is\r
972      *         needed\r
973      */\r
974     boolean hideSG(DiagramSelection selection) {\r
975         if (ghostNode.isVisible()) {\r
976             // Make sure there's no leftover graphics.\r
977             ghostNode.removeNodes();\r
978             ghostNode.setVisible(Boolean.FALSE);\r
979             return true;\r
980         }\r
981         return false;\r
982     }\r
983 \r
984     protected void scheduleActivateOwnerPart() {\r
985         if (site == null)\r
986             return;\r
987         SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {\r
988             @Override\r
989             public void run() {\r
990                 hasFocus = true;\r
991                 site.getPage().activate(site.getPart());\r
992             }\r
993         });\r
994     }\r
995 \r
996     @EventHandler(priority = 0)\r
997     public boolean handleMouse(MouseExitEvent e) {\r
998         DiagramSelection ds = getProjectSelection();\r
999         if (!ds.isEmpty()) {\r
1000             if (hideSG(ds))\r
1001                 setDirty();\r
1002         }\r
1003 \r
1004         // The part might no longer have focus.\r
1005         // [Tuukka] commented out to fix Apros #3678\r
1006         //hasFocus = false;\r
1007 \r
1008         return false;\r
1009     }\r
1010 \r
1011     @EventHandler(priority = 0)\r
1012     public boolean handleMouse(MouseEnterEvent e) {\r
1013         DiagramSelection ds = getProjectSelection();\r
1014         if (!ds.isEmpty()) {\r
1015             if (site != null) {\r
1016                 if (inPasteMode(e)) {\r
1017                     scheduleActivateOwnerPart();\r
1018                 }\r
1019             }\r
1020         }\r
1021         return false;\r
1022     }\r
1023 \r
1024     @EventHandler(priority = 0)\r
1025     public boolean handleMouse(MouseMovedEvent e) {\r
1026         DiagramSelection ds = getProjectSelection();\r
1027         if (!ds.isEmpty()) {\r
1028             MouseInfo mi = mouseUtil.getMouseInfo(0);\r
1029             //System.out.println("LAST MOUSE INFO: " + mi);\r
1030             if (mi != null)\r
1031                 mouseInfo = mi;\r
1032 \r
1033             if (inPasteMode(e)) {\r
1034                 // Make sure that this owner part is active now.\r
1035                 if (!hasFocus)\r
1036                     scheduleActivateOwnerPart();\r
1037 \r
1038                 updateSG(ds);\r
1039                 setDirty();\r
1040             } else {\r
1041                 if (hideSG(ds))\r
1042                     setDirty();\r
1043             }\r
1044         }\r
1045         return false;\r
1046     }\r
1047 \r
1048     void updateSG(DiagramSelection selection) {\r
1049         MouseInfo mi = mouseUtil.getMouseInfo(0);\r
1050         if (mi == null)\r
1051             return;\r
1052 \r
1053         //ghostNode.setComposite(AlphaComposite.SrcAtop.derive(0.40f));\r
1054         //ghostNode.setComposite(null);\r
1055 \r
1056         moveGhostElements(selection, mi.canvasPosition);\r
1057         if (selection.getSourceCanvas() != getContext()) {\r
1058             for (IElement e : selection.getOriginalElements()) {\r
1059                 INode node = e.getHint(ElementHints.KEY_SG_NODE);\r
1060                 //System.out.println("ghost element: " + e + ", node=" + node);\r
1061                 if (node instanceof IG2DNode) {\r
1062                     LocalDelegateNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e),\r
1063                             LocalDelegateNode.class);\r
1064                     delegate.setDelegate( (IG2DNode) node );\r
1065                 }\r
1066             }\r
1067         } else {\r
1068             for (IElement e : selection.getOriginalElements()) {\r
1069                 //System.out.println("ghost element: " + e);\r
1070                 INode node = e.getHint(ElementHints.KEY_SG_NODE);\r
1071                 if (node != null) {\r
1072                     //System.out.println("ghost node: " + node);\r
1073                     ghostNodeMapper.add(node);\r
1074                     String nodeId = ghostNodeMapper.getId(node);\r
1075                     //System.out.println("ghost node id: " + nodeId);\r
1076                     LinkNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e), LinkNode.class);\r
1077                     delegate.setDelegateId( nodeId );\r
1078                 }\r
1079             }\r
1080         }\r
1081 \r
1082         ghostNode.setVisible(true);\r
1083     }\r
1084 \r
1085     private <T extends INode> T getOrCreateNode(ParentNode<?> parentNode, String id, Class<T> clazz) {\r
1086         INode n = ghostNode.getNode(id);\r
1087         if (clazz.isInstance(n))\r
1088             return clazz.cast(n);\r
1089         ghostNode.removeNode(id);\r
1090         return ghostNode.addNode(id, clazz);\r
1091     }\r
1092 \r
1093     private boolean hasHighlight() {\r
1094         return highlightMode != null;\r
1095     }\r
1096 \r
1097     private void removeHighlight() {\r
1098         if (isRemoved())\r
1099             return;\r
1100         assert getContext().getThreadAccess().currentThreadAccess();\r
1101         if (highlightMode != null) {\r
1102             if (!highlightMode.isRemoved()) {\r
1103                 highlightMode.remove();\r
1104                 setDirty();\r
1105             }\r
1106             highlightMode = null;\r
1107         }\r
1108     }\r
1109 \r
1110     private boolean inPasteMode(MouseEvent e) {\r
1111         return (e.stateMask & MouseEvent.CTRL_MASK) != 0;\r
1112     }\r
1113 \r
1114     void selectedMessage(DiagramSelection ds) {\r
1115         int size = ds.getOriginalElements().size();\r
1116         StringBuilder sb = new StringBuilder();\r
1117         if (size == 0) {\r
1118             sb.append("No elements to ");\r
1119             if (ds.isCut())\r
1120                 sb.append("cut");\r
1121             else\r
1122                 sb.append("copy");\r
1123         } else {\r
1124             if (ds.isCut())\r
1125                 sb.append("Cut ");\r
1126             else\r
1127                 sb.append("Copied ");\r
1128             sb.append(size);\r
1129             sb.append(" element");\r
1130             if (size > 1)\r
1131                 sb.append('s');\r
1132         }\r
1133         message(sb.toString());\r
1134     }\r
1135 \r
1136     void message(final String message) {\r
1137         if (statusLine == null)\r
1138             return;\r
1139         swtExec(new Runnable() {\r
1140             @Override\r
1141             public void run() {\r
1142                 statusLine.setMessage(message);\r
1143                 statusLine.setErrorMessage(null);\r
1144             }\r
1145         });\r
1146     }\r
1147 \r
1148     void error(final String message) {\r
1149         if (statusLine == null)\r
1150             return;\r
1151         swtExec(new Runnable() {\r
1152             @Override\r
1153             public void run() {\r
1154                 statusLine.setErrorMessage(message);\r
1155             }\r
1156         });\r
1157     }\r
1158 \r
1159     void swtExec(Runnable r) {\r
1160         ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);\r
1161     }\r
1162 \r
1163     // MONITOR PASTE SUPPORT\r
1164 \r
1165     private boolean tryPasteMonitors() {\r
1166         SimanticsClipboard clipboard = Simantics.getClipboard();\r
1167         for (Set<Representation> content : clipboard.getContents()) {\r
1168             try {\r
1169                 final Variable var_ = ClipboardUtils.accept(content, SimanticsKeys.KEY_VARIABLE);\r
1170                 if (var_ != null) {\r
1171                     final Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
1172                     final Resource runtime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);\r
1173                     final Resource elementResource = Simantics.getSession().syncRequest(new Read<Resource>() {\r
1174                         @Override\r
1175                         public Resource perform(ReadGraph graph) throws DatabaseException {\r
1176                             DiagramResource DIA = DiagramResource.getInstance(graph);\r
1177 \r
1178                             String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);\r
1179                             if (diagramVariable == null)\r
1180                                 return null;\r
1181 \r
1182                             Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);\r
1183                             if (diaVar == null)\r
1184                                 return null;\r
1185 \r
1186                             Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));\r
1187                             if(var == null)\r
1188                                 return null;\r
1189 \r
1190                             Variable component = Variables.getChild(graph, diaVar, var);\r
1191                             if (component == null)\r
1192                                 return null;\r
1193 \r
1194                             Resource componentResource = component.getPossibleRepresents(graph);\r
1195                             if (componentResource == null)\r
1196                                 return null;\r
1197 \r
1198                             return graph.getPossibleObject(componentResource, ModelingResources.getInstance(graph).ComponentToElement);\r
1199                         }\r
1200                     });\r
1201 \r
1202                     if (elementResource == null)\r
1203                         return false;\r
1204 \r
1205                     final AffineTransform monitorTransform = Simantics.getSession().syncRequest(new Read<AffineTransform>() {\r
1206                         @Override\r
1207                         public AffineTransform perform(ReadGraph graph) throws DatabaseException {\r
1208                             AffineTransform at = null;\r
1209 \r
1210                             if (graph.isInstanceOf(elementResource, DiagramResource.getInstance(graph).Connection)) {\r
1211                                 Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, elementResource);\r
1212                                 if (tailNode != null) {\r
1213                                     at = DiagramGraphUtil.getAffineTransform(graph, tailNode);\r
1214                                 }\r
1215                             }\r
1216                             if (at == null)\r
1217                                 at = DiagramGraphUtil.getAffineTransform(graph, elementResource);\r
1218 \r
1219                             return at;\r
1220                         }\r
1221                     });\r
1222 \r
1223                     MouseInfo mi = mouseUtil.getMouseInfo(0);\r
1224                     if (mi == null)\r
1225                         mi = mouseInfo;\r
1226                     final double dx = mi.canvasPosition.getX() - monitorTransform.getTranslateX();\r
1227                     final double dy = mi.canvasPosition.getY() - monitorTransform.getTranslateY();\r
1228 \r
1229                     Simantics.getSession().asyncRequest(new WriteRequest() {\r
1230 \r
1231                         @Override\r
1232                         public void perform(WriteGraph graph) throws DatabaseException {\r
1233                             Layer0 L0 = Layer0.getInstance(graph);\r
1234                             Layer0X L0X = Layer0X.getInstance(graph);\r
1235                             DiagramResource DIA = DiagramResource.getInstance(graph);\r
1236                             G2DResource G2D = G2DResource.getInstance(graph);\r
1237 \r
1238                             String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);\r
1239                             if (diagramVariable == null)\r
1240                                 return;\r
1241 \r
1242                             Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);\r
1243                             if (diaVar == null)\r
1244                                 return;\r
1245 \r
1246                             Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));\r
1247                             if(var == null)\r
1248                                 return;\r
1249 \r
1250                             Variable component = Variables.getChild(graph, diaVar, var);\r
1251                             if (component == null)\r
1252                                 return;\r
1253 \r
1254                             Resource componentResource = component.getPossibleRepresents(graph);\r
1255                             if (componentResource == null)\r
1256                                 return;\r
1257 \r
1258                             String suffix = Variables.getRVI(graph, component, var);\r
1259 \r
1260                             Resource resource = graph.newResource();\r
1261                             graph.claim(resource, L0.InstanceOf, null, DIA.Monitor);\r
1262 \r
1263                             final double scale = monitorScale;\r
1264 \r
1265                             DiagramGraphUtil.setTransform(graph, resource, new AffineTransform(scale, 0, 0, scale, dx, dy));\r
1266 \r
1267                             OrderedSetUtils.add(graph, diagramResource, resource);\r
1268 \r
1269                             // 5.1. Give running name to element and increment the counter attached to the diagram.\r
1270                             Long l = graph.getPossibleRelatedValue(diagramResource, DIA.HasModCount, Bindings.LONG);\r
1271                             if (l == null)\r
1272                                 l = Long.valueOf(0L);\r
1273                             graph.claimLiteral(resource, L0.HasName, l.toString(), Bindings.STRING);\r
1274                             graph.claimLiteral(diagramResource, DIA.HasModCount, ++l, Bindings.LONG);\r
1275 \r
1276                             // 5.2. Make the diagram consist of the new element\r
1277                             graph.claim(diagramResource, L0.ConsistsOf, resource);\r
1278 \r
1279                             graph.claim(resource, G2D.HasHorizontalAlignment, null, G2D.Alignment_Leading);\r
1280                             graph.claimLiteral(resource, DIA.HasDirection, 0.0);\r
1281 \r
1282                             graph.claim(resource, DIA.HasMonitorComponent, componentResource);\r
1283                             graph.claimLiteral(resource, DIA.HasMonitorSuffix, suffix);\r
1284 \r
1285                             Resource model = Variables.getModel(graph, diaVar);\r
1286                             if (model != null) {\r
1287                                 Resource template = graph.getPossibleObject(model, DIA.HasDefaultMonitorTemplate);\r
1288                                 if (template != null) {\r
1289                                     graph.claim(resource, L0X.ObtainsProperty1, null, template);\r
1290                                 }\r
1291                             }\r
1292                             \r
1293                         }\r
1294                     });\r
1295                 }\r
1296             } catch (DatabaseException e1) {\r
1297                 Logger.defaultLogError(e1);\r
1298             }\r
1299         }\r
1300         return true;\r
1301     }\r
1302 \r
1303 }\r