]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/e4/CopyPasteHandler.java
Removed unnecessary dependencies on org.apache.log4j
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / e4 / CopyPasteHandler.java
index 132fb0aff51374e3f4a8bb704e7329e92559d3e0..7f709c70993eb62839ed8d0a283cbdfd8f54e9ad 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.diagram.handler.e4;\r
-\r
-import java.awt.Color;\r
-import java.awt.event.KeyEvent;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.Set;\r
-\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;\r
-import org.eclipse.e4.ui.workbench.modeling.EPartService;\r
-import org.eclipse.e4.ui.workbench.modeling.IPartListener;\r
-import org.eclipse.jface.action.IStatusLineManager;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.ui.PlatformUI;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.OrderedSetUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.util.ClipboardUtils;\r
-import org.simantics.db.layer0.util.SimanticsClipboard;\r
-import org.simantics.db.layer0.util.SimanticsClipboard.Representation;\r
-import org.simantics.db.layer0.util.SimanticsKeys;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.layer0.variable.Variables;\r
-import org.simantics.db.request.Read;\r
-import org.simantics.diagram.Logger;\r
-import org.simantics.diagram.content.Change;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.content.DiagramContentChanges;\r
-import org.simantics.diagram.content.DiagramContentTracker;\r
-import org.simantics.diagram.handler.CopyPasteStrategy;\r
-import org.simantics.diagram.handler.CopyPasteUtil;\r
-import org.simantics.diagram.handler.DefaultCopyPasteStrategy;\r
-import org.simantics.diagram.handler.DiagramSelection;\r
-import org.simantics.diagram.handler.DiagramSelectionRepresentation;\r
-import org.simantics.diagram.handler.ElementAssortment;\r
-import org.simantics.diagram.handler.ElementObjectAssortment;\r
-import org.simantics.diagram.handler.ElementType;\r
-import org.simantics.diagram.handler.HighlightMode;\r
-import org.simantics.diagram.handler.PasteException;\r
-import org.simantics.diagram.handler.PasteOperation;\r
-import org.simantics.diagram.internal.Activator;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.stubs.G2DResource;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;\r
-import org.simantics.diagram.ui.DiagramModelHints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
-import org.simantics.g2d.connection.ConnectionEntity;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.Topology;\r
-import org.simantics.g2d.diagram.handler.Topology.Connection;\r
-import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
-import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
-import org.simantics.g2d.diagram.participant.Selection;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementHints;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.handler.BendsHandler;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.Move;\r
-import org.simantics.g2d.element.handler.TerminalTopology;\r
-import org.simantics.g2d.element.handler.Transform;\r
-import org.simantics.g2d.elementclass.FlagHandler;\r
-import org.simantics.g2d.participant.GridPainter;\r
-import org.simantics.g2d.participant.MouseUtil;\r
-import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.operation.Layer0X;\r
-import org.simantics.project.IProject;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.ParentNode;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
-import org.simantics.scenegraph.g2d.events.command.Command;\r
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
-import org.simantics.scenegraph.g2d.nodes.LocalDelegateNode;\r
-import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
-import org.simantics.scenegraph.utils.NodeMapper;\r
-import org.simantics.utils.datastructures.collections.CollectionUtils;\r
-import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.datastructures.hints.IHintListener;\r
-import org.simantics.utils.datastructures.hints.IHintObservable;\r
-import org.simantics.utils.logging.TimeLogger;\r
-import org.simantics.utils.threads.SWTThread;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-import org.simantics.utils.ui.SWTUtils;\r
-\r
-/**\r
- * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and\r
- * Commands.PASTE commands for an IDiagram.\r
- * \r
- * <p>\r
- * The handler attempts to copy/paste the current selection for pointer 0,\r
- * meaning {@link Selection#SELECTION0}.\r
- * </p>\r
- * \r
- * <p>\r
- * The handler logic follows the specifications at <a\r
- * href="http://www.simantics.org/wiki/index.php/UC:Copy_Item" >UC:Copy Item</a>\r
- * and <a href="http://www.simantics.org/wiki/index.php/UC:Cut_Item" >UC:Cut\r
- * Item</a>.\r
- * </p>\r
- * \r
- * @see Selection current diagram selection source\r
- * \r
- * @author Tuukka Lehtonen\r
- * \r
- *         FIXME: translucent ghosting makes rendering REALLY sluggish, add a\r
- *         timer that makes the ghost opaque when the user is interacting and\r
- *         translucent only when still for a while.\r
- */\r
-public class CopyPasteHandler extends AbstractDiagramParticipant {\r
-\r
-    public static final Key KEY_CUT_SELECTION_FRAME_COLOR      = new KeyOf(Color.class, "CUT_SELECTION_FRAME_COLOR");\r
-    public static final Key KEY_CUT_SELECTION_CONTENT_COLOR    = new KeyOf(Color.class, "CUT_SELECTION_CONTENT_COLOR");\r
-    public static final Key KEY_COPIED_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "COPY_SELECTION_FRAME_COLOR");\r
-    public static final Key KEY_COPIED_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "COPY_SELECTION_CONTENT_COLOR");\r
-\r
-    /**\r
-     * A key for storing the current selection within the currently active\r
-     * project for copy/paste implementation.\r
-     */\r
-    private static final Key               KEY_DIAGRAM_SELECTION              = DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION;\r
-\r
-    private static final boolean          DEBUG                              = false;\r
-    private static final boolean          DEBUG_SELECTION_UPDATE             = false;\r
-\r
-    public static final int               COPY_GHOSTING_PAINT_PRIORITY       = 600;\r
-\r
-    private static final int              HIGHLIGHT_PAINT_PRIORITY           = 500;\r
-\r
-    @Dependency\r
-    private Selection                     sel;\r
-    @Dependency\r
-    private MouseUtil                     mouseUtil;\r
-\r
-    protected final IStatusLineManager    statusLine;\r
-    protected final CopyPasteStrategy     strategy;\r
-    protected MPart                       mPart;\r
-    protected MPart                       listenedMPart;\r
-\r
-    /**\r
-     * Workbench part listener for {@link #listenedMPart} to keep proper track of\r
-     * whether this part is focused or not.\r
-     */\r
-    \r
-    IPartListener partListener2 = new IPartListener() {\r
-        \r
-        @Override\r
-        public void partVisible(MPart part) {\r
-        }\r
-        \r
-        @Override\r
-        public void partHidden(MPart part) {\r
-            // Make sure this listener is removed properly in any case.\r
-            if (listenedMPart != null) {\r
-                mPart.getContext().get(EPartService.class).removePartListener(partListener2);\r
-                listenedMPart = null;\r
-            }\r
-        }\r
-        \r
-        @Override\r
-        public void partDeactivated(MPart part) {\r
-            if (part == mPart)\r
-                hasFocus = false;\r
-        }\r
-        \r
-        @Override\r
-        public void partBroughtToTop(MPart part) {\r
-        }\r
-        \r
-        @Override\r
-        public void partActivated(MPart part) {\r
-            if (part == mPart)\r
-                hasFocus = true;\r
-        }\r
-    };\r
-\r
-    /**\r
-     * Indicates whether CopyPasteHandler thinks that {@link #mPart} has focus. \r
-     */\r
-    protected boolean                     hasFocus                           = false;\r
-\r
-    private AbstractCanvasParticipant     highlightMode                      = null;\r
-    private IProject                      observedProject                    = null;\r
-\r
-    /**\r
-     * A counter for how many times pasting has been performed without mouse and\r
-     * ghosting or how many times paste has been performed without moving the\r
-     * mouse on the diagram. This is used to offset the paste position\r
-     * accordingly so that copied elements don't wind up directly on top of each\r
-     * other.\r
-     */\r
-    private int                           pasteWithoutMovingGhostCounter     = 0;\r
-\r
-    /**\r
-     * An offset used when pasting without mouse/ghosting. It forces keyboard\r
-     * pastes to stack up on top of the latest paste performed with\r
-     * mouse/ghosting.\r
-     */\r
-    private final Point2D                 pasteOffset                        = new Point2D.Double(0, 0);\r
-\r
-    /**\r
-     * Stores the last MouseInfo for mouse 0 from the time of the previous\r
-     * received mouse event. Used for deciding the paste position.\r
-     * \r
-     * @see #getPastePos(DiagramSelection)\r
-     */\r
-    private MouseInfo                     mouseInfo;\r
-\r
-    /**\r
-     * Scale to use for pasted diagram monitors from variables.\r
-     */\r
-    private double                        monitorScale = 0.2;\r
-\r
-    /**\r
-     * For updating the diagram selection after graph changes.\r
-     */\r
-    private DiagramSelectionUpdater       selectionUpdater                   = null;\r
-\r
-    public CopyPasteHandler() {\r
-        this(new DefaultCopyPasteStrategy());\r
-    }\r
-\r
-    public CopyPasteHandler(CopyPasteStrategy strategy) {\r
-        this(strategy, null);\r
-    }\r
-\r
-    public CopyPasteHandler(IStatusLineManager statusLine) {\r
-        this(new DefaultCopyPasteStrategy(), statusLine);\r
-    }\r
-\r
-    public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine) {\r
-        this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();\r
-        this.statusLine = statusLine;\r
-    }\r
-\r
-    public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine, double monitorScale) {\r
-        this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();\r
-        this.statusLine = statusLine;\r
-        setMonitorScale(monitorScale);\r
-    }\r
-\r
-    public CopyPasteHandler setMonitorScale(double scale) {\r
-        this.monitorScale = scale;\r
-        return this;\r
-    }\r
-\r
-    public CopyPasteHandler setWorkbenchSite(MPart part) {\r
-        this.mPart = part;\r
-        return this;\r
-    }\r
-\r
-    protected boolean isPasteAllowed() {\r
-        return listenedMPart == null || hasFocus;\r
-    }\r
-\r
-    @Override\r
-    public void addedToContext(ICanvasContext ctx) {\r
-        super.addedToContext(ctx);\r
-        addProjectListener(peekProject());\r
-\r
-        listenedMPart = mPart;\r
-        if (listenedMPart != null) {\r
-            listenedMPart.getContext().get(EPartService.class).addPartListener(partListener2);\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void removedFromContext(ICanvasContext ctx) {\r
-        // Remove project selection if its ours to prevent leaking memory.\r
-        DiagramSelection ds = getProjectSelection();\r
-        if (ds.getSourceCanvas() == ctx) {\r
-            removeProjectSelection();\r
-        }\r
-\r
-        if (listenedMPart != null) {\r
-            listenedMPart.getContext().get(EPartService.class).removePartListener(partListener2);\r
-            listenedMPart = null;\r
-        }\r
-\r
-        removeProjectListener();\r
-        super.removedFromContext(ctx);\r
-    }\r
-\r
-    @Override\r
-    protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
-        if (oldDiagram != null) {\r
-            if (selectionUpdater != null) {\r
-                selectionUpdater.untrack();\r
-                selectionUpdater = null;\r
-            }\r
-        }\r
-        if (newDiagram != null) {\r
-            selectionUpdater = new DiagramSelectionUpdater(getContext(), newDiagram).track();\r
-        }\r
-    }\r
-\r
-    IHintListener projectDiagramSelectionListener = new HintListenerAdapter() {\r
-        @Override\r
-        public void hintChanged(IHintObservable sender, Key key, Object oldValue, final Object newValue) {\r
-            //System.out.println(this + ": " + sender + ": " + newValue);\r
-            ICanvasContext ctx = getContext();\r
-            if (ctx != null && hasHighlight()) {\r
-                //System.out.println(this + " HAS HIGHLIGHT");\r
-                if (newValue == null || ((DiagramSelection) newValue).getSourceCanvas() != ctx) {\r
-                    //System.out.println(this + " REMOVING HIGHLIGHT");\r
-                    ctx.getThreadAccess().asyncExec(new Runnable() {\r
-                        @Override\r
-                        public void run() {\r
-                            removeHighlight();\r
-                        }\r
-                    });\r
-                }\r
-            }\r
-        }\r
-    };\r
-\r
-    private void addProjectListener(IProject observable) {\r
-        if (observable != null) {\r
-            observable.addKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);\r
-            observedProject = observable;\r
-        }\r
-    }\r
-\r
-    private void removeProjectListener() {\r
-        if (observedProject != null) {\r
-            observedProject.removeKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);\r
-            observedProject = null;\r
-        }\r
-    }\r
-\r
-    IProject getProject() {\r
-        return Simantics.getProject();\r
-    }\r
-\r
-    IProject peekProject() {\r
-        return Simantics.peekProject();\r
-    }\r
-\r
-    public DiagramSelection getClipboardDiagramSelection() {\r
-        for (Set<Representation> content : Simantics.getClipboard().getContents()) {\r
-            try {\r
-                DiagramSelection sel = ClipboardUtils.accept(content, DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION);\r
-                if (sel != null)\r
-                    return sel;\r
-            } catch (DatabaseException e) {\r
-                Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to retrieve clipboard content.", e));\r
-            }\r
-        }\r
-        return DiagramSelection.EMPTY;\r
-    }\r
-\r
-    private DiagramSelection getProjectSelection() {\r
-        IProject p = peekProject();\r
-        if (p == null)\r
-            return DiagramSelection.EMPTY;\r
-        DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);\r
-        return ds != null ? ds : DiagramSelection.EMPTY;\r
-    }\r
-\r
-    void setDiagramSelection(DiagramSelection selection) {\r
-        setProjectSelection(selection);\r
-        strategy.copyToClipboard(selection);\r
-    }\r
-\r
-    void setProjectSelection(DiagramSelection selection) {\r
-        assert selection != null;\r
-        IProject p = getProject();\r
-        if (p == null)\r
-            throw new IllegalStateException("no active project for selection");\r
-        clearSG();\r
-        pasteWithoutMovingGhostCounter = 0;\r
-        pasteOffset.setLocation(0, 0);\r
-        p.setHint(KEY_DIAGRAM_SELECTION, selection);\r
-    }\r
-\r
-    private void removeProjectSelection() {\r
-        setProjectSelection(DiagramSelection.EMPTY);\r
-        removeHighlight();\r
-        clearSG();\r
-        setDirty();\r
-    }\r
-\r
-    /**\r
-     * This DELETE command handler is required to remove any diagram selections\r
-     * when modules are deleted. This will prevent extra highlight painting from\r
-     * being left over in case a module marked for copying is deleted.\r
-     */\r
-    @EventHandler(priority = 100)\r
-    public boolean handleDelete(CommandEvent e) {\r
-        if (e.command.equals( Commands.DELETE) ) {\r
-            if (highlightMode != null) {\r
-                message(null);\r
-                removeProjectSelection();\r
-                return false;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleKey(org.simantics.scenegraph.g2d.events.KeyEvent e) {\r
-        if (e.keyCode == KeyEvent.VK_CONTROL) {\r
-            DiagramSelection ds = getProjectSelection();\r
-            if (!ds.isEmpty()) {\r
-                if (e instanceof KeyPressedEvent) {\r
-                    if (ds.isCut())\r
-                        message("Move selection");\r
-                    else\r
-                        message("Paste selection");\r
-                    updateSG(ds);\r
-                } else if (e instanceof KeyReleasedEvent) {\r
-                    selectedMessage(ds);\r
-                    hideSG(ds);\r
-                }\r
-                setDirty();\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleCommand(CommandEvent e) {\r
-        if (e.command.equals( Commands.CANCEL) ) {\r
-            DiagramSelection s = getProjectSelection();\r
-            if (highlightMode != null || !s.isEmpty()) {\r
-                message(null);\r
-                removeProjectSelection();\r
-                return true;\r
-            }\r
-            return false;\r
-        }\r
-        if (e.command.equals( Commands.CUT ) || e.command.equals( Commands.COPY )) {\r
-            boolean ret = initiateCopy( e.command.equals( Commands.CUT ) );\r
-            if (!ret)\r
-                removeProjectSelection();\r
-            return ret;\r
-        }\r
-        // Must have focus in order to paste! If mouse has left the editor, no\r
-        // pastes should be performed.\r
-        if (isPasteAllowed() && e.command.equals( Commands.PASTE )) {\r
-            DiagramSelection ds = getClipboardDiagramSelection();\r
-            if (ds.isEmpty()) {\r
-                return tryPasteMonitors();\r
-            }\r
-            return paste(e.command, ds);\r
-        }\r
-        return false;\r
-    }\r
-\r
-    boolean initiateCopy(boolean cut) {\r
-        //System.out.println("INITIATING COPY");\r
-        int selectionId = 0;\r
-\r
-        Set<IElement> ss = sel.getSelection(selectionId);\r
-        Point2D copyPos = getCopyStartPos(ss);\r
-        if (ss.isEmpty() || copyPos == null) {\r
-            message("Nothing to " + (cut ? "cut" : "copy"));\r
-            return false;\r
-        }\r
-\r
-        // Validate selection, don't initiate copy if selection is invalid.\r
-        ElementAssortment ea = new ElementAssortment(ss);\r
-        String error = fixAssortment(ea, cut);\r
-\r
-        if (error != null) {\r
-            message(error);\r
-            return false;\r
-        }\r
-\r
-        pruneAssortment(ea, cut);\r
-        if (ea.isEmpty()) {\r
-            message("Nothing to " + (cut ? "cut" : "copy"));\r
-            return false;\r
-        }\r
-\r
-        if (DEBUG)\r
-            System.out.println("Start copy with " + ea);\r
-\r
-        // Treat OTHER type elements as disconnected floating graphical elements\r
-        // that are always copied.\r
-\r
-        // Anything with connection parts cannot be copied\r
-        if (!cut && ea.containsAny(CopyPasteUtil.CONNECTION_PARTS)) {\r
-            error("Cannot copy connection segments nor branch points.");\r
-            return false;\r
-        }\r
-//        if (ea.contains(CopyPasteUtil.MONITORS)) {\r
-//            // TODO: allow copying of monitors, means fixing the component reference relations\r
-//            error("Monitor " + (cut ? "cut" : "copy") + " not supported yet.");\r
-//            return false;\r
-//        }\r
-\r
-        // Pre-validate flag selection cases\r
-        if (ea.contains(CopyPasteUtil.FLAGS)) {\r
-            if (cut) {\r
-                // Allow cutting of single flags or cutting of any amount of\r
-                // flags within a single diagram.\r
-            } else {\r
-//                // Deny flag copy if other kinds of elements are selected.\r
-//                if (ea.containsAny(NOT_FLAGS)) {\r
-//                    return false;\r
-//                }\r
-                // Only copy flags without correspondence for now.\r
-                if (CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
-                    if (!CopyPasteUtil.checkFlagsCorrespondences(ea.flags, false)) {\r
-                        error("Cannot copy flag that already has a correspondence.");\r
-                        return false;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        // Selection is valid, go ahead and initiate a copy operation.\r
-        Resource sourceDiagram = diagram.<Resource>getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
-        DiagramSelection ds = new DiagramSelection(getContext(), sourceDiagram, ea.getAll(), cut, copyPos);\r
-        setDiagramSelection(ds);\r
-\r
-        removeHighlight();\r
-        highlightMode = new HighlightMode(ds, selectionId, HIGHLIGHT_PAINT_PRIORITY);\r
-        getContext().add(highlightMode);\r
-\r
-        selectedMessage(ds);\r
-\r
-//        System.out.println("INITIATED COPY: " + ds);\r
-        return true;\r
-    }\r
-\r
-    public boolean paste(Command command, DiagramSelection ds) {\r
-        if (ds.isEmpty()) {\r
-            message(null);\r
-            return false;\r
-        }\r
-        \r
-        TimeLogger.resetTimeAndLog(getClass(), "paste");\r
-\r
-        ElementObjectAssortment ea = ds.getAssortment();\r
-\r
-        if (DEBUG)\r
-            System.out.println("Initiate paste with " + ea);\r
-\r
-        try {\r
-            if (CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
-                // Do not copy if any of the flags already have a correspondence.\r
-                if (!CopyPasteUtil.onlyFlagsWithoutCorrespondence(Simantics.getSession(), ea))\r
-                    return true;\r
-\r
-                if (ds.isCut()) {\r
-                    normalPaste(command, ds, ea, true);\r
-                    removeHighlight();\r
-                    setDiagramSelection(DiagramSelection.EMPTY);\r
-                    resetSourceSelection(ds);\r
-                } else {\r
-                    normalPaste(command, ds, ea, false);\r
-                    // There is no point in leaving the old copy selection hanging\r
-                    // around after copying a flag since it is a one shot operation.\r
-                    removeHighlight();\r
-                    setDiagramSelection(DiagramSelection.EMPTY);\r
-                }\r
-            } else {\r
-                if (ds.isCut()) {\r
-                    normalPaste(command, ds, ea, true);\r
-                    removeHighlight();\r
-                    setDiagramSelection(DiagramSelection.EMPTY);\r
-                    resetSourceSelection(ds);\r
-                } else {\r
-                    normalPaste(command, ds, ea, false);\r
-\r
-//                // This is necessary to keep the ghost diagram properly up-to-date\r
-//                // after paste operations.\r
-//                setProjectSelection(ds.remutate());\r
-                }\r
-            }\r
-\r
-            message(null);\r
-\r
-        } catch (PasteException e) {\r
-            error( e.getLocalizedMessage() );\r
-            ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );\r
-        } catch (DatabaseException e) {\r
-            error( e.getLocalizedMessage() );\r
-            ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );\r
-        }\r
-\r
-        // Clear ghosting\r
-        clearSG();\r
-        setDirty();\r
-\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * In cut/paste cases, the source selection should reset (removed) after the\r
-     * paste operation has been performed. This method will reset the source\r
-     * selection of the specified DiagramSelection from the source canvas\r
-     * context. This will work regardless of which diagram/editor the selection\r
-     * originates from.\r
-     * \r
-     * @param ds the source selection to reset\r
-     */\r
-    void resetSourceSelection(DiagramSelection ds) {\r
-        ICanvasContext cc = ds.getSourceCanvas();\r
-        boolean sameDiagram = diagram == ds.getSourceDiagram();\r
-        if (!sameDiagram && cc != null && !cc.isDisposed()) {\r
-            for (Selection sourceSelection : cc.getItemsByClass(Selection.class)) {\r
-                Collection<IElement> empty = Collections.emptySet();\r
-                sourceSelection.setSelection(0, empty);\r
-            }\r
-        }\r
-    }\r
-\r
-    private void normalPaste(Command command, DiagramSelection ds, ElementObjectAssortment ea, boolean cut) throws PasteException {\r
-        final Point2D copyPos = ds.getCopyPos();\r
-        final Point2D pastePos = getPastePos(ds);\r
-\r
-        double dx = pastePos.getX() - copyPos.getX();\r
-        double dy = pastePos.getY() - copyPos.getY();\r
-        final Point2D pasteOffset = new Point2D.Double(dx, dy);\r
-\r
-        try {\r
-            // Get diagram contents before the paste operation\r
-            Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
-            final DiagramContentTracker tracker =\r
-                diagramResource == null ?\r
-                        null\r
-                        : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);\r
-\r
-            strategy.paste(new PasteOperation(command, getContext(), ds.getSourceDiagram(), diagramResource, diagram, ea, cut, pasteOffset));\r
-\r
-            if (tracker != null) {\r
-                // Get difference of diagram contents to find out what was added.\r
-                DiagramContentChanges changes = tracker.update();\r
-                selectionUpdater.setNewSelection(0, changes.pick(changes.elements, Change.ADDED));\r
-                if (DEBUG_SELECTION_UPDATE)\r
-                    System.out.println("stored diagram changes @" + System.currentTimeMillis() + ": " + selectionUpdater.getNewSelection());\r
-            }\r
-\r
-        } catch (DatabaseException e) {\r
-            ErrorLogger.defaultLogError(e);\r
-        }\r
-    }\r
-\r
-    private String fixAssortment(ElementAssortment ea, boolean cut) {\r
-        Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
-        List<Connection> conns = new ArrayList<Connection>();\r
-\r
-        // Include flags whether they are selected or not\r
-        for (IElement edge : ea.edges) {\r
-            Connection bc = diagramTopology.getConnection(edge, EdgeEnd.Begin);\r
-            if (bc != null && bc.node != null) {\r
-                if (bc.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)\r
-                    ea.add(ElementType.Flag, bc.node);\r
-            }\r
-            Connection ec = diagramTopology.getConnection(edge, EdgeEnd.End);\r
-            if (ec != null && ec.node != null) {\r
-                if (ec.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)\r
-                    ea.add(ElementType.Flag, ec.node);\r
-            }\r
-        }\r
-\r
-        // Include connections for selected flags if we're not potentially\r
-        // making flag continuations.\r
-        if (!CopyPasteUtil.isFlagsOnlySelection(ea)) {\r
-            for (IElement flag : ea.flags) {\r
-                conns.clear();\r
-                diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);\r
-                for (Connection conn : conns) {\r
-                    IElement edge = conn.edge;\r
-                    ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                    ea.add(ElementType.Connection, ce.getConnection());\r
-                }\r
-            }\r
-        }\r
-\r
-        // For each selected connection, make sure that all connected elements\r
-        // are in the selection, otherwise don't copy the connection.\r
-        List<IElement> connectionsToRemove = new ArrayList<IElement>(ea.connections.size());\r
-        for (IElement connection : ea.connections) {\r
-            ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);\r
-            Collection<Connection> connectors = ch.getTerminalConnections(connection, null);\r
-            boolean allConnectorsSelected = true;\r
-            for (Connection c : connectors) {\r
-                if (!(ea.nodes.contains(c.node) || ea.flags.contains(c.node) || ea.references.contains(c.node))) {\r
-                    allConnectorsSelected = false;\r
-                    break;\r
-                }\r
-            }\r
-            if (!allConnectorsSelected)\r
-                connectionsToRemove.add(connection);\r
-        }\r
-        ea.removeAll(ElementType.Connection, connectionsToRemove);\r
-\r
-        // Remove external flags whose connection(s) are not included\r
-        List<IElement> flagsToRemove = new ArrayList<IElement>(ea.flags.size());\r
-        for (IElement flag : ea.flags) {\r
-            if (CopyPasteUtil.flagIsExternal(flag)) {\r
-                conns.clear();\r
-                diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);\r
-                for (Connection conn : conns) {\r
-                    IElement edge = conn.edge;\r
-                    ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                    IElement connection = ce.getConnection();\r
-                    if (!ea.connections.contains(connection)) {\r
-                        flagsToRemove.add(flag);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-        ea.removeAll(ElementType.Flag, flagsToRemove);\r
-\r
-        if (cut) {\r
-            // Issue #1874: Prevent cut/paste for connected components\r
-            // https://www.simulationsite.net/redmine/issues/1874\r
-            // Fail if any of the included nodes has connections to it that are not\r
-            // included in the operation.\r
-            Collection<Connection> connections = new ArrayList<Connection>();\r
-            for (IElement node : CollectionUtils.join(ea.nodes, ea.flags)) {\r
-                connections.clear();\r
-                for (Connection connection : getAllConnections(node, connections)) {\r
-                    ConnectionEntity ce = connection.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                    IElement conn = ce.getConnection();\r
-                    if (ea.connections.contains(conn))\r
-                        continue;\r
-\r
-                    return "Cannot cut a node without all its connections.";\r
-                }\r
-            }\r
-        }\r
-\r
-        // Remove all reference elements from the assortment whose parent elements are not in it.\r
-        if (!cut) {\r
-            Collection<IElement> referenceElementsToRemove = new ArrayList<IElement>();\r
-            for (IElement ref : ea.references) {\r
-                IElement parent = ref.getHint(ElementHints.KEY_PARENT_ELEMENT);\r
-                if (parent != null) {\r
-                    if (!ea.all.contains(parent)) {\r
-                        // Cannot copy reference element whose parent is not copied also.\r
-                        referenceElementsToRemove.add(ref);\r
-                    }\r
-                } else {\r
-                    // OK, reference element has no parent. Free to copy/cut in any way.\r
-                }\r
-            }\r
-            if (!referenceElementsToRemove.isEmpty()) {\r
-                ea.removeAll(ElementType.Reference, referenceElementsToRemove);\r
-                if (ea.isEmpty()) {\r
-                    return "Cannot copy reference elements whose parent is not copied.";\r
-                }\r
-            }\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    private Collection<Terminal> getTerminals(IElement node) {\r
-        ArrayList<Terminal> result = new ArrayList<Terminal>();\r
-        for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))\r
-            tt.getTerminals(node, result);\r
-        return result;\r
-    }\r
-\r
-    private Collection<Connection> getAllConnections(IElement node, Collection<Connection> result) {\r
-        IDiagram diagram = node.getDiagram();\r
-        Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
-        if (topology == null)\r
-            return result;\r
-        for (Terminal t : getTerminals(node))\r
-            topology.getConnections(node, t, result);\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * Classifies the specified diagram selection elements into categories\r
-     * appointed by the <code>ElementType</code> enumeration and prunes any\r
-     * edges from the selection returned assortment whose both ends are not\r
-     * connected to nodes within the selection.\r
-     *\r
-     * @param ea\r
-     * @return\r
-     */\r
-    private void pruneAssortment(ElementAssortment ea, boolean cut) {\r
-        // Edges and branch points are never copied as such.\r
-        // They are always included as parts of copied connections.\r
-        // Edges can never be transformed or modified in any way as such,\r
-        // therefore it is safe to do this.\r
-        ea.clear(ElementType.Edge);\r
-\r
-        if (!cut)\r
-            ea.clear(ElementType.BranchPoint);\r
-    }\r
-\r
-    private Point2D getPastePos(DiagramSelection ds) {\r
-        MouseInfo mi = mouseUtil.getMouseInfo(0);\r
-        if (mi == null)\r
-            mi = mouseInfo;\r
-\r
-        if (mi != null) {\r
-            double xoff = mi.canvasPosition.getX() - ds.getCopyPos().getX();\r
-            double yoff = mi.canvasPosition.getY() - ds.getCopyPos().getY();\r
-            if (xoff == pasteOffset.getX() && yoff == pasteOffset.getY()) {\r
-                // The mouse has not moved since last paste so let's offset the\r
-                // paste down and right.\r
-                double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);\r
-                return new Point2D.Double(\r
-                        mi.canvasPosition.getX() + counterOffset,\r
-                        mi.canvasPosition.getY() + counterOffset);\r
-            }\r
-            pasteWithoutMovingGhostCounter = 0;\r
-            pasteOffset.setLocation(xoff, yoff);\r
-            return mi.canvasPosition;\r
-        } else {\r
-            //return ds.getCopyPos();\r
-            Point2D p = ds.getCopyPos();\r
-            double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);\r
-            return new Point2D.Double(\r
-                    p.getX() + pasteOffset.getX() + counterOffset,\r
-                    p.getY() + pasteOffset.getY() + counterOffset);\r
-        }\r
-    }\r
-\r
-    private double getOffsetGridSize() {\r
-        Double grid = getHint(GridPainter.KEY_GRID_SIZE);\r
-        return (grid == null || grid == 0) ? 1.0 : grid;\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @return\r
-     */\r
-    private static boolean isConnectionOrEdge(IElement e) {\r
-        ElementClass ec = e.getElementClass();\r
-        return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @return\r
-     */\r
-    private static boolean isMoveable(IElement e) {\r
-        ElementClass ec = e.getElementClass();\r
-        return ec.containsClass(Move.class) && ec.containsClass(Transform.class);\r
-    }\r
-\r
-    /**\r
-     * @param ss\r
-     * @return <code>null</code> if a point of reference cannot be determined\r
-     *         for the specified selection.\r
-     */\r
-    private Point2D getCopyStartPos(Set<IElement> ss) {\r
-//        MouseInfo mi = mouseUtil.getMouseInfo(0);\r
-//        if (mi != null) {\r
-//            return (Point2D) mi.canvasPosition.clone();\r
-//        }\r
-\r
-        // Find bounding rectangle top left corner\r
-        double mx = Double.MAX_VALUE;\r
-        double my = Double.MAX_VALUE;\r
-        for (IElement e : ss) {\r
-            if (isConnectionOrEdge(e) || !isMoveable(e))\r
-                continue;\r
-\r
-            //Point2D pos = ElementUtils.getPos(e);\r
-            Point2D pos = ElementUtils.getAbsolutePos(e);\r
-            if (pos.getX() < mx)\r
-                mx = pos.getX();\r
-            if (pos.getY() < my)\r
-                my = pos.getY();\r
-        }\r
-\r
-        // Find element nearest to the top left corner\r
-        Point2D nearest = null;\r
-        double dist = Double.MAX_VALUE;\r
-        for (IElement e : ss) {\r
-            if (isConnectionOrEdge(e) || !isMoveable(e))\r
-                continue;\r
-\r
-            Point2D pos = ElementUtils.getAbsolutePos(e);\r
-            double dx = pos.getX() - mx;\r
-            double dy = pos.getY() - my;\r
-            double d = dx*dx + dy*dy;\r
-            if (d < dist) {\r
-                dist = d;\r
-                nearest = pos;\r
-            }\r
-        }\r
-\r
-        return nearest;\r
-    }\r
-\r
-    private void moveGhostElements(DiagramSelection ds, Point2D pastePos) {\r
-        Point2D copyPos = ds.getCopyPos();\r
-        double dx = (pastePos.getX() - copyPos.getX());\r
-        double dy = (pastePos.getY() - copyPos.getY());\r
-\r
-        // Snap delta\r
-        Point2D snap = CopyPasteUtil.snap(getContext(), new Point2D.Double(dx, dy));\r
-\r
-        ghostNode.setTransform(AffineTransform.getTranslateInstance(snap.getX(), snap.getY()));\r
-        //System.out.println("ghost node: " + ghostNode);\r
-    }\r
-\r
-    protected SingleElementNode ghostNode = null;\r
-    protected NodeMapper ghostNodeMapper = new NodeMapper();\r
-\r
-    @SGInit\r
-    public void initSG(G2DParentNode parent) {\r
-        ghostNode = parent.addNode("cut/copy ghost", SingleElementNode.class);\r
-        ghostNode.setZIndex(COPY_GHOSTING_PAINT_PRIORITY);\r
-        //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.40f));\r
-        ghostNode.setVisible(Boolean.FALSE);\r
-    }\r
-\r
-    @SGCleanup\r
-    public void cleanupSG() {\r
-        ghostNode.remove();\r
-    }\r
-\r
-    void clearSG() {\r
-        ghostNode.removeNodes();\r
-        ghostNode.setVisible(Boolean.FALSE);\r
-        ghostNodeMapper.clear();\r
-    }\r
-\r
-    /**\r
-     * @param selection\r
-     * @return <code>true</code> if the ghost nodes were hidden and a refresh is\r
-     *         needed\r
-     */\r
-    boolean hideSG(DiagramSelection selection) {\r
-        if (ghostNode.isVisible()) {\r
-            // Make sure there's no leftover graphics.\r
-            ghostNode.removeNodes();\r
-            ghostNode.setVisible(Boolean.FALSE);\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    protected void scheduleActivateOwnerPart() {\r
-        if (mPart == null)\r
-            return;\r
-        SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                hasFocus = true;\r
-                mPart.getContext().get(EPartService.class).activate(mPart);\r
-            }\r
-        });\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleMouse(MouseExitEvent e) {\r
-        DiagramSelection ds = getProjectSelection();\r
-        if (!ds.isEmpty()) {\r
-            if (hideSG(ds))\r
-                setDirty();\r
-        }\r
-\r
-        // The part might no longer have focus.\r
-        // [Tuukka] commented out to fix Apros #3678\r
-        //hasFocus = false;\r
-\r
-        return false;\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleMouse(MouseEnterEvent e) {\r
-        DiagramSelection ds = getProjectSelection();\r
-        if (!ds.isEmpty()) {\r
-            if (mPart != null) {\r
-                if (inPasteMode(e)) {\r
-                    scheduleActivateOwnerPart();\r
-                }\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleMouse(MouseMovedEvent e) {\r
-        DiagramSelection ds = getProjectSelection();\r
-        if (!ds.isEmpty()) {\r
-            MouseInfo mi = mouseUtil.getMouseInfo(0);\r
-            //System.out.println("LAST MOUSE INFO: " + mi);\r
-            if (mi != null)\r
-                mouseInfo = mi;\r
-\r
-            if (inPasteMode(e)) {\r
-                // Make sure that this owner part is active now.\r
-                if (!hasFocus)\r
-                    scheduleActivateOwnerPart();\r
-\r
-                updateSG(ds);\r
-                setDirty();\r
-            } else {\r
-                if (hideSG(ds))\r
-                    setDirty();\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    void updateSG(DiagramSelection selection) {\r
-        MouseInfo mi = mouseUtil.getMouseInfo(0);\r
-        if (mi == null)\r
-            return;\r
-\r
-        //ghostNode.setComposite(AlphaComposite.SrcAtop.derive(0.40f));\r
-        //ghostNode.setComposite(null);\r
-\r
-        moveGhostElements(selection, mi.canvasPosition);\r
-        if (selection.getSourceCanvas() != getContext()) {\r
-            for (IElement e : selection.getOriginalElements()) {\r
-                INode node = e.getHint(ElementHints.KEY_SG_NODE);\r
-                //System.out.println("ghost element: " + e + ", node=" + node);\r
-                if (node instanceof IG2DNode) {\r
-                    LocalDelegateNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e),\r
-                            LocalDelegateNode.class);\r
-                    delegate.setDelegate( (IG2DNode) node );\r
-                }\r
-            }\r
-        } else {\r
-            for (IElement e : selection.getOriginalElements()) {\r
-                //System.out.println("ghost element: " + e);\r
-                INode node = e.getHint(ElementHints.KEY_SG_NODE);\r
-                if (node != null) {\r
-                    //System.out.println("ghost node: " + node);\r
-                    ghostNodeMapper.add(node);\r
-                    String nodeId = ghostNodeMapper.getId(node);\r
-                    //System.out.println("ghost node id: " + nodeId);\r
-                    LinkNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e), LinkNode.class);\r
-                    delegate.setDelegateId( nodeId );\r
-                }\r
-            }\r
-        }\r
-\r
-        ghostNode.setVisible(true);\r
-    }\r
-\r
-    private <T extends INode> T getOrCreateNode(ParentNode<?> parentNode, String id, Class<T> clazz) {\r
-        INode n = ghostNode.getNode(id);\r
-        if (clazz.isInstance(n))\r
-            return clazz.cast(n);\r
-        ghostNode.removeNode(id);\r
-        return ghostNode.addNode(id, clazz);\r
-    }\r
-\r
-    private boolean hasHighlight() {\r
-        return highlightMode != null;\r
-    }\r
-\r
-    private void removeHighlight() {\r
-        if (isRemoved())\r
-            return;\r
-        assert getContext().getThreadAccess().currentThreadAccess();\r
-        if (highlightMode != null) {\r
-            if (!highlightMode.isRemoved()) {\r
-                highlightMode.remove();\r
-                setDirty();\r
-            }\r
-            highlightMode = null;\r
-        }\r
-    }\r
-\r
-    private boolean inPasteMode(MouseEvent e) {\r
-        return (e.stateMask & MouseEvent.CTRL_MASK) != 0;\r
-    }\r
-\r
-    void selectedMessage(DiagramSelection ds) {\r
-        int size = ds.getOriginalElements().size();\r
-        StringBuilder sb = new StringBuilder();\r
-        if (size == 0) {\r
-            sb.append("No elements to ");\r
-            if (ds.isCut())\r
-                sb.append("cut");\r
-            else\r
-                sb.append("copy");\r
-        } else {\r
-            if (ds.isCut())\r
-                sb.append("Cut ");\r
-            else\r
-                sb.append("Copied ");\r
-            sb.append(size);\r
-            sb.append(" element");\r
-            if (size > 1)\r
-                sb.append('s');\r
-        }\r
-        message(sb.toString());\r
-    }\r
-\r
-    void message(final String message) {\r
-        if (statusLine == null)\r
-            return;\r
-        swtExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                statusLine.setMessage(message);\r
-                statusLine.setErrorMessage(null);\r
-            }\r
-        });\r
-    }\r
-\r
-    void error(final String message) {\r
-        if (statusLine == null)\r
-            return;\r
-        swtExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                statusLine.setErrorMessage(message);\r
-            }\r
-        });\r
-    }\r
-\r
-    void swtExec(Runnable r) {\r
-        ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);\r
-    }\r
-\r
-    // MONITOR PASTE SUPPORT\r
-\r
-    private boolean tryPasteMonitors() {\r
-        SimanticsClipboard clipboard = Simantics.getClipboard();\r
-        for (Set<Representation> content : clipboard.getContents()) {\r
-            try {\r
-                final Variable var_ = ClipboardUtils.accept(content, SimanticsKeys.KEY_VARIABLE);\r
-                if (var_ != null) {\r
-                    final Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
-                    final Resource runtime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);\r
-                    final Resource elementResource = Simantics.getSession().syncRequest(new Read<Resource>() {\r
-                        @Override\r
-                        public Resource perform(ReadGraph graph) throws DatabaseException {\r
-                            DiagramResource DIA = DiagramResource.getInstance(graph);\r
-\r
-                            String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);\r
-                            if (diagramVariable == null)\r
-                                return null;\r
-\r
-                            Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);\r
-                            if (diaVar == null)\r
-                                return null;\r
-\r
-                            Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));\r
-                            if(var == null)\r
-                                return null;\r
-\r
-                            Variable component = Variables.getChild(graph, diaVar, var);\r
-                            if (component == null)\r
-                                return null;\r
-\r
-                            Resource componentResource = component.getPossibleRepresents(graph);\r
-                            if (componentResource == null)\r
-                                return null;\r
-\r
-                            return graph.getPossibleObject(componentResource, ModelingResources.getInstance(graph).ComponentToElement);\r
-                        }\r
-                    });\r
-\r
-                    if (elementResource == null)\r
-                        return false;\r
-\r
-                    final AffineTransform monitorTransform = Simantics.getSession().syncRequest(new Read<AffineTransform>() {\r
-                        @Override\r
-                        public AffineTransform perform(ReadGraph graph) throws DatabaseException {\r
-                            AffineTransform at = null;\r
-\r
-                            if (graph.isInstanceOf(elementResource, DiagramResource.getInstance(graph).Connection)) {\r
-                                Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, elementResource);\r
-                                if (tailNode != null) {\r
-                                    at = DiagramGraphUtil.getAffineTransform(graph, tailNode);\r
-                                }\r
-                            }\r
-                            if (at == null)\r
-                                at = DiagramGraphUtil.getAffineTransform(graph, elementResource);\r
-\r
-                            return at;\r
-                        }\r
-                    });\r
-\r
-                    MouseInfo mi = mouseUtil.getMouseInfo(0);\r
-                    if (mi == null)\r
-                        mi = mouseInfo;\r
-                    final double dx = mi.canvasPosition.getX() - monitorTransform.getTranslateX();\r
-                    final double dy = mi.canvasPosition.getY() - monitorTransform.getTranslateY();\r
-\r
-                    Simantics.getSession().asyncRequest(new WriteRequest() {\r
-\r
-                        @Override\r
-                        public void perform(WriteGraph graph) throws DatabaseException {\r
-                            Layer0 L0 = Layer0.getInstance(graph);\r
-                            Layer0X L0X = Layer0X.getInstance(graph);\r
-                            DiagramResource DIA = DiagramResource.getInstance(graph);\r
-                            G2DResource G2D = G2DResource.getInstance(graph);\r
-\r
-                            String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);\r
-                            if (diagramVariable == null)\r
-                                return;\r
-\r
-                            Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);\r
-                            if (diaVar == null)\r
-                                return;\r
-\r
-                            Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));\r
-                            if(var == null)\r
-                                return;\r
-\r
-                            Variable component = Variables.getChild(graph, diaVar, var);\r
-                            if (component == null)\r
-                                return;\r
-\r
-                            Resource componentResource = component.getPossibleRepresents(graph);\r
-                            if (componentResource == null)\r
-                                return;\r
-\r
-                            String suffix = Variables.getRVI(graph, component, var);\r
-\r
-                            Resource resource = graph.newResource();\r
-                            graph.claim(resource, L0.InstanceOf, null, DIA.Monitor);\r
-\r
-                            final double scale = monitorScale;\r
-\r
-                            DiagramGraphUtil.setTransform(graph, resource, new AffineTransform(scale, 0, 0, scale, dx, dy));\r
-\r
-                            OrderedSetUtils.add(graph, diagramResource, resource);\r
-\r
-                            // 5.1. Give running name to element and increment the counter attached to the diagram.\r
-                            Long l = graph.getPossibleRelatedValue(diagramResource, DIA.HasModCount, Bindings.LONG);\r
-                            if (l == null)\r
-                                l = Long.valueOf(0L);\r
-                            graph.claimLiteral(resource, L0.HasName, l.toString(), Bindings.STRING);\r
-                            graph.claimLiteral(diagramResource, DIA.HasModCount, ++l, Bindings.LONG);\r
-\r
-                            // 5.2. Make the diagram consist of the new element\r
-                            graph.claim(diagramResource, L0.ConsistsOf, resource);\r
-\r
-                            graph.claim(resource, G2D.HasHorizontalAlignment, null, G2D.Alignment_Leading);\r
-                            graph.claimLiteral(resource, DIA.HasDirection, 0.0);\r
-\r
-                            graph.claim(resource, DIA.HasMonitorComponent, componentResource);\r
-                            graph.claimLiteral(resource, DIA.HasMonitorSuffix, suffix);\r
-\r
-                            Resource model = Variables.getModel(graph, diaVar);\r
-                            if (model != null) {\r
-                                Resource template = graph.getPossibleObject(model, DIA.HasDefaultMonitorTemplate);\r
-                                if (template != null) {\r
-                                    graph.claim(resource, L0X.ObtainsProperty1, null, template);\r
-                                }\r
-                            }\r
-                            \r
-                        }\r
-                    });\r
-                }\r
-            } catch (DatabaseException e1) {\r
-               Logger.defaultLogError(e1);\r
-            }\r
-        }\r
-        return true;\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.handler.e4;
+
+import java.awt.Color;
+import java.awt.event.KeyEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.IPartListener;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.OrderedSetUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.util.ClipboardUtils;
+import org.simantics.db.layer0.util.SimanticsClipboard;
+import org.simantics.db.layer0.util.SimanticsClipboard.Representation;
+import org.simantics.db.layer0.util.SimanticsKeys;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.db.request.Read;
+import org.simantics.diagram.content.Change;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.content.DiagramContentChanges;
+import org.simantics.diagram.content.DiagramContentTracker;
+import org.simantics.diagram.handler.CopyPasteStrategy;
+import org.simantics.diagram.handler.CopyPasteUtil;
+import org.simantics.diagram.handler.DefaultCopyPasteStrategy;
+import org.simantics.diagram.handler.DiagramSelection;
+import org.simantics.diagram.handler.DiagramSelectionRepresentation;
+import org.simantics.diagram.handler.ElementAssortment;
+import org.simantics.diagram.handler.ElementObjectAssortment;
+import org.simantics.diagram.handler.ElementType;
+import org.simantics.diagram.handler.HighlightMode;
+import org.simantics.diagram.handler.PasteException;
+import org.simantics.diagram.handler.PasteOperation;
+import org.simantics.diagram.internal.Activator;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.stubs.G2DResource;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;
+import org.simantics.diagram.ui.DiagramModelHints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.ConnectionEntity;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.Move;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.element.handler.Transform;
+import org.simantics.g2d.elementclass.FlagHandler;
+import org.simantics.g2d.participant.GridPainter;
+import org.simantics.g2d.participant.MouseUtil;
+import org.simantics.g2d.participant.MouseUtil.MouseInfo;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.operation.Layer0X;
+import org.simantics.project.IProject;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.ParentNode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.g2d.events.command.Command;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scenegraph.g2d.nodes.LinkNode;
+import org.simantics.scenegraph.g2d.nodes.LocalDelegateNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scenegraph.utils.NodeMapper;
+import org.simantics.utils.datastructures.collections.CollectionUtils;
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintListener;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+import org.simantics.utils.logging.TimeLogger;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.SWTUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and
+ * Commands.PASTE commands for an IDiagram.
+ * 
+ * <p>
+ * The handler attempts to copy/paste the current selection for pointer 0,
+ * meaning {@link Selection#SELECTION0}.
+ * </p>
+ * 
+ * <p>
+ * The handler logic follows the specifications at <a
+ * href="http://www.simantics.org/wiki/index.php/UC:Copy_Item" >UC:Copy Item</a>
+ * and <a href="http://www.simantics.org/wiki/index.php/UC:Cut_Item" >UC:Cut
+ * Item</a>.
+ * </p>
+ * 
+ * @see Selection current diagram selection source
+ * 
+ * @author Tuukka Lehtonen
+ * 
+ *         FIXME: translucent ghosting makes rendering REALLY sluggish, add a
+ *         timer that makes the ghost opaque when the user is interacting and
+ *         translucent only when still for a while.
+ */
+public class CopyPasteHandler extends AbstractDiagramParticipant {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CopyPasteHandler.class);
+
+    public static final Key KEY_CUT_SELECTION_FRAME_COLOR      = new KeyOf(Color.class, "CUT_SELECTION_FRAME_COLOR");
+    public static final Key KEY_CUT_SELECTION_CONTENT_COLOR    = new KeyOf(Color.class, "CUT_SELECTION_CONTENT_COLOR");
+    public static final Key KEY_COPIED_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "COPY_SELECTION_FRAME_COLOR");
+    public static final Key KEY_COPIED_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "COPY_SELECTION_CONTENT_COLOR");
+
+    /**
+     * A key for storing the current selection within the currently active
+     * project for copy/paste implementation.
+     */
+    private static final Key               KEY_DIAGRAM_SELECTION              = DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION;
+
+    private static final boolean          DEBUG                              = false;
+    private static final boolean          DEBUG_SELECTION_UPDATE             = false;
+
+    public static final int               COPY_GHOSTING_PAINT_PRIORITY       = 600;
+
+    private static final int              HIGHLIGHT_PAINT_PRIORITY           = 500;
+
+    @Dependency
+    private Selection                     sel;
+    @Dependency
+    private MouseUtil                     mouseUtil;
+
+    protected final IStatusLineManager    statusLine;
+    protected final CopyPasteStrategy     strategy;
+    protected MPart                       mPart;
+    protected MPart                       listenedMPart;
+
+    /**
+     * Workbench part listener for {@link #listenedMPart} to keep proper track of
+     * whether this part is focused or not.
+     */
+    
+    IPartListener partListener2 = new IPartListener() {
+        
+        @Override
+        public void partVisible(MPart part) {
+        }
+        
+        @Override
+        public void partHidden(MPart part) {
+            // Make sure this listener is removed properly in any case.
+            if (listenedMPart != null) {
+                mPart.getContext().get(EPartService.class).removePartListener(partListener2);
+                listenedMPart = null;
+            }
+        }
+        
+        @Override
+        public void partDeactivated(MPart part) {
+            if (part == mPart)
+                hasFocus = false;
+        }
+        
+        @Override
+        public void partBroughtToTop(MPart part) {
+        }
+        
+        @Override
+        public void partActivated(MPart part) {
+            if (part == mPart)
+                hasFocus = true;
+        }
+    };
+
+    /**
+     * Indicates whether CopyPasteHandler thinks that {@link #mPart} has focus. 
+     */
+    protected boolean                     hasFocus                           = false;
+
+    private AbstractCanvasParticipant     highlightMode                      = null;
+    private IProject                      observedProject                    = null;
+
+    /**
+     * A counter for how many times pasting has been performed without mouse and
+     * ghosting or how many times paste has been performed without moving the
+     * mouse on the diagram. This is used to offset the paste position
+     * accordingly so that copied elements don't wind up directly on top of each
+     * other.
+     */
+    private int                           pasteWithoutMovingGhostCounter     = 0;
+
+    /**
+     * An offset used when pasting without mouse/ghosting. It forces keyboard
+     * pastes to stack up on top of the latest paste performed with
+     * mouse/ghosting.
+     */
+    private final Point2D                 pasteOffset                        = new Point2D.Double(0, 0);
+
+    /**
+     * Stores the last MouseInfo for mouse 0 from the time of the previous
+     * received mouse event. Used for deciding the paste position.
+     * 
+     * @see #getPastePos(DiagramSelection)
+     */
+    private MouseInfo                     mouseInfo;
+
+    /**
+     * Scale to use for pasted diagram monitors from variables.
+     */
+    private double                        monitorScale = 0.2;
+
+    /**
+     * For updating the diagram selection after graph changes.
+     */
+    private DiagramSelectionUpdater       selectionUpdater                   = null;
+
+    public CopyPasteHandler() {
+        this(new DefaultCopyPasteStrategy());
+    }
+
+    public CopyPasteHandler(CopyPasteStrategy strategy) {
+        this(strategy, null);
+    }
+
+    public CopyPasteHandler(IStatusLineManager statusLine) {
+        this(new DefaultCopyPasteStrategy(), statusLine);
+    }
+
+    public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine) {
+        this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
+        this.statusLine = statusLine;
+    }
+
+    public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine, double monitorScale) {
+        this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
+        this.statusLine = statusLine;
+        setMonitorScale(monitorScale);
+    }
+
+    public CopyPasteHandler setMonitorScale(double scale) {
+        this.monitorScale = scale;
+        return this;
+    }
+
+    public CopyPasteHandler setWorkbenchSite(MPart part) {
+        this.mPart = part;
+        return this;
+    }
+
+    protected boolean isPasteAllowed() {
+        return listenedMPart == null || hasFocus;
+    }
+
+    @Override
+    public void addedToContext(ICanvasContext ctx) {
+        super.addedToContext(ctx);
+        addProjectListener(peekProject());
+
+        listenedMPart = mPart;
+        if (listenedMPart != null) {
+            listenedMPart.getContext().get(EPartService.class).addPartListener(partListener2);
+        }
+    }
+
+    @Override
+    public void removedFromContext(ICanvasContext ctx) {
+        // Remove project selection if its ours to prevent leaking memory.
+        DiagramSelection ds = getProjectSelection();
+        if (ds.getSourceCanvas() == ctx) {
+            removeProjectSelection();
+        }
+
+        if (listenedMPart != null) {
+            listenedMPart.getContext().get(EPartService.class).removePartListener(partListener2);
+            listenedMPart = null;
+        }
+
+        removeProjectListener();
+        super.removedFromContext(ctx);
+    }
+
+    @Override
+    protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
+        if (oldDiagram != null) {
+            if (selectionUpdater != null) {
+                selectionUpdater.untrack();
+                selectionUpdater = null;
+            }
+        }
+        if (newDiagram != null) {
+            selectionUpdater = new DiagramSelectionUpdater(getContext(), newDiagram).track();
+        }
+    }
+
+    IHintListener projectDiagramSelectionListener = new HintListenerAdapter() {
+        @Override
+        public void hintChanged(IHintObservable sender, Key key, Object oldValue, final Object newValue) {
+            //System.out.println(this + ": " + sender + ": " + newValue);
+            ICanvasContext ctx = getContext();
+            if (ctx != null && hasHighlight()) {
+                //System.out.println(this + " HAS HIGHLIGHT");
+                if (newValue == null || ((DiagramSelection) newValue).getSourceCanvas() != ctx) {
+                    //System.out.println(this + " REMOVING HIGHLIGHT");
+                    ctx.getThreadAccess().asyncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            removeHighlight();
+                        }
+                    });
+                }
+            }
+        }
+    };
+
+    private void addProjectListener(IProject observable) {
+        if (observable != null) {
+            observable.addKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
+            observedProject = observable;
+        }
+    }
+
+    private void removeProjectListener() {
+        if (observedProject != null) {
+            observedProject.removeKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
+            observedProject = null;
+        }
+    }
+
+    IProject getProject() {
+        return Simantics.getProject();
+    }
+
+    IProject peekProject() {
+        return Simantics.peekProject();
+    }
+
+    public DiagramSelection getClipboardDiagramSelection() {
+        for (Set<Representation> content : Simantics.getClipboard().getContents()) {
+            try {
+                DiagramSelection sel = ClipboardUtils.accept(content, DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION);
+                if (sel != null)
+                    return sel;
+            } catch (DatabaseException e) {
+                Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to retrieve clipboard content.", e));
+            }
+        }
+        return DiagramSelection.EMPTY;
+    }
+
+    private DiagramSelection getProjectSelection() {
+        IProject p = peekProject();
+        if (p == null)
+            return DiagramSelection.EMPTY;
+        DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);
+        return ds != null ? ds : DiagramSelection.EMPTY;
+    }
+
+    void setDiagramSelection(DiagramSelection selection) {
+        setProjectSelection(selection);
+        strategy.copyToClipboard(selection);
+    }
+
+    void setProjectSelection(DiagramSelection selection) {
+        assert selection != null;
+        IProject p = getProject();
+        if (p == null)
+            throw new IllegalStateException("no active project for selection");
+        clearSG();
+        pasteWithoutMovingGhostCounter = 0;
+        pasteOffset.setLocation(0, 0);
+        p.setHint(KEY_DIAGRAM_SELECTION, selection);
+    }
+
+    private void removeProjectSelection() {
+        setProjectSelection(DiagramSelection.EMPTY);
+        removeHighlight();
+        clearSG();
+        setDirty();
+    }
+
+    /**
+     * This DELETE command handler is required to remove any diagram selections
+     * when modules are deleted. This will prevent extra highlight painting from
+     * being left over in case a module marked for copying is deleted.
+     */
+    @EventHandler(priority = 100)
+    public boolean handleDelete(CommandEvent e) {
+        if (e.command.equals( Commands.DELETE) ) {
+            if (highlightMode != null) {
+                message(null);
+                removeProjectSelection();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleKey(org.simantics.scenegraph.g2d.events.KeyEvent e) {
+        if (e.keyCode == KeyEvent.VK_CONTROL) {
+            DiagramSelection ds = getProjectSelection();
+            if (!ds.isEmpty()) {
+                if (e instanceof KeyPressedEvent) {
+                    if (ds.isCut())
+                        message("Move selection");
+                    else
+                        message("Paste selection");
+                    updateSG(ds);
+                } else if (e instanceof KeyReleasedEvent) {
+                    selectedMessage(ds);
+                    hideSG(ds);
+                }
+                setDirty();
+            }
+        }
+        return false;
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleCommand(CommandEvent e) {
+        if (e.command.equals( Commands.CANCEL) ) {
+            DiagramSelection s = getProjectSelection();
+            if (highlightMode != null || !s.isEmpty()) {
+                message(null);
+                removeProjectSelection();
+                return true;
+            }
+            return false;
+        }
+        if (e.command.equals( Commands.CUT ) || e.command.equals( Commands.COPY )) {
+            boolean ret = initiateCopy( e.command.equals( Commands.CUT ) );
+            if (!ret)
+                removeProjectSelection();
+            return ret;
+        }
+        // Must have focus in order to paste! If mouse has left the editor, no
+        // pastes should be performed.
+        if (isPasteAllowed() && e.command.equals( Commands.PASTE )) {
+            DiagramSelection ds = getClipboardDiagramSelection();
+            if (ds.isEmpty()) {
+                return tryPasteMonitors();
+            }
+            return paste(e.command, ds);
+        }
+        return false;
+    }
+
+    boolean initiateCopy(boolean cut) {
+        //System.out.println("INITIATING COPY");
+        int selectionId = 0;
+
+        Set<IElement> ss = sel.getSelection(selectionId);
+        Point2D copyPos = getCopyStartPos(ss);
+        if (ss.isEmpty() || copyPos == null) {
+            message("Nothing to " + (cut ? "cut" : "copy"));
+            return false;
+        }
+
+        // Validate selection, don't initiate copy if selection is invalid.
+        ElementAssortment ea = new ElementAssortment(ss);
+        String error = fixAssortment(ea, cut);
+
+        if (error != null) {
+            message(error);
+            return false;
+        }
+
+        pruneAssortment(ea, cut);
+        if (ea.isEmpty()) {
+            message("Nothing to " + (cut ? "cut" : "copy"));
+            return false;
+        }
+
+        if (DEBUG)
+            System.out.println("Start copy with " + ea);
+
+        // Treat OTHER type elements as disconnected floating graphical elements
+        // that are always copied.
+
+        // Anything with connection parts cannot be copied
+        if (!cut && ea.containsAny(CopyPasteUtil.CONNECTION_PARTS)) {
+            error("Cannot copy connection segments nor branch points.");
+            return false;
+        }
+//        if (ea.contains(CopyPasteUtil.MONITORS)) {
+//            // TODO: allow copying of monitors, means fixing the component reference relations
+//            error("Monitor " + (cut ? "cut" : "copy") + " not supported yet.");
+//            return false;
+//        }
+
+        // Pre-validate flag selection cases
+        if (ea.contains(CopyPasteUtil.FLAGS)) {
+            if (cut) {
+                // Allow cutting of single flags or cutting of any amount of
+                // flags within a single diagram.
+            } else {
+//                // Deny flag copy if other kinds of elements are selected.
+//                if (ea.containsAny(NOT_FLAGS)) {
+//                    return false;
+//                }
+                // Only copy flags without correspondence for now.
+                if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
+                    if (!CopyPasteUtil.checkFlagsCorrespondences(ea.flags, false)) {
+                        error("Cannot copy flag that already has a correspondence.");
+                        return false;
+                    }
+                }
+            }
+        }
+
+        // Selection is valid, go ahead and initiate a copy operation.
+        Resource sourceDiagram = diagram.<Resource>getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+        DiagramSelection ds = new DiagramSelection(getContext(), sourceDiagram, ea.getAll(), cut, copyPos);
+        setDiagramSelection(ds);
+
+        removeHighlight();
+        highlightMode = new HighlightMode(ds, selectionId, HIGHLIGHT_PAINT_PRIORITY);
+        getContext().add(highlightMode);
+
+        selectedMessage(ds);
+
+//        System.out.println("INITIATED COPY: " + ds);
+        return true;
+    }
+
+    public boolean paste(Command command, DiagramSelection ds) {
+        if (ds.isEmpty()) {
+            message(null);
+            return false;
+        }
+        
+        TimeLogger.resetTimeAndLog(getClass(), "paste");
+
+        ElementObjectAssortment ea = ds.getAssortment();
+
+        if (DEBUG)
+            System.out.println("Initiate paste with " + ea);
+
+        try {
+            if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
+                // Do not copy if any of the flags already have a correspondence.
+                if (!CopyPasteUtil.onlyFlagsWithoutCorrespondence(Simantics.getSession(), ea))
+                    return true;
+
+                if (ds.isCut()) {
+                    normalPaste(command, ds, ea, true);
+                    removeHighlight();
+                    setDiagramSelection(DiagramSelection.EMPTY);
+                    resetSourceSelection(ds);
+                } else {
+                    normalPaste(command, ds, ea, false);
+                    // There is no point in leaving the old copy selection hanging
+                    // around after copying a flag since it is a one shot operation.
+                    removeHighlight();
+                    setDiagramSelection(DiagramSelection.EMPTY);
+                }
+            } else {
+                if (ds.isCut()) {
+                    normalPaste(command, ds, ea, true);
+                    removeHighlight();
+                    setDiagramSelection(DiagramSelection.EMPTY);
+                    resetSourceSelection(ds);
+                } else {
+                    normalPaste(command, ds, ea, false);
+
+//                // This is necessary to keep the ghost diagram properly up-to-date
+//                // after paste operations.
+//                setProjectSelection(ds.remutate());
+                }
+            }
+
+            message(null);
+
+        } catch (PasteException e) {
+            error( e.getLocalizedMessage() );
+            ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
+        } catch (DatabaseException e) {
+            error( e.getLocalizedMessage() );
+            ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
+        }
+
+        // Clear ghosting
+        clearSG();
+        setDirty();
+
+        return true;
+    }
+
+    /**
+     * In cut/paste cases, the source selection should reset (removed) after the
+     * paste operation has been performed. This method will reset the source
+     * selection of the specified DiagramSelection from the source canvas
+     * context. This will work regardless of which diagram/editor the selection
+     * originates from.
+     * 
+     * @param ds the source selection to reset
+     */
+    void resetSourceSelection(DiagramSelection ds) {
+        ICanvasContext cc = ds.getSourceCanvas();
+        boolean sameDiagram = diagram == ds.getSourceDiagram();
+        if (!sameDiagram && cc != null && !cc.isDisposed()) {
+            for (Selection sourceSelection : cc.getItemsByClass(Selection.class)) {
+                Collection<IElement> empty = Collections.emptySet();
+                sourceSelection.setSelection(0, empty);
+            }
+        }
+    }
+
+    private void normalPaste(Command command, DiagramSelection ds, ElementObjectAssortment ea, boolean cut) throws PasteException {
+        final Point2D copyPos = ds.getCopyPos();
+        final Point2D pastePos = getPastePos(ds);
+
+        double dx = pastePos.getX() - copyPos.getX();
+        double dy = pastePos.getY() - copyPos.getY();
+        final Point2D pasteOffset = new Point2D.Double(dx, dy);
+
+        try {
+            // Get diagram contents before the paste operation
+            Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+            final DiagramContentTracker tracker =
+                diagramResource == null ?
+                        null
+                        : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);
+
+            strategy.paste(new PasteOperation(command, getContext(), ds.getSourceDiagram(), diagramResource, diagram, ea, cut, pasteOffset));
+
+            if (tracker != null) {
+                // Get difference of diagram contents to find out what was added.
+                DiagramContentChanges changes = tracker.update();
+                selectionUpdater.setNewSelection(0, changes.pick(changes.elements, Change.ADDED));
+                if (DEBUG_SELECTION_UPDATE)
+                    System.out.println("stored diagram changes @" + System.currentTimeMillis() + ": " + selectionUpdater.getNewSelection());
+            }
+
+        } catch (DatabaseException e) {
+            ErrorLogger.defaultLogError(e);
+        }
+    }
+
+    private String fixAssortment(ElementAssortment ea, boolean cut) {
+        Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
+        List<Connection> conns = new ArrayList<Connection>();
+
+        // Include flags whether they are selected or not
+        for (IElement edge : ea.edges) {
+            Connection bc = diagramTopology.getConnection(edge, EdgeEnd.Begin);
+            if (bc != null && bc.node != null) {
+                if (bc.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
+                    ea.add(ElementType.Flag, bc.node);
+            }
+            Connection ec = diagramTopology.getConnection(edge, EdgeEnd.End);
+            if (ec != null && ec.node != null) {
+                if (ec.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
+                    ea.add(ElementType.Flag, ec.node);
+            }
+        }
+
+        // Include connections for selected flags if we're not potentially
+        // making flag continuations.
+        if (!CopyPasteUtil.isFlagsOnlySelection(ea)) {
+            for (IElement flag : ea.flags) {
+                conns.clear();
+                diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
+                for (Connection conn : conns) {
+                    IElement edge = conn.edge;
+                    ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                    ea.add(ElementType.Connection, ce.getConnection());
+                }
+            }
+        }
+
+        // For each selected connection, make sure that all connected elements
+        // are in the selection, otherwise don't copy the connection.
+        List<IElement> connectionsToRemove = new ArrayList<IElement>(ea.connections.size());
+        for (IElement connection : ea.connections) {
+            ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);
+            Collection<Connection> connectors = ch.getTerminalConnections(connection, null);
+            boolean allConnectorsSelected = true;
+            for (Connection c : connectors) {
+                if (!(ea.nodes.contains(c.node) || ea.flags.contains(c.node) || ea.references.contains(c.node))) {
+                    allConnectorsSelected = false;
+                    break;
+                }
+            }
+            if (!allConnectorsSelected)
+                connectionsToRemove.add(connection);
+        }
+        ea.removeAll(ElementType.Connection, connectionsToRemove);
+
+        // Remove external flags whose connection(s) are not included
+        List<IElement> flagsToRemove = new ArrayList<IElement>(ea.flags.size());
+        for (IElement flag : ea.flags) {
+            if (CopyPasteUtil.flagIsExternal(flag)) {
+                conns.clear();
+                diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
+                for (Connection conn : conns) {
+                    IElement edge = conn.edge;
+                    ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                    IElement connection = ce.getConnection();
+                    if (!ea.connections.contains(connection)) {
+                        flagsToRemove.add(flag);
+                    }
+                }
+            }
+        }
+        ea.removeAll(ElementType.Flag, flagsToRemove);
+
+        if (cut) {
+            // Issue #1874: Prevent cut/paste for connected components
+            // https://www.simulationsite.net/redmine/issues/1874
+            // Fail if any of the included nodes has connections to it that are not
+            // included in the operation.
+            Collection<Connection> connections = new ArrayList<Connection>();
+            for (IElement node : CollectionUtils.join(ea.nodes, ea.flags)) {
+                connections.clear();
+                for (Connection connection : getAllConnections(node, connections)) {
+                    ConnectionEntity ce = connection.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                    IElement conn = ce.getConnection();
+                    if (ea.connections.contains(conn))
+                        continue;
+
+                    return "Cannot cut a node without all its connections.";
+                }
+            }
+        }
+
+        // Remove all reference elements from the assortment whose parent elements are not in it.
+        if (!cut) {
+            Collection<IElement> referenceElementsToRemove = new ArrayList<IElement>();
+            for (IElement ref : ea.references) {
+                IElement parent = ref.getHint(ElementHints.KEY_PARENT_ELEMENT);
+                if (parent != null) {
+                    if (!ea.all.contains(parent)) {
+                        // Cannot copy reference element whose parent is not copied also.
+                        referenceElementsToRemove.add(ref);
+                    }
+                } else {
+                    // OK, reference element has no parent. Free to copy/cut in any way.
+                }
+            }
+            if (!referenceElementsToRemove.isEmpty()) {
+                ea.removeAll(ElementType.Reference, referenceElementsToRemove);
+                if (ea.isEmpty()) {
+                    return "Cannot copy reference elements whose parent is not copied.";
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Collection<Terminal> getTerminals(IElement node) {
+        ArrayList<Terminal> result = new ArrayList<Terminal>();
+        for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))
+            tt.getTerminals(node, result);
+        return result;
+    }
+
+    private Collection<Connection> getAllConnections(IElement node, Collection<Connection> result) {
+        IDiagram diagram = node.getDiagram();
+        Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
+        if (topology == null)
+            return result;
+        for (Terminal t : getTerminals(node))
+            topology.getConnections(node, t, result);
+        return result;
+    }
+
+    /**
+     * Classifies the specified diagram selection elements into categories
+     * appointed by the <code>ElementType</code> enumeration and prunes any
+     * edges from the selection returned assortment whose both ends are not
+     * connected to nodes within the selection.
+     *
+     * @param ea
+     * @return
+     */
+    private void pruneAssortment(ElementAssortment ea, boolean cut) {
+        // Edges and branch points are never copied as such.
+        // They are always included as parts of copied connections.
+        // Edges can never be transformed or modified in any way as such,
+        // therefore it is safe to do this.
+        ea.clear(ElementType.Edge);
+
+        if (!cut)
+            ea.clear(ElementType.BranchPoint);
+    }
+
+    private Point2D getPastePos(DiagramSelection ds) {
+        MouseInfo mi = mouseUtil.getMouseInfo(0);
+        if (mi == null)
+            mi = mouseInfo;
+
+        if (mi != null) {
+            double xoff = mi.canvasPosition.getX() - ds.getCopyPos().getX();
+            double yoff = mi.canvasPosition.getY() - ds.getCopyPos().getY();
+            if (xoff == pasteOffset.getX() && yoff == pasteOffset.getY()) {
+                // The mouse has not moved since last paste so let's offset the
+                // paste down and right.
+                double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
+                return new Point2D.Double(
+                        mi.canvasPosition.getX() + counterOffset,
+                        mi.canvasPosition.getY() + counterOffset);
+            }
+            pasteWithoutMovingGhostCounter = 0;
+            pasteOffset.setLocation(xoff, yoff);
+            return mi.canvasPosition;
+        } else {
+            //return ds.getCopyPos();
+            Point2D p = ds.getCopyPos();
+            double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
+            return new Point2D.Double(
+                    p.getX() + pasteOffset.getX() + counterOffset,
+                    p.getY() + pasteOffset.getY() + counterOffset);
+        }
+    }
+
+    private double getOffsetGridSize() {
+        Double grid = getHint(GridPainter.KEY_GRID_SIZE);
+        return (grid == null || grid == 0) ? 1.0 : grid;
+    }
+
+    /**
+     * @param e
+     * @return
+     */
+    private static boolean isConnectionOrEdge(IElement e) {
+        ElementClass ec = e.getElementClass();
+        return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);
+    }
+
+    /**
+     * @param e
+     * @return
+     */
+    private static boolean isMoveable(IElement e) {
+        ElementClass ec = e.getElementClass();
+        return ec.containsClass(Move.class) && ec.containsClass(Transform.class);
+    }
+
+    /**
+     * @param ss
+     * @return <code>null</code> if a point of reference cannot be determined
+     *         for the specified selection.
+     */
+    private Point2D getCopyStartPos(Set<IElement> ss) {
+//        MouseInfo mi = mouseUtil.getMouseInfo(0);
+//        if (mi != null) {
+//            return (Point2D) mi.canvasPosition.clone();
+//        }
+
+        // Find bounding rectangle top left corner
+        double mx = Double.MAX_VALUE;
+        double my = Double.MAX_VALUE;
+        for (IElement e : ss) {
+            if (isConnectionOrEdge(e) || !isMoveable(e))
+                continue;
+
+            //Point2D pos = ElementUtils.getPos(e);
+            Point2D pos = ElementUtils.getAbsolutePos(e);
+            if (pos.getX() < mx)
+                mx = pos.getX();
+            if (pos.getY() < my)
+                my = pos.getY();
+        }
+
+        // Find element nearest to the top left corner
+        Point2D nearest = null;
+        double dist = Double.MAX_VALUE;
+        for (IElement e : ss) {
+            if (isConnectionOrEdge(e) || !isMoveable(e))
+                continue;
+
+            Point2D pos = ElementUtils.getAbsolutePos(e);
+            double dx = pos.getX() - mx;
+            double dy = pos.getY() - my;
+            double d = dx*dx + dy*dy;
+            if (d < dist) {
+                dist = d;
+                nearest = pos;
+            }
+        }
+
+        return nearest;
+    }
+
+    private void moveGhostElements(DiagramSelection ds, Point2D pastePos) {
+        Point2D copyPos = ds.getCopyPos();
+        double dx = (pastePos.getX() - copyPos.getX());
+        double dy = (pastePos.getY() - copyPos.getY());
+
+        // Snap delta
+        Point2D snap = CopyPasteUtil.snap(getContext(), new Point2D.Double(dx, dy));
+
+        ghostNode.setTransform(AffineTransform.getTranslateInstance(snap.getX(), snap.getY()));
+        //System.out.println("ghost node: " + ghostNode);
+    }
+
+    protected SingleElementNode ghostNode = null;
+    protected NodeMapper ghostNodeMapper = new NodeMapper();
+
+    @SGInit
+    public void initSG(G2DParentNode parent) {
+        ghostNode = parent.addNode("cut/copy ghost", SingleElementNode.class);
+        ghostNode.setZIndex(COPY_GHOSTING_PAINT_PRIORITY);
+        //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.40f));
+        ghostNode.setVisible(Boolean.FALSE);
+    }
+
+    @SGCleanup
+    public void cleanupSG() {
+        ghostNode.remove();
+    }
+
+    void clearSG() {
+        ghostNode.removeNodes();
+        ghostNode.setVisible(Boolean.FALSE);
+        ghostNodeMapper.clear();
+    }
+
+    /**
+     * @param selection
+     * @return <code>true</code> if the ghost nodes were hidden and a refresh is
+     *         needed
+     */
+    boolean hideSG(DiagramSelection selection) {
+        if (ghostNode.isVisible()) {
+            // Make sure there's no leftover graphics.
+            ghostNode.removeNodes();
+            ghostNode.setVisible(Boolean.FALSE);
+            return true;
+        }
+        return false;
+    }
+
+    protected void scheduleActivateOwnerPart() {
+        if (mPart == null)
+            return;
+        SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {
+            @Override
+            public void run() {
+                hasFocus = true;
+                mPart.getContext().get(EPartService.class).activate(mPart);
+            }
+        });
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleMouse(MouseExitEvent e) {
+        DiagramSelection ds = getProjectSelection();
+        if (!ds.isEmpty()) {
+            if (hideSG(ds))
+                setDirty();
+        }
+
+        // The part might no longer have focus.
+        // [Tuukka] commented out to fix Apros #3678
+        //hasFocus = false;
+
+        return false;
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleMouse(MouseEnterEvent e) {
+        DiagramSelection ds = getProjectSelection();
+        if (!ds.isEmpty()) {
+            if (mPart != null) {
+                if (inPasteMode(e)) {
+                    scheduleActivateOwnerPart();
+                }
+            }
+        }
+        return false;
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleMouse(MouseMovedEvent e) {
+        DiagramSelection ds = getProjectSelection();
+        if (!ds.isEmpty()) {
+            MouseInfo mi = mouseUtil.getMouseInfo(0);
+            //System.out.println("LAST MOUSE INFO: " + mi);
+            if (mi != null)
+                mouseInfo = mi;
+
+            if (inPasteMode(e)) {
+                // Make sure that this owner part is active now.
+                if (!hasFocus)
+                    scheduleActivateOwnerPart();
+
+                updateSG(ds);
+                setDirty();
+            } else {
+                if (hideSG(ds))
+                    setDirty();
+            }
+        }
+        return false;
+    }
+
+    void updateSG(DiagramSelection selection) {
+        MouseInfo mi = mouseUtil.getMouseInfo(0);
+        if (mi == null)
+            return;
+
+        //ghostNode.setComposite(AlphaComposite.SrcAtop.derive(0.40f));
+        //ghostNode.setComposite(null);
+
+        moveGhostElements(selection, mi.canvasPosition);
+        if (selection.getSourceCanvas() != getContext()) {
+            for (IElement e : selection.getOriginalElements()) {
+                INode node = e.getHint(ElementHints.KEY_SG_NODE);
+                //System.out.println("ghost element: " + e + ", node=" + node);
+                if (node instanceof IG2DNode) {
+                    LocalDelegateNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e),
+                            LocalDelegateNode.class);
+                    delegate.setDelegate( (IG2DNode) node );
+                }
+            }
+        } else {
+            for (IElement e : selection.getOriginalElements()) {
+                //System.out.println("ghost element: " + e);
+                INode node = e.getHint(ElementHints.KEY_SG_NODE);
+                if (node != null) {
+                    //System.out.println("ghost node: " + node);
+                    ghostNodeMapper.add(node);
+                    String nodeId = ghostNodeMapper.getId(node);
+                    //System.out.println("ghost node id: " + nodeId);
+                    LinkNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e), LinkNode.class);
+                    delegate.setDelegateId( nodeId );
+                }
+            }
+        }
+
+        ghostNode.setVisible(true);
+    }
+
+    private <T extends INode> T getOrCreateNode(ParentNode<?> parentNode, String id, Class<T> clazz) {
+        INode n = ghostNode.getNode(id);
+        if (clazz.isInstance(n))
+            return clazz.cast(n);
+        ghostNode.removeNode(id);
+        return ghostNode.addNode(id, clazz);
+    }
+
+    private boolean hasHighlight() {
+        return highlightMode != null;
+    }
+
+    private void removeHighlight() {
+        if (isRemoved())
+            return;
+        assert getContext().getThreadAccess().currentThreadAccess();
+        if (highlightMode != null) {
+            if (!highlightMode.isRemoved()) {
+                highlightMode.remove();
+                setDirty();
+            }
+            highlightMode = null;
+        }
+    }
+
+    private boolean inPasteMode(MouseEvent e) {
+        return (e.stateMask & MouseEvent.CTRL_MASK) != 0;
+    }
+
+    void selectedMessage(DiagramSelection ds) {
+        int size = ds.getOriginalElements().size();
+        StringBuilder sb = new StringBuilder();
+        if (size == 0) {
+            sb.append("No elements to ");
+            if (ds.isCut())
+                sb.append("cut");
+            else
+                sb.append("copy");
+        } else {
+            if (ds.isCut())
+                sb.append("Cut ");
+            else
+                sb.append("Copied ");
+            sb.append(size);
+            sb.append(" element");
+            if (size > 1)
+                sb.append('s');
+        }
+        message(sb.toString());
+    }
+
+    void message(final String message) {
+        if (statusLine == null)
+            return;
+        swtExec(new Runnable() {
+            @Override
+            public void run() {
+                statusLine.setMessage(message);
+                statusLine.setErrorMessage(null);
+            }
+        });
+    }
+
+    void error(final String message) {
+        if (statusLine == null)
+            return;
+        swtExec(new Runnable() {
+            @Override
+            public void run() {
+                statusLine.setErrorMessage(message);
+            }
+        });
+    }
+
+    void swtExec(Runnable r) {
+        ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
+    }
+
+    // MONITOR PASTE SUPPORT
+
+    private boolean tryPasteMonitors() {
+        SimanticsClipboard clipboard = Simantics.getClipboard();
+        for (Set<Representation> content : clipboard.getContents()) {
+            try {
+                final Variable var_ = ClipboardUtils.accept(content, SimanticsKeys.KEY_VARIABLE);
+                if (var_ != null) {
+                    final Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+                    final Resource runtime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
+                    final Resource elementResource = Simantics.getSession().syncRequest(new Read<Resource>() {
+                        @Override
+                        public Resource perform(ReadGraph graph) throws DatabaseException {
+                            DiagramResource DIA = DiagramResource.getInstance(graph);
+
+                            String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
+                            if (diagramVariable == null)
+                                return null;
+
+                            Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
+                            if (diaVar == null)
+                                return null;
+
+                            Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
+                            if(var == null)
+                                return null;
+
+                            Variable component = Variables.getChild(graph, diaVar, var);
+                            if (component == null)
+                                return null;
+
+                            Resource componentResource = component.getPossibleRepresents(graph);
+                            if (componentResource == null)
+                                return null;
+
+                            return graph.getPossibleObject(componentResource, ModelingResources.getInstance(graph).ComponentToElement);
+                        }
+                    });
+
+                    if (elementResource == null)
+                        return false;
+
+                    final AffineTransform monitorTransform = Simantics.getSession().syncRequest(new Read<AffineTransform>() {
+                        @Override
+                        public AffineTransform perform(ReadGraph graph) throws DatabaseException {
+                            AffineTransform at = null;
+
+                            if (graph.isInstanceOf(elementResource, DiagramResource.getInstance(graph).Connection)) {
+                                Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, elementResource);
+                                if (tailNode != null) {
+                                    at = DiagramGraphUtil.getAffineTransform(graph, tailNode);
+                                }
+                            }
+                            if (at == null)
+                                at = DiagramGraphUtil.getAffineTransform(graph, elementResource);
+
+                            return at;
+                        }
+                    });
+
+                    MouseInfo mi = mouseUtil.getMouseInfo(0);
+                    if (mi == null)
+                        mi = mouseInfo;
+                    final double dx = mi.canvasPosition.getX() - monitorTransform.getTranslateX();
+                    final double dy = mi.canvasPosition.getY() - monitorTransform.getTranslateY();
+
+                    Simantics.getSession().asyncRequest(new WriteRequest() {
+
+                        @Override
+                        public void perform(WriteGraph graph) throws DatabaseException {
+                            Layer0 L0 = Layer0.getInstance(graph);
+                            Layer0X L0X = Layer0X.getInstance(graph);
+                            DiagramResource DIA = DiagramResource.getInstance(graph);
+                            G2DResource G2D = G2DResource.getInstance(graph);
+
+                            String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
+                            if (diagramVariable == null)
+                                return;
+
+                            Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
+                            if (diaVar == null)
+                                return;
+
+                            Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
+                            if(var == null)
+                                return;
+
+                            Variable component = Variables.getChild(graph, diaVar, var);
+                            if (component == null)
+                                return;
+
+                            Resource componentResource = component.getPossibleRepresents(graph);
+                            if (componentResource == null)
+                                return;
+
+                            String suffix = Variables.getRVI(graph, component, var);
+
+                            Resource resource = graph.newResource();
+                            graph.claim(resource, L0.InstanceOf, null, DIA.Monitor);
+
+                            final double scale = monitorScale;
+
+                            DiagramGraphUtil.setTransform(graph, resource, new AffineTransform(scale, 0, 0, scale, dx, dy));
+
+                            OrderedSetUtils.add(graph, diagramResource, resource);
+
+                            // 5.1. Give running name to element and increment the counter attached to the diagram.
+                            Long l = graph.getPossibleRelatedValue(diagramResource, DIA.HasModCount, Bindings.LONG);
+                            if (l == null)
+                                l = Long.valueOf(0L);
+                            graph.claimLiteral(resource, L0.HasName, l.toString(), Bindings.STRING);
+                            graph.claimLiteral(diagramResource, DIA.HasModCount, ++l, Bindings.LONG);
+
+                            // 5.2. Make the diagram consist of the new element
+                            graph.claim(diagramResource, L0.ConsistsOf, resource);
+
+                            graph.claim(resource, G2D.HasHorizontalAlignment, null, G2D.Alignment_Leading);
+                            graph.claimLiteral(resource, DIA.HasDirection, 0.0);
+
+                            graph.claim(resource, DIA.HasMonitorComponent, componentResource);
+                            graph.claimLiteral(resource, DIA.HasMonitorSuffix, suffix);
+
+                            Resource model = Variables.getModel(graph, diaVar);
+                            if (model != null) {
+                                Resource template = graph.getPossibleObject(model, DIA.HasDefaultMonitorTemplate);
+                                if (template != null) {
+                                    graph.claim(resource, L0X.ObtainsProperty1, null, template);
+                                }
+                            }
+                            
+                        }
+                    });
+                }
+            } catch (DatabaseException e1) {
+                LOGGER.error("Monitor paste failed", e1);
+            }
+        }
+        return true;
+    }
+
+}