1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.handler.e4;
\r
14 import java.awt.Color;
\r
15 import java.awt.event.KeyEvent;
\r
16 import java.awt.geom.AffineTransform;
\r
17 import java.awt.geom.Point2D;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Collection;
\r
20 import java.util.Collections;
\r
21 import java.util.List;
\r
22 import java.util.Set;
\r
24 import org.eclipse.core.runtime.IStatus;
\r
25 import org.eclipse.core.runtime.Status;
\r
26 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
\r
27 import org.eclipse.e4.ui.workbench.modeling.EPartService;
\r
28 import org.eclipse.e4.ui.workbench.modeling.IPartListener;
\r
29 import org.eclipse.jface.action.IStatusLineManager;
\r
30 import org.eclipse.swt.widgets.Display;
\r
31 import org.eclipse.ui.PlatformUI;
\r
32 import org.simantics.Simantics;
\r
33 import org.simantics.databoard.Bindings;
\r
34 import org.simantics.db.ReadGraph;
\r
35 import org.simantics.db.Resource;
\r
36 import org.simantics.db.WriteGraph;
\r
37 import org.simantics.db.common.request.WriteRequest;
\r
38 import org.simantics.db.common.utils.OrderedSetUtils;
\r
39 import org.simantics.db.exception.DatabaseException;
\r
40 import org.simantics.db.layer0.util.ClipboardUtils;
\r
41 import org.simantics.db.layer0.util.SimanticsClipboard;
\r
42 import org.simantics.db.layer0.util.SimanticsClipboard.Representation;
\r
43 import org.simantics.db.layer0.util.SimanticsKeys;
\r
44 import org.simantics.db.layer0.variable.Variable;
\r
45 import org.simantics.db.layer0.variable.Variables;
\r
46 import org.simantics.db.request.Read;
\r
47 import org.simantics.diagram.Logger;
\r
48 import org.simantics.diagram.content.Change;
\r
49 import org.simantics.diagram.content.ConnectionUtil;
\r
50 import org.simantics.diagram.content.DiagramContentChanges;
\r
51 import org.simantics.diagram.content.DiagramContentTracker;
\r
52 import org.simantics.diagram.handler.CopyPasteStrategy;
\r
53 import org.simantics.diagram.handler.CopyPasteUtil;
\r
54 import org.simantics.diagram.handler.DefaultCopyPasteStrategy;
\r
55 import org.simantics.diagram.handler.DiagramSelection;
\r
56 import org.simantics.diagram.handler.DiagramSelectionRepresentation;
\r
57 import org.simantics.diagram.handler.ElementAssortment;
\r
58 import org.simantics.diagram.handler.ElementObjectAssortment;
\r
59 import org.simantics.diagram.handler.ElementType;
\r
60 import org.simantics.diagram.handler.HighlightMode;
\r
61 import org.simantics.diagram.handler.PasteException;
\r
62 import org.simantics.diagram.handler.PasteOperation;
\r
63 import org.simantics.diagram.internal.Activator;
\r
64 import org.simantics.diagram.stubs.DiagramResource;
\r
65 import org.simantics.diagram.stubs.G2DResource;
\r
66 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
\r
67 import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater;
\r
68 import org.simantics.diagram.ui.DiagramModelHints;
\r
69 import org.simantics.g2d.canvas.ICanvasContext;
\r
70 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
\r
71 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
72 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
\r
73 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
\r
74 import org.simantics.g2d.connection.ConnectionEntity;
\r
75 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
76 import org.simantics.g2d.diagram.IDiagram;
\r
77 import org.simantics.g2d.diagram.handler.Topology;
\r
78 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
79 import org.simantics.g2d.diagram.handler.Topology.Terminal;
\r
80 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
\r
81 import org.simantics.g2d.diagram.participant.Selection;
\r
82 import org.simantics.g2d.element.ElementClass;
\r
83 import org.simantics.g2d.element.ElementHints;
\r
84 import org.simantics.g2d.element.ElementUtils;
\r
85 import org.simantics.g2d.element.IElement;
\r
86 import org.simantics.g2d.element.handler.BendsHandler;
\r
87 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
88 import org.simantics.g2d.element.handler.Move;
\r
89 import org.simantics.g2d.element.handler.TerminalTopology;
\r
90 import org.simantics.g2d.element.handler.Transform;
\r
91 import org.simantics.g2d.elementclass.FlagHandler;
\r
92 import org.simantics.g2d.participant.GridPainter;
\r
93 import org.simantics.g2d.participant.MouseUtil;
\r
94 import org.simantics.g2d.participant.MouseUtil.MouseInfo;
\r
95 import org.simantics.layer0.Layer0;
\r
96 import org.simantics.modeling.ModelingResources;
\r
97 import org.simantics.operation.Layer0X;
\r
98 import org.simantics.project.IProject;
\r
99 import org.simantics.scenegraph.INode;
\r
100 import org.simantics.scenegraph.ParentNode;
\r
101 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
102 import org.simantics.scenegraph.g2d.IG2DNode;
\r
103 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
104 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
\r
105 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
\r
106 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
107 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
\r
108 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
\r
109 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
110 import org.simantics.scenegraph.g2d.events.command.Command;
\r
111 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
\r
112 import org.simantics.scenegraph.g2d.events.command.Commands;
\r
113 import org.simantics.scenegraph.g2d.nodes.LinkNode;
\r
114 import org.simantics.scenegraph.g2d.nodes.LocalDelegateNode;
\r
115 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
116 import org.simantics.scenegraph.utils.NodeMapper;
\r
117 import org.simantics.utils.datastructures.collections.CollectionUtils;
\r
118 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
119 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
120 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
121 import org.simantics.utils.datastructures.hints.IHintListener;
\r
122 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
123 import org.simantics.utils.logging.TimeLogger;
\r
124 import org.simantics.utils.threads.SWTThread;
\r
125 import org.simantics.utils.threads.ThreadUtils;
\r
126 import org.simantics.utils.ui.ErrorLogger;
\r
127 import org.simantics.utils.ui.SWTUtils;
\r
130 * CopyPasteHandler is a canvas handler for Commands.CUT, Commands.COPY and
\r
131 * Commands.PASTE commands for an IDiagram.
\r
134 * The handler attempts to copy/paste the current selection for pointer 0,
\r
135 * meaning {@link Selection#SELECTION0}.
\r
139 * The handler logic follows the specifications at <a
\r
140 * href="http://www.simantics.org/wiki/index.php/UC:Copy_Item" >UC:Copy Item</a>
\r
141 * and <a href="http://www.simantics.org/wiki/index.php/UC:Cut_Item" >UC:Cut
\r
145 * @see Selection current diagram selection source
\r
147 * @author Tuukka Lehtonen
\r
149 * FIXME: translucent ghosting makes rendering REALLY sluggish, add a
\r
150 * timer that makes the ghost opaque when the user is interacting and
\r
151 * translucent only when still for a while.
\r
153 public class CopyPasteHandler extends AbstractDiagramParticipant {
\r
155 public static final Key KEY_CUT_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "CUT_SELECTION_FRAME_COLOR");
\r
156 public static final Key KEY_CUT_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "CUT_SELECTION_CONTENT_COLOR");
\r
157 public static final Key KEY_COPIED_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "COPY_SELECTION_FRAME_COLOR");
\r
158 public static final Key KEY_COPIED_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "COPY_SELECTION_CONTENT_COLOR");
\r
161 * A key for storing the current selection within the currently active
\r
162 * project for copy/paste implementation.
\r
164 private static final Key KEY_DIAGRAM_SELECTION = DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION;
\r
166 private static final boolean DEBUG = false;
\r
167 private static final boolean DEBUG_SELECTION_UPDATE = false;
\r
169 public static final int COPY_GHOSTING_PAINT_PRIORITY = 600;
\r
171 private static final int HIGHLIGHT_PAINT_PRIORITY = 500;
\r
174 private Selection sel;
\r
176 private MouseUtil mouseUtil;
\r
178 protected final IStatusLineManager statusLine;
\r
179 protected final CopyPasteStrategy strategy;
\r
180 protected MPart mPart;
\r
181 protected MPart listenedMPart;
\r
184 * Workbench part listener for {@link #listenedMPart} to keep proper track of
\r
185 * whether this part is focused or not.
\r
188 IPartListener partListener2 = new IPartListener() {
\r
191 public void partVisible(MPart part) {
\r
195 public void partHidden(MPart part) {
\r
196 // Make sure this listener is removed properly in any case.
\r
197 if (listenedMPart != null) {
\r
198 mPart.getContext().get(EPartService.class).removePartListener(partListener2);
\r
199 listenedMPart = null;
\r
204 public void partDeactivated(MPart part) {
\r
210 public void partBroughtToTop(MPart part) {
\r
214 public void partActivated(MPart part) {
\r
221 * Indicates whether CopyPasteHandler thinks that {@link #mPart} has focus.
\r
223 protected boolean hasFocus = false;
\r
225 private AbstractCanvasParticipant highlightMode = null;
\r
226 private IProject observedProject = null;
\r
229 * A counter for how many times pasting has been performed without mouse and
\r
230 * ghosting or how many times paste has been performed without moving the
\r
231 * mouse on the diagram. This is used to offset the paste position
\r
232 * accordingly so that copied elements don't wind up directly on top of each
\r
235 private int pasteWithoutMovingGhostCounter = 0;
\r
238 * An offset used when pasting without mouse/ghosting. It forces keyboard
\r
239 * pastes to stack up on top of the latest paste performed with
\r
242 private final Point2D pasteOffset = new Point2D.Double(0, 0);
\r
245 * Stores the last MouseInfo for mouse 0 from the time of the previous
\r
246 * received mouse event. Used for deciding the paste position.
\r
248 * @see #getPastePos(DiagramSelection)
\r
250 private MouseInfo mouseInfo;
\r
253 * Scale to use for pasted diagram monitors from variables.
\r
255 private double monitorScale = 0.2;
\r
258 * For updating the diagram selection after graph changes.
\r
260 private DiagramSelectionUpdater selectionUpdater = null;
\r
262 public CopyPasteHandler() {
\r
263 this(new DefaultCopyPasteStrategy());
\r
266 public CopyPasteHandler(CopyPasteStrategy strategy) {
\r
267 this(strategy, null);
\r
270 public CopyPasteHandler(IStatusLineManager statusLine) {
\r
271 this(new DefaultCopyPasteStrategy(), statusLine);
\r
274 public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine) {
\r
275 this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
\r
276 this.statusLine = statusLine;
\r
279 public CopyPasteHandler(CopyPasteStrategy strategy, IStatusLineManager statusLine, double monitorScale) {
\r
280 this.strategy = strategy != null ? strategy : new DefaultCopyPasteStrategy();
\r
281 this.statusLine = statusLine;
\r
282 setMonitorScale(monitorScale);
\r
285 public CopyPasteHandler setMonitorScale(double scale) {
\r
286 this.monitorScale = scale;
\r
290 public CopyPasteHandler setWorkbenchSite(MPart part) {
\r
295 protected boolean isPasteAllowed() {
\r
296 return listenedMPart == null || hasFocus;
\r
300 public void addedToContext(ICanvasContext ctx) {
\r
301 super.addedToContext(ctx);
\r
302 addProjectListener(peekProject());
\r
304 listenedMPart = mPart;
\r
305 if (listenedMPart != null) {
\r
306 listenedMPart.getContext().get(EPartService.class).addPartListener(partListener2);
\r
311 public void removedFromContext(ICanvasContext ctx) {
\r
312 // Remove project selection if its ours to prevent leaking memory.
\r
313 DiagramSelection ds = getProjectSelection();
\r
314 if (ds.getSourceCanvas() == ctx) {
\r
315 removeProjectSelection();
\r
318 if (listenedMPart != null) {
\r
319 listenedMPart.getContext().get(EPartService.class).removePartListener(partListener2);
\r
320 listenedMPart = null;
\r
323 removeProjectListener();
\r
324 super.removedFromContext(ctx);
\r
328 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
\r
329 if (oldDiagram != null) {
\r
330 if (selectionUpdater != null) {
\r
331 selectionUpdater.untrack();
\r
332 selectionUpdater = null;
\r
335 if (newDiagram != null) {
\r
336 selectionUpdater = new DiagramSelectionUpdater(getContext(), newDiagram).track();
\r
340 IHintListener projectDiagramSelectionListener = new HintListenerAdapter() {
\r
342 public void hintChanged(IHintObservable sender, Key key, Object oldValue, final Object newValue) {
\r
343 //System.out.println(this + ": " + sender + ": " + newValue);
\r
344 ICanvasContext ctx = getContext();
\r
345 if (ctx != null && hasHighlight()) {
\r
346 //System.out.println(this + " HAS HIGHLIGHT");
\r
347 if (newValue == null || ((DiagramSelection) newValue).getSourceCanvas() != ctx) {
\r
348 //System.out.println(this + " REMOVING HIGHLIGHT");
\r
349 ctx.getThreadAccess().asyncExec(new Runnable() {
\r
351 public void run() {
\r
360 private void addProjectListener(IProject observable) {
\r
361 if (observable != null) {
\r
362 observable.addKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
\r
363 observedProject = observable;
\r
367 private void removeProjectListener() {
\r
368 if (observedProject != null) {
\r
369 observedProject.removeKeyHintListener(KEY_DIAGRAM_SELECTION, projectDiagramSelectionListener);
\r
370 observedProject = null;
\r
374 IProject getProject() {
\r
375 return Simantics.getProject();
\r
378 IProject peekProject() {
\r
379 return Simantics.peekProject();
\r
382 public DiagramSelection getClipboardDiagramSelection() {
\r
383 for (Set<Representation> content : Simantics.getClipboard().getContents()) {
\r
385 DiagramSelection sel = ClipboardUtils.accept(content, DiagramSelectionRepresentation.KEY_DIAGRAM_SELECTION);
\r
388 } catch (DatabaseException e) {
\r
389 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to retrieve clipboard content.", e));
\r
392 return DiagramSelection.EMPTY;
\r
395 private DiagramSelection getProjectSelection() {
\r
396 IProject p = peekProject();
\r
398 return DiagramSelection.EMPTY;
\r
399 DiagramSelection ds = p.getHint(KEY_DIAGRAM_SELECTION);
\r
400 return ds != null ? ds : DiagramSelection.EMPTY;
\r
403 void setDiagramSelection(DiagramSelection selection) {
\r
404 setProjectSelection(selection);
\r
405 strategy.copyToClipboard(selection);
\r
408 void setProjectSelection(DiagramSelection selection) {
\r
409 assert selection != null;
\r
410 IProject p = getProject();
\r
412 throw new IllegalStateException("no active project for selection");
\r
414 pasteWithoutMovingGhostCounter = 0;
\r
415 pasteOffset.setLocation(0, 0);
\r
416 p.setHint(KEY_DIAGRAM_SELECTION, selection);
\r
419 private void removeProjectSelection() {
\r
420 setProjectSelection(DiagramSelection.EMPTY);
\r
427 * This DELETE command handler is required to remove any diagram selections
\r
428 * when modules are deleted. This will prevent extra highlight painting from
\r
429 * being left over in case a module marked for copying is deleted.
\r
431 @EventHandler(priority = 100)
\r
432 public boolean handleDelete(CommandEvent e) {
\r
433 if (e.command.equals( Commands.DELETE) ) {
\r
434 if (highlightMode != null) {
\r
436 removeProjectSelection();
\r
443 @EventHandler(priority = 0)
\r
444 public boolean handleKey(org.simantics.scenegraph.g2d.events.KeyEvent e) {
\r
445 if (e.keyCode == KeyEvent.VK_CONTROL) {
\r
446 DiagramSelection ds = getProjectSelection();
\r
447 if (!ds.isEmpty()) {
\r
448 if (e instanceof KeyPressedEvent) {
\r
450 message("Move selection");
\r
452 message("Paste selection");
\r
454 } else if (e instanceof KeyReleasedEvent) {
\r
455 selectedMessage(ds);
\r
464 @EventHandler(priority = 0)
\r
465 public boolean handleCommand(CommandEvent e) {
\r
466 if (e.command.equals( Commands.CANCEL) ) {
\r
467 DiagramSelection s = getProjectSelection();
\r
468 if (highlightMode != null || !s.isEmpty()) {
\r
470 removeProjectSelection();
\r
475 if (e.command.equals( Commands.CUT ) || e.command.equals( Commands.COPY )) {
\r
476 boolean ret = initiateCopy( e.command.equals( Commands.CUT ) );
\r
478 removeProjectSelection();
\r
481 // Must have focus in order to paste! If mouse has left the editor, no
\r
482 // pastes should be performed.
\r
483 if (isPasteAllowed() && e.command.equals( Commands.PASTE )) {
\r
484 DiagramSelection ds = getClipboardDiagramSelection();
\r
485 if (ds.isEmpty()) {
\r
486 return tryPasteMonitors();
\r
488 return paste(e.command, ds);
\r
493 boolean initiateCopy(boolean cut) {
\r
494 //System.out.println("INITIATING COPY");
\r
495 int selectionId = 0;
\r
497 Set<IElement> ss = sel.getSelection(selectionId);
\r
498 Point2D copyPos = getCopyStartPos(ss);
\r
499 if (ss.isEmpty() || copyPos == null) {
\r
500 message("Nothing to " + (cut ? "cut" : "copy"));
\r
504 // Validate selection, don't initiate copy if selection is invalid.
\r
505 ElementAssortment ea = new ElementAssortment(ss);
\r
506 String error = fixAssortment(ea, cut);
\r
508 if (error != null) {
\r
513 pruneAssortment(ea, cut);
\r
514 if (ea.isEmpty()) {
\r
515 message("Nothing to " + (cut ? "cut" : "copy"));
\r
520 System.out.println("Start copy with " + ea);
\r
522 // Treat OTHER type elements as disconnected floating graphical elements
\r
523 // that are always copied.
\r
525 // Anything with connection parts cannot be copied
\r
526 if (!cut && ea.containsAny(CopyPasteUtil.CONNECTION_PARTS)) {
\r
527 error("Cannot copy connection segments nor branch points.");
\r
530 // if (ea.contains(CopyPasteUtil.MONITORS)) {
\r
531 // // TODO: allow copying of monitors, means fixing the component reference relations
\r
532 // error("Monitor " + (cut ? "cut" : "copy") + " not supported yet.");
\r
536 // Pre-validate flag selection cases
\r
537 if (ea.contains(CopyPasteUtil.FLAGS)) {
\r
539 // Allow cutting of single flags or cutting of any amount of
\r
540 // flags within a single diagram.
\r
542 // // Deny flag copy if other kinds of elements are selected.
\r
543 // if (ea.containsAny(NOT_FLAGS)) {
\r
546 // Only copy flags without correspondence for now.
\r
547 if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
\r
548 if (!CopyPasteUtil.checkFlagsCorrespondences(ea.flags, false)) {
\r
549 error("Cannot copy flag that already has a correspondence.");
\r
556 // Selection is valid, go ahead and initiate a copy operation.
\r
557 Resource sourceDiagram = diagram.<Resource>getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
\r
558 DiagramSelection ds = new DiagramSelection(getContext(), sourceDiagram, ea.getAll(), cut, copyPos);
\r
559 setDiagramSelection(ds);
\r
562 highlightMode = new HighlightMode(ds, selectionId, HIGHLIGHT_PAINT_PRIORITY);
\r
563 getContext().add(highlightMode);
\r
565 selectedMessage(ds);
\r
567 // System.out.println("INITIATED COPY: " + ds);
\r
571 public boolean paste(Command command, DiagramSelection ds) {
\r
572 if (ds.isEmpty()) {
\r
577 TimeLogger.resetTimeAndLog(getClass(), "paste");
\r
579 ElementObjectAssortment ea = ds.getAssortment();
\r
582 System.out.println("Initiate paste with " + ea);
\r
585 if (CopyPasteUtil.isFlagsOnlySelection(ea)) {
\r
586 // Do not copy if any of the flags already have a correspondence.
\r
587 if (!CopyPasteUtil.onlyFlagsWithoutCorrespondence(Simantics.getSession(), ea))
\r
591 normalPaste(command, ds, ea, true);
\r
593 setDiagramSelection(DiagramSelection.EMPTY);
\r
594 resetSourceSelection(ds);
\r
596 normalPaste(command, ds, ea, false);
\r
597 // There is no point in leaving the old copy selection hanging
\r
598 // around after copying a flag since it is a one shot operation.
\r
600 setDiagramSelection(DiagramSelection.EMPTY);
\r
604 normalPaste(command, ds, ea, true);
\r
606 setDiagramSelection(DiagramSelection.EMPTY);
\r
607 resetSourceSelection(ds);
\r
609 normalPaste(command, ds, ea, false);
\r
611 // // This is necessary to keep the ghost diagram properly up-to-date
\r
612 // // after paste operations.
\r
613 // setProjectSelection(ds.remutate());
\r
619 } catch (PasteException e) {
\r
620 error( e.getLocalizedMessage() );
\r
621 ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
\r
622 } catch (DatabaseException e) {
\r
623 error( e.getLocalizedMessage() );
\r
624 ErrorLogger.defaultLog( new Status(IStatus.INFO, Activator.PLUGIN_ID, "Problem in diagram paste operation, see exception for details.", e) );
\r
635 * In cut/paste cases, the source selection should reset (removed) after the
\r
636 * paste operation has been performed. This method will reset the source
\r
637 * selection of the specified DiagramSelection from the source canvas
\r
638 * context. This will work regardless of which diagram/editor the selection
\r
641 * @param ds the source selection to reset
\r
643 void resetSourceSelection(DiagramSelection ds) {
\r
644 ICanvasContext cc = ds.getSourceCanvas();
\r
645 boolean sameDiagram = diagram == ds.getSourceDiagram();
\r
646 if (!sameDiagram && cc != null && !cc.isDisposed()) {
\r
647 for (Selection sourceSelection : cc.getItemsByClass(Selection.class)) {
\r
648 Collection<IElement> empty = Collections.emptySet();
\r
649 sourceSelection.setSelection(0, empty);
\r
654 private void normalPaste(Command command, DiagramSelection ds, ElementObjectAssortment ea, boolean cut) throws PasteException {
\r
655 final Point2D copyPos = ds.getCopyPos();
\r
656 final Point2D pastePos = getPastePos(ds);
\r
658 double dx = pastePos.getX() - copyPos.getX();
\r
659 double dy = pastePos.getY() - copyPos.getY();
\r
660 final Point2D pasteOffset = new Point2D.Double(dx, dy);
\r
663 // Get diagram contents before the paste operation
\r
664 Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
\r
665 final DiagramContentTracker tracker =
\r
666 diagramResource == null ?
\r
668 : DiagramContentTracker.start(getContext(), Simantics.getSession(), diagramResource);
\r
670 strategy.paste(new PasteOperation(command, getContext(), ds.getSourceDiagram(), diagramResource, diagram, ea, cut, pasteOffset));
\r
672 if (tracker != null) {
\r
673 // Get difference of diagram contents to find out what was added.
\r
674 DiagramContentChanges changes = tracker.update();
\r
675 selectionUpdater.setNewSelection(0, changes.pick(changes.elements, Change.ADDED));
\r
676 if (DEBUG_SELECTION_UPDATE)
\r
677 System.out.println("stored diagram changes @" + System.currentTimeMillis() + ": " + selectionUpdater.getNewSelection());
\r
680 } catch (DatabaseException e) {
\r
681 ErrorLogger.defaultLogError(e);
\r
685 private String fixAssortment(ElementAssortment ea, boolean cut) {
\r
686 Topology diagramTopology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
\r
687 List<Connection> conns = new ArrayList<Connection>();
\r
689 // Include flags whether they are selected or not
\r
690 for (IElement edge : ea.edges) {
\r
691 Connection bc = diagramTopology.getConnection(edge, EdgeEnd.Begin);
\r
692 if (bc != null && bc.node != null) {
\r
693 if (bc.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
\r
694 ea.add(ElementType.Flag, bc.node);
\r
696 Connection ec = diagramTopology.getConnection(edge, EdgeEnd.End);
\r
697 if (ec != null && ec.node != null) {
\r
698 if (ec.node.getElementClass().getAtMostOneItemOfClass(FlagHandler.class) != null)
\r
699 ea.add(ElementType.Flag, ec.node);
\r
703 // Include connections for selected flags if we're not potentially
\r
704 // making flag continuations.
\r
705 if (!CopyPasteUtil.isFlagsOnlySelection(ea)) {
\r
706 for (IElement flag : ea.flags) {
\r
708 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
\r
709 for (Connection conn : conns) {
\r
710 IElement edge = conn.edge;
\r
711 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
712 ea.add(ElementType.Connection, ce.getConnection());
\r
717 // For each selected connection, make sure that all connected elements
\r
718 // are in the selection, otherwise don't copy the connection.
\r
719 List<IElement> connectionsToRemove = new ArrayList<IElement>(ea.connections.size());
\r
720 for (IElement connection : ea.connections) {
\r
721 ConnectionHandler ch = connection.getElementClass().getSingleItem(ConnectionHandler.class);
\r
722 Collection<Connection> connectors = ch.getTerminalConnections(connection, null);
\r
723 boolean allConnectorsSelected = true;
\r
724 for (Connection c : connectors) {
\r
725 if (!(ea.nodes.contains(c.node) || ea.flags.contains(c.node) || ea.references.contains(c.node))) {
\r
726 allConnectorsSelected = false;
\r
730 if (!allConnectorsSelected)
\r
731 connectionsToRemove.add(connection);
\r
733 ea.removeAll(ElementType.Connection, connectionsToRemove);
\r
735 // Remove external flags whose connection(s) are not included
\r
736 List<IElement> flagsToRemove = new ArrayList<IElement>(ea.flags.size());
\r
737 for (IElement flag : ea.flags) {
\r
738 if (CopyPasteUtil.flagIsExternal(flag)) {
\r
740 diagramTopology.getConnections(flag, ElementUtils.getSingleTerminal(flag), conns);
\r
741 for (Connection conn : conns) {
\r
742 IElement edge = conn.edge;
\r
743 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
744 IElement connection = ce.getConnection();
\r
745 if (!ea.connections.contains(connection)) {
\r
746 flagsToRemove.add(flag);
\r
751 ea.removeAll(ElementType.Flag, flagsToRemove);
\r
754 // Issue #1874: Prevent cut/paste for connected components
\r
755 // https://www.simulationsite.net/redmine/issues/1874
\r
756 // Fail if any of the included nodes has connections to it that are not
\r
757 // included in the operation.
\r
758 Collection<Connection> connections = new ArrayList<Connection>();
\r
759 for (IElement node : CollectionUtils.join(ea.nodes, ea.flags)) {
\r
760 connections.clear();
\r
761 for (Connection connection : getAllConnections(node, connections)) {
\r
762 ConnectionEntity ce = connection.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
763 IElement conn = ce.getConnection();
\r
764 if (ea.connections.contains(conn))
\r
767 return "Cannot cut a node without all its connections.";
\r
772 // Remove all reference elements from the assortment whose parent elements are not in it.
\r
774 Collection<IElement> referenceElementsToRemove = new ArrayList<IElement>();
\r
775 for (IElement ref : ea.references) {
\r
776 IElement parent = ref.getHint(ElementHints.KEY_PARENT_ELEMENT);
\r
777 if (parent != null) {
\r
778 if (!ea.all.contains(parent)) {
\r
779 // Cannot copy reference element whose parent is not copied also.
\r
780 referenceElementsToRemove.add(ref);
\r
783 // OK, reference element has no parent. Free to copy/cut in any way.
\r
786 if (!referenceElementsToRemove.isEmpty()) {
\r
787 ea.removeAll(ElementType.Reference, referenceElementsToRemove);
\r
788 if (ea.isEmpty()) {
\r
789 return "Cannot copy reference elements whose parent is not copied.";
\r
797 private Collection<Terminal> getTerminals(IElement node) {
\r
798 ArrayList<Terminal> result = new ArrayList<Terminal>();
\r
799 for (TerminalTopology tt : node.getElementClass().getItemsByClass(TerminalTopology.class))
\r
800 tt.getTerminals(node, result);
\r
804 private Collection<Connection> getAllConnections(IElement node, Collection<Connection> result) {
\r
805 IDiagram diagram = node.getDiagram();
\r
806 Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
\r
807 if (topology == null)
\r
809 for (Terminal t : getTerminals(node))
\r
810 topology.getConnections(node, t, result);
\r
815 * Classifies the specified diagram selection elements into categories
\r
816 * appointed by the <code>ElementType</code> enumeration and prunes any
\r
817 * edges from the selection returned assortment whose both ends are not
\r
818 * connected to nodes within the selection.
\r
823 private void pruneAssortment(ElementAssortment ea, boolean cut) {
\r
824 // Edges and branch points are never copied as such.
\r
825 // They are always included as parts of copied connections.
\r
826 // Edges can never be transformed or modified in any way as such,
\r
827 // therefore it is safe to do this.
\r
828 ea.clear(ElementType.Edge);
\r
831 ea.clear(ElementType.BranchPoint);
\r
834 private Point2D getPastePos(DiagramSelection ds) {
\r
835 MouseInfo mi = mouseUtil.getMouseInfo(0);
\r
840 double xoff = mi.canvasPosition.getX() - ds.getCopyPos().getX();
\r
841 double yoff = mi.canvasPosition.getY() - ds.getCopyPos().getY();
\r
842 if (xoff == pasteOffset.getX() && yoff == pasteOffset.getY()) {
\r
843 // The mouse has not moved since last paste so let's offset the
\r
844 // paste down and right.
\r
845 double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
\r
846 return new Point2D.Double(
\r
847 mi.canvasPosition.getX() + counterOffset,
\r
848 mi.canvasPosition.getY() + counterOffset);
\r
850 pasteWithoutMovingGhostCounter = 0;
\r
851 pasteOffset.setLocation(xoff, yoff);
\r
852 return mi.canvasPosition;
\r
854 //return ds.getCopyPos();
\r
855 Point2D p = ds.getCopyPos();
\r
856 double counterOffset = getOffsetGridSize() * (++pasteWithoutMovingGhostCounter);
\r
857 return new Point2D.Double(
\r
858 p.getX() + pasteOffset.getX() + counterOffset,
\r
859 p.getY() + pasteOffset.getY() + counterOffset);
\r
863 private double getOffsetGridSize() {
\r
864 Double grid = getHint(GridPainter.KEY_GRID_SIZE);
\r
865 return (grid == null || grid == 0) ? 1.0 : grid;
\r
872 private static boolean isConnectionOrEdge(IElement e) {
\r
873 ElementClass ec = e.getElementClass();
\r
874 return ec.containsClass(ConnectionHandler.class)|| ec.containsClass(BendsHandler.class);
\r
881 private static boolean isMoveable(IElement e) {
\r
882 ElementClass ec = e.getElementClass();
\r
883 return ec.containsClass(Move.class) && ec.containsClass(Transform.class);
\r
888 * @return <code>null</code> if a point of reference cannot be determined
\r
889 * for the specified selection.
\r
891 private Point2D getCopyStartPos(Set<IElement> ss) {
\r
892 // MouseInfo mi = mouseUtil.getMouseInfo(0);
\r
893 // if (mi != null) {
\r
894 // return (Point2D) mi.canvasPosition.clone();
\r
897 // Find bounding rectangle top left corner
\r
898 double mx = Double.MAX_VALUE;
\r
899 double my = Double.MAX_VALUE;
\r
900 for (IElement e : ss) {
\r
901 if (isConnectionOrEdge(e) || !isMoveable(e))
\r
904 //Point2D pos = ElementUtils.getPos(e);
\r
905 Point2D pos = ElementUtils.getAbsolutePos(e);
\r
906 if (pos.getX() < mx)
\r
908 if (pos.getY() < my)
\r
912 // Find element nearest to the top left corner
\r
913 Point2D nearest = null;
\r
914 double dist = Double.MAX_VALUE;
\r
915 for (IElement e : ss) {
\r
916 if (isConnectionOrEdge(e) || !isMoveable(e))
\r
919 Point2D pos = ElementUtils.getAbsolutePos(e);
\r
920 double dx = pos.getX() - mx;
\r
921 double dy = pos.getY() - my;
\r
922 double d = dx*dx + dy*dy;
\r
932 private void moveGhostElements(DiagramSelection ds, Point2D pastePos) {
\r
933 Point2D copyPos = ds.getCopyPos();
\r
934 double dx = (pastePos.getX() - copyPos.getX());
\r
935 double dy = (pastePos.getY() - copyPos.getY());
\r
938 Point2D snap = CopyPasteUtil.snap(getContext(), new Point2D.Double(dx, dy));
\r
940 ghostNode.setTransform(AffineTransform.getTranslateInstance(snap.getX(), snap.getY()));
\r
941 //System.out.println("ghost node: " + ghostNode);
\r
944 protected SingleElementNode ghostNode = null;
\r
945 protected NodeMapper ghostNodeMapper = new NodeMapper();
\r
948 public void initSG(G2DParentNode parent) {
\r
949 ghostNode = parent.addNode("cut/copy ghost", SingleElementNode.class);
\r
950 ghostNode.setZIndex(COPY_GHOSTING_PAINT_PRIORITY);
\r
951 //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.40f));
\r
952 ghostNode.setVisible(Boolean.FALSE);
\r
956 public void cleanupSG() {
\r
957 ghostNode.remove();
\r
961 ghostNode.removeNodes();
\r
962 ghostNode.setVisible(Boolean.FALSE);
\r
963 ghostNodeMapper.clear();
\r
968 * @return <code>true</code> if the ghost nodes were hidden and a refresh is
\r
971 boolean hideSG(DiagramSelection selection) {
\r
972 if (ghostNode.isVisible()) {
\r
973 // Make sure there's no leftover graphics.
\r
974 ghostNode.removeNodes();
\r
975 ghostNode.setVisible(Boolean.FALSE);
\r
981 protected void scheduleActivateOwnerPart() {
\r
984 SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), new Runnable() {
\r
986 public void run() {
\r
988 mPart.getContext().get(EPartService.class).activate(mPart);
\r
993 @EventHandler(priority = 0)
\r
994 public boolean handleMouse(MouseExitEvent e) {
\r
995 DiagramSelection ds = getProjectSelection();
\r
996 if (!ds.isEmpty()) {
\r
1001 // The part might no longer have focus.
\r
1002 // [Tuukka] commented out to fix Apros #3678
\r
1003 //hasFocus = false;
\r
1008 @EventHandler(priority = 0)
\r
1009 public boolean handleMouse(MouseEnterEvent e) {
\r
1010 DiagramSelection ds = getProjectSelection();
\r
1011 if (!ds.isEmpty()) {
\r
1012 if (mPart != null) {
\r
1013 if (inPasteMode(e)) {
\r
1014 scheduleActivateOwnerPart();
\r
1021 @EventHandler(priority = 0)
\r
1022 public boolean handleMouse(MouseMovedEvent e) {
\r
1023 DiagramSelection ds = getProjectSelection();
\r
1024 if (!ds.isEmpty()) {
\r
1025 MouseInfo mi = mouseUtil.getMouseInfo(0);
\r
1026 //System.out.println("LAST MOUSE INFO: " + mi);
\r
1030 if (inPasteMode(e)) {
\r
1031 // Make sure that this owner part is active now.
\r
1033 scheduleActivateOwnerPart();
\r
1045 void updateSG(DiagramSelection selection) {
\r
1046 MouseInfo mi = mouseUtil.getMouseInfo(0);
\r
1050 //ghostNode.setComposite(AlphaComposite.SrcAtop.derive(0.40f));
\r
1051 //ghostNode.setComposite(null);
\r
1053 moveGhostElements(selection, mi.canvasPosition);
\r
1054 if (selection.getSourceCanvas() != getContext()) {
\r
1055 for (IElement e : selection.getOriginalElements()) {
\r
1056 INode node = e.getHint(ElementHints.KEY_SG_NODE);
\r
1057 //System.out.println("ghost element: " + e + ", node=" + node);
\r
1058 if (node instanceof IG2DNode) {
\r
1059 LocalDelegateNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e),
\r
1060 LocalDelegateNode.class);
\r
1061 delegate.setDelegate( (IG2DNode) node );
\r
1065 for (IElement e : selection.getOriginalElements()) {
\r
1066 //System.out.println("ghost element: " + e);
\r
1067 INode node = e.getHint(ElementHints.KEY_SG_NODE);
\r
1068 if (node != null) {
\r
1069 //System.out.println("ghost node: " + node);
\r
1070 ghostNodeMapper.add(node);
\r
1071 String nodeId = ghostNodeMapper.getId(node);
\r
1072 //System.out.println("ghost node id: " + nodeId);
\r
1073 LinkNode delegate = getOrCreateNode(ghostNode, ElementUtils.generateNodeId(e), LinkNode.class);
\r
1074 delegate.setDelegateId( nodeId );
\r
1079 ghostNode.setVisible(true);
\r
1082 private <T extends INode> T getOrCreateNode(ParentNode<?> parentNode, String id, Class<T> clazz) {
\r
1083 INode n = ghostNode.getNode(id);
\r
1084 if (clazz.isInstance(n))
\r
1085 return clazz.cast(n);
\r
1086 ghostNode.removeNode(id);
\r
1087 return ghostNode.addNode(id, clazz);
\r
1090 private boolean hasHighlight() {
\r
1091 return highlightMode != null;
\r
1094 private void removeHighlight() {
\r
1097 assert getContext().getThreadAccess().currentThreadAccess();
\r
1098 if (highlightMode != null) {
\r
1099 if (!highlightMode.isRemoved()) {
\r
1100 highlightMode.remove();
\r
1103 highlightMode = null;
\r
1107 private boolean inPasteMode(MouseEvent e) {
\r
1108 return (e.stateMask & MouseEvent.CTRL_MASK) != 0;
\r
1111 void selectedMessage(DiagramSelection ds) {
\r
1112 int size = ds.getOriginalElements().size();
\r
1113 StringBuilder sb = new StringBuilder();
\r
1115 sb.append("No elements to ");
\r
1119 sb.append("copy");
\r
1122 sb.append("Cut ");
\r
1124 sb.append("Copied ");
\r
1126 sb.append(" element");
\r
1130 message(sb.toString());
\r
1133 void message(final String message) {
\r
1134 if (statusLine == null)
\r
1136 swtExec(new Runnable() {
\r
1138 public void run() {
\r
1139 statusLine.setMessage(message);
\r
1140 statusLine.setErrorMessage(null);
\r
1145 void error(final String message) {
\r
1146 if (statusLine == null)
\r
1148 swtExec(new Runnable() {
\r
1150 public void run() {
\r
1151 statusLine.setErrorMessage(message);
\r
1156 void swtExec(Runnable r) {
\r
1157 ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
\r
1160 // MONITOR PASTE SUPPORT
\r
1162 private boolean tryPasteMonitors() {
\r
1163 SimanticsClipboard clipboard = Simantics.getClipboard();
\r
1164 for (Set<Representation> content : clipboard.getContents()) {
\r
1166 final Variable var_ = ClipboardUtils.accept(content, SimanticsKeys.KEY_VARIABLE);
\r
1167 if (var_ != null) {
\r
1168 final Resource diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
\r
1169 final Resource runtime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
\r
1170 final Resource elementResource = Simantics.getSession().syncRequest(new Read<Resource>() {
\r
1172 public Resource perform(ReadGraph graph) throws DatabaseException {
\r
1173 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
1175 String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
\r
1176 if (diagramVariable == null)
\r
1179 Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
\r
1180 if (diaVar == null)
\r
1183 Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
\r
1187 Variable component = Variables.getChild(graph, diaVar, var);
\r
1188 if (component == null)
\r
1191 Resource componentResource = component.getPossibleRepresents(graph);
\r
1192 if (componentResource == null)
\r
1195 return graph.getPossibleObject(componentResource, ModelingResources.getInstance(graph).ComponentToElement);
\r
1199 if (elementResource == null)
\r
1202 final AffineTransform monitorTransform = Simantics.getSession().syncRequest(new Read<AffineTransform>() {
\r
1204 public AffineTransform perform(ReadGraph graph) throws DatabaseException {
\r
1205 AffineTransform at = null;
\r
1207 if (graph.isInstanceOf(elementResource, DiagramResource.getInstance(graph).Connection)) {
\r
1208 Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, elementResource);
\r
1209 if (tailNode != null) {
\r
1210 at = DiagramGraphUtil.getAffineTransform(graph, tailNode);
\r
1214 at = DiagramGraphUtil.getAffineTransform(graph, elementResource);
\r
1220 MouseInfo mi = mouseUtil.getMouseInfo(0);
\r
1223 final double dx = mi.canvasPosition.getX() - monitorTransform.getTranslateX();
\r
1224 final double dy = mi.canvasPosition.getY() - monitorTransform.getTranslateY();
\r
1226 Simantics.getSession().asyncRequest(new WriteRequest() {
\r
1229 public void perform(WriteGraph graph) throws DatabaseException {
\r
1230 Layer0 L0 = Layer0.getInstance(graph);
\r
1231 Layer0X L0X = Layer0X.getInstance(graph);
\r
1232 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
1233 G2DResource G2D = G2DResource.getInstance(graph);
\r
1235 String diagramVariable = graph.getPossibleRelatedValue(runtime, DIA.RuntimeDiagram_HasVariable);
\r
1236 if (diagramVariable == null)
\r
1239 Variable diaVar = Variables.getPossibleVariable(graph, diagramVariable);
\r
1240 if (diaVar == null)
\r
1243 Variable var = Variables.switchRealization(graph, var_, Variables.getRealization(graph, diaVar));
\r
1247 Variable component = Variables.getChild(graph, diaVar, var);
\r
1248 if (component == null)
\r
1251 Resource componentResource = component.getPossibleRepresents(graph);
\r
1252 if (componentResource == null)
\r
1255 String suffix = Variables.getRVI(graph, component, var);
\r
1257 Resource resource = graph.newResource();
\r
1258 graph.claim(resource, L0.InstanceOf, null, DIA.Monitor);
\r
1260 final double scale = monitorScale;
\r
1262 DiagramGraphUtil.setTransform(graph, resource, new AffineTransform(scale, 0, 0, scale, dx, dy));
\r
1264 OrderedSetUtils.add(graph, diagramResource, resource);
\r
1266 // 5.1. Give running name to element and increment the counter attached to the diagram.
\r
1267 Long l = graph.getPossibleRelatedValue(diagramResource, DIA.HasModCount, Bindings.LONG);
\r
1269 l = Long.valueOf(0L);
\r
1270 graph.claimLiteral(resource, L0.HasName, l.toString(), Bindings.STRING);
\r
1271 graph.claimLiteral(diagramResource, DIA.HasModCount, ++l, Bindings.LONG);
\r
1273 // 5.2. Make the diagram consist of the new element
\r
1274 graph.claim(diagramResource, L0.ConsistsOf, resource);
\r
1276 graph.claim(resource, G2D.HasHorizontalAlignment, null, G2D.Alignment_Leading);
\r
1277 graph.claimLiteral(resource, DIA.HasDirection, 0.0);
\r
1279 graph.claim(resource, DIA.HasMonitorComponent, componentResource);
\r
1280 graph.claimLiteral(resource, DIA.HasMonitorSuffix, suffix);
\r
1282 Resource model = Variables.getModel(graph, diaVar);
\r
1283 if (model != null) {
\r
1284 Resource template = graph.getPossibleObject(model, DIA.HasDefaultMonitorTemplate);
\r
1285 if (template != null) {
\r
1286 graph.claim(resource, L0X.ObtainsProperty1, null, template);
\r
1293 } catch (DatabaseException e1) {
\r
1294 Logger.defaultLogError(e1);
\r