diff --git a/BasicRdl/BasicRdl.csproj b/BasicRdl/BasicRdl.csproj
index b427804af..49b7887bd 100644
--- a/BasicRdl/BasicRdl.csproj
+++ b/BasicRdl/BasicRdl.csproj
@@ -49,127 +49,127 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4Addin/CDP4Addin.csproj b/CDP4Addin/CDP4Addin.csproj
index 169a218bf..6f099ead9 100644
--- a/CDP4Addin/CDP4Addin.csproj
+++ b/CDP4Addin/CDP4Addin.csproj
@@ -38,38 +38,38 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CDP4BudgetViewer/CDP4Budget.csproj b/CDP4BudgetViewer/CDP4Budget.csproj
index 6c156235a..87d085926 100644
--- a/CDP4BudgetViewer/CDP4Budget.csproj
+++ b/CDP4BudgetViewer/CDP4Budget.csproj
@@ -41,100 +41,100 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4BuiltInRules/CDP4BuiltInRules.csproj b/CDP4BuiltInRules/CDP4BuiltInRules.csproj
index 4d9f33438..9d4090c3a 100644
--- a/CDP4BuiltInRules/CDP4BuiltInRules.csproj
+++ b/CDP4BuiltInRules/CDP4BuiltInRules.csproj
@@ -46,127 +46,127 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4Composition.Tests/DragDrop/ElementUsageDropValidatorTestFixture.cs b/CDP4Composition.Tests/DragDrop/ElementUsageDropValidatorTestFixture.cs
new file mode 100644
index 000000000..6f0f0499c
--- /dev/null
+++ b/CDP4Composition.Tests/DragDrop/ElementUsageDropValidatorTestFixture.cs
@@ -0,0 +1,144 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) 2015-2026 Starion Group S.A.
+//
+// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate, Rowan de Voogt
+//
+// This file is part of CDP4-COMET IME Community Edition.
+// The CDP4-COMET IME Community Edition is the Starion Concurrent Design Desktop Application and Excel Integration
+// compliant with ECSS-E-TM-10-25 Annex A and Annex C.
+//
+// The CDP4-COMET IME Community Edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Affero General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or any later version.
+//
+// The CDP4-COMET IME Community Edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace CDP4Composition.Tests.DragDrop
+{
+ using System;
+ using System.Windows;
+
+ using CDP4Common.CommonData;
+ using CDP4Common.EngineeringModelData;
+
+ using CDP4Composition.DragDrop;
+
+ using CDP4Dal.Permission;
+
+ using Moq;
+
+ using NUnit.Framework;
+
+ ///
+ /// Suite of tests for the class.
+ ///
+ [TestFixture]
+ public class ElementUsageDropValidatorTestFixture
+ {
+ private Mock permissionService;
+ private Iteration iteration;
+ private ElementDefinition sourceDefinition;
+ private ElementDefinition targetDefinition;
+ private ElementDefinition referencedDefinition;
+ private ElementUsage elementUsage;
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.permissionService = new Mock();
+ this.permissionService.Setup(x => x.CanWrite(It.IsAny(), It.IsAny())).Returns(true);
+
+ var engineeringModel = new EngineeringModel(Guid.NewGuid(), null, null);
+ this.iteration = new Iteration(Guid.NewGuid(), null, null);
+ engineeringModel.Iteration.Add(this.iteration);
+
+ this.sourceDefinition = new ElementDefinition(Guid.NewGuid(), null, null);
+ this.targetDefinition = new ElementDefinition(Guid.NewGuid(), null, null);
+ this.referencedDefinition = new ElementDefinition(Guid.NewGuid(), null, null);
+ this.iteration.Element.Add(this.sourceDefinition);
+ this.iteration.Element.Add(this.targetDefinition);
+ this.iteration.Element.Add(this.referencedDefinition);
+
+ this.elementUsage = new ElementUsage(Guid.NewGuid(), null, null) { ElementDefinition = this.referencedDefinition };
+ this.sourceDefinition.ContainedElement.Add(this.elementUsage);
+ }
+
+ [Test]
+ public void VerifyThatDroppingOnAnotherDefinitionReturnsMove()
+ {
+ var effect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, this.targetDefinition, this.permissionService.Object);
+
+ Assert.That(effect, Is.EqualTo(DragDropEffects.Move));
+ }
+
+ [Test]
+ public void VerifyThatMovingOntoTheCurrentContainerReturnsNone()
+ {
+ var effect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, this.sourceDefinition, this.permissionService.Object);
+
+ Assert.That(effect, Is.EqualTo(DragDropEffects.None));
+ }
+
+ [Test]
+ public void VerifyThatDroppingOntoTheReferencedDefinitionReturnsNone()
+ {
+ // the usage references referencedDefinition; dropping it there would create a self-containment loop
+ var moveEffect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, this.referencedDefinition, this.permissionService.Object);
+
+ Assert.That(moveEffect, Is.EqualTo(DragDropEffects.None));
+ }
+
+ [Test]
+ public void VerifyThatContainmentLoopReturnsNone()
+ {
+ // referencedDefinition (the usage's type) already uses targetDefinition -> re-parenting there would create a loop
+ var loopUsage = new ElementUsage(Guid.NewGuid(), null, null) { ElementDefinition = this.targetDefinition };
+ this.referencedDefinition.ContainedElement.Add(loopUsage);
+
+ var effect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, this.targetDefinition, this.permissionService.Object);
+
+ Assert.That(effect, Is.EqualTo(DragDropEffects.None));
+ }
+
+ [Test]
+ public void VerifyThatMissingPermissionReturnsNone()
+ {
+ this.permissionService.Setup(x => x.CanWrite(It.IsAny(), It.IsAny())).Returns(false);
+
+ var effect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, this.targetDefinition, this.permissionService.Object);
+
+ Assert.That(effect, Is.EqualTo(DragDropEffects.None));
+ }
+
+ [Test]
+ public void VerifyThatDifferentModelReturnsNone()
+ {
+ var otherModel = new EngineeringModel(Guid.NewGuid(), null, null);
+ var otherIteration = new Iteration(Guid.NewGuid(), null, null);
+ otherModel.Iteration.Add(otherIteration);
+ var foreignDefinition = new ElementDefinition(Guid.NewGuid(), null, null);
+ otherIteration.Element.Add(foreignDefinition);
+
+ var effect = ElementUsageDropValidator.GetDropEffect(this.elementUsage, foreignDefinition, this.permissionService.Object);
+
+ Assert.That(effect, Is.EqualTo(DragDropEffects.None));
+ }
+
+ [Test]
+ public void VerifyThatNullArgumentsReturnNone()
+ {
+ Assert.That(ElementUsageDropValidator.GetDropEffect(null, this.targetDefinition, this.permissionService.Object), Is.EqualTo(DragDropEffects.None));
+ Assert.That(ElementUsageDropValidator.GetDropEffect(this.elementUsage, null, this.permissionService.Object), Is.EqualTo(DragDropEffects.None));
+ }
+ }
+}
diff --git a/CDP4Composition/CDP4Composition.csproj b/CDP4Composition/CDP4Composition.csproj
index 80337fbc6..7666b5f68 100644
--- a/CDP4Composition/CDP4Composition.csproj
+++ b/CDP4Composition/CDP4Composition.csproj
@@ -35,38 +35,38 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CDP4Composition/DragDrop/ElementUsageDropValidator.cs b/CDP4Composition/DragDrop/ElementUsageDropValidator.cs
new file mode 100644
index 000000000..7092b8053
--- /dev/null
+++ b/CDP4Composition/DragDrop/ElementUsageDropValidator.cs
@@ -0,0 +1,84 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) 2015-2026 Starion Group S.A.
+//
+// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate, Rowan de Voogt
+//
+// This file is part of CDP4-COMET IME Community Edition.
+// The CDP4-COMET IME Community Edition is the Starion Concurrent Design Desktop Application and Excel Integration
+// compliant with ECSS-E-TM-10-25 Annex A and Annex C.
+//
+// The CDP4-COMET IME Community Edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Affero General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or any later version.
+//
+// The CDP4-COMET IME Community Edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace CDP4Composition.DragDrop
+{
+ using System.Windows;
+
+ using CDP4Common.CommonData;
+ using CDP4Common.EngineeringModelData;
+
+ using CDP4Dal.Permission;
+
+ ///
+ /// Centralizes the validation of dropping an onto an , so that the
+ /// move (re-parent) drag-and-drop behaviour stays consistent - and safe against invalid models - across the Element Definitions
+ /// browser and the Product Tree.
+ ///
+ public static class ElementUsageDropValidator
+ {
+ ///
+ /// Computes the for dropping an onto an
+ /// to move (re-parent) it.
+ ///
+ /// The being dragged.
+ /// The that is the drop target.
+ /// The used to check write permission.
+ /// The resulting .
+ public static DragDropEffects GetDropEffect(ElementUsage elementUsage, ElementDefinition targetElementDefinition, IPermissionService permissionService)
+ {
+ if (elementUsage == null || targetElementDefinition == null)
+ {
+ return DragDropEffects.None;
+ }
+
+ // permission to move an element usage into the target definition
+ if (!permissionService.CanWrite(ClassKind.ElementUsage, targetElementDefinition))
+ {
+ return DragDropEffects.None;
+ }
+
+ // moving between models is not supported
+ if (elementUsage.TopContainer != targetElementDefinition.TopContainer)
+ {
+ return DragDropEffects.None;
+ }
+
+ // dropping the usage onto the definition it references, or onto a definition already used inside it, would create a containment loop
+ if (elementUsage.ElementDefinition.HasUsageOf(targetElementDefinition))
+ {
+ return DragDropEffects.None;
+ }
+
+ // a move onto the usage's current container would be a no-op
+ if (elementUsage.Container == targetElementDefinition)
+ {
+ return DragDropEffects.None;
+ }
+
+ return DragDropEffects.Move;
+ }
+ }
+}
diff --git a/CDP4Composition/Services/IThingCreator.cs b/CDP4Composition/Services/IThingCreator.cs
index e7102f1de..b066e6fb6 100644
--- a/CDP4Composition/Services/IThingCreator.cs
+++ b/CDP4Composition/Services/IThingCreator.cs
@@ -84,6 +84,21 @@ public interface IThingCreator
///
Task CreateElementUsage(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session);
+ ///
+ /// Moves an existing into another , preserving all of its properties
+ /// (name, short-name, owner, excluded options and contained s).
+ ///
+ ///
+ /// The that is to be moved.
+ ///
+ ///
+ /// The that becomes the new container of the .
+ ///
+ ///
+ /// The in which the move is performed.
+ ///
+ Task MoveElementUsage(ElementUsage elementUsage, ElementDefinition targetElementDefinition, ISession session);
+
///
/// Method for creating a for requirement verification between a and a .
///
diff --git a/CDP4Composition/Services/ThingCreator.cs b/CDP4Composition/Services/ThingCreator.cs
index d0532868c..7ccc2e5a2 100644
--- a/CDP4Composition/Services/ThingCreator.cs
+++ b/CDP4Composition/Services/ThingCreator.cs
@@ -269,7 +269,58 @@ public async Task CreateElementUsage(ElementDefinition container, ElementDefinit
{
logger.Error(ex, "The ElementUsage could not be created");
throw;
- }
+ }
+ }
+
+ ///
+ /// Moves an existing into another , preserving all of its properties.
+ ///
+ ///
+ /// The that is to be moved.
+ ///
+ ///
+ /// The that becomes the new container of the .
+ ///
+ ///
+ /// The in which the move is performed.
+ ///
+ public async Task MoveElementUsage(ElementUsage elementUsage, ElementDefinition targetElementDefinition, ISession session)
+ {
+ if (elementUsage == null)
+ {
+ throw new ArgumentNullException(nameof(elementUsage), "The elementUsage must not be null");
+ }
+
+ if (targetElementDefinition == null)
+ {
+ throw new ArgumentNullException(nameof(targetElementDefinition), "The targetElementDefinition must not be null");
+ }
+
+ if (session == null)
+ {
+ throw new ArgumentNullException(nameof(session), "The session may not be null");
+ }
+
+ // re-parent the usage by adding it to the target definition; the same pattern is used to move a Requirement between specifications.
+ var transactionContext = TransactionContextResolver.ResolveContext(targetElementDefinition);
+ var transaction = new ThingTransaction(transactionContext);
+
+ var usageClone = elementUsage.Clone(false);
+ transaction.CreateOrUpdate(usageClone);
+
+ var elementDefinitionClone = targetElementDefinition.Clone(false);
+ elementDefinitionClone.ContainedElement.Add(usageClone);
+ transaction.CreateOrUpdate(elementDefinitionClone);
+
+ try
+ {
+ await session.Write(transaction.FinalizeTransaction());
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "The ElementUsage could not be moved");
+ throw;
+ }
}
///
diff --git a/CDP4CrossViewEditor/CDP4CrossViewEditor.csproj b/CDP4CrossViewEditor/CDP4CrossViewEditor.csproj
index 42389ea8d..e7284233c 100644
--- a/CDP4CrossViewEditor/CDP4CrossViewEditor.csproj
+++ b/CDP4CrossViewEditor/CDP4CrossViewEditor.csproj
@@ -45,100 +45,100 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4Dashboard/CDP4Dashboard.csproj b/CDP4Dashboard/CDP4Dashboard.csproj
index 3e9bed56e..cf4731e4c 100644
--- a/CDP4Dashboard/CDP4Dashboard.csproj
+++ b/CDP4Dashboard/CDP4Dashboard.csproj
@@ -37,127 +37,127 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4IME/COMET-CE.csproj b/CDP4IME/COMET-CE.csproj
index 826406942..b9cd2fa55 100644
--- a/CDP4IME/COMET-CE.csproj
+++ b/CDP4IME/COMET-CE.csproj
@@ -49,55 +49,55 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CDP4LogInfo/CDP4LogInfo.csproj b/CDP4LogInfo/CDP4LogInfo.csproj
index 7a8741d54..4f75e1af6 100644
--- a/CDP4LogInfo/CDP4LogInfo.csproj
+++ b/CDP4LogInfo/CDP4LogInfo.csproj
@@ -51,100 +51,100 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
diff --git a/CDP4ObjectBrowser/CDP4ObjectBrowser.csproj b/CDP4ObjectBrowser/CDP4ObjectBrowser.csproj
index 0c1418eee..707f496b8 100644
--- a/CDP4ObjectBrowser/CDP4ObjectBrowser.csproj
+++ b/CDP4ObjectBrowser/CDP4ObjectBrowser.csproj
@@ -45,100 +45,100 @@
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+
runtime
-
+