diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index 627571163..8250aa911 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -12,19 +12,21 @@ namespace Artemis.Core.Models.Profile.Conditions public DisplayConditionGroup(DisplayConditionPart parent) { Parent = parent; - DisplayConditionGroupEntity = new DisplayConditionGroupEntity(); + Entity = new DisplayConditionGroupEntity(); } public DisplayConditionGroup(DisplayConditionPart parent, DisplayConditionGroupEntity entity) { Parent = parent; - DisplayConditionGroupEntity = entity; - BooleanOperator = (BooleanOperator) DisplayConditionGroupEntity.BooleanOperator; + Entity = entity; + BooleanOperator = (BooleanOperator) Entity.BooleanOperator; - foreach (var childEntity in DisplayConditionGroupEntity.Children) + foreach (var childEntity in Entity.Children) { if (childEntity is DisplayConditionGroupEntity groupEntity) AddChild(new DisplayConditionGroup(this, groupEntity)); + else if (childEntity is DisplayConditionListEntity listEntity) + AddChild(new DisplayConditionList(this, listEntity)); else if (childEntity is DisplayConditionPredicateEntity predicateEntity) AddChild(new DisplayConditionPredicate(this, predicateEntity)); else if (childEntity is DisplayConditionListPredicateEntity listPredicateEntity) @@ -33,7 +35,7 @@ namespace Artemis.Core.Models.Profile.Conditions } public BooleanOperator BooleanOperator { get; set; } - public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; } + public DisplayConditionGroupEntity Entity { get; set; } public override bool Evaluate() { @@ -80,10 +82,10 @@ namespace Artemis.Core.Models.Profile.Conditions internal override void ApplyToEntity() { - DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator; + Entity.BooleanOperator = (int) BooleanOperator; - DisplayConditionGroupEntity.Children.Clear(); - DisplayConditionGroupEntity.Children.AddRange(Children.Select(c => c.GetEntity())); + Entity.Children.Clear(); + Entity.Children.AddRange(Children.Select(c => c.GetEntity())); foreach (var child in Children) child.ApplyToEntity(); } @@ -96,7 +98,7 @@ namespace Artemis.Core.Models.Profile.Conditions internal override DisplayConditionPartEntity GetEntity() { - return DisplayConditionGroupEntity; + return Entity; } } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs new file mode 100644 index 000000000..d21e96406 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections; +using System.Linq; +using System.Linq.Expressions; +using Artemis.Core.Exceptions; +using Artemis.Core.Models.Profile.Conditions.Abstract; +using Artemis.Core.Plugins.Abstract.DataModels; +using Artemis.Core.Services.Interfaces; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Entities.Profile.Abstract; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public class DisplayConditionList : DisplayConditionPart + { + public DisplayConditionList(DisplayConditionPart parent) + { + Parent = parent; + Entity = new DisplayConditionListEntity(); + + // There is always a child root group, add it + AddChild(new DisplayConditionGroup(this)); + } + + public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity) + { + Parent = parent; + Entity = entity; + ListOperator = (ListOperator) entity.ListOperator; + + // There should only be one child and it should be a group + var rootGroup = Entity.Children.SingleOrDefault() as DisplayConditionGroupEntity; + if (rootGroup == null) + { + Entity.Children.Clear(); + AddChild(new DisplayConditionGroup(this)); + } + else + AddChild(new DisplayConditionGroup(this, rootGroup)); + } + + public DisplayConditionListEntity Entity { get; set; } + + public ListOperator ListOperator { get; set; } + public DataModel ListDataModel { get; private set; } + public string ListPropertyPath { get; private set; } + + public override bool Evaluate() + { + if (CompiledListAccessor == null) + return false; + + return EvaluateObject(CompiledListAccessor(ListDataModel)); + } + + public override bool EvaluateObject(object target) + { + if (!(target is IList list)) + return false; + + var objectList = list.Cast(); + return ListOperator switch + { + ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)), + ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)), + ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)), + ListOperator.Count => false, + _ => throw new ArgumentOutOfRangeException() + }; + } + + internal override void ApplyToEntity() + { + // Target list + Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; + Entity.ListPropertyPath = ListPropertyPath; + + // Operator + Entity.ListOperator = (int) ListOperator; + + // Children + Entity.Children.Clear(); + Entity.Children.AddRange(Children.Select(c => c.GetEntity())); + foreach (var child in Children) + child.ApplyToEntity(); + } + + internal override DisplayConditionPartEntity GetEntity() + { + return Entity; + } + + internal override void Initialize(IDataModelService dataModelService) + { + // Target list + if (Entity.ListDataModelGuid != null) + { + var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value); + if (dataModel != null && dataModel.ContainsPath(Entity.ListPropertyPath)) + UpdateList(dataModel, Entity.ListPropertyPath); + } + + // Children + var rootGroup = (DisplayConditionGroup) Children.Single(); + rootGroup.Initialize(dataModelService); + } + + public void UpdateList(DataModel dataModel, string path) + { + if (dataModel != null && path == null) + throw new ArtemisCoreException("If a data model is provided, a path is also required"); + if (dataModel == null && path != null) + throw new ArtemisCoreException("If path is provided, a data model is also required"); + + if (dataModel != null) + { + if (!dataModel.ContainsPath(path)) + throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); + if (dataModel.GetListTypeAtPath(path) == null) + throw new ArtemisCoreException($"The path '{path}' does not contain a list"); + } + + ListDataModel = dataModel; + ListPropertyPath = path; + + if (dataModel != null) + { + var parameter = Expression.Parameter(typeof(object), "listDataModel"); + var accessor = path.Split('.').Aggregate( + Expression.Convert(parameter, dataModel.GetType()), + (expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList))); + + var lambda = Expression.Lambda>(accessor, parameter); + CompiledListAccessor = lambda.Compile(); + } + } + + public Func CompiledListAccessor { get; set; } + } + + public enum ListOperator + { + Any, + All, + None, + Count + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs index d79c2efa1..76ae54239 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs @@ -1,106 +1,47 @@ using System; -using System.Collections; using System.Linq; using System.Linq.Expressions; using Artemis.Core.Exceptions; +using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.Conditions.Abstract; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; +using Newtonsoft.Json; namespace Artemis.Core.Models.Profile.Conditions { public class DisplayConditionListPredicate : DisplayConditionPart { - public DisplayConditionListPredicate(DisplayConditionPart parent) + public DisplayConditionListPredicate(DisplayConditionPart parent, PredicateType predicateType) { Parent = parent; - DisplayConditionListPredicateEntity = new DisplayConditionListPredicateEntity(); - - // There is always a child root group, add it - AddChild(new DisplayConditionGroup(this)); + PredicateType = predicateType; + Entity = new DisplayConditionListPredicateEntity(); } public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity) { Parent = parent; - DisplayConditionListPredicateEntity = entity; - ListOperator = (ListOperator) entity.ListOperator; - - // There should only be one child and it should be a group - var rootGroup = DisplayConditionListPredicateEntity.Children.SingleOrDefault() as DisplayConditionGroupEntity; - if (rootGroup == null) - { - DisplayConditionListPredicateEntity.Children.Clear(); - AddChild(new DisplayConditionGroup(this)); - } - else - AddChild(new DisplayConditionGroup(this, rootGroup)); + Entity = entity; + PredicateType = (PredicateType) entity.PredicateType; } - public DisplayConditionListPredicateEntity DisplayConditionListPredicateEntity { get; set; } + public DisplayConditionListPredicateEntity Entity { get; set; } - public ListOperator ListOperator { get; set; } + public PredicateType PredicateType { get; set; } + public DisplayConditionOperator Operator { get; private set; } + + public Type ListType { get; private set; } public DataModel ListDataModel { get; private set; } public string ListPropertyPath { get; private set; } - public override bool Evaluate() - { - return EvaluateObject(CompiledListAccessor(ListDataModel)); - } + public string LeftPropertyPath { get; private set; } + public string RightPropertyPath { get; private set; } + public object RightStaticValue { get; private set; } - public override bool EvaluateObject(object target) - { - if (!(target is IList list)) - return false; - - var objectList = list.Cast(); - return ListOperator switch - { - ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)), - ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)), - ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)), - ListOperator.Count => false, - _ => throw new ArgumentOutOfRangeException() - }; - } - - internal override void ApplyToEntity() - { - // Target list - DisplayConditionListPredicateEntity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; - DisplayConditionListPredicateEntity.ListPropertyPath = ListPropertyPath; - - // Operator - DisplayConditionListPredicateEntity.ListOperator = (int) ListOperator; - - // Children - DisplayConditionListPredicateEntity.Children.Clear(); - DisplayConditionListPredicateEntity.Children.AddRange(Children.Select(c => c.GetEntity())); - foreach (var child in Children) - child.ApplyToEntity(); - } - - internal override DisplayConditionPartEntity GetEntity() - { - return DisplayConditionListPredicateEntity; - } - - internal override void Initialize(IDataModelService dataModelService) - { - // Target list - if (DisplayConditionListPredicateEntity.ListDataModelGuid != null) - { - var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionListPredicateEntity.ListDataModelGuid.Value); - if (dataModel != null && dataModel.ContainsPath(DisplayConditionListPredicateEntity.ListPropertyPath)) - UpdateList(dataModel, DisplayConditionListPredicateEntity.ListPropertyPath); - } - - // Children - var rootGroup = (DisplayConditionGroup) Children.Single(); - rootGroup.Initialize(dataModelService); - } + public Func CompiledListPredicate { get; private set; } public void UpdateList(DataModel dataModel, string path) { @@ -111,35 +52,322 @@ namespace Artemis.Core.Models.Profile.Conditions if (dataModel != null) { - if (!dataModel.ContainsPath(path)) - throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); - if (dataModel.GetListTypeAtPath(path) == null) - throw new ArtemisCoreException($"The path '{path}' does not contain a list"); + var listType = dataModel.GetListTypeAtPath(path); + if (listType == null) + throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{path}'"); + + ListType = listType; + } + else + { + ListType = null; } ListDataModel = dataModel; ListPropertyPath = path; - if (dataModel != null) - { - var parameter = Expression.Parameter(typeof(object), "listDataModel"); - var accessor = path.Split('.').Aggregate( - Expression.Convert(parameter, dataModel.GetType()), - (expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList))); + if (!ListContainsInnerPath(LeftPropertyPath)) + LeftPropertyPath = null; + if (!ListContainsInnerPath(RightPropertyPath)) + RightPropertyPath = null; - var lambda = Expression.Lambda>(accessor, parameter); - CompiledListAccessor = lambda.Compile(); + CreateExpression(); + } + + public void UpdateLeftSide(string path) + { + if (!ListContainsInnerPath(path)) + throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}"); + + LeftPropertyPath = path; + + ValidateOperator(); + ValidateRightSide(); + CreateExpression(); + } + + public void UpdateRightSideDynamic(string path) + { + if (!ListContainsInnerPath(path)) + throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}"); + + PredicateType = PredicateType.Dynamic; + RightPropertyPath = path; + + CreateExpression(); + } + + public void UpdateRightSideStatic(object staticValue) + { + PredicateType = PredicateType.Static; + RightPropertyPath = null; + + SetStaticValue(staticValue); + CreateExpression(); + } + + public void UpdateOperator(DisplayConditionOperator displayConditionOperator) + { + if (displayConditionOperator == null) + { + Operator = null; + return; + } + + if (LeftPropertyPath == null) + { + Operator = displayConditionOperator; + return; + } + + var leftType = GetTypeAtInnerPath(LeftPropertyPath); + if (displayConditionOperator.SupportsType(leftType)) + Operator = displayConditionOperator; + + CreateExpression(); + } + + private void CreateExpression() + { + CompiledListPredicate = null; + + if (Operator == null) + return; + + // If the operator does not support a right side, create a static expression because the right side will simply be null + if (PredicateType == PredicateType.Dynamic && Operator.SupportsRightSide) + CreateDynamicExpression(); + + CreateStaticExpression(); + } + + internal override void ApplyToEntity() + { + Entity.PredicateType = (int) PredicateType; + Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; + Entity.ListPropertyPath = ListPropertyPath; + + Entity.LeftPropertyPath = LeftPropertyPath; + Entity.RightPropertyPath = RightPropertyPath; + Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); + + Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; + Entity.OperatorType = Operator?.GetType().Name; + } + + public override bool Evaluate() + { + return false; + } + + public override bool EvaluateObject(object target) + { + return CompiledListPredicate != null && CompiledListPredicate(target); + } + + internal override void Initialize(IDataModelService dataModelService) + { + // Left side + if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) + UpdateLeftSide(Entity.LeftPropertyPath); + + // Operator + if (Entity.OperatorPluginGuid != null) + { + var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType); + if (conditionOperator != null) + UpdateOperator(conditionOperator); + } + + // Right side dynamic + if (PredicateType == PredicateType.Dynamic && Entity.RightPropertyPath != null) + { + if (ListContainsInnerPath(Entity.RightPropertyPath)) + UpdateLeftSide(Entity.LeftPropertyPath); + } + // Right side static + else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null) + { + try + { + if (LeftPropertyPath != null) + { + // Use the left side type so JSON.NET has a better idea what to do + var leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + object rightSideValue; + + try + { + rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); + } + // If deserialization fails, use the type's default + catch (JsonSerializationException e) + { + dataModelService.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) + { + dataModelService.LogListPredicateDeserializationFailure(this, e); + } } } - public Func CompiledListAccessor { get; set; } - } + internal override DisplayConditionPartEntity GetEntity() + { + return Entity; + } - public enum ListOperator - { - Any, - All, - None, - Count + private void ValidateOperator() + { + if (LeftPropertyPath == null || Operator == null) + return; + + var leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + if (!Operator.SupportsType(leftSideType)) + Operator = null; + } + + private void ValidateRightSide() + { + var leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + if (PredicateType == PredicateType.Dynamic) + { + if (RightPropertyPath == null) + return; + + var rightSideType = GetTypeAtInnerPath(RightPropertyPath); + if (!leftSideType.IsCastableFrom(rightSideType)) + UpdateRightSideDynamic(null); + } + else + { + if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType())) + UpdateRightSideStatic(RightStaticValue); + else + UpdateRightSideStatic(null); + } + } + + private void SetStaticValue(object staticValue) + { + // If the left side is empty simply apply the value, any validation will wait + if (LeftPropertyPath == null) + { + RightStaticValue = staticValue; + return; + } + + var leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + + // If not null ensure the types match and if not, convert it + if (staticValue != null && staticValue.GetType() == leftSideType) + RightStaticValue = staticValue; + else if (staticValue != null) + RightStaticValue = Convert.ChangeType(staticValue, leftSideType); + // If null create a default instance for value types or simply make it null for reference types + else if (leftSideType.IsValueType) + RightStaticValue = Activator.CreateInstance(leftSideType); + else + RightStaticValue = null; + } + + private void CreateDynamicExpression() + { + if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null) + return; + + // List accessors share the same parameter because a list always contains one item per entry + var leftSideParameter = Expression.Parameter(typeof(object), "listItem"); + var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter); + var rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter); + + // A conversion may be required if the types differ + // This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here + if (rightSideAccessor.Type != leftSideAccessor.Type) + rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); + + var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor); + var lambda = Expression.Lambda>(conditionExpression, leftSideParameter); + CompiledListPredicate = lambda.Compile(); + } + + private void CreateStaticExpression() + { + if (LeftPropertyPath == null || Operator == null) + return; + + // List accessors share the same parameter because a list always contains one item per entry + var leftSideParameter = Expression.Parameter(typeof(object), "listItem"); + var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter); + + // If the left side is a value type but the input is empty, this isn't a valid expression + if (leftSideAccessor.Type.IsValueType && RightStaticValue == null) + return; + + // If the right side value is null, the constant type cannot be inferred and must be provided manually + var rightSideConstant = RightStaticValue != null + ? Expression.Constant(RightStaticValue) + : Expression.Constant(null, leftSideAccessor.Type); + + var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant); + var lambda = Expression.Lambda>(conditionExpression, leftSideParameter); + CompiledListPredicate = lambda.Compile(); + } + + private Expression CreateListAccessor(string path, ParameterExpression listParameter) + { + return path.Split('.').Aggregate( + Expression.Convert(listParameter, ListType), // Cast to the appropriate type + Expression.Property + ); + } + + public bool ListContainsInnerPath(string path) + { + if (ListType == null) + return false; + + var parts = path.Split('.'); + var current = ListType; + foreach (var part in parts) + { + var property = current.GetProperty(part); + current = property?.PropertyType; + + if (property == null) + return false; + } + + return true; + } + + public Type GetTypeAtInnerPath(string path) + { + if (!ListContainsInnerPath(path)) + return null; + + var parts = path.Split('.'); + var current = ListType; + + Type result = null; + foreach (var part in parts) + { + var property = current.GetProperty(part); + current = property.PropertyType; + result = property.PropertyType; + } + + return result; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 8e42f97a6..57f56c3e3 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -18,17 +18,17 @@ namespace Artemis.Core.Models.Profile.Conditions { Parent = parent; PredicateType = predicateType; - DisplayConditionPredicateEntity = new DisplayConditionPredicateEntity(); + Entity = new DisplayConditionPredicateEntity(); } public DisplayConditionPredicate(DisplayConditionPart parent, DisplayConditionPredicateEntity entity) { Parent = parent; - DisplayConditionPredicateEntity = entity; + Entity = entity; PredicateType = (PredicateType) entity.PredicateType; } - public DisplayConditionPredicateEntity DisplayConditionPredicateEntity { get; set; } + public DisplayConditionPredicateEntity Entity { get; set; } public PredicateType PredicateType { get; set; } public DisplayConditionOperator Operator { get; private set; } @@ -38,6 +38,8 @@ namespace Artemis.Core.Models.Profile.Conditions public DataModel RightDataModel { get; private set; } public string RightPropertyPath { get; private set; } public object RightStaticValue { get; private set; } + public DataModel ListDataModel { get; private set; } + public string ListPropertyPath { get; private set; } public Func CompiledDynamicPredicate { get; private set; } public Func CompiledStaticPredicate { get; private set; } @@ -135,16 +137,16 @@ namespace Artemis.Core.Models.Profile.Conditions internal override void ApplyToEntity() { - DisplayConditionPredicateEntity.PredicateType = (int) PredicateType; - DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid; - DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath; + Entity.PredicateType = (int) PredicateType; + Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid; + Entity.LeftPropertyPath = LeftPropertyPath; - DisplayConditionPredicateEntity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; - DisplayConditionPredicateEntity.RightPropertyPath = RightPropertyPath; - DisplayConditionPredicateEntity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); + Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; + Entity.RightPropertyPath = RightPropertyPath; + Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - DisplayConditionPredicateEntity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; - DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name; + Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; + Entity.OperatorType = Operator?.GetType().Name; } public override bool Evaluate() @@ -168,30 +170,30 @@ namespace Artemis.Core.Models.Profile.Conditions internal override void Initialize(IDataModelService dataModelService) { // Left side - if (DisplayConditionPredicateEntity.LeftDataModelGuid != null) + if (Entity.LeftDataModelGuid != null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.LeftDataModelGuid.Value); - if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.LeftPropertyPath)) - UpdateLeftSide(dataModel, DisplayConditionPredicateEntity.LeftPropertyPath); + var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value); + if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath)) + UpdateLeftSide(dataModel, Entity.LeftPropertyPath); } // Operator - if (DisplayConditionPredicateEntity.OperatorPluginGuid != null) + if (Entity.OperatorPluginGuid != null) { - var conditionOperator = dataModelService.GetConditionOperator(DisplayConditionPredicateEntity.OperatorPluginGuid.Value, DisplayConditionPredicateEntity.OperatorType); + var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType); if (conditionOperator != null) UpdateOperator(conditionOperator); } // Right side dynamic - if (PredicateType == PredicateType.Dynamic && DisplayConditionPredicateEntity.RightDataModelGuid != null) + if (PredicateType == PredicateType.Dynamic && Entity.RightDataModelGuid != null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.RightDataModelGuid.Value); - if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.RightPropertyPath)) - UpdateRightSide(dataModel, DisplayConditionPredicateEntity.RightPropertyPath); + var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value); + if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath)) + UpdateRightSide(dataModel, Entity.RightPropertyPath); } // Right side static - else if (PredicateType == PredicateType.Static && DisplayConditionPredicateEntity.RightStaticValue != null) + else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null) { try { @@ -203,12 +205,12 @@ namespace Artemis.Core.Models.Profile.Conditions try { - rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType); + rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); } // If deserialization fails, use the type's default catch (JsonSerializationException e) { - dataModelService.LogDeserializationFailure(this, e); + dataModelService.LogPredicateDeserializationFailure(this, e); rightSideValue = Activator.CreateInstance(leftSideType); } @@ -217,7 +219,7 @@ namespace Artemis.Core.Models.Profile.Conditions else { // Hope for the best... - UpdateRightSide(JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue)); + UpdateRightSide(JsonConvert.DeserializeObject(Entity.RightStaticValue)); } } catch (JsonReaderException) @@ -230,7 +232,7 @@ namespace Artemis.Core.Models.Profile.Conditions internal override DisplayConditionPartEntity GetEntity() { - return DisplayConditionPredicateEntity; + return Entity; } private void ValidateOperator() @@ -292,7 +294,7 @@ namespace Artemis.Core.Models.Profile.Conditions if (LeftDataModel == null || RightDataModel == null || Operator == null) return; - var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null; + var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null; Expression leftSideAccessor; Expression rightSideAccessor; @@ -335,7 +337,7 @@ namespace Artemis.Core.Models.Profile.Conditions if (LeftDataModel == null || Operator == null) return; - var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null; + var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null; Expression leftSideAccessor; ParameterExpression leftSideParameter; @@ -373,7 +375,7 @@ namespace Artemis.Core.Models.Profile.Conditions private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter) { - var listType = dataModel.GetListTypeAtPath(path); + var listType = dataModel.GetListTypeInPath(path); if (listType != null) throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list"); @@ -386,7 +388,7 @@ namespace Artemis.Core.Models.Profile.Conditions private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter) { - var listType = dataModel.GetListTypeAtPath(path); + var listType = dataModel.GetListTypeInPath(path); if (listType == null) throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list"); diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 0eefa1da7..05e10ea10 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -299,7 +299,7 @@ namespace Artemis.Core.Models.Profile ApplyRenderElementToEntity(); // Conditions - RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity; + RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; DisplayConditionGroup?.ApplyToEntity(); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index fdd212c4f..753a90e38 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -206,7 +206,7 @@ namespace Artemis.Core.Models.Profile } // Conditions - RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity; + RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; DisplayConditionGroup?.ApplyToEntity(); } diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs index 5aaee3fee..9b78aaf52 100644 --- a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs @@ -31,14 +31,8 @@ namespace Artemis.Core.Plugins.Abstract.DataModels var current = GetType(); foreach (var part in parts) { - var property = current.GetProperty(part); - - // For lists, look into the list type instead of the list itself - if (property != null && typeof(IList).IsAssignableFrom(property.PropertyType)) - current = property.PropertyType.GetGenericArguments()[0]; - else - current = property?.PropertyType; - + var property = current?.GetProperty(part); + current = property?.PropertyType; if (property == null) return false; } @@ -58,20 +52,14 @@ namespace Artemis.Core.Plugins.Abstract.DataModels foreach (var part in parts) { var property = current.GetProperty(part); - - // For lists, look into the list type instead of the list itself - if (typeof(IList).IsAssignableFrom(property.PropertyType)) - current = property.PropertyType.GetGenericArguments()[0]; - else - current = property.PropertyType; - + current = property.PropertyType; result = property.PropertyType; } return result; } - public Type GetListTypeAtPath(string path) + public Type GetListTypeInPath(string path) { if (!ContainsPath(path)) return null; @@ -79,8 +67,13 @@ namespace Artemis.Core.Plugins.Abstract.DataModels var parts = path.Split('.'); var current = GetType(); + var index = 0; foreach (var part in parts) { + // Only return a type if the path CONTAINS a list, not if it points TO a list + if (index == parts.Length - 1) + return null; + var property = current.GetProperty(part); // For lists, look into the list type instead of the list itself @@ -88,14 +81,24 @@ namespace Artemis.Core.Plugins.Abstract.DataModels return property.PropertyType.GetGenericArguments()[0]; current = property.PropertyType; + index++; } return null; } + public Type GetListTypeAtPath(string path) + { + if (!ContainsPath(path)) + return null; + + var child = GetTypeAtPath(path); + return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null; + } + public string GetListInnerPath(string path) { - if (GetListTypeAtPath(path) == null) + if (GetListTypeInPath(path) == null) throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list"); var parts = path.Split('.'); diff --git a/src/Artemis.Core/Services/DataModelService.cs b/src/Artemis.Core/Services/DataModelService.cs index 54c48978f..60720b227 100644 --- a/src/Artemis.Core/Services/DataModelService.cs +++ b/src/Artemis.Core/Services/DataModelService.cs @@ -178,9 +178,27 @@ namespace Artemis.Core.Services return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType); } - public void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception) + public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception) { - _logger.Warning(exception, "Failed to deserialize display condition predicate {predicate}", displayConditionPredicate); + _logger.Warning( + exception, + "Failed to deserialize display condition predicate {left} {operator} {right}", + displayConditionPredicate.Entity.LeftPropertyPath, + displayConditionPredicate.Entity.OperatorType, + displayConditionPredicate.Entity.RightPropertyPath + ); + } + + public void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception) + { + _logger.Warning( + exception, + "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", + displayConditionPredicate.Entity.ListPropertyPath, + displayConditionPredicate.Entity.LeftPropertyPath, + displayConditionPredicate.Entity.OperatorType, + displayConditionPredicate.Entity.RightPropertyPath + ); } private void RegisterBuiltInConditionOperators() diff --git a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs index e8d985889..4b9fc1220 100644 --- a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs @@ -60,6 +60,7 @@ namespace Artemis.Core.Services.Interfaces List GetCompatibleConditionOperators(Type type); DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType); - void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception); + void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception); + void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RenderElementService.cs b/src/Artemis.Core/Services/RenderElementService.cs index ed7024a5a..ef4d68dfa 100644 --- a/src/Artemis.Core/Services/RenderElementService.cs +++ b/src/Artemis.Core/Services/RenderElementService.cs @@ -154,8 +154,15 @@ namespace Artemis.Core.Services ? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition) : new DisplayConditionGroup(null); - displayCondition.Initialize(_dataModelService); - renderElement.DisplayConditionGroup = displayCondition; + try + { + displayCondition.Initialize(_dataModelService); + renderElement.DisplayConditionGroup = displayCondition; + } + catch (Exception e) + { + _logger.Warning(e, $"Failed to init display conditions for {renderElement}"); + } } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DisplayConditionListEntity.cs b/src/Artemis.Storage/Entities/Profile/DisplayConditionListEntity.cs new file mode 100644 index 000000000..34907ebc7 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DisplayConditionListEntity.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Artemis.Storage.Entities.Profile.Abstract; + +namespace Artemis.Storage.Entities.Profile +{ + public class DisplayConditionListEntity : DisplayConditionPartEntity + { + public DisplayConditionListEntity() + { + Children = new List(); + } + + public Guid? ListDataModelGuid { get; set; } + public string ListPropertyPath { get; set; } + + public int ListOperator { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DisplayConditionListPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/DisplayConditionListPredicateEntity.cs index 6d40fae9c..289a7db5e 100644 --- a/src/Artemis.Storage/Entities/Profile/DisplayConditionListPredicateEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DisplayConditionListPredicateEntity.cs @@ -1,19 +1,21 @@ using System; -using System.Collections.Generic; using Artemis.Storage.Entities.Profile.Abstract; namespace Artemis.Storage.Entities.Profile { public class DisplayConditionListPredicateEntity : DisplayConditionPartEntity { - public DisplayConditionListPredicateEntity() - { - Children = new List(); - } + public int PredicateType { get; set; } public Guid? ListDataModelGuid { get; set; } public string ListPropertyPath { get; set; } - public int ListOperator { get; set; } + public string LeftPropertyPath { get; set; } + public string RightPropertyPath { 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.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 3281a1e5c..c3ef78f8d 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -208,16 +208,15 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared var path = propertyPath.Split("."); var currentPart = path.First(); - if (IsRootViewModel) { - var child = Children.FirstOrDefault(c => c.DataModel != null && + var child = Children.FirstOrDefault(c => c.DataModel != null && c.DataModel.PluginInfo.Guid == dataModelGuid); return child?.GetChildByPath(dataModelGuid, propertyPath); } else { - var child = Children.FirstOrDefault(c => c.DataModel != null && + var child = Children.FirstOrDefault(c => c.DataModel != null && c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart); if (child == null) return null; diff --git a/src/Artemis.UI/DataTemplateSelectors/ComboBoxTemplateSelector.cs b/src/Artemis.UI/DataTemplateSelectors/ComboBoxTemplateSelector.cs new file mode 100644 index 000000000..529182bfd --- /dev/null +++ b/src/Artemis.UI/DataTemplateSelectors/ComboBoxTemplateSelector.cs @@ -0,0 +1,53 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Windows.Media; + +namespace Artemis.UI.DataTemplateSelectors +{ + // Source: https://stackoverflow.com/a/33421573/5015269 + public class ComboBoxTemplateSelector : DataTemplateSelector + { + public DataTemplate SelectedItemTemplate { get; set; } + public DataTemplateSelector SelectedItemTemplateSelector { get; set; } + public DataTemplate DropdownItemsTemplate { get; set; } + public DataTemplateSelector DropdownItemsTemplateSelector { get; set; } + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + var itemToCheck = container; + + // Search up the visual tree, stopping at either a ComboBox or + // a ComboBoxItem (or null). This will determine which template to use + while (itemToCheck != null && !(itemToCheck is ComboBoxItem) && !(itemToCheck is ComboBox)) + itemToCheck = VisualTreeHelper.GetParent(itemToCheck); + + // If you stopped at a ComboBoxItem, you're in the dropdown + var inDropDown = itemToCheck is ComboBoxItem; + + return inDropDown + ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container) + : SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container); + } + } + + public class ComboBoxTemplateSelectorExtension : MarkupExtension + { + public DataTemplate SelectedItemTemplate { get; set; } + public DataTemplateSelector SelectedItemTemplateSelector { get; set; } + public DataTemplate DropdownItemsTemplate { get; set; } + public DataTemplateSelector DropdownItemsTemplateSelector { get; set; } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return new ComboBoxTemplateSelector() + { + SelectedItemTemplate = SelectedItemTemplate, + SelectedItemTemplateSelector = SelectedItemTemplateSelector, + DropdownItemsTemplate = DropdownItemsTemplate, + DropdownItemsTemplateSelector = DropdownItemsTemplateSelector + }; + } + } +} \ 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 01f869ce1..d29816eae 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -78,7 +78,7 @@ namespace Artemis.UI.Ninject.Factories public interface IDisplayConditionsVmFactory : IVmFactory { DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent); - DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionViewModel parent); + DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList, DisplayConditionViewModel parent); DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent); } diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml index a4d285d1e..33f04e1d5 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml @@ -6,17 +6,18 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput" xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core" + xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}"> - + - - + + @@ -30,15 +31,8 @@ - - - - - - - - + @@ -51,7 +45,9 @@ HorizontalAlignment="Left" ItemsSource="{Binding Path=Descriptors}" SelectedValue="{Binding Path=SelectedDescriptor}" - ItemTemplate="{StaticResource DescriptorTemplate}" /> + ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector + SelectedItemTemplate={StaticResource SimpleTemplate}, + DropdownItemsTemplate={StaticResource ExtendedTemplate}}" /> \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs index 372b5a575..8e3d15757 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs @@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions else if (type == "Dynamic") DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Dynamic)); else if (type == "List") - DisplayConditionGroup.AddChild(new DisplayConditionListPredicate(DisplayConditionGroup)); + DisplayConditionGroup.AddChild(new DisplayConditionList(DisplayConditionGroup)); Update(); _profileEditorService.UpdateSelectedProfileElement(); @@ -108,8 +108,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions case DisplayConditionGroup displayConditionGroup: Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this)); break; - case DisplayConditionListPredicate displayConditionListPredicate: - Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this)); + case DisplayConditionList displayConditionListPredicate: + Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this)); break; case DisplayConditionPredicate displayConditionPredicate: Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this)); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml similarity index 98% rename from src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml index d5c2c0fd1..8652aa903 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml @@ -1,4 +1,4 @@ - + d:DataContext="{d:DesignInstance Type=local:DisplayConditionListViewModel, IsDesignTimeCreatable=False}"> diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml.cs similarity index 76% rename from src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml.cs index 2e1d47eb0..0659f4681 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateView.xaml.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml.cs @@ -4,11 +4,11 @@ using System.Windows.Controls; namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { /// - /// Interaction logic for DisplayConditionListPredicateView.xaml + /// Interaction logic for DisplayConditionListView.xaml /// - public partial class DisplayConditionListPredicateView : UserControl + public partial class DisplayConditionListView : UserControl { - public DisplayConditionListPredicateView() + public DisplayConditionListView() { InitializeComponent(); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs similarity index 82% rename from src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs index 9d86619af..1c26b8682 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs @@ -17,7 +17,7 @@ using Humanizer; namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { - public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel + public class DisplayConditionListViewModel : DisplayConditionViewModel { private readonly IProfileEditorService _profileEditorService; private readonly IDataModelVisualizationService _dataModelVisualizationService; @@ -27,13 +27,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private DataModelPropertiesViewModel _targetDataModel; private readonly Timer _updateTimer; - public DisplayConditionListPredicateViewModel( - DisplayConditionListPredicate displayConditionListPredicate, + public DisplayConditionListViewModel( + DisplayConditionList displayConditionList, DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelVisualizationService dataModelVisualizationService, IDisplayConditionsVmFactory displayConditionsVmFactory, - ISettingsService settingsService) : base(displayConditionListPredicate, parent) + ISettingsService settingsService) : base(displayConditionList, parent) { _profileEditorService = profileEditorService; _dataModelVisualizationService = dataModelVisualizationService; @@ -51,7 +51,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public DelegateCommand SelectListPropertyCommand { get; } public PluginSetting ShowDataModelValues { get; } - public DisplayConditionListPredicate DisplayConditionListPredicate => (DisplayConditionListPredicate) Model; + public DisplayConditionList DisplayConditionList => (DisplayConditionList) Model; public bool IsInitialized { @@ -73,21 +73,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions set => SetAndNotify(ref _selectedListProperty, value); } - public string SelectedListOperator => DisplayConditionListPredicate.ListOperator.Humanize(); + public string SelectedListOperator => DisplayConditionList.ListOperator.Humanize(); public void SelectListOperator(string type) { var enumValue = Enum.Parse(type); - DisplayConditionListPredicate.ListOperator = enumValue; + DisplayConditionList.ListOperator = enumValue; NotifyOfPropertyChange(nameof(SelectedListOperator)); } public void AddCondition(string type) { if (type == "Static") - DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Static)); + DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Static)); else if (type == "Dynamic") - DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Dynamic)); + DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Dynamic)); Update(); _profileEditorService.UpdateSelectedProfileElement(); @@ -95,7 +95,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public void AddGroup() { - DisplayConditionListPredicate.AddChild(new DisplayConditionGroup(DisplayConditionListPredicate)); + DisplayConditionList.AddChild(new DisplayConditionGroup(DisplayConditionList)); Update(); _profileEditorService.UpdateSelectedProfileElement(); @@ -146,7 +146,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public void ApplyList() { - DisplayConditionListPredicate.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath); + DisplayConditionList.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath); _profileEditorService.UpdateSelectedProfileElement(); Update(); @@ -155,7 +155,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public override DataModelPropertiesViewModel GetDataModelOverride() { if (SelectedListProperty != null) - return (DataModelPropertiesViewModel) SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService); + return SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService); return base.GetDataModelOverride(); } @@ -168,9 +168,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions NotifyOfPropertyChange(nameof(SelectedListOperator)); // Update the selected list property - if (DisplayConditionListPredicate.ListDataModel != null && DisplayConditionListPredicate.ListPropertyPath != null) + if (DisplayConditionList.ListDataModel != null && DisplayConditionList.ListPropertyPath != null) { - var child = TargetDataModel.GetChildByPath(DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid, DisplayConditionListPredicate.ListPropertyPath); + var child = TargetDataModel.GetChildByPath( + DisplayConditionList.ListDataModel.PluginInfo.Guid, + DisplayConditionList.ListPropertyPath + ); SelectedListProperty = child as DataModelListViewModel; } @@ -178,7 +181,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions TargetDataModel.ApplyTypeFilter(true, typeof(IList)); // Remove VMs of effects no longer applied on the layer - var toRemove = Children.Where(c => !DisplayConditionListPredicate.Children.Contains(c.Model)).ToList(); + var toRemove = Children.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList(); // Using RemoveRange breaks our lovely animations foreach (var displayConditionViewModel in toRemove) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index a289bf599..490182a86 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -8,12 +8,10 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:utilities="clr-namespace:Artemis.UI.Utilities" - xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared" x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionPredicateView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}" - x:Name="DisplayConditionPredicateRoot"> + d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}"> diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml index 9f5dee20f..13371f599 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml @@ -9,6 +9,7 @@ xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" xmlns:profile="clr-namespace:Artemis.Core.Models.Profile;assembly=Artemis.Core" xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core" + xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" mc:Ignorable="d" behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True" d:DesignHeight="450" d:DesignWidth="800" @@ -22,12 +23,12 @@ Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" /> - + - - + + @@ -44,14 +45,6 @@ - - - - - - - - @@ -138,7 +131,9 @@ VerticalAlignment="Top" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" - ItemTemplate="{StaticResource ProfileDescriptorTemplate}"> + ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector + SelectedItemTemplate={StaticResource SimpleTemplate}, + DropdownItemsTemplate={StaticResource ExtendedTemplate}}" >