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.modeling.actions;
\r
14 import java.awt.Shape;
\r
15 import java.awt.geom.AffineTransform;
\r
16 import java.awt.geom.Rectangle2D;
\r
17 import java.util.ArrayList;
\r
18 import java.util.Collection;
\r
19 import java.util.Collections;
\r
20 import java.util.Comparator;
\r
21 import java.util.HashSet;
\r
22 import java.util.List;
\r
23 import java.util.Set;
\r
24 import java.util.concurrent.TimeUnit;
\r
25 import java.util.function.Consumer;
\r
27 import org.eclipse.core.runtime.IStatus;
\r
28 import org.eclipse.core.runtime.Status;
\r
29 import org.eclipse.jface.action.IStatusLineManager;
\r
30 import org.eclipse.jface.window.Window;
\r
31 import org.eclipse.swt.widgets.Shell;
\r
32 import org.eclipse.ui.IEditorInput;
\r
33 import org.eclipse.ui.IEditorPart;
\r
34 import org.eclipse.ui.IWorkbenchPart;
\r
35 import org.eclipse.ui.IWorkbenchWindow;
\r
36 import org.eclipse.ui.PartInitException;
\r
37 import org.simantics.Simantics;
\r
38 import org.simantics.databoard.Bindings;
\r
39 import org.simantics.databoard.util.URIStringUtils;
\r
40 import org.simantics.db.ReadGraph;
\r
41 import org.simantics.db.Resource;
\r
42 import org.simantics.db.Session;
\r
43 import org.simantics.db.common.NamedResource;
\r
44 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
\r
45 import org.simantics.db.common.request.ReadRequest;
\r
46 import org.simantics.db.common.utils.NameUtils;
\r
47 import org.simantics.db.common.utils.OrderedSetUtils;
\r
48 import org.simantics.db.exception.DatabaseException;
\r
49 import org.simantics.db.layer0.variable.RVI;
\r
50 import org.simantics.db.layer0.variable.Variable;
\r
51 import org.simantics.db.layer0.variable.Variables;
\r
52 import org.simantics.diagram.content.ConnectionUtil;
\r
53 import org.simantics.diagram.flag.FlagUtil;
\r
54 import org.simantics.diagram.stubs.DiagramResource;
\r
55 import org.simantics.g2d.canvas.ICanvasContext;
\r
56 import org.simantics.g2d.diagram.DiagramHints;
\r
57 import org.simantics.g2d.diagram.IDiagram;
\r
58 import org.simantics.g2d.diagram.handler.DataElementMap;
\r
59 import org.simantics.g2d.diagram.participant.Selection;
\r
60 import org.simantics.g2d.element.ElementUtils;
\r
61 import org.simantics.g2d.element.IElement;
\r
62 import org.simantics.g2d.elementclass.FlagClass;
\r
63 import org.simantics.g2d.participant.CanvasBoundsParticipant;
\r
64 import org.simantics.g2d.participant.TransformUtil;
\r
65 import org.simantics.g2d.utils.GeometryUtils;
\r
66 import org.simantics.layer0.Layer0;
\r
67 import org.simantics.layer0.utils.operations.Operation;
\r
68 import org.simantics.modeling.ModelingOperationConstants;
\r
69 import org.simantics.modeling.ModelingResources;
\r
70 import org.simantics.modeling.utils.Monitors;
\r
71 import org.simantics.structural.stubs.StructuralResource2;
\r
72 import org.simantics.ui.workbench.ResourceEditorInput;
\r
73 import org.simantics.ui.workbench.ResourceEditorInput2;
\r
74 import org.simantics.utils.datastructures.persistent.IContextMap;
\r
75 import org.simantics.utils.page.MarginUtils;
\r
76 import org.simantics.utils.strings.AlphanumComparator;
\r
77 import org.simantics.utils.threads.ThreadUtils;
\r
78 import org.simantics.utils.ui.ErrorLogger;
\r
79 import org.simantics.utils.ui.workbench.WorkbenchUtils;
\r
81 public class NavigateToTarget extends Operation {
\r
83 public NavigateToTarget() {
\r
84 super("Navigate to Target");
\r
88 public void exec(Session session, IContextMap parameters) {
\r
89 final Resource r = (Resource) parameters.get(SUBJECT);
\r
90 final IWorkbenchWindow window = (IWorkbenchWindow) parameters.get(ModelingOperationConstants.WORKBENCH_WINDOW);
\r
91 final IWorkbenchPart part = (IWorkbenchPart) parameters.get(ModelingOperationConstants.WORKBENCH_PART);
\r
93 session.asyncRequest(new ReadRequest() {
\r
95 public void run(ReadGraph g) throws DatabaseException {
\r
96 final Resource thisDiagram = getOwnerList(g, r);
\r
97 if (thisDiagram == null)
\r
100 Set<Resource> counterparts = FlagUtil.getCounterparts(g, r);
\r
101 if (counterparts.size() > 1) {
\r
102 // Ask user which target to navigate to.
\r
103 asyncQueryTarget(window.getShell(), g, thisDiagram, counterparts,
\r
104 target -> navigateToTarget( window, part, thisDiagram, target));
\r
105 } else if (counterparts.size() == 1) {
\r
106 // Target is defined.
\r
107 final String error = navigateToTarget( g, window, part, thisDiagram, counterparts.iterator().next() );
\r
108 if (error != null && !error.isEmpty()) {
\r
109 window.getShell().getDisplay().asyncExec(new Runnable() {
\r
111 public void run() {
\r
112 IStatusLineManager status = WorkbenchUtils.getStatusLine(part);
\r
113 status.setErrorMessage(error);
\r
118 // Try other methods of getting a navigation target besides flags.
\r
119 Resource target = Monitors.getMonitoredElement(g, r);
\r
120 if (target != null)
\r
121 navigateToTarget( g, window, part, thisDiagram, target );
\r
124 }, new ProcedureAdapter<Object>() {
\r
126 public void exception(Throwable t) {
\r
127 ErrorLogger.defaultLogError(t);
\r
132 protected void navigateToTarget(final IWorkbenchWindow window, final IWorkbenchPart sourceEditor,
\r
133 final Resource sourceDiagram, final Resource target) {
\r
134 Simantics.getSession().asyncRequest(new ReadRequest() {
\r
136 public void run(ReadGraph graph) throws DatabaseException {
\r
137 navigateToTarget(graph, window, sourceEditor, sourceDiagram, target);
\r
139 }, new ProcedureAdapter<Object>() {
\r
140 public void exception(Throwable t) {
\r
141 ErrorLogger.defaultLogError(t);
\r
149 * @param sourceEditor
\r
150 * @param sourceDiagram
\r
152 * @return null if everything's OK
\r
153 * @throws DatabaseException
\r
155 protected String navigateToTarget(ReadGraph graph, IWorkbenchWindow window, IWorkbenchPart sourceEditor,
\r
156 Resource sourceDiagram, final Resource target) throws DatabaseException {
\r
157 ModelingResources MOD = ModelingResources.getInstance(graph);
\r
159 final Resource otherDiagram = getOwnerList(graph, target);
\r
160 if (otherDiagram == null)
\r
163 if (!sourceDiagram.equals(otherDiagram)) {
\r
165 // Find the structural path
\r
166 Resource otherComposite = graph.getPossibleObject(otherDiagram, MOD.DiagramToComposite);
\r
167 if (otherComposite == null)
\r
170 Variable compositeVariable = Variables.getVariable(graph, otherComposite);
\r
171 final Resource model = Variables.getPossibleModel(graph, compositeVariable);
\r
172 final RVI rvi = model == null ? null : compositeVariable.getPossibleRVI(graph);
\r
173 if (model == null || rvi == null)
\r
174 return "Navigating via flags only possible under model configuration";
\r
176 window.getShell().getDisplay().asyncExec(
\r
177 editorActivator(sourceEditor, otherDiagram, model, rvi, part -> {
\r
178 final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);
\r
179 assert openedCanvas != null;
\r
180 // CanvasContext-wide denial of initial zoom-to-fit on diagram open.
\r
181 openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);
\r
182 ThreadUtils.asyncExec( openedCanvas.getThreadAccess(),
\r
183 elementSelectorZoomer( openedCanvas, Collections.<Object>singleton( target ), false ) );
\r
186 ICanvasContext canvas = (ICanvasContext) sourceEditor.getAdapter( ICanvasContext.class );
\r
187 assert canvas != null;
\r
188 ThreadUtils.asyncExec( canvas.getThreadAccess(),
\r
189 elementSelectorZoomer( canvas, Collections.<Object>singleton( target ), true ));
\r
194 protected void asyncQueryTarget(final Shell parentShell, ReadGraph graph, Resource sourceDiagram,
\r
195 Set<Resource> counterparts, final Consumer<Resource> navigationCallback) throws DatabaseException {
\r
196 ModelingResources MOD = ModelingResources.getInstance(graph);
\r
197 StructuralResource2 STR = StructuralResource2.getInstance(graph);
\r
198 ConnectionUtil cu = new ConnectionUtil(graph);
\r
200 final List<NamedResource> outputs = new ArrayList<NamedResource>(counterparts.size());
\r
201 final List<NamedResource> inputs = new ArrayList<NamedResource>(counterparts.size());
\r
202 for (Resource counterpart : counterparts) {
\r
203 final Resource diagram = getOwnerList(graph, counterpart);
\r
204 if (diagram == null)
\r
207 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
\r
208 if (composite == null)
\r
210 Variable v = Variables.getPossibleVariable(graph, composite);
\r
214 String rvi = Variables.getRVI(graph, v);
\r
215 rvi = URIStringUtils.unescape(rvi);
\r
217 Resource connectedComponent = null;
\r
219 for (Resource connector : graph.getObjects(counterpart, STR.IsConnectedTo)) {
\r
220 Resource diagramConnection = ConnectionUtil.tryGetConnection(graph, connector);
\r
221 if (diagramConnection != null) {
\r
222 Collection<Resource> connectors = cu.getConnectors(diagramConnection, new HashSet<Resource>());
\r
223 connectors.remove(connector);
\r
224 if (connectors.isEmpty()) {
\r
227 connectedComponent = graph.getPossibleObject(diagramConnection, MOD.ElementToComponent);
\r
228 if (connectedComponent == null) {
\r
229 for (Resource conn : connectors) {
\r
230 Resource element = cu.getConnectedComponent(diagramConnection, conn);
\r
231 if (element == null)
\r
233 connectedComponent = graph.getPossibleObject(element, MOD.ElementToComponent);
\r
234 if (connectedComponent != null)
\r
241 if (connectedComponent != null) {
\r
242 rvi += Variables.Role.CHILD.getIdentifier();
\r
243 rvi += NameUtils.getSafeName(graph, connectedComponent);
\r
246 boolean localFlag = sourceDiagram.equals(diagram);
\r
248 Layer0 L0 = Layer0.getInstance(graph);
\r
250 String label = graph.getPossibleRelatedValue2(counterpart, L0.HasLabel, Bindings.STRING);
\r
251 if (label != null && !label.isEmpty())
\r
252 rvi += " " + label;
\r
256 FlagClass.Type type = FlagUtil.getFlagType(graph, counterpart, FlagClass.Type.In);
\r
259 inputs.add( new NamedResource(rvi, counterpart) );
\r
262 outputs.add( new NamedResource(rvi, counterpart) );
\r
267 // To make result ordering stable and logical
\r
268 Collections.sort(outputs, COMPARATOR);
\r
269 Collections.sort(inputs, COMPARATOR);
\r
271 outputs.addAll(inputs);
\r
272 if (outputs.isEmpty())
\r
275 parentShell.getDisplay().asyncExec(new Runnable() {
\r
277 public void run() {
\r
278 if (parentShell.isDisposed())
\r
280 NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
\r
281 parentShell, outputs.toArray(new NamedResource[0]),
\r
282 "Choose Navigation Target",
\r
283 "Select single navigation target from list");
\r
284 if (dialog.open() != Window.OK)
\r
286 if (dialog.getSelection() != null)
\r
287 navigationCallback.accept( dialog.getSelection().getResource() );
\r
292 private static final Comparator<? super NamedResource> COMPARATOR = new Comparator<NamedResource>() {
\r
294 public int compare(NamedResource o1, NamedResource o2) {
\r
295 return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
\r
299 public static Runnable editorActivator(final IWorkbenchPart part, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
\r
300 String sourcePartId = part.getSite().getId();
\r
301 return editorActivator(sourcePartId, diagram, model, rvi, successCallback);
\r
304 public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
\r
307 // open and activate new editor
\r
308 IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, createEditorInput(editorPartId, diagram, model, rvi));
\r
309 newPart.getSite().getPage().activate(newPart);
\r
310 successCallback.accept(newPart);
\r
311 } catch (PartInitException e) {
\r
312 ErrorLogger.defaultLogError(e);
\r
317 private static IEditorInput createEditorInput(String editorPartId, Resource diagram, Resource model, RVI rvi) {
\r
319 return new ResourceEditorInput2(editorPartId, diagram, model, rvi);
\r
321 return new ResourceEditorInput(editorPartId, diagram);
\r
325 * @param editorPartId
\r
329 * @param successCallback
\r
331 * @deprecated use {@link #editorActivator(String, Resource, Resource, RVI, Callback)} instead
\r
334 public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final String rvi, final Consumer<IEditorPart> successCallback) {
\r
337 // open and activate new editor
\r
338 IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, new ResourceEditorInput2(editorPartId, diagram, model, rvi));
\r
339 newPart.getSite().getPage().activate(newPart);
\r
340 successCallback.accept(newPart);
\r
341 } catch (PartInitException e) {
\r
342 ErrorLogger.defaultLogError(e);
\r
347 public static Runnable elementSelectorZoomer(final ICanvasContext canvas, final Collection<? extends Object> elementObjects, final boolean keepZoom) {
\r
348 return new Runnable() {
\r
351 public void run() {
\r
352 //System.out.println(tries + ": elementSelectorZoomer: " + canvas.isDisposed() + ", " + elementObjects);
\r
353 if (canvas.isDisposed())
\r
356 // This will prevent eternal looping in unexpected situations.
\r
357 if (++tries > 10) {
\r
358 ErrorLogger.defaultLog(new Status(IStatus.INFO, "",
\r
359 "NavigateToTarget.elementSelectorZoomer failed to find any of the requested elements "
\r
360 + elementObjects + ". Giving up."));
\r
364 IDiagram diagram = canvas.getHintStack().getHint(DiagramHints.KEY_DIAGRAM);
\r
365 if (diagram == null || !zoomToSelection(canvas, diagram, selectElement(canvas, diagram, elementObjects), keepZoom)) {
\r
366 // Reschedule for later in hopes that initialization is complete.
\r
367 ThreadUtils.getNonBlockingWorkExecutor().schedule(this, 200, TimeUnit.MILLISECONDS);
\r
373 public static Set<IElement> selectElement(final ICanvasContext canvas, final IDiagram diagram, final Collection<? extends Object> elementObjects) {
\r
375 Set<IElement> selection = new HashSet<IElement>(elementObjects.size());
\r
376 DataElementMap dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
\r
377 for (Object obj : elementObjects) {
\r
378 IElement element = dataMap.getElement(diagram, obj);
\r
379 if (element != null) {
\r
380 selection.add(element);
\r
383 if (!selection.isEmpty()) {
\r
384 for (Selection s : canvas.getItemsByClass(Selection.class)) {
\r
385 s.setSelection(0, selection);
\r
391 public static boolean zoomToSelection(final ICanvasContext canvas, final IDiagram diagram, Set<IElement> selection, final boolean keepZoom) {
\r
392 final TransformUtil util = canvas.getSingleItem(TransformUtil.class);
\r
393 CanvasBoundsParticipant boundsParticipant = canvas.getAtMostOneItemOfClass(CanvasBoundsParticipant.class);
\r
394 if (boundsParticipant == null)
\r
397 final Rectangle2D controlBounds = boundsParticipant.getControlBounds().getFrame();
\r
398 if (controlBounds == null || controlBounds.isEmpty())
\r
401 final Shape shp = ElementUtils.getElementBoundsOnDiagram(selection);
\r
405 ThreadUtils.asyncExec(canvas.getThreadAccess(), new Runnable() {
\r
407 public void run() {
\r
408 if (canvas.isDisposed())
\r
411 Rectangle2D diagramRect = shp.getBounds2D();
\r
413 // Make sure that even empty bounds can be zoomed into.
\r
414 org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1);
\r
417 double scaleFactor = GeometryUtils.getScale(util.getTransform());
\r
418 double cwh = controlBounds.getWidth() / (scaleFactor*2);
\r
419 double chh = controlBounds.getHeight() / (scaleFactor*2);
\r
421 AffineTransform view = new AffineTransform();
\r
422 view.scale(scaleFactor, scaleFactor);
\r
423 view.translate(-diagramRect.getCenterX()+cwh, -diagramRect.getCenterY()+chh);
\r
425 util.setTransform(view);
\r
427 MarginUtils.Margin margin = MarginUtils.marginOf(40, 0, 0);
\r
428 MarginUtils.Margins margins = new MarginUtils.Margins(margin, margin, margin, margin);
\r
429 util.fitArea(controlBounds, diagramRect, margins);
\r
436 public static Resource getOwnerList(ReadGraph g, Resource listElement) throws DatabaseException {
\r
437 return OrderedSetUtils.getSingleOwnerList(g, listElement, DiagramResource.getInstance(g).Composite);
\r