X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fhandler%2FCopyPasteHandler.java;h=52217c3cfcf2b84a6ddc9795d7d13d3f66af80ba;hb=48135dcd03588783f9c1b688aaa53cdaacba6ef2;hp=32350da374a36206e84c8ee6146858b63f216e76;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteHandler.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteHandler.java
index 32350da37..52217c3cf 100644
--- a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteHandler.java
+++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/CopyPasteHandler.java
@@ -1,1303 +1,1303 @@
-/*******************************************************************************
- * 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;
-
-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.jface.action.IStatusLineManager;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.ui.IPartListener;
-import org.eclipse.ui.IWorkbenchPart;
-import org.eclipse.ui.IWorkbenchPartSite;
-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.Logger;
-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.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;
-
-/**
- * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and
- * Commands.PASTE commands for an IDiagram.
- *
- *
- * The handler attempts to copy/paste the current selection for pointer 0,
- * meaning {@link Selection#SELECTION0}.
- *
- *
- *
- * The handler logic follows the specifications at UC:Copy Item
- * and UC:Cut
- * Item.
- *
- *
- * @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 {
-
- 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 IWorkbenchPartSite site;
- protected IWorkbenchPartSite listenedSite;
-
- /**
- * Workbench part listener for {@link #listenedSite} to keep proper track of
- * whether this part is focused or not.
- */
- IPartListener partListener = new IPartListener() {
- @Override
- public void partOpened(IWorkbenchPart part) {
- }
- @Override
- public void partDeactivated(IWorkbenchPart part) {
- if (part == site.getPart())
- hasFocus = false;
- }
- @Override
- public void partClosed(IWorkbenchPart part) {
- // Make sure this listener is removed properly in any case.
- if (listenedSite != null) {
- listenedSite.getPage().removePartListener(partListener);
- listenedSite = null;
- }
- }
- @Override
- public void partBroughtToTop(IWorkbenchPart part) {
- }
- @Override
- public void partActivated(IWorkbenchPart part) {
- if (part == site.getPart())
- hasFocus = true;
- }
- };
-
- /**
- * Indicates whether CopyPasteHandler thinks that {@link #site} 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(IWorkbenchPartSite site) {
- this.site = site;
- return this;
- }
-
- protected boolean isPasteAllowed() {
- return listenedSite == null || hasFocus;
- }
-
- @Override
- public void addedToContext(ICanvasContext ctx) {
- super.addedToContext(ctx);
- addProjectListener(peekProject());
-
- listenedSite = site;
- if (listenedSite != null) {
- listenedSite.getPage().addPartListener(partListener);
- }
- }
-
- @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 (listenedSite != null) {
- listenedSite.getPage().removePartListener(partListener);
- listenedSite = 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 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 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;
- }
-
- // Allow the possible CopyStrategy to filter the copied element
- // assortment also.
- if (strategy instanceof CopyStrategy) {
- if (DEBUG)
- System.out.println("Commencing CopyStrategy filtering with " + ea);
- IStatus status = ((CopyStrategy) strategy).copy(new CopyOperation(getContext(), ea, cut));
- if (!status.isOK()) {
- switch (status.getSeverity()) {
- case IStatus.CANCEL:
- case IStatus.WARNING:
- message(status.getMessage());
- break;
- case IStatus.ERROR:
- error(status.getMessage());
- break;
- }
- 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.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 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 conns = new ArrayList();
-
- // 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 connectionsToRemove = new ArrayList(ea.connections.size());
- for (IElement connection : ea.connections) {
- ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);
- Collection 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 flagsToRemove = new ArrayList(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 connections = new ArrayList();
- 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 referenceElementsToRemove = new ArrayList();
- 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 getTerminals(IElement node) {
- ArrayList result = new ArrayList();
- for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))
- tt.getTerminals(node, result);
- return result;
- }
-
- private Collection getAllConnections(IElement node, Collection 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 ElementType
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 null
if a point of reference cannot be determined
- * for the specified selection.
- */
- private Point2D getCopyStartPos(Set 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 true
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 (site == null)
- return;
- SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {
- @Override
- public void run() {
- hasFocus = true;
- site.getPage().activate(site.getPart());
- }
- });
- }
-
- @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 (site != 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 getOrCreateNode(ParentNode> parentNode, String id, Class 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 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() {
- @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() {
- @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.defaultLogError(e1);
- }
- }
- return true;
- }
-
-}
+/*******************************************************************************
+ * 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;
+
+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.jface.action.IStatusLineManager;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IPartListener;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartSite;
+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.Logger;
+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.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;
+
+/**
+ * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and
+ * Commands.PASTE commands for an IDiagram.
+ *
+ *
+ * The handler attempts to copy/paste the current selection for pointer 0,
+ * meaning {@link Selection#SELECTION0}.
+ *
+ *
+ *
+ * The handler logic follows the specifications at UC:Copy Item
+ * and UC:Cut
+ * Item.
+ *
+ *
+ * @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 {
+
+ 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;
+
+ protected static final int HIGHLIGHT_PAINT_PRIORITY = 500;
+
+ @Dependency
+ protected Selection sel;
+ @Dependency
+ protected MouseUtil mouseUtil;
+
+ protected final IStatusLineManager statusLine;
+ protected final CopyPasteStrategy strategy;
+ protected IWorkbenchPartSite site;
+ protected IWorkbenchPartSite listenedSite;
+
+ /**
+ * Workbench part listener for {@link #listenedSite} to keep proper track of
+ * whether this part is focused or not.
+ */
+ IPartListener partListener = new IPartListener() {
+ @Override
+ public void partOpened(IWorkbenchPart part) {
+ }
+ @Override
+ public void partDeactivated(IWorkbenchPart part) {
+ if (part == site.getPart())
+ hasFocus = false;
+ }
+ @Override
+ public void partClosed(IWorkbenchPart part) {
+ // Make sure this listener is removed properly in any case.
+ if (listenedSite != null) {
+ listenedSite.getPage().removePartListener(partListener);
+ listenedSite = null;
+ }
+ }
+ @Override
+ public void partBroughtToTop(IWorkbenchPart part) {
+ }
+ @Override
+ public void partActivated(IWorkbenchPart part) {
+ if (part == site.getPart())
+ hasFocus = true;
+ }
+ };
+
+ /**
+ * Indicates whether CopyPasteHandler thinks that {@link #site} has focus.
+ */
+ protected boolean hasFocus = false;
+
+ protected 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(IWorkbenchPartSite site) {
+ this.site = site;
+ return this;
+ }
+
+ protected boolean isPasteAllowed() {
+ return listenedSite == null || hasFocus;
+ }
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+ addProjectListener(peekProject());
+
+ listenedSite = site;
+ if (listenedSite != null) {
+ listenedSite.getPage().addPartListener(partListener);
+ }
+ }
+
+ @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 (listenedSite != null) {
+ listenedSite.getPage().removePartListener(partListener);
+ listenedSite = 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 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;
+ }
+
+ protected DiagramSelection getProjectSelection() {
+ IProject p = peekProject();
+ if (p == null)
+ return DiagramSelection.EMPTY;
+ DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);
+ return ds != null ? ds : DiagramSelection.EMPTY;
+ }
+
+ protected void setDiagramSelection(DiagramSelection selection) {
+ setProjectSelection(selection);
+ strategy.copyToClipboard(selection);
+ }
+
+ protected 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);
+ }
+
+ protected 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;
+ }
+
+ protected boolean initiateCopy(boolean cut) {
+ //System.out.println("INITIATING COPY");
+ int selectionId = 0;
+
+ Set 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;
+ }
+
+ // Allow the possible CopyStrategy to filter the copied element
+ // assortment also.
+ if (strategy instanceof CopyStrategy) {
+ if (DEBUG)
+ System.out.println("Commencing CopyStrategy filtering with " + ea);
+ IStatus status = ((CopyStrategy) strategy).copy(new CopyOperation(getContext(), ea, cut));
+ if (!status.isOK()) {
+ switch (status.getSeverity()) {
+ case IStatus.CANCEL:
+ case IStatus.WARNING:
+ message(status.getMessage());
+ break;
+ case IStatus.ERROR:
+ error(status.getMessage());
+ break;
+ }
+ 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.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 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);
+ }
+ }
+
+ protected String fixAssortment(ElementAssortment ea, boolean cut) {
+ Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
+ List conns = new ArrayList();
+
+ // 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 connectionsToRemove = new ArrayList(ea.connections.size());
+ for (IElement connection : ea.connections) {
+ ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);
+ Collection 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 flagsToRemove = new ArrayList(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 connections = new ArrayList();
+ 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 referenceElementsToRemove = new ArrayList();
+ 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 getTerminals(IElement node) {
+ ArrayList result = new ArrayList();
+ for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))
+ tt.getTerminals(node, result);
+ return result;
+ }
+
+ private Collection getAllConnections(IElement node, Collection 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 ElementType
enumeration and prunes any
+ * edges from the selection returned assortment whose both ends are not
+ * connected to nodes within the selection.
+ *
+ * @param ea
+ * @return
+ */
+ protected 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
+ */
+ protected static boolean isConnectionOrEdge(IElement e) {
+ ElementClass ec = e.getElementClass();
+ return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);
+ }
+
+ /**
+ * @param e
+ * @return
+ */
+ protected static boolean isMoveable(IElement e) {
+ ElementClass ec = e.getElementClass();
+ return ec.containsClass(Move.class) && ec.containsClass(Transform.class);
+ }
+
+ /**
+ * @param ss
+ * @return null
if a point of reference cannot be determined
+ * for the specified selection.
+ */
+ protected Point2D getCopyStartPos(Set 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 true
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 (site == null)
+ return;
+ SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {
+ @Override
+ public void run() {
+ hasFocus = true;
+ site.getPage().activate(site.getPart());
+ }
+ });
+ }
+
+ @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 (site != 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 getOrCreateNode(ParentNode> parentNode, String id, Class clazz) {
+ INode n = ghostNode.getNode(id);
+ if (clazz.isInstance(n))
+ return clazz.cast(n);
+ ghostNode.removeNode(id);
+ return ghostNode.addNode(id, clazz);
+ }
+
+ protected boolean hasHighlight() {
+ return highlightMode != null;
+ }
+
+ protected 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;
+ }
+
+ protected 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());
+ }
+
+ protected void message(final String message) {
+ if (statusLine == null)
+ return;
+ swtExec(new Runnable() {
+ @Override
+ public void run() {
+ statusLine.setMessage(message);
+ statusLine.setErrorMessage(null);
+ }
+ });
+ }
+
+ protected void error(final String message) {
+ if (statusLine == null)
+ return;
+ swtExec(new Runnable() {
+ @Override
+ public void run() {
+ statusLine.setErrorMessage(message);
+ }
+ });
+ }
+
+ protected void swtExec(Runnable r) {
+ ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
+ }
+
+ // MONITOR PASTE SUPPORT
+
+ private boolean tryPasteMonitors() {
+ SimanticsClipboard clipboard = Simantics.getClipboard();
+ for (Set 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() {
+ @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() {
+ @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.defaultLogError(e1);
+ }
+ }
+ return true;
+ }
+
+}