From 252076ed80eef446cc62c5b193d34b7bea3cdc80 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Tue, 27 Nov 2018 14:28:48 +0200 Subject: [PATCH] Ask to link library to model when dropping symbol from unlinked library This simplifies workflow for users that want to add symbols from a user library but have not yet linked the library to the model. gitlab #215 Change-Id: I1eb4c0680a14dc21fca8dc8961baf1f059ecccd9 --- .../META-INF/MANIFEST.MF | 5 +- .../modeling/ui/diagramEditor/Messages.java | 16 + .../PopulateElementDropParticipant.java | 308 ++++++++++-------- .../ui/diagramEditor/dnd/DropSuggestion.java | 43 +++ .../ui/diagramEditor/dnd/DropSuggestions.java | 104 ++++++ .../ui/diagramEditor/dnd/MarkupDialog.java | 118 +++++++ .../ui/diagramEditor/dnd/Messages.java | 18 + .../ui/diagramEditor/dnd/messages.properties | 4 + .../ui/diagramEditor/messages.properties | 2 + .../org.simantics.wiki.ui.feature/feature.xml | 9 +- features/pom.xml | 1 + 11 files changed, 491 insertions(+), 137 deletions(-) create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/Messages.java create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestion.java create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestions.java create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/MarkupDialog.java create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/Messages.java create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/messages.properties create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/messages.properties diff --git a/bundles/org.simantics.modeling.ui/META-INF/MANIFEST.MF b/bundles/org.simantics.modeling.ui/META-INF/MANIFEST.MF index 340e36b7c..b8a6da23c 100644 --- a/bundles/org.simantics.modeling.ui/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.modeling.ui/META-INF/MANIFEST.MF @@ -62,7 +62,10 @@ Require-Bundle: org.simantics.project;bundle-version="1.0.0", org.simantics.export.core;bundle-version="1.0.0", org.slf4j.api, org.simantics.graphfile.ontology, - org.eclipse.e4.core.services + org.eclipse.e4.core.services, + org.eclipse.mylyn.wikitext;bundle-version="3.0.6", + org.eclipse.mylyn.wikitext.markdown;bundle-version="3.0.6", + org.eclipse.mylyn.wikitext.ui;bundle-version="3.0.6" Export-Package: org.simantics.modeling.ui, org.simantics.modeling.ui.actions, org.simantics.modeling.ui.chart.property, diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/Messages.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/Messages.java new file mode 100644 index 000000000..6a58dac08 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/Messages.java @@ -0,0 +1,16 @@ +package org.simantics.modeling.ui.diagramEditor; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.simantics.modeling.ui.diagramEditor.messages"; //$NON-NLS-1$ + public static String PopulateElementDropParticipant_PreDropFixesFailed; + public static String PopulateElementDropParticipant_PreDropFixesFailed_Title; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/PopulateElementDropParticipant.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/PopulateElementDropParticipant.java index 4f2740602..49adcb65b 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/PopulateElementDropParticipant.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/PopulateElementDropParticipant.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2018 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 @@ -8,6 +8,7 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - gitlab #215 *******************************************************************************/ package org.simantics.modeling.ui.diagramEditor; @@ -22,37 +23,39 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPartSite; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; -import org.simantics.db.WriteGraph; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.ResourceRead2; -import org.simantics.db.common.request.UniqueRead; -import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.request.IsLinkedTo; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; +import org.simantics.db.request.Read; +import org.simantics.db.request.Write; import org.simantics.db.service.SerialisationSupport; import org.simantics.diagram.adapter.GraphToDiagramSynchronizer; import org.simantics.diagram.content.Change; import org.simantics.diagram.content.DiagramContentChanges; import org.simantics.diagram.content.DiagramContentTracker; import org.simantics.diagram.stubs.DiagramResource; -import org.simantics.diagram.symbollibrary.ISymbolItem; import org.simantics.diagram.synchronization.runtime.DiagramSelectionUpdater; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.diagram.ui.ElementClassTransferable; @@ -76,22 +79,40 @@ import org.simantics.g2d.element.IElement; import org.simantics.g2d.participant.TransformUtil; import org.simantics.modeling.ModelingResources; import org.simantics.modeling.ui.Activator; +import org.simantics.modeling.ui.diagramEditor.dnd.DropSuggestion; +import org.simantics.modeling.ui.diagramEditor.dnd.DropSuggestions; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.ui.dnd.LocalObjectTransfer; import org.simantics.ui.dnd.LocalObjectTransferable; import org.simantics.ui.selection.WorkbenchSelectionElement; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.logging.TimeLogger; +import org.simantics.utils.strings.EString; +import org.simantics.utils.ui.SWTUtils; +import org.simantics.utils.ui.dialogs.ShowError; import org.simantics.utils.ui.workbench.WorkbenchUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This participant populates Elements from ElementClass-resources drops */ public class PopulateElementDropParticipant extends AbstractDiagramParticipant implements IDropTargetParticipant { + private static final Logger LOGGER = LoggerFactory.getLogger(PopulateElementDropParticipant.class); + + /** + * List of {@link DropSuggestion} instances that need to be applied to be model + * before the drop can commence. Used with the hint context from + * {@link IDnDContext#getHints()}. + */ + private static final Key KEY_SUGGESTIONS = new IHintContext.KeyOf(List.class); + @Dependency PickContext pickContext; @Dependency TransformUtil transformUtil; - + protected GraphToDiagramSynchronizer synchronizer; protected IWorkbenchPartSite partSite; @@ -111,19 +132,14 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i Transferable tr = dtde.getTransferable(); if (tr.isDataFlavorSupported(LocalObjectTransferable.FLAVOR)) { - // System.out.println("joo"); Object obj = null; // This must be done to have SWT transfer set the source data try { obj = tr.getTransferData(LocalObjectTransferable.FLAVOR); // System.out.println("GOT FROM AWT: " + obj); - } catch (UnsupportedFlavorException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (UnsupportedFlavorException | IOException e) { + LOGGER.error("Could not get AWT transferable data", e); //$NON-NLS-1$ } // Check SWT @@ -143,32 +159,20 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i } else { Resource r = (Resource) ((IAdaptable) elm).getAdapter(Resource.class); if (r != null) { - if (elm instanceof ISymbolItem) { - /* FIXME fix this check - ISymbolItem symbol = (ISymbolItem) elm; - Resource dia = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); - try { - if (!DiagramTypeUtils.symbolAllowedOnDiagram(synchronizer.getSession(), symbol, dia)) { - // Deny dragging of this symbol - continue; - } - } catch (DatabaseException e) { - e.printStackTrace(); - continue; - }*/ - } - try { - Object errorOrSymbolResource = validateDrop(synchronizer.getSession(), r, - diagram. getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE)); + Object errorOrSymbolResource = validateDrag(synchronizer.getSession(), r, + diagram. getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), + dp.getHints()); if (errorOrSymbolResource instanceof Resource) { - ElementClassDragItem item = new ElementClassDragItem(synchronizer.getNodeClass((Resource)errorOrSymbolResource)); + Resource symbol = (Resource) errorOrSymbolResource; + ElementClassDragItem item = new ElementClassDragItem(synchronizer.getNodeClass(symbol)); item.getHintContext().setHint(ElementHints.KEY_TRANSFORM, AffineTransform.getScaleInstance(1, 1)); + item.getHintContext().setHint(ElementHints.KEY_OBJECT, symbol); dp.add(item); } } catch (DatabaseException e) { - // Ignore node-class retrieval failures. - //System.out.println("error: " + e.getMessage()); + // Ignore node-class retrieval failures, so only log as debug + LOGGER.debug("Could not retrieve node class for dropped symbol", e); //$NON-NLS-1$ } } } @@ -207,62 +211,69 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i } } - private Object validateDrop(RequestProcessor processor, final Resource draggedResource, final Resource dropTarget) throws DatabaseException { - return processor.syncRequest(new UniqueRead() { - @Override - public Object perform(ReadGraph graph) throws DatabaseException { -// System.out.println("dragged resource: " + draggedResource); -// System.out.println("drop target resource: " + dropTarget); - Resource sourceModel = graph.syncRequest(new PossibleIndexRoot(draggedResource)); - Resource targetModel = graph.syncRequest(new PossibleIndexRoot(dropTarget)); -// System.out.println("source model: " + sourceModel); -// System.out.println("target model: " + targetModel); - - // Prevent dragging data from one source model to another. - // If source is not part of any model, everything is okay. - if (sourceModel != null && !graph.syncRequest(new IsLinkedTo(targetModel, sourceModel))) { - // Prevent a symbol instantiating within its own configuration. - // NOTE: this doesn't handle transitive cycles. - return "Cannot instantiate " + NameUtils.getSafeName(graph, draggedResource) + " into model " - + NameUtils.getURIOrSafeNameInternal(graph, targetModel) + ". The source namespace (" - + NameUtils.getURIOrSafeNameInternal(graph, sourceModel) + ") is not linked to the target model."; - } - - // Prevent dragging to published components - ModelingResources MOD = ModelingResources.getInstance(graph); - StructuralResource2 STR = StructuralResource2.getInstance(graph); - Resource configuration = graph.getPossibleObject(dropTarget, MOD.DiagramToComposite); - if (configuration != null) { - Resource componentTypeFromDiagram = graph.getPossibleObject(configuration, STR.Defines); - if(componentTypeFromDiagram != null) { - if(Layer0Utils.isPublished(graph, componentTypeFromDiagram)) - return "Cannot create elements into a diagram that belongs to a published user component."; - } - } + private Object validateDrag(RequestProcessor processor, final Resource draggedResource, final Resource dropTarget, IHintContext dndHints) throws DatabaseException { + return processor.syncRequest((Read) graph -> { + List suggestions = dndHints.getHint(KEY_SUGGESTIONS); + if (suggestions == null) { + suggestions = new ArrayList<>(); + dndHints.setHint(KEY_SUGGESTIONS, suggestions); + } - // Check if dragged object is symbol or component type and determine other - Resource componentType; - Resource symbol = graph.getPossibleObject(draggedResource, MOD.ComponentTypeToSymbol); - - if(symbol != null) - componentType = draggedResource; - else { - componentType = graph.getPossibleObject(draggedResource, MOD.SymbolToComponentType); - symbol = draggedResource; - } - - // Prevent dragging a symbol of component type into its own configuration. - if (componentType != null) { - if (configuration != null) { - Resource componentTypeFromDiagram = graph.getPossibleObject(configuration, STR.Defines); - if (componentTypeFromDiagram != null && componentType.equals(componentTypeFromDiagram)) { - return "Cannot instantiate user component within its own configuration."; - } - } + //System.out.println("dragged resource: " + draggedResource); + //System.out.println("drop target resource: " + dropTarget); + Resource sourceRoot = graph.syncRequest(new PossibleIndexRoot(draggedResource)); + Resource targetRoot = graph.syncRequest(new PossibleIndexRoot(dropTarget)); + //System.out.println("source model: " + sourceRoot); + //System.out.println("target model: " + targetRoot); + + // Prevent dragging data from one source model to another. + // If source is not part of any model, everything is okay. + if (sourceRoot != null && !graph.syncRequest(new IsLinkedTo(targetRoot, sourceRoot))) { + // Prevent instantiation from source roots that are already dependent on the target root. + // This would form a dependency cycle. + if (graph.syncRequest(new IsLinkedTo(sourceRoot, targetRoot))) { + return NLS.bind("Cannot instantiate {0} into namespace {1}. The source namespace ({2}) is already linked to the target namespace. Linking the target to the source would form a dependency cycle.", //$NON-NLS-1$ + new Object[] { + NameUtils.getSafeName(graph, draggedResource), + NameUtils.getURIOrSafeNameInternal(graph, targetRoot), + NameUtils.getURIOrSafeNameInternal(graph, sourceRoot) + }); } - return symbol; + // It is OK to continue for now, even though the target root is not linked to the source root. + // The question of whether to link the target root to the source root will asked at drop time. + suggestions.add(DropSuggestions.linkToLibrary(graph, targetRoot, sourceRoot)); } + + ModelingResources MOD = ModelingResources.getInstance(graph); + StructuralResource2 STR = StructuralResource2.getInstance(graph); + + Resource configurationComposite = graph.getPossibleObject(dropTarget, MOD.DiagramToComposite); + Resource componentTypeFromDiagram = configurationComposite != null ? graph.getPossibleObject(configurationComposite, STR.Defines) : null; + + // Prevent dragging to published components + if (componentTypeFromDiagram != null && Layer0Utils.isPublished(graph, componentTypeFromDiagram)) + return "Cannot create elements into a diagram that belongs to a published user component."; //$NON-NLS-1$ + + // Check if dragged object is symbol or component type and determine other + Resource componentType; + Resource symbol = graph.getPossibleObject(draggedResource, MOD.ComponentTypeToSymbol); + if (symbol != null) + componentType = draggedResource; + else { + componentType = graph.getPossibleObject(draggedResource, MOD.SymbolToComponentType); + symbol = draggedResource; + } + + // Prevent dragging a symbol of component type into its own configuration. + if (componentType != null + && configurationComposite != null + && componentTypeFromDiagram != null + && componentType.equals(componentTypeFromDiagram)) { + return "Cannot instantiate user component within its own configuration."; //$NON-NLS-1$ + } + + return symbol; }); } @@ -277,95 +288,122 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i } private IElement tryPick(Point p) { + Point2D canvas = transformUtil.controlToCanvas(p, null); - Point2D canvas = transformUtil.controlToCanvas(p, null); - assertDependencies(); PickRequest req = new PickRequest(canvas); req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; - List picks = new ArrayList(); + List picks = new ArrayList<>(); pickContext.pick(diagram, req, picks); if(picks.size() == 1) return picks.iterator().next(); - + return null; - } @Override public void drop(DropTargetDropEvent dtde, final IDnDContext dp) { - TimeLogger.resetTimeAndLog(getClass(), "drop"); + TimeLogger.resetTimeAndLog(getClass(), "drop"); //$NON-NLS-1$ - final IDiagram d = getHint(DiagramHints.KEY_DIAGRAM); + final Point loc = dtde.getLocation(); + final IDiagram d = diagram; if (d == null) return; - IElement pick = tryPick(dtde.getLocation()); - if(pick != null) { + try { + validateDrop(d, dp, () -> performDrop(d, loc, dp)); + } catch (DatabaseException e) { + LOGGER.error("Element drop validation failed", e); //$NON-NLS-1$ + } + } - final List wses = new ArrayList(); + private void validateDrop(IDiagram diagram, IDnDContext dp, Runnable dropFunction) + throws DatabaseException { + List reqs = dp.getHints().getHint(KEY_SUGGESTIONS); + if (reqs != null && !reqs.isEmpty()) { + // Ask user if suggestions should be ran before dropping. + // If not, cancel. + Shell parentShell = partSite.getWorkbenchWindow().getShell(); + SWTUtils.asyncExec(parentShell, () -> { + if (parentShell.isDisposed()) + return; + if (!DropSuggestions.askSuggestions(parentShell, reqs)) + return; - for(IDragItem i : dp.toArray()) - if(i instanceof WSEDragItem) - wses.add(((WSEDragItem)i).getObject()); + try { + Simantics.getSession().syncRequest(DropSuggestions.performSuggestionsRequest(reqs)); + + getThread().asyncExec(() -> { + if (isRemoved()) + return; + dropFunction.run(); + }); + } catch (DatabaseException e) { + String format = Messages.PopulateElementDropParticipant_PreDropFixesFailed; + String formattedSuggestions = EString.implode(reqs); + LOGGER.error(format, formattedSuggestions, e); + ShowError.showError(Messages.PopulateElementDropParticipant_PreDropFixesFailed_Title, NLS.bind(format, formattedSuggestions), e); + } + }); + } else { + dropFunction.run(); + } + } - final Resource element = (Resource)ElementUtils.getData(d, pick); - if(element != null && !wses.isEmpty()) { + private void performDrop(IDiagram d, Point loc, IDnDContext dp) { + IElement pick = tryPick(loc); + if (pick != null) { + List wses = Arrays.stream(dp.toArray()) + .filter(WSEDragItem.class::isInstance) + .map(di -> ((WSEDragItem) di).getObject()) + .collect(Collectors.toList()); + final Resource element = (Resource) ElementUtils.getData(d, pick); + if (element != null && !wses.isEmpty()) { try { Session db = Simantics.getSession(); DiagramResource DIA = DiagramResource.getInstance(db); Variable function = db.syncRequest(new PossibleVariableProperty(element, DIA.symbolDropHandler)); if (function != null) { - db.syncRequest(new WriteRequest() { - @Override - public void perform(WriteGraph graph) throws DatabaseException { - Simantics.invokeSCLWrite(graph, function, wses); - } - }); + db.syncRequest((Write) graph -> Simantics.invokeSCLWrite(graph, function, wses)); return; } } catch (DatabaseException e) { Activator.getDefault().getLog() .log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, - "Invocation to custom symbolDropHandler for element " - + element + " failed.", + "Invocation to custom symbolDropHandler for element " //$NON-NLS-1$ + + element + " failed.", //$NON-NLS-1$ e)); return; } - - } - - + } } - - Runnable creator = new Runnable() { - public void run() { - DiagramUtils.mutateDiagram(d, m -> { - IDragItem items[] = dp.toArray(); - for (IDragItem i : items) { - if (!(i instanceof ElementClassDragItem)) - continue; + Runnable creator = () -> { + DiagramUtils.mutateDiagram(d, m -> { + IDragItem items[] = dp.toArray(); - ElementClassDragItem res = (ElementClassDragItem) i; - ElementClass ec = res.getElementClass(); + for (IDragItem i : items) { + if (!(i instanceof ElementClassDragItem)) + continue; - Point2D pos = dp.getItemPosition(i); - // System.out.println(pos); - assert (pos != null); + ElementClassDragItem res = (ElementClassDragItem) i; + ElementClass ec = res.getElementClass(); - IElement element = m.newElement(ec); - element.setHints(res.getHintContext().getHints()); + Point2D pos = dp.getItemPosition(i); + // System.out.println(pos); + assert (pos != null); - setupDroppedElement(element, pos); + IElement element = m.newElement(ec); + element.setHints(res.getHintContext().getHints()); - // Remove only the drag items we've processed. - dp.remove(i); - } - }); - } + setupDroppedElement(element, pos); + + // Remove only the drag items we've processed. + dp.remove(i); + } + }); }; selectNewDiagramContentAfter(d, partSite, creator); @@ -408,7 +446,7 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i } } } catch (DatabaseException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Diagram content change tracking failed.", e)); + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Diagram content change tracking failed.", e)); //$NON-NLS-1$ } } @@ -437,7 +475,7 @@ public class PopulateElementDropParticipant extends AbstractDiagramParticipant i public int getAllowedOps() { return DnDConstants.ACTION_COPY; } - + @Override public double getPriority() { return 10.0; diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestion.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestion.java new file mode 100644 index 000000000..52d239c9a --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestion.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2018 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.modeling.ui.diagramEditor.dnd; + +import org.simantics.db.WriteGraph; +import org.simantics.db.exception.DatabaseException; + +/** + * Represents a suggestion of a fix/change that must be made to the target model + * before actually performing the element to diagram drop database changes. + * + * The {@link #toString()} method should return a clear description of the + * suggested fix/change. + * + * @author Tuukka Lehtonen + * @since 1.37.0 + */ +public interface DropSuggestion { + + /** + * Perform the suggested database fix/change encapsulated by this instance. + * + * @param graph + * @throws DatabaseException + */ + void fix(WriteGraph graph) throws DatabaseException; + + /** + * @return a clear end-user readable description of the suggested fix/change + */ + @Override + String toString(); + +} diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestions.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestions.java new file mode 100644 index 000000000..0ff441541 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/DropSuggestions.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2018 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.modeling.ui.diagramEditor.dnd; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Shell; +import org.simantics.NameLabelMode; +import org.simantics.NameLabelUtil; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.ValidationException; +import org.simantics.db.layer0.request.IsLinkedTo; +import org.simantics.db.request.Write; +import org.simantics.layer0.Layer0; + +/** + * @author Tuukka Lehtonen + * @since 1.37.0 + */ +public class DropSuggestions { + + public static boolean askSuggestions(Shell parent, List suggestions) { + return Dialog.OK == MarkupDialog.open(parent, + "diagram.dropSuggestionDialog", //$NON-NLS-1$ + Messages.DropSuggestions_ApplySuggestions_DialogTitle, + Messages.DropSuggestions_ApplySuggestions_DialogMsg, + formatSuggestions(suggestions), + SWT.ICON_QUESTION, + SWT.NONE, + SWT.BORDER | SWT.WRAP | SWT.V_SCROLL); + } + + private static String formatSuggestions(List suggestions) { + return suggestions.stream() + .map(DropSuggestion::toString) + .collect(Collectors.joining("\n", "- ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + public static Write performSuggestionsRequest(List suggestions) { + return (Write) graph -> { + for (DropSuggestion s : suggestions) { + s.fix(graph); + } + }; + } + + public static DropSuggestion linkToLibrary(ReadGraph graph, Resource linkSource, Resource linkTarget) throws DatabaseException { + return new LinkToLibrary( + NLS.bind(Messages.DropSuggestions_Description_LinkToLibrary, + NameLabelUtil.modalName(graph, linkSource, NameLabelMode.NAME_AND_LABEL), + NameLabelUtil.modalName(graph, linkTarget, NameLabelMode.NAME_AND_LABEL)) + , linkSource, linkTarget); + } + + public static class LinkToLibrary implements DropSuggestion { + + private final String desc; + private final Resource linkFrom; + private final Resource linkTo; + + public LinkToLibrary(String desc, Resource linkFrom, Resource linkTo) { + this.desc = desc; + this.linkFrom = linkFrom; + this.linkTo = linkTo; + } + + @Override + public void fix(WriteGraph graph) throws DatabaseException { + // Ensure that there is no reverse link between the namespaces + if (graph.syncRequest(new IsLinkedTo(linkFrom, linkTo))) { + throw new ValidationException( + NLS.bind(Messages.DropSuggestions_Problem_CyclicDependency + , NameUtils.getURIOrSafeNameInternal(graph, linkTo) + , NameUtils.getURIOrSafeNameInternal(graph, linkFrom))); + } + Layer0 L0 = Layer0.getInstance(graph); + graph.claim(linkFrom, L0.IsLinkedTo, L0.IsLinkedTo_Inverse, linkTo); + } + + @Override + public String toString() { + return desc; + } + + } + +} diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/MarkupDialog.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/MarkupDialog.java new file mode 100644 index 000000000..bf99b5874 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/MarkupDialog.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2018 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.modeling.ui.diagramEditor.dnd; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.text.Document; +import org.eclipse.mylyn.wikitext.markdown.MarkdownLanguage; +import org.eclipse.mylyn.wikitext.ui.viewer.MarkupViewer; +import org.eclipse.mylyn.wikitext.ui.viewer.MarkupViewerConfiguration; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.simantics.modeling.ui.Activator; + +/** + * @author Tuukka Lehtonen + * @since 1.37.0 + */ +class MarkupDialog extends Dialog { + + private String title; + private String topText; + private String message; + private int dialogIconType; + private int viewerStyle; + private MarkupViewer viewer; + + private IDialogSettings dialogBoundsSettings; + + protected MarkupDialog(Shell parentShell, String dialogId, String title, String topText, String message, int dialogIconType, int viewerStyle) { + super(parentShell); + this.title = title; + this.topText = topText; + this.message = message; + this.dialogIconType = dialogIconType; + this.viewerStyle = viewerStyle; + setShellStyle(getShellStyle() | SWT.RESIZE); + + IDialogSettings settings = Activator.getDefault().getDialogSettings(); + dialogBoundsSettings = settings.getSection(dialogId); + if (dialogBoundsSettings == null) + dialogBoundsSettings = settings.addNewSection(dialogId); + } + + @Override + protected IDialogSettings getDialogBoundsSettings() { + return dialogBoundsSettings; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText(title); + newShell.setImage(newShell.getDisplay().getSystemImage(dialogIconType)); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite) super.createDialogArea(parent); + + Text text = new Text(composite, SWT.READ_ONLY | SWT.MULTI | SWT.WRAP); + text.setEditable(false); + text.setText(topText); + GridDataFactory.fillDefaults() + .grab(true, false) + .hint(convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH), SWT.DEFAULT) + .applyTo(text); + + viewer = new MarkupViewer(composite, null, viewerStyle); + viewer.setMarkupLanguage(new MarkdownLanguage()); + viewer.setEditable(false); + MarkupViewerConfiguration configuration = new MarkupViewerConfiguration(viewer, Activator.getDefault().getPreferenceStore()); + viewer.configure(configuration); + viewer.setDocument(new Document(message)); + GridDataFactory.fillDefaults() + .grab(true, true) + .hint(convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH), SWT.DEFAULT) + .applyTo(viewer.getControl()); + + return composite; + } + + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.PROCEED_ID, IDialogConstants.PROCEED_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } + + @Override + protected void buttonPressed(int buttonId) { + if (IDialogConstants.PROCEED_ID == buttonId) { + okPressed(); + } else if (IDialogConstants.CANCEL_ID == buttonId) { + cancelPressed(); + } + } + + public static int open(Shell parent, String dialogId, String title, String topText, String message, int dialogIconType, int dialogStyle, int sourceViewerStyle) { + MarkupDialog dialog = new MarkupDialog(parent, dialogId, title, topText, message, dialogIconType, sourceViewerStyle); + dialogStyle &= SWT.SHEET; + dialog.setShellStyle(dialog.getShellStyle() | dialogStyle); + return dialog.open(); + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/Messages.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/Messages.java new file mode 100644 index 000000000..b8c611bd8 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/Messages.java @@ -0,0 +1,18 @@ +package org.simantics.modeling.ui.diagramEditor.dnd; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.simantics.modeling.ui.diagramEditor.dnd.messages"; //$NON-NLS-1$ + public static String DropSuggestions_ApplySuggestions_DialogMsg; + public static String DropSuggestions_ApplySuggestions_DialogTitle; + public static String DropSuggestions_Description_LinkToLibrary; + public static String DropSuggestions_Problem_CyclicDependency; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/messages.properties b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/messages.properties new file mode 100644 index 000000000..b606334eb --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/dnd/messages.properties @@ -0,0 +1,4 @@ +DropSuggestions_ApplySuggestions_DialogMsg=In order to perform the requested drop operation, the changes listed below need to be performed.\\n\\nAre you sure you want to proceed? +DropSuggestions_ApplySuggestions_DialogTitle=Changes Required +DropSuggestions_Description_LinkToLibrary=Link target model **{0}** \\u2192 library **{1}** +DropSuggestions_Problem_CyclicDependency=Source namespace {0} is already linked to target namespace {1}. Creating a target \\u2192 source link would create a cyclic dependency. diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/messages.properties b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/messages.properties new file mode 100644 index 000000000..7ed685f76 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/messages.properties @@ -0,0 +1,2 @@ +PopulateElementDropParticipant_PreDropFixesFailed=Drop failed because the following pre-drop fixes could not be performed:\n\n{0} +PopulateElementDropParticipant_PreDropFixesFailed_Title=Drop Failed diff --git a/features/org.simantics.wiki.ui.feature/feature.xml b/features/org.simantics.wiki.ui.feature/feature.xml index d1dc4dd43..1a1ce6968 100644 --- a/features/org.simantics.wiki.ui.feature/feature.xml +++ b/features/org.simantics.wiki.ui.feature/feature.xml @@ -2,7 +2,7 @@ @@ -83,6 +83,13 @@ version="0.0.0" unpack="false"/> + + + -- 2.43.2