/*******************************************************************************
* 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;
}
}