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