X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fhandler%2Fe4%2FCopyPasteHandler.java;h=7f709c70993eb62839ed8d0a283cbdfd8f54e9ad;hp=132fb0aff51374e3f4a8bb704e7329e92559d3e0;hb=b4374193caf61635d382af556ec913bf278d53a8;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/e4/CopyPasteHandler.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/e4/CopyPasteHandler.java index 132fb0aff..7f709c709 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/e4/CopyPasteHandler.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/e4/CopyPasteHandler.java @@ -1,1300 +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.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.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.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; - -/** - * 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 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 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; - } - - 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 (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 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.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. + * + *

+ * 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 { + + 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 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; + } + + 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 (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 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.error("Monitor paste failed", e1); + } + } + return true; + } + +}