/******************************************************************************* * 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 (SetElementType
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(Settrue
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