diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs similarity index 89% rename from src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs rename to src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs index 72b693f5d..0b4b5cb03 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPredicate.cs @@ -9,20 +9,19 @@ namespace Artemis.Core /// A predicate in a data model condition using either two data model values or one data model value and a /// static value /// - public class DataModelConditionPredicate : DataModelConditionPart + public abstract class DataModelConditionPredicate : DataModelConditionPart { /// /// Creates a new instance of the class /// /// /// - public DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) + /// A new empty entity + protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity) { Parent = parent; + Entity = entity; PredicateType = predicateType; - Entity = new DataModelConditionPredicateEntity(); - - Initialize(); } internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity) @@ -30,8 +29,6 @@ namespace Artemis.Core Parent = parent; Entity = entity; PredicateType = (ProfileRightSideType) entity.PredicateType; - - Initialize(); } /// @@ -42,31 +39,117 @@ namespace Artemis.Core /// /// Gets the operator /// - public BaseConditionOperator? Operator { get; private set; } + public BaseConditionOperator? Operator { get; protected set; } /// /// Gets the path of the left property /// - public DataModelPath? LeftPath { get; private set; } + public DataModelPath? LeftPath { get; protected set; } /// /// Gets the path of the right property /// - public DataModelPath? RightPath { get; private set; } + public DataModelPath? RightPath { get; protected set; } /// /// Gets the right static value, only used it is /// /// - public object? RightStaticValue { get; private set; } + public object? RightStaticValue { get; protected set; } internal DataModelConditionPredicateEntity Entity { get; set; } + /// + public override string ToString() + { + if (PredicateType == ProfileRightSideType.Dynamic) + return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}"; + return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}"; + } + + #region IDisposable + + /// + protected override void Dispose(bool disposing) + { + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; + + LeftPath?.Dispose(); + RightPath?.Dispose(); + + base.Dispose(disposing); + } + + #endregion + + #region Initialization + + internal void Initialize() + { + ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; + + InitializeLeftPath(); + + // Operator + if (Entity.OperatorPluginGuid != null) + { + BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; + if (conditionOperator != null) + UpdateOperator(conditionOperator); + } + + // Right side dynamic + if (PredicateType == ProfileRightSideType.Dynamic) + InitializeRightPath(); + // Right side static + else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) + try + { + if (LeftPath != null && LeftPath.IsValid) + { + // Use the left side type so JSON.NET has a better idea what to do + Type leftSideType = LeftPath.GetPropertyType()!; + object? rightSideValue; + + try + { + rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); + } + // If deserialization fails, use the type's default + catch (JsonSerializationException e) + { + DeserializationLogger.LogPredicateDeserializationFailure(this, e); + rightSideValue = Activator.CreateInstance(leftSideType); + } + + UpdateRightSideStatic(rightSideValue); + } + else + { + // Hope for the best... + UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue)); + } + } + catch (JsonReaderException e) + { + DeserializationLogger.LogPredicateDeserializationFailure(this, e); + } + } + + protected abstract void InitializeLeftPath(); + protected abstract void InitializeRightPath(); + + #endregion + + #region Modification + /// /// Updates the left side of the predicate /// /// The path pointing to the left side value inside the data model - public void UpdateLeftSide(DataModelPath? path) + public virtual void UpdateLeftSide(DataModelPath? path) { if (path != null && !path.IsValid) throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path"); @@ -161,153 +244,10 @@ namespace Artemis.Core ValidateRightSide(); } - /// - public override bool Evaluate() - { - if (Operator == null || LeftPath == null || !LeftPath.IsValid) - return false; - - // Compare with a static value - if (PredicateType == ProfileRightSideType.Static) - { - object? leftSideValue = LeftPath.GetValue(); - if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null) - return false; - - return Operator.InternalEvaluate(leftSideValue, RightStaticValue); - } - - if (RightPath == null || !RightPath.IsValid) - return false; - - // Compare with dynamic values - return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue()); - } - /// /// Determines the best type to use for the right side op this predicate /// - public Type? GetPreferredRightSideType() - { - Type? preferredType = Operator?.RightSideType; - Type? leftSideType = LeftPath?.GetPropertyType(); - if (preferredType == null) - return null; - - if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType)) - preferredType = leftSideType; - - return preferredType; - } - - /// - public override string ToString() - { - if (PredicateType == ProfileRightSideType.Dynamic) - return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}"; - return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}"; - } - - /// - protected override void Dispose(bool disposing) - { - ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; - - LeftPath?.Dispose(); - RightPath?.Dispose(); - - base.Dispose(disposing); - } - - /// - internal override bool EvaluateObject(object target) - { - return false; - } - - internal override void Save() - { - // Don't save an invalid state - if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid) - return; - - Entity.PredicateType = (int) PredicateType; - - LeftPath?.Save(); - Entity.LeftPath = LeftPath?.Entity; - RightPath?.Save(); - Entity.RightPath = RightPath?.Entity; - - Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - - if (Operator != null) - { - Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; - Entity.OperatorType = Operator.GetType().Name; - } - } - - internal void Initialize() - { - ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; - - // Left side - if (Entity.LeftPath != null) - LeftPath = new DataModelPath(null, Entity.LeftPath); - - // Operator - if (Entity.OperatorPluginGuid != null) - { - BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; - if (conditionOperator != null) - UpdateOperator(conditionOperator); - } - - // Right side dynamic - if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) - RightPath = new DataModelPath(null, Entity.RightPath); - // Right side static - else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) - try - { - if (LeftPath != null && LeftPath.IsValid) - { - // Use the left side type so JSON.NET has a better idea what to do - Type leftSideType = LeftPath.GetPropertyType()!; - object? rightSideValue; - - try - { - rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); - } - // If deserialization fails, use the type's default - catch (JsonSerializationException e) - { - DeserializationLogger.LogPredicateDeserializationFailure(this, e); - rightSideValue = Activator.CreateInstance(leftSideType); - } - - UpdateRightSideStatic(rightSideValue); - } - else - { - // Hope for the best... - UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue)); - } - } - catch (JsonReaderException) - { - // ignored - // TODO: Some logging would be nice - } - } - - internal override DataModelConditionPartEntity GetEntity() - { - return Entity; - } + public abstract Type? GetPreferredRightSideType(); private void ValidateOperator() { @@ -343,6 +283,72 @@ namespace Artemis.Core } } + #endregion + + #region Evaluation + + /// + public override bool Evaluate() + { + if (Operator == null || LeftPath == null || !LeftPath.IsValid) + return false; + + // Compare with a static value + if (PredicateType == ProfileRightSideType.Static) + { + object? leftSideValue = LeftPath.GetValue(); + if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null) + return false; + + return Operator.InternalEvaluate(leftSideValue, RightStaticValue); + } + + if (RightPath == null || !RightPath.IsValid) + return false; + + // Compare with dynamic values + return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue()); + } + + /// + internal override bool EvaluateObject(object target) + { + return false; + } + + #endregion + + #region Storage + + internal override DataModelConditionPartEntity GetEntity() + { + return Entity; + } + + internal override void Save() + { + // Don't save an invalid state + if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid) + return; + + Entity.PredicateType = (int) PredicateType; + + LeftPath?.Save(); + Entity.LeftPath = LeftPath?.Entity; + RightPath?.Save(); + Entity.RightPath = RightPath?.Entity; + + Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); + + if (Operator != null) + { + Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; + Entity.OperatorType = Operator.GetType().Name; + } + } + + #endregion + #region Event handlers private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e) diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs new file mode 100644 index 000000000..2f29d2f40 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGeneralPredicate.cs @@ -0,0 +1,63 @@ +using System; +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Core +{ + /// + /// A predicate in a data model condition using either two data model values or one data model value and a + /// static value + /// + public class DataModelConditionGeneralPredicate : DataModelConditionPredicate + { + /// + /// Creates a new instance of the class + /// + /// + /// + public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) + : base(parent, predicateType, new DataModelConditionGeneralPredicateEntity()) + { + Initialize(); + } + + internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity) + : base(parent, entity) + { + Initialize(); + } + + #region Modification + + /// + public override Type? GetPreferredRightSideType() + { + Type? preferredType = Operator?.RightSideType; + Type? leftSideType = LeftPath?.GetPropertyType(); + if (preferredType == null) + return null; + + if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType)) + preferredType = leftSideType; + + return preferredType; + } + + #endregion + + #region Initialization + + protected override void InitializeLeftPath() + { + if (Entity.LeftPath != null) + LeftPath = new DataModelPath(null, Entity.LeftPath); + } + + protected override void InitializeRightPath() + { + if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) + RightPath = new DataModelPath(null, Entity.RightPath); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs index 3c6cf18b9..d96d7065d 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs @@ -42,8 +42,8 @@ namespace Artemis.Core AddChild(new DataModelConditionList(this, listEntity)); else if (childEntity is DataModelConditionEventEntity eventEntity) AddChild(new DataModelConditionEvent(this, eventEntity)); - else if (childEntity is DataModelConditionPredicateEntity predicateEntity) - AddChild(new DataModelConditionPredicate(this, predicateEntity)); + else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity) + AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity)); else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity) AddChild(new DataModelConditionListPredicate(this, listPredicateEntity)); } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs index a297c65c2..06f520fe6 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs @@ -1,16 +1,12 @@ using System; -using System.Reflection; -using Artemis.Core.DataModelExpansions; -using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; -using Newtonsoft.Json; namespace Artemis.Core { /// /// A predicate like evaluated inside a /// - public class DataModelConditionListPredicate : DataModelConditionPart + public class DataModelConditionListPredicate : DataModelConditionPredicate { /// /// Creates a new instance of the class @@ -18,173 +14,97 @@ namespace Artemis.Core /// /// public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) + : base(parent, predicateType, new DataModelConditionListPredicateEntity()) { - Parent = parent; - PredicateType = predicateType; - Entity = new DataModelConditionListPredicateEntity(); - DataModelConditionList = null!; ApplyParentList(); Initialize(); } internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity) + : base(parent, entity) { - Parent = parent; - Entity = entity; - PredicateType = (ProfileRightSideType) entity.PredicateType; - DataModelConditionList = null!; ApplyParentList(); Initialize(); } - /// - /// Gets or sets the predicate type - /// - public ProfileRightSideType PredicateType { get; set; } - - /// - /// Gets the operator - /// - public BaseConditionOperator? Operator { get; private set; } - /// /// Gets the data model condition list this predicate belongs to /// public DataModelConditionList DataModelConditionList { get; private set; } - /// - /// Gets the path of the left property - /// - public DataModelPath? LeftPath { get; private set; } + private void ApplyParentList() + { + DataModelConditionPart? current = Parent; + while (current != null) + { + if (current is DataModelConditionList parentList) + { + DataModelConditionList = parentList; + return; + } - /// - /// Gets the path of the right property - /// - public DataModelPath? RightPath { get; private set; } + current = current.Parent; + } - /// - /// Gets the right static value, only used it is - /// - /// - public object? RightStaticValue { get; private set; } + if (DataModelConditionList == null) + throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list"); + } - internal DataModelConditionListPredicateEntity Entity { get; set; } + private object? GetListPathValue(DataModelPath path, object target) + { + if (!(path.Target is ListPredicateWrapperDataModel wrapper)) + throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target"); + + wrapper.UntypedValue = target; + return path.GetValue(); + } + + #region Initialization + + protected override void InitializeLeftPath() + { + if (Entity.LeftPath != null) + LeftPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath) + : null; + } + + protected override void InitializeRightPath() + { + if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) + { + // Right side dynamic inside the list + // TODO: Come up with a general wrapper solution because this will clash with events + if (Entity.RightPath.DataModelGuid == Constants.CorePluginInfo.Guid) + RightPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath) + : null; + // Right side dynamic + else + RightPath = new DataModelPath(null, Entity.RightPath); + } + } + + #endregion + + #region Modification /// /// Updates the left side of the predicate /// /// The path pointing to the left side value inside the list - public void UpdateLeftSide(DataModelPath? path) + public override void UpdateLeftSide(DataModelPath? path) { if (DataModelConditionList.IsPrimitiveList) throw new ArtemisCoreException("Cannot apply a left side to a predicate inside a primitive list"); - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path"); - - LeftPath?.Dispose(); - LeftPath = path != null ? new DataModelPath(path) : null; - - ValidateOperator(); - ValidateRightSide(); + base.UpdateLeftSide(path); } - /// - /// Updates the right side of the predicate using a path and makes the predicate dynamic - /// - /// The path pointing to the right side value - public void UpdateRightSideDynamic(DataModelPath? path) - { - if (path != null && !path.IsValid) - throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path"); - if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right)) - throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}"); - - RightPath?.Dispose(); - RightPath = path != null ? new DataModelPath(path) : null; - - PredicateType = ProfileRightSideType.Dynamic; - } - - /// - /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression - /// - /// The right side value to use - public void UpdateRightSideStatic(object? staticValue) - { - PredicateType = ProfileRightSideType.Static; - RightPath?.Dispose(); - RightPath = null; - - SetStaticValue(staticValue); - } - - /// - /// Updates the operator of the predicate and re-compiles the expression - /// - /// - public void UpdateOperator(BaseConditionOperator? conditionOperator) - { - if (conditionOperator == null) - { - Operator = null; - return; - } - - // No need to clear compiled expressions, without a left data model they are already null - if (LeftPath == null || !LeftPath.IsValid) - { - Operator = conditionOperator; - return; - } - - Type leftType = LeftPath.GetPropertyType()!; - // Left side can't go empty so enforce a match - if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left)) - throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " + - $"it does not support left side type {leftType.Name}"); - - Operator = conditionOperator; - ValidateRightSide(); - } - - /// - /// Not supported for list predicates, always returns false - /// - public override bool Evaluate() - { - return false; - } - - /// - /// Determines whether the provided path is contained inside the list - /// - /// The path to evaluate - public bool ListContainsInnerPath(string path) - { - if (DataModelConditionList.ListType == null) - return false; - - string[] parts = path.Split('.'); - Type current = DataModelConditionList.ListType; - foreach (string part in parts) - { - PropertyInfo? property = current.GetProperty(part); - current = property?.PropertyType; - - if (property == null) - return false; - } - - return true; - } - - /// - /// Determines the best type to use for the right side op this predicate - /// - public Type? GetPreferredRightSideType() + /// + public override Type? GetPreferredRightSideType() { Type? preferredType = Operator?.RightSideType; Type? leftSideType = DataModelConditionList.IsPrimitiveList @@ -199,22 +119,18 @@ namespace Artemis.Core return preferredType; } - #region IDisposable - - /// - protected override void Dispose(bool disposing) - { - ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; - - LeftPath?.Dispose(); - RightPath?.Dispose(); - - base.Dispose(disposing); - } - #endregion + #region Evaluation + + /// + /// Not supported for list predicates, always returns false + /// + public override bool Evaluate() + { + return false; + } + internal override bool EvaluateObject(object target) { if (Operator == null || LeftPath == null || !LeftPath.IsValid) @@ -245,227 +161,6 @@ namespace Artemis.Core return false; } - internal Type GetTypeAtInnerPath(string path) - { - if (!ListContainsInnerPath(path)) - return null; - - string[] parts = path.Split('.'); - Type current = DataModelConditionList.ListType; - - Type result = null; - foreach (string part in parts) - { - PropertyInfo? property = current.GetProperty(part); - current = property.PropertyType; - result = property.PropertyType; - } - - return result; - } - - internal override void Save() - { - Entity.PredicateType = (int) PredicateType; - - LeftPath?.Save(); - Entity.LeftPath = LeftPath?.Entity; - RightPath?.Save(); - Entity.RightPath = RightPath?.Entity; - - Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - - if (Operator != null) - { - Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; - Entity.OperatorType = Operator.GetType().Name; - } - } - - internal override DataModelConditionPartEntity GetEntity() - { - return Entity; - } - - private void ApplyParentList() - { - DataModelConditionPart? current = Parent; - while (current != null) - { - if (current is DataModelConditionList parentList) - { - DataModelConditionList = parentList; - return; - } - - current = current.Parent; - } - - if (DataModelConditionList == null) - throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list"); - } - - private void Initialize() - { - ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; - - // Left side - if (Entity.LeftPath != null) - { - LeftPath = DataModelConditionList.ListType != null - ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath) - : null; - } - - // Operator - if (Entity.OperatorPluginGuid != null) - { - BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; - if (conditionOperator != null) - UpdateOperator(conditionOperator); - } - - // Right side dynamic - if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) - { - // Right side dynamic inside the list - // TODO: Come up with a general wrapper solution because this will clash with events - if (Entity.RightPath.DataModelGuid == Constants.CorePluginInfo.Guid) - { - RightPath = DataModelConditionList.ListType != null - ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath) - : null; - } - // Right side dynamic - else - RightPath = new DataModelPath(null, Entity.RightPath); - } - // Right side static - else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) - try - { - if (LeftPath != null && LeftPath.IsValid) - { - // Use the left side type so JSON.NET has a better idea what to do - Type leftSideType = LeftPath.GetPropertyType()!; - object? rightSideValue; - - try - { - rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); - } - // If deserialization fails, use the type's default - catch (JsonSerializationException e) - { - DeserializationLogger.LogListPredicateDeserializationFailure(this, e); - rightSideValue = Activator.CreateInstance(leftSideType); - } - - UpdateRightSideStatic(rightSideValue); - } - else - { - // Hope for the best... we must infer the type from JSON now - UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue)); - } - } - catch (JsonException e) - { - DeserializationLogger.LogListPredicateDeserializationFailure(this, e); - } - } - - private void ValidateOperator() - { - if (LeftPath == null || !LeftPath.IsValid || Operator == null) - return; - - Type leftType = LeftPath.GetPropertyType()!; - if (!Operator.SupportsType(leftType, ConditionParameterSide.Left)) - Operator = null; - } - - private void ValidateRightSide() - { - if (Operator == null) - return; - - if (PredicateType == ProfileRightSideType.Dynamic) - { - if (RightPath == null || !RightPath.IsValid) - return; - - Type rightSideType = RightPath.GetPropertyType()!; - if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right)) - UpdateRightSideDynamic(null); - } - else - { - if (RightStaticValue == null) - return; - - if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right)) - UpdateRightSideStatic(null); - } - } - - private void SetStaticValue(object? staticValue) - { - RightPath?.Dispose(); - RightPath = null; - - // If the operator is null simply apply the value, any validation will wait - if (Operator == null) - { - RightStaticValue = staticValue; - return; - } - // If the operator does not support a right side, always set it to null - if (Operator.RightSideType == null) - { - RightStaticValue = null; - return; - } - - // If not null ensure the types match and if not, convert it - Type? preferredType = GetPreferredRightSideType(); - if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null) - RightStaticValue = staticValue; - else if (staticValue != null) - RightStaticValue = Convert.ChangeType(staticValue, preferredType); - // If null create a default instance for value types or simply make it null for reference types - else if (preferredType.IsValueType) - RightStaticValue = Activator.CreateInstance(preferredType); - else - RightStaticValue = null; - } - - private object? GetListPathValue(DataModelPath path, object target) - { - if (!(path.Target is ListPredicateWrapperDataModel wrapper)) - throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target"); - - wrapper.UntypedValue = target; - return path.GetValue(); - } - - #region Event handlers - - private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) - { - BaseConditionOperator conditionOperator = e.Registration.ConditionOperator; - if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) - UpdateOperator(conditionOperator); - } - - private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) - { - if (e.Registration.ConditionOperator != Operator) - return; - Operator = null; - } - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/DeserializationLogger.cs b/src/Artemis.Core/Utilities/DeserializationLogger.cs index e1c176283..de4424def 100644 --- a/src/Artemis.Core/Utilities/DeserializationLogger.cs +++ b/src/Artemis.Core/Utilities/DeserializationLogger.cs @@ -24,17 +24,6 @@ namespace Artemis.Core ); } - public static void LogListPredicateDeserializationFailure(DataModelConditionListPredicate dataModelConditionPredicate, JsonException exception) - { - _logger.Warning( - exception, - "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", - dataModelConditionPredicate.Entity.LeftPath?.Path, - dataModelConditionPredicate.Entity.OperatorType, - dataModelConditionPredicate.Entity.RightPath?.Path - ); - } - public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception) { _logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName); diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs new file mode 100644 index 000000000..1a0fa93ea --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs @@ -0,0 +1,6 @@ +namespace Artemis.Storage.Entities.Profile.Conditions +{ + public class DataModelConditionGeneralPredicateEntity : DataModelConditionPredicateEntity + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs index 513a9af43..cfc4b5372 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs @@ -1,20 +1,6 @@ -using System; -using Artemis.Storage.Entities.Profile.Abstract; - -namespace Artemis.Storage.Entities.Profile.Conditions +namespace Artemis.Storage.Entities.Profile.Conditions { - public class DataModelConditionListPredicateEntity : DataModelConditionPartEntity + public class DataModelConditionListPredicateEntity : DataModelConditionPredicateEntity { - public int PredicateType { get; set; } - - public DataModelPathEntity LeftPath { get; set; } - public DataModelPathEntity RightPath { get; set; } - - // Stored as a string to be able to control serialization and deserialization ourselves - public string RightStaticValue { get; set; } - - public string OperatorType { get; set; } - public Guid? OperatorPluginGuid { get; set; } - } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs index 08ee41d25..c913a6a3c 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs @@ -3,7 +3,7 @@ using Artemis.Storage.Entities.Profile.Abstract; namespace Artemis.Storage.Entities.Profile.Conditions { - public class DataModelConditionPredicateEntity : DataModelConditionPartEntity + public abstract class DataModelConditionPredicateEntity : DataModelConditionPartEntity { public int PredicateType { get; set; } public DataModelPathEntity LeftPath { get; set; } @@ -14,6 +14,5 @@ namespace Artemis.Storage.Entities.Profile.Conditions // Stored as a string to be able to control serialization and deserialization ourselves public string RightStaticValue { get; set; } - } } \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs b/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs new file mode 100644 index 000000000..3dd494e40 --- /dev/null +++ b/src/Artemis.Storage/Migrations/M6PredicateAbstraction.cs @@ -0,0 +1,34 @@ +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M6PredicateAbstraction : IStorageMigration + { + public int UserVersion => 7; + + public void Apply(LiteRepository repository) + { + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + foreach (BsonDocument bsonDocument in collection.FindAll()) + { + + foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray) + { + bsonLayer["DisplayCondition"] = null; + foreach (BsonValue bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray) + bsonPropertyEntity["DataBindingEntities"].AsArray.Clear(); + } + + foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray) + { + bsonLayer["DisplayCondition"] = null; + foreach (BsonValue bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray) + bsonPropertyEntity["DataBindingEntities"].AsArray.Clear(); + } + + collection.Update(bsonDocument); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj.DotSettings b/src/Artemis.UI/Artemis.UI.csproj.DotSettings new file mode 100644 index 000000000..7c2d2de36 --- /dev/null +++ b/src/Artemis.UI/Artemis.UI.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 374948a90..d0bfc092b 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -68,7 +68,7 @@ namespace Artemis.UI.Ninject.Factories DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, bool isListGroup); DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList); DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent); - DataModelConditionPredicateViewModel DataModelConditionPredicateViewModel(DataModelConditionPredicate dataModelConditionPredicate); + DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate); DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs similarity index 91% rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs index 08ea2cb05..d0542e439 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs @@ -13,7 +13,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Conditions { - public class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable + public abstract class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable { private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; @@ -41,20 +41,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions Operators = new BindableCollection(); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); - - Initialize(); } public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model; public PluginSetting ShowDataModelValues { get; } - - public BindableCollection Operators - { - get => _operators; - set => SetAndNotify(ref _operators, value); - } - public BaseConditionOperator SelectedOperator { get => _selectedOperator; @@ -74,6 +65,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions } public DelegateCommand SelectOperatorCommand { get; } + public BindableCollection Operators { get; } + + protected SolidColorBrush LeftSideColor { get; set; } public override void Delete() { @@ -81,17 +75,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions _profileEditorService.UpdateSelectedProfileElement(); } - public void Initialize() + public virtual void Initialize() { LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected; + if (LeftSideColor != null) + LeftSideSelectionViewModel.ButtonBrush = LeftSideColor; + // Determine which types are currently supported - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - _supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - _supportedInputTypes.Add(typeof(IEnumerable<>)); - _supportedInputTypes.Add(typeof(DataModelEvent)); - _supportedInputTypes.Add(typeof(DataModelEvent<>)); + _supportedInputTypes = GetSupportedInputTypes(); + Update(); } @@ -100,7 +93,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath); - Type leftSideType = LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); + Type leftSideType = GetLeftSideType(); // Get the supported operators Operators.Clear(); @@ -139,7 +132,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions RightSideInputViewModel.Value = preferredType.GetDefault(); else RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue; - + if (RightSideInputViewModel.TargetType != preferredType) RightSideInputViewModel.UpdateTargetType(preferredType); } @@ -183,15 +176,23 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions Update(); } + protected abstract List GetSupportedInputTypes(); + protected abstract Type GetLeftSideType(); + + protected virtual List GetExtraRightSideDataModelViewModels() + { + return null; + } + private void ExecuteSelectOperatorCommand(object context) { - if (!(context is BaseConditionOperator DataModelConditionOperator)) + if (!(context is BaseConditionOperator dataModelConditionOperator)) return; - SelectedOperator = DataModelConditionOperator; + SelectedOperator = dataModelConditionOperator; ApplyOperator(); } - + #region IDisposable public void Dispose() @@ -218,6 +219,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions RightSideSelectionViewModel.DisplaySwitchButton = true; RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected; RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested; + + List extra = GetExtraRightSideDataModelViewModels(); + if (extra != null) + RightSideSelectionViewModel.ExtraDataModelViewModels.AddRange(extra); } private void CreateRightSideInputViewModel() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs index b5f3bad1f..3c5d63e93 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs @@ -69,7 +69,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public void AddCondition() { if (!IsListGroup) - DataModelConditionGroup.AddChild(new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); + DataModelConditionGroup.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); else DataModelConditionGroup.AddChild(new DataModelConditionListPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); @@ -109,9 +109,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions case DataModelConditionEvent dataModelConditionEvent: viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent)); break; - case DataModelConditionPredicate dataModelConditionPredicate: + case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate: if (!IsListGroup) - viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionPredicateViewModel(dataModelConditionPredicate)); + viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate)); break; case DataModelConditionListPredicate dataModelConditionListPredicate: if (IsListGroup) @@ -175,7 +175,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions DataModelConditionGroup.RemoveChild(listViewModel.Model); // Insert a list in the same position - DataModelConditionPredicate predicate = new DataModelConditionPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic); + DataModelConditionGeneralPredicate predicate = new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic); predicate.UpdateLeftSide(listViewModel.LeftSideSelectionViewModel.DataModelPath); DataModelConditionGroup.AddChild(predicate, index); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs deleted file mode 100644 index 31ad33c2c..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Services; -using Artemis.UI.Exceptions; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public class DataModelConditionListPredicateViewModel : DataModelConditionViewModel, IDisposable - { - private readonly IConditionOperatorService _conditionOperatorService; - private readonly IDataModelUIService _dataModelUIService; - private readonly IProfileEditorService _profileEditorService; - private bool _isPrimitiveList; - private DataModelDynamicViewModel _leftSideSelectionViewModel; - private BindableCollection _operators; - private DataModelStaticViewModel _rightSideInputViewModel; - private DataModelDynamicViewModel _rightSideSelectionViewModel; - private BaseConditionOperator _selectedOperator; - - private List _supportedInputTypes; - - public DataModelConditionListPredicateViewModel( - DataModelConditionListPredicate dataModelConditionListPredicate, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IConditionOperatorService conditionOperatorService) : base(dataModelConditionListPredicate) - { - _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; - _conditionOperatorService = conditionOperatorService; - _supportedInputTypes = new List(); - - SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand); - Operators = new BindableCollection(); - - Initialize(); - } - - public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model; - - public BindableCollection Operators - { - get => _operators; - set => SetAndNotify(ref _operators, value); - } - - public DataModelDynamicViewModel LeftSideSelectionViewModel - { - get => _leftSideSelectionViewModel; - set => SetAndNotify(ref _leftSideSelectionViewModel, value); - } - - public DataModelDynamicViewModel RightSideSelectionViewModel - { - get => _rightSideSelectionViewModel; - set => SetAndNotify(ref _rightSideSelectionViewModel, value); - } - - public DataModelStaticViewModel RightSideInputViewModel - { - get => _rightSideInputViewModel; - set => SetAndNotify(ref _rightSideInputViewModel, value); - } - - public BaseConditionOperator SelectedOperator - { - get => _selectedOperator; - set => SetAndNotify(ref _selectedOperator, value); - } - - public DelegateCommand SelectOperatorCommand { get; } - - public override void Delete() - { - base.Delete(); - _profileEditorService.UpdateSelectedProfileElement(); - } - - public void Initialize() - { - DataModelPropertiesViewModel listDataModel = GetListDataModel(); - if (listDataModel.Children.Count == 1 && listDataModel.Children.First() is DataModelListPropertyViewModel) - _isPrimitiveList = true; - else - _isPrimitiveList = false; - - // Get the data models - if (!_isPrimitiveList) - { - LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); - LeftSideSelectionViewModel.ChangeDataModel(listDataModel); - LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected; - } - - // Determine which types are currently supported - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - _supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - - Update(); - } - - public override void Update() - { - Guid? listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid; - if (listDataModelGuid == null) - return; - - if (!_isPrimitiveList) - { - // Lists use a different color - LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188)); - LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); - LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.LeftPath); - } - - Type leftSideType = _isPrimitiveList - ? DataModelConditionListPredicate.DataModelConditionList.ListType - : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); - - // Get the supported operators - Operators.Clear(); - Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left)); - if (DataModelConditionListPredicate.Operator == null) - DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object), ConditionParameterSide.Left))); - SelectedOperator = DataModelConditionListPredicate.Operator; - - // Without a selected operator or one that supports a right side, leave the right side input empty - if (SelectedOperator == null || SelectedOperator.RightSideType == null) - { - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); - return; - } - - // Ensure the right side has the proper VM - if (DataModelConditionListPredicate.PredicateType == ProfileRightSideType.Dynamic) - { - DisposeRightSideStaticViewModel(); - if (RightSideSelectionViewModel == null) - CreateRightSideSelectionViewModel(); - - RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.RightPath); - RightSideSelectionViewModel.FilterTypes = new[] {SelectedOperator.RightSideType}; - } - else - { - DisposeRightSideDynamicViewModel(); - if (RightSideInputViewModel == null) - CreateRightSideInputViewModel(); - - Type preferredType = DataModelConditionListPredicate.GetPreferredRightSideType(); - if (preferredType.IsValueType && DataModelConditionListPredicate.RightStaticValue == null) - RightSideInputViewModel.Value = preferredType.GetDefault(); - else - RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue; - - if (RightSideInputViewModel.TargetType != preferredType) - RightSideInputViewModel.UpdateTargetType(preferredType); - } - } - - public void ApplyLeftSide() - { - DataModelConditionListPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath); - _profileEditorService.UpdateSelectedProfileElement(); - - SelectedOperator = DataModelConditionListPredicate.Operator; - Update(); - } - - public void ApplyRightSideDynamic() - { - DataModelConditionListPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath); - _profileEditorService.UpdateSelectedProfileElement(); - - Update(); - } - - public void ApplyRightSideStatic(object value) - { - DataModelConditionListPredicate.UpdateRightSideStatic(value); - _profileEditorService.UpdateSelectedProfileElement(); - - Update(); - } - - public void ApplyOperator() - { - DataModelConditionListPredicate.UpdateOperator(SelectedOperator); - _profileEditorService.UpdateSelectedProfileElement(); - - Update(); - } - - private DataModelPropertiesViewModel GetListDataModel() - { - if (DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid == null) - throw new ArtemisUIException("Failed to retrieve the list data model VM for this list predicate because it has no list path"); - - DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); - DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath( - DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid.Value, - DataModelConditionListPredicate.DataModelConditionList.ListPath.Path - ); - - return listDataModel.GetListTypeViewModel(_dataModelUIService); - } - - private void ExecuteSelectOperatorCommand(object context) - { - if (!(context is BaseConditionOperator dataModelConditionOperator)) - return; - - SelectedOperator = dataModelConditionOperator; - ApplyOperator(); - } - - #region IDisposable - - public void Dispose() - { - if (!_isPrimitiveList) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - } - - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); - } - - #endregion - - #region Event handlers - - private void LeftSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyLeftSide(); - } - - private void RightSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyRightSideDynamic(); - } - - private void RightSideOnValueEntered(object sender, DataModelInputStaticEventArgs e) - { - ApplyRightSideStatic(e.Value); - } - - private void RightSideSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) - { - DataModelConditionListPredicate.PredicateType = ProfileRightSideType.Static; - Update(); - } - - private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e) - { - DataModelConditionListPredicate.PredicateType = ProfileRightSideType.Dynamic; - Update(); - } - - #endregion - - #region View model management - - private void CreateRightSideSelectionViewModel() - { - RightSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); - RightSideSelectionViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush"); - RightSideSelectionViewModel.DisplaySwitchButton = true; - RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected; - RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested; - - // Add an extra data model to the selection VM to allow self-referencing the current item - // The safe cast prevents adding this extra VM on primitive lists where they serve no purpose - if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue) - RightSideSelectionViewModel.ExtraDataModelViewModels.Add(listValue); - } - - private void CreateRightSideInputViewModel() - { - Type preferredType = DataModelConditionListPredicate.GetPreferredRightSideType(); - RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(preferredType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription()); - RightSideInputViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush"); - RightSideInputViewModel.DisplaySwitchButton = true; - RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered; - RightSideInputViewModel.SwitchToDynamicRequested += RightSideInputViewModelOnSwitchToDynamicRequested; - } - - private void DisposeRightSideStaticViewModel() - { - if (RightSideInputViewModel == null) - return; - RightSideInputViewModel.ValueUpdated -= RightSideOnValueEntered; - RightSideInputViewModel.SwitchToDynamicRequested -= RightSideInputViewModelOnSwitchToDynamicRequested; - RightSideInputViewModel.Dispose(); - RightSideInputViewModel = null; - } - - private void DisposeRightSideDynamicViewModel() - { - if (RightSideSelectionViewModel == null) - return; - RightSideSelectionViewModel.PropertySelected -= RightSideOnPropertySelected; - RightSideSelectionViewModel.SwitchToStaticRequested -= RightSideSelectionViewModelOnSwitchToStaticRequested; - RightSideSelectionViewModel.Dispose(); - RightSideSelectionViewModel = null; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs index 7990bd83a..7bbf7a2d8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs @@ -45,7 +45,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public void AddCondition() { - DataModelConditionList.AddChild(new DataModelConditionPredicate(DataModelConditionList, ProfileRightSideType.Dynamic)); + DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic)); Update(); _profileEditorService.UpdateSelectedProfileElement(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml similarity index 97% rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml index 8bdc6a14e..931d22b7b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml @@ -7,10 +7,10 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:utilities="clr-namespace:Artemis.UI.Utilities" xmlns:conditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.Conditions" - x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionPredicateView" + x:Class="Artemis.UI.Screens.ProfileEditor.Conditions.DataModelConditionGeneralPredicateView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type conditions:DataModelConditionPredicateViewModel}}"> + d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type conditions:DataModelConditionGeneralPredicateViewModel}}"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs similarity index 74% rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs index 0891cd5fc..22411462b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateView.xaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs @@ -4,11 +4,11 @@ using System.Windows.Controls; namespace Artemis.UI.Screens.ProfileEditor.Conditions { /// - /// Interaction logic for DataModelConditionPredicateView.xaml + /// Interaction logic for DataModelConditionGeneralPredicateView.xaml /// - public partial class DataModelConditionPredicateView : UserControl + public partial class DataModelConditionGeneralPredicateView : UserControl { - public DataModelConditionPredicateView() + public DataModelConditionGeneralPredicateView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs new file mode 100644 index 000000000..2b346da0f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; + +namespace Artemis.UI.Screens.ProfileEditor.Conditions +{ + public class DataModelConditionGeneralPredicateViewModel : DataModelConditionPredicateViewModel + { + private readonly IDataModelUIService _dataModelUIService; + + public DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate, + IProfileEditorService profileEditorService, + IDataModelUIService dataModelUIService, + IConditionOperatorService conditionOperatorService, + ISettingsService settingsService) + : base(dataModelConditionGeneralPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService) + { + _dataModelUIService = dataModelUIService; + Initialize(); + } + + protected override List GetSupportedInputTypes() + { + IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; + List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); + supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); + supportedInputTypes.Add(typeof(IEnumerable<>)); + supportedInputTypes.Add(typeof(DataModelEvent)); + supportedInputTypes.Add(typeof(DataModelEvent<>)); + + return supportedInputTypes; + } + + protected override Type GetLeftSideType() + { + return LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml similarity index 100% rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml.cs similarity index 100% rename from src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateView.xaml.cs rename to src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml.cs diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs new file mode 100644 index 000000000..6e5f9cabb --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Media; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Exceptions; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; + +namespace Artemis.UI.Screens.ProfileEditor.Conditions +{ + public class DataModelConditionListPredicateViewModel : DataModelConditionPredicateViewModel + { + private readonly IDataModelUIService _dataModelUIService; + private readonly IProfileEditorService _profileEditorService; + private bool _isPrimitiveList; + + public DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate, + IProfileEditorService profileEditorService, + IDataModelUIService dataModelUIService, + IConditionOperatorService conditionOperatorService, + ISettingsService settingsService) + : base(dataModelConditionListPredicate, profileEditorService, dataModelUIService, conditionOperatorService, settingsService) + { + _profileEditorService = profileEditorService; + _dataModelUIService = dataModelUIService; + + LeftSideColor = new SolidColorBrush(Color.FromRgb(71, 108, 188)); + Initialize(); + } + + public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model; + + public override void Initialize() + { + base.Initialize(); + + DataModelPropertiesViewModel listDataModel = GetListDataModel(); + if (listDataModel.Children.Count == 1 && listDataModel.Children.First() is DataModelListPropertyViewModel) + _isPrimitiveList = true; + else + _isPrimitiveList = false; + + // Get the data models + if (!_isPrimitiveList) + LeftSideSelectionViewModel.ChangeDataModel(listDataModel); + } + + protected override List GetSupportedInputTypes() + { + IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; + List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); + supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); + + return supportedInputTypes; + } + + protected override Type GetLeftSideType() + { + return DataModelConditionListPredicate.DataModelConditionList.IsPrimitiveList + ? DataModelConditionListPredicate.DataModelConditionList.ListType + : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); + } + + protected override List GetExtraRightSideDataModelViewModels() + { + if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue) + return new List {listValue}; + return null; + } + + private DataModelPropertiesViewModel GetListDataModel() + { + if (DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid == null) + throw new ArtemisUIException("Failed to retrieve the list data model VM for this list predicate because it has no list path"); + + DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); + DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath( + DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid.Value, + DataModelConditionListPredicate.DataModelConditionList.ListPath.Path + ); + + return listDataModel.GetListTypeViewModel(_dataModelUIService); + } + } +} \ No newline at end of file