1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 02:03:32 +00:00

Merge pull request #476 from Artemis-RGB/dynamic-datamodels

Dynamic datamodel - Conditions
This commit is contained in:
Robert Beekman 2020-10-09 23:07:40 +02:00 committed by GitHub
commit 551e32528e
39 changed files with 620 additions and 818 deletions

View File

@ -15,7 +15,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the parent of this part /// Gets the parent of this part
/// </summary> /// </summary>
public DataModelConditionPart Parent { get; internal set; } public DataModelConditionPart? Parent { get; internal set; }
/// <summary> /// <summary>
/// Gets the children of this part /// Gets the children of this part

View File

@ -50,6 +50,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="a">The parameter on the left side of the expression</param> /// <param name="a">The parameter on the left side of the expression</param>
/// <param name="b">The parameter on the right side of the expression</param> /// <param name="b">The parameter on the right side of the expression</param>
public abstract bool Evaluate(object a, object b); public abstract bool Evaluate(object? a, object? b);
} }
} }

View File

@ -2,8 +2,6 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@ -37,37 +35,27 @@ namespace Artemis.Core
Initialize(); Initialize();
} }
internal DataModelConditionListEntity Entity { get; set; }
/// <summary> /// <summary>
/// Gets or sets the list operator /// Gets or sets the list operator
/// </summary> /// </summary>
public ListOperator ListOperator { get; set; } public ListOperator ListOperator { get; set; }
/// <summary>
/// Gets the path of the list property
/// </summary>
public DataModelPath? ListPath { get; private set; }
/// <summary> /// <summary>
/// Gets the type of the content of the list this predicate is evaluated on /// Gets the type of the content of the list this predicate is evaluated on
/// </summary> /// </summary>
public Type ListType { get; set; } public Type? ListType { get; set; }
/// <summary> /// <summary>
/// Gets whether the list contains primitives /// Gets whether the list contains primitives
/// </summary> /// </summary>
public bool IsPrimitiveList { get; set; } public bool IsPrimitiveList { get; set; }
/// <summary> internal DataModelConditionListEntity Entity { get; set; }
/// Gets the currently used instance of the list data model
/// </summary>
public DataModel ListDataModel { get; private set; }
/// <summary>
/// Gets the path of the list property in the <see cref="ListDataModel" />
/// </summary>
public string ListPropertyPath { get; private set; }
/// <summary>
/// Gets the compiled function that accesses the list this condition evaluates on
/// </summary>
public Func<object, IList> CompiledListAccessor { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public override bool Evaluate() public override bool Evaluate()
@ -75,50 +63,44 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
if (CompiledListAccessor == null) if (ListPath == null || !ListPath.IsValid)
return false; return false;
return EvaluateObject(CompiledListAccessor(ListDataModel)); return EvaluateObject(ListPath.GetValue());
} }
/// <summary> /// <summary>
/// Updates the list the predicate is evaluated on /// Updates the list the predicate is evaluated on
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the list</param>
/// <param name="path">The path pointing to the list inside the list</param> /// <param name="path">The path pointing to the list inside the list</param>
public void UpdateList(DataModel dataModel, string path) public void UpdateList(DataModelPath? path)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
if (dataModel != null && path == null) if (path != null && !path.IsValid)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("Cannot update list to an invalid path");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) ListPath?.Dispose();
{ ListPath = path != null ? new DataModelPath(path) : null;
Type 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;
IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string);
ListDataModel = dataModel;
ListPropertyPath = path;
}
// Remove the old root group that was tied to the old data model // Remove the old root group that was tied to the old data model
while (Children.Any()) while (Children.Any())
RemoveChild(Children[0]); RemoveChild(Children[0]);
if (dataModel == null) if (ListPath != null)
return; {
Type listType = ListPath.GetPropertyType()!;
ListType = listType.GetGenericArguments()[0];
IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
// Create a new root group // Create a new root group
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
CreateExpression(); }
else
{
ListType = null;
}
} }
#region IDisposable #region IDisposable
@ -128,8 +110,7 @@ namespace Artemis.Core
{ {
_disposed = true; _disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; ListPath?.Dispose();
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (DataModelConditionPart child in Children) foreach (DataModelConditionPart child in Children)
child.Dispose(); child.Dispose();
@ -139,7 +120,7 @@ namespace Artemis.Core
#endregion #endregion
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
@ -163,11 +144,8 @@ namespace Artemis.Core
internal override void Save() internal override void Save()
{ {
// Target list // Target list
if (ListDataModel != null) ListPath?.Save();
{ Entity.ListPath = ListPath?.Entity;
Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid;
Entity.ListPropertyPath = ListPropertyPath;
}
// Operator // Operator
Entity.ListOperator = (int) ListOperator; Entity.ListOperator = (int) ListOperator;
@ -186,80 +164,39 @@ namespace Artemis.Core
internal void Initialize() internal void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; if (Entity.ListPath == null)
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
if (Entity.ListDataModelGuid == null)
return; return;
// Get the data model ... // Ensure the list path is valid and points to a list
DataModel dataModel = DataModelStore.Get(Entity.ListDataModelGuid.Value)?.DataModel; DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
if (dataModel == null) Type listType = listPath.GetPropertyType()!;
return; // Can't check this on an invalid list, if it becomes valid later lets hope for the best
// ... and ensure the path is valid if (listPath.IsValid && !typeof(IList).IsAssignableFrom(listType))
Type listType = dataModel.GetListTypeAtPath(Entity.ListPropertyPath);
if (listType == null)
return; return;
ListType = listType; ListPath = listPath;
IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string); if (ListPath.IsValid)
ListDataModel = dataModel; {
ListPropertyPath = Entity.ListPropertyPath; ListType = listType.GetGenericArguments()[0];
IsPrimitiveList = ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
CreateExpression(); }
else
if (ListDataModel == null) {
return; ListType = null;
IsPrimitiveList = false;
}
// There should only be one child and it should be a group // There should only be one child and it should be a group
if (Entity.Children.SingleOrDefault() is DataModelConditionGroupEntity rootGroup) if (Entity.Children.SingleOrDefault() is DataModelConditionGroupEntity rootGroup)
{
AddChild(new DataModelConditionGroup(this, rootGroup)); AddChild(new DataModelConditionGroup(this, rootGroup));
}
else else
{ {
Entity.Children.Clear(); Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
} }
} }
private void CreateExpression()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionList");
ParameterExpression parameter = Expression.Parameter(typeof(object), "listDataModel");
Expression accessor = ListPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, ListDataModel.GetType()),
Expression.Property
);
accessor = Expression.Convert(accessor, typeof(IList));
Expression<Func<object, IList>> lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
CompiledListAccessor = lambda.Compile();
}
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.ListDataModelGuid && dataModel.ContainsPath(Entity.ListPropertyPath))
{
ListDataModel = dataModel;
ListPropertyPath = Entity.ListPropertyPath;
CreateExpression();
}
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (ListDataModel != e.Registration.DataModel)
return;
ListDataModel = null;
CompiledListAccessor = null;
}
#endregion
} }
/// <summary> /// <summary>

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
@ -25,6 +23,7 @@ namespace Artemis.Core
PredicateType = predicateType; PredicateType = predicateType;
Entity = new DataModelConditionListPredicateEntity(); Entity = new DataModelConditionListPredicateEntity();
DataModelConditionList = null!;
ApplyParentList(); ApplyParentList();
Initialize(); Initialize();
} }
@ -35,12 +34,11 @@ namespace Artemis.Core
Entity = entity; Entity = entity;
PredicateType = (ListRightSideType) entity.PredicateType; PredicateType = (ListRightSideType) entity.PredicateType;
DataModelConditionList = null!;
ApplyParentList(); ApplyParentList();
Initialize(); Initialize();
} }
internal DataModelConditionListPredicateEntity Entity { get; set; }
/// <summary> /// <summary>
/// Gets or sets the predicate type /// Gets or sets the predicate type
/// </summary> /// </summary>
@ -49,7 +47,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the operator /// Gets the operator
/// </summary> /// </summary>
public ConditionOperator Operator { get; private set; } public ConditionOperator? Operator { get; private set; }
/// <summary> /// <summary>
/// Gets the data model condition list this predicate belongs to /// Gets the data model condition list this predicate belongs to
@ -57,46 +55,40 @@ namespace Artemis.Core
public DataModelConditionList DataModelConditionList { get; private set; } public DataModelConditionList DataModelConditionList { get; private set; }
/// <summary> /// <summary>
/// Gets the path of the left property in the <see cref="ListType" /> /// Gets the path of the left property
/// </summary> /// </summary>
public string LeftPropertyPath { get; private set; } public DataModelPath? LeftPath { get; private set; }
/// <summary> /// <summary>
/// Gets the currently used instance of the right side data model /// Gets the path of the right property
/// <para>Note: This is null when using a path inside the list</para>
/// </summary> /// </summary>
public DataModel RightDataModel { get; set; } public DataModelPath? RightPath { get; private set; }
/// <summary>
/// Gets the path of the right property in the <see cref="ListType" />
/// </summary>
public string RightPropertyPath { get; private set; }
/// <summary> /// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is /// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ListRightSideType.Static" /> /// <see cref="ListRightSideType.Static" />
/// </summary> /// </summary>
public object RightStaticValue { get; private set; } public object? RightStaticValue { get; private set; }
public Func<object, object> LeftSideAccessor { get; set; } internal DataModelConditionListPredicateEntity Entity { get; set; }
public Func<object, object> RightSideAccessor { get; set; }
/// <summary> /// <summary>
/// Updates the left side of the predicate /// Updates the left side of the predicate
/// </summary> /// </summary>
/// <param name="path">The path pointing to the left side value inside the list</param> /// <param name="path">The path pointing to the left side value inside the list</param>
public void UpdateLeftSide(string path) public void UpdateLeftSide(DataModelPath? path)
{ {
if (DataModelConditionList.IsPrimitiveList) if (DataModelConditionList.IsPrimitiveList)
throw new ArtemisCoreException("Cannot apply a left side to a predicate inside a primitive list"); throw new ArtemisCoreException("Cannot apply a left side to a predicate inside a primitive list");
if (!ListContainsInnerPath(path))
throw new ArtemisCoreException($"List type {DataModelConditionList.ListType.Name} does not contain path {path}");
LeftPropertyPath = path; 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(); ValidateOperator();
ValidateRightSide(); ValidateRightSide();
CreateExpression();
} }
/// <summary> /// <summary>
@ -104,61 +96,48 @@ namespace Artemis.Core
/// and re-compiles the expression /// and re-compiles the expression
/// </summary> /// </summary>
/// <param name="path">The path pointing to the right side value inside the list</param> /// <param name="path">The path pointing to the right side value inside the list</param>
public void UpdateRightSideDynamic(string path) public void UpdateRightSideDynamicList(DataModelPath? path)
{ {
if (!ListContainsInnerPath(path)) if (path != null && !path.IsValid)
throw new ArtemisCoreException($"List type {DataModelConditionList.ListType.Name} does not contain path {path}"); throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
RightPath?.Dispose();
RightPath = path != null ? new DataModelPath(path) : null;
PredicateType = ListRightSideType.DynamicList; PredicateType = ListRightSideType.DynamicList;
RightPropertyPath = path;
CreateExpression();
} }
/// <summary> /// <summary>
/// Updates the right side of the predicate using path to a value in a data model, makes the predicate dynamic and /// Updates the right side of the predicate using path to a value in a data model, makes the predicate dynamic and
/// re-compiles the expression /// re-compiles the expression
/// </summary> /// </summary>
/// <param name="dataModel"></param>
/// <param name="path">The path pointing to the right side value inside the list</param> /// <param name="path">The path pointing to the right side value inside the list</param>
public void UpdateRightSideDynamic(DataModel dataModel, string path) public void UpdateRightSideDynamic(DataModelPath? path)
{ {
if (dataModel != null && path == null) RightPath?.Dispose();
throw new ArtemisCoreException("If a data model is provided, a path is also required"); RightPath = path;
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}'");
}
PredicateType = ListRightSideType.Dynamic; PredicateType = ListRightSideType.Dynamic;
RightDataModel = dataModel;
RightPropertyPath = path;
CreateExpression();
} }
/// <summary> /// <summary>
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
/// </summary> /// </summary>
/// <param name="staticValue">The right side value to use</param> /// <param name="staticValue">The right side value to use</param>
public void UpdateRightSideStatic(object staticValue) public void UpdateRightSideStatic(object? staticValue)
{ {
PredicateType = ListRightSideType.Static; PredicateType = ListRightSideType.Static;
RightPropertyPath = null; RightPath?.Dispose();
RightPath = null;
SetStaticValue(staticValue); SetStaticValue(staticValue);
CreateExpression();
} }
/// <summary> /// <summary>
/// Updates the operator of the predicate and re-compiles the expression /// Updates the operator of the predicate and re-compiles the expression
/// </summary> /// </summary>
/// <param name="conditionOperator"></param> /// <param name="conditionOperator"></param>
public void UpdateOperator(ConditionOperator conditionOperator) public void UpdateOperator(ConditionOperator? conditionOperator)
{ {
if (conditionOperator == null) if (conditionOperator == null)
{ {
@ -166,17 +145,20 @@ namespace Artemis.Core
return; return;
} }
if (LeftPropertyPath == null) // No need to clear compiled expressions, without a left data model they are already null
if (LeftPath == null || !LeftPath.IsValid)
{ {
Operator = conditionOperator; Operator = conditionOperator;
return; return;
} }
Type leftType = GetTypeAtInnerPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!conditionOperator.SupportsType(leftType))
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}");
if (conditionOperator.SupportsType(leftType)) if (conditionOperator.SupportsType(leftType))
Operator = conditionOperator; Operator = conditionOperator;
CreateExpression();
} }
/// <summary> /// <summary>
@ -209,26 +191,46 @@ namespace Artemis.Core
return true; return true;
} }
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object target)
{ {
if (Operator == null || LeftSideAccessor == null || PredicateType != ListRightSideType.Static && RightSideAccessor == null) if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false; return false;
// Compare with a static value // Compare with a static value
if (PredicateType == ListRightSideType.Static) if (PredicateType == ListRightSideType.Static)
{ {
if (!DataModelConditionList.ListType.IsValueType && RightStaticValue == null) object? leftSideValue = GetListPathValue(LeftPath, target);
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false; return false;
return Operator.Evaluate(LeftSideAccessor(target), RightStaticValue); return Operator.Evaluate(leftSideValue, RightStaticValue);
} }
if (RightPath == null || !RightPath.IsValid)
return false;
// Compare with dynamic values // Compare with dynamic values
if (PredicateType == ListRightSideType.Dynamic) if (PredicateType == ListRightSideType.Dynamic)
return Operator.Evaluate(LeftSideAccessor(target), RightSideAccessor(RightDataModel)); return Operator.Evaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
if (PredicateType == ListRightSideType.DynamicList) if (PredicateType == ListRightSideType.DynamicList)
return Operator.Evaluate(LeftSideAccessor(target), RightSideAccessor(target)); return Operator.Evaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
return false; return false;
} }
@ -255,10 +257,12 @@ namespace Artemis.Core
internal override void Save() internal override void Save()
{ {
Entity.PredicateType = (int) PredicateType; Entity.PredicateType = (int) PredicateType;
Entity.LeftPropertyPath = LeftPropertyPath;
Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; LeftPath?.Save();
Entity.RightPropertyPath = RightPropertyPath; Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
if (Operator != null) if (Operator != null)
@ -275,19 +279,12 @@ namespace Artemis.Core
private void ApplyParentList() private void ApplyParentList()
{ {
DataModelConditionPart current = Parent; DataModelConditionPart? current = Parent;
while (current != null) while (current != null)
{ {
if (current is DataModelConditionList parentList) if (current is DataModelConditionList parentList)
{ {
DataModelConditionList = parentList; DataModelConditionList = parentList;
if (LeftPropertyPath != null && !ListContainsInnerPath(LeftPropertyPath))
LeftPropertyPath = null;
if (RightPropertyPath != null && !ListContainsInnerPath(RightPropertyPath))
RightPropertyPath = null;
return; return;
} }
@ -300,46 +297,44 @@ namespace Artemis.Core
private void Initialize() private void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side // Left side
if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) if (Entity.LeftPath != null)
UpdateLeftSide(Entity.LeftPropertyPath); {
LeftPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath)
: null;
}
// Operator // Operator
if (Entity.OperatorPluginGuid != null) if (Entity.OperatorPluginGuid != null)
{ {
ConditionOperator conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null) if (conditionOperator != null)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
// Right side dynamic // Right side dynamic
if (PredicateType == ListRightSideType.Dynamic && Entity.RightDataModelGuid != null && Entity.RightPropertyPath != null) if (PredicateType == ListRightSideType.Dynamic && Entity.RightPath != null)
{ RightPath = new DataModelPath(null, Entity.RightPath);
DataModel dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSideDynamic(dataModel, Entity.RightPropertyPath);
}
// Right side dynamic inside the list // Right side dynamic inside the list
else if (PredicateType == ListRightSideType.DynamicList && Entity.RightPropertyPath != null) else if (PredicateType == ListRightSideType.DynamicList && Entity.RightPath != null)
{ {
if (ListContainsInnerPath(Entity.RightPropertyPath)) RightPath = DataModelConditionList.ListType != null
UpdateRightSideDynamic(Entity.RightPropertyPath); ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath)
: null;
} }
// Right side static // Right side static
else if (PredicateType == ListRightSideType.Static && Entity.RightStaticValue != null) else if (PredicateType == ListRightSideType.Static && Entity.RightStaticValue != null)
{
try try
{ {
if (LeftPropertyPath != null) if (LeftPath != null && LeftPath.IsValid)
{ {
// Use the left side type so JSON.NET has a better idea what to do // Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); Type leftSideType = LeftPath.GetPropertyType()!;
object rightSideValue; object? rightSideValue;
try try
{ {
@ -364,73 +359,62 @@ namespace Artemis.Core
{ {
DeserializationLogger.LogListPredicateDeserializationFailure(this, e); DeserializationLogger.LogListPredicateDeserializationFailure(this, e);
} }
}
}
private void CreateExpression()
{
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 == ListRightSideType.DynamicList && Operator.SupportsRightSide)
CreateDynamicListAccessors();
else if (PredicateType == ListRightSideType.Dynamic && Operator.SupportsRightSide)
CreateDynamicAccessors();
else
CreateStaticAccessors();
} }
private void ValidateOperator() private void ValidateOperator()
{ {
if (LeftPropertyPath == null || Operator == null) if (LeftPath == null || !LeftPath.IsValid || Operator == null)
return; return;
Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!Operator.SupportsType(leftSideType)) if (!Operator.SupportsType(leftType))
Operator = null; Operator = null;
} }
private void ValidateRightSide() private void ValidateRightSide()
{ {
Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); Type? leftType = LeftPath?.GetPropertyType();
if (PredicateType == ListRightSideType.Dynamic) if (PredicateType == ListRightSideType.Dynamic)
{ {
if (RightDataModel == null) if (RightPath == null || !RightPath.IsValid)
return; return;
Type rightSideType = RightDataModel.GetTypeAtPath(RightPropertyPath); Type rightSideType = RightPath.GetPropertyType()!;
if (!leftSideType.IsCastableFrom(rightSideType)) if (leftType != null && !leftType.IsCastableFrom(rightSideType))
UpdateRightSideDynamic(null, null); UpdateRightSideDynamic(null);
} }
else if (PredicateType == ListRightSideType.DynamicList) else if (PredicateType == ListRightSideType.DynamicList)
{ {
if (RightPropertyPath == null) if (RightPath == null || !RightPath.IsValid)
return; return;
Type rightSideType = GetTypeAtInnerPath(RightPropertyPath); Type rightSideType = RightPath.GetPropertyType()!;
if (!leftSideType.IsCastableFrom(rightSideType)) if (leftType != null && !leftType.IsCastableFrom(rightSideType))
UpdateRightSideDynamic(null); UpdateRightSideDynamicList(null);
} }
else else
{ {
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType())) if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType())))
UpdateRightSideStatic(RightStaticValue); UpdateRightSideStatic(RightStaticValue);
else else
UpdateRightSideStatic(null); UpdateRightSideStatic(null);
} }
} }
private void SetStaticValue(object staticValue) private void SetStaticValue(object? staticValue)
{ {
RightPath?.Dispose();
RightPath = null;
// If the left side is empty simply apply the value, any validation will wait // If the left side is empty simply apply the value, any validation will wait
if (LeftPropertyPath == null) if (LeftPath == null || !LeftPath.IsValid)
{ {
RightStaticValue = staticValue; RightStaticValue = staticValue;
return; return;
} }
Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); // If the left path is valid we can expect a type
Type leftSideType = LeftPath.GetPropertyType()!;
// If not null ensure the types match and if not, convert it // If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == leftSideType) if (staticValue != null && staticValue.GetType() == leftSideType)
@ -444,100 +428,17 @@ namespace Artemis.Core
RightStaticValue = null; RightStaticValue = null;
} }
private void CreateDynamicListAccessors() private object? GetListPathValue(DataModelPath path, object target)
{ {
if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null) if (!(path.Target is ListPredicateWrapperDataModel wrapper))
return; throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
// List accessors share the same parameter because a list always contains one item per entry wrapper.UntypedValue = target;
ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem"); return path.GetValue();
Expression leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
Expression rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter);
// A conversion may be required if the types differ
// This can cause issues if the DataModelConditionOperator 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);
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, leftSideParameter).Compile();
} }
private void CreateDynamicAccessors()
{
if (LeftPropertyPath == null || RightPropertyPath == null || RightDataModel == null || Operator == null)
return;
// List accessors share the same parameter because a list always contains one item per entry
ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem");
Expression leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
Expression rightSideAccessor = ExpressionUtilities.CreateDataModelAccessor(RightDataModel, RightPropertyPath, "right", out ParameterExpression rightSideParameter);
// A conversion may be required if the types differ
// This can cause issues if the DataModelConditionOperator 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);
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, rightSideParameter).Compile();
}
private void CreateStaticAccessors()
{
if (!DataModelConditionList.IsPrimitiveList && LeftPropertyPath == null || Operator == null)
return;
// List accessors share the same parameter because a list always contains one item per entry
ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem");
Expression leftSideAccessor = DataModelConditionList.IsPrimitiveList
? Expression.Convert(leftSideParameter, DataModelConditionList.ListType)
: CreateListAccessor(LeftPropertyPath, leftSideParameter);
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = null;
}
private Expression CreateListAccessor(string path, ParameterExpression listParameter)
{
// Create an expression that checks every part of the path for null
// In the same iteration, create the accessor
Expression source = Expression.Convert(listParameter, DataModelConditionList.ListType);
return ExpressionUtilities.CreateNullCheckedAccessor(source, path);
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
#endregion
#region Event handlers #region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSideDynamic(dataModel, Entity.RightPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (RightDataModel == e.Registration.DataModel)
{
RightSideAccessor = null;
RightDataModel = null;
}
}
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{ {
ConditionOperator conditionOperator = e.Registration.ConditionOperator; ConditionOperator conditionOperator = e.Registration.ConditionOperator;

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@ -45,109 +43,76 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the operator /// Gets the operator
/// </summary> /// </summary>
public ConditionOperator Operator { get; private set; } public ConditionOperator? Operator { get; private set; }
/// <summary> /// <summary>
/// Gets the currently used instance of the left data model /// Gets the path of the left property
/// </summary> /// </summary>
public DataModel LeftDataModel { get; private set; } public DataModelPath? LeftPath { get; private set; }
/// <summary> /// <summary>
/// Gets the path of the left property in the <see cref="LeftDataModel" /> /// Gets the path of the right property
/// </summary> /// </summary>
public string LeftPropertyPath { get; private set; } public DataModelPath? RightPath { get; private set; }
/// <summary>
/// Gets the currently used instance of the right data model
/// </summary>
public DataModel RightDataModel { get; private set; }
/// <summary>
/// Gets the path of the right property in the <see cref="RightDataModel" />
/// </summary>
public string RightPropertyPath { get; private set; }
/// <summary> /// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is /// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ProfileRightSideType.Static" /> /// <see cref="ProfileRightSideType.Static" />
/// </summary> /// </summary>
public object RightStaticValue { get; private set; } public object? RightStaticValue { get; private set; }
public Func<object, object> LeftSideAccessor { get; set; }
public Func<object, object> RightSideAccessor { get; set; }
internal DataModelConditionPredicateEntity Entity { get; set; } internal DataModelConditionPredicateEntity Entity { get; set; }
/// <summary> /// <summary>
/// Updates the left side of the predicate /// Updates the left side of the predicate
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the left side value</param>
/// <param name="path">The path pointing to the left side value inside the data model</param> /// <param name="path">The path pointing to the left side value inside the data model</param>
public void UpdateLeftSide(DataModel dataModel, string path) public void UpdateLeftSide(DataModelPath? path)
{ {
if (dataModel != null && path == null) if (path != null && !path.IsValid)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) LeftPath?.Dispose();
{ LeftPath = path != null ? new DataModelPath(path) : null;
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
}
LeftDataModel = dataModel;
LeftPropertyPath = path;
ValidateOperator(); ValidateOperator();
ValidateRightSide(); ValidateRightSide();
CreateAccessors();
} }
/// <summary> /// <summary>
/// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression /// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the right side value</param>
/// <param name="path">The path pointing to the right side value inside the data model</param> /// <param name="path">The path pointing to the right side value inside the data model</param>
public void UpdateRightSide(DataModel dataModel, string path) public void UpdateRightSideDynamic(DataModelPath? path)
{ {
if (dataModel != null && path == null) if (path != null && !path.IsValid)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) RightPath?.Dispose();
{ RightPath = path != null ? new DataModelPath(path) : null;
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
}
PredicateType = ProfileRightSideType.Dynamic; PredicateType = ProfileRightSideType.Dynamic;
RightDataModel = dataModel;
RightPropertyPath = path;
CreateAccessors();
} }
/// <summary> /// <summary>
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
/// </summary> /// </summary>
/// <param name="staticValue">The right side value to use</param> /// <param name="staticValue">The right side value to use</param>
public void UpdateRightSide(object staticValue) public void UpdateRightSideStatic(object? staticValue)
{ {
PredicateType = ProfileRightSideType.Static; PredicateType = ProfileRightSideType.Static;
RightDataModel = null; RightPath?.Dispose();
RightPropertyPath = null; RightPath = null;
// If the left side is empty simply apply the value, any validation will wait // If the left side is empty simply apply the value, any validation will wait
if (LeftDataModel == null) if (LeftPath == null || !LeftPath.IsValid)
{ {
RightStaticValue = staticValue; RightStaticValue = staticValue;
return; return;
} }
Type leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); // If the left path is valid we can expect a type
Type leftSideType = LeftPath.GetPropertyType()!;
// If not null ensure the types match and if not, convert it // If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == leftSideType) if (staticValue != null && staticValue.GetType() == leftSideType)
@ -159,63 +124,77 @@ namespace Artemis.Core
RightStaticValue = Activator.CreateInstance(leftSideType); RightStaticValue = Activator.CreateInstance(leftSideType);
else else
RightStaticValue = null; RightStaticValue = null;
CreateAccessors();
} }
/// <summary> /// <summary>
/// Updates the operator of the predicate and re-compiles the expression /// Updates the operator of the predicate and re-compiles the expression
/// </summary> /// </summary>
/// <param name="conditionOperator"></param> /// <param name="conditionOperator"></param>
public void UpdateOperator(ConditionOperator conditionOperator) public void UpdateOperator(ConditionOperator? conditionOperator)
{ {
// Calling CreateExpression will clear compiled expressions // Calling CreateExpression will clear compiled expressions
if (conditionOperator == null) if (conditionOperator == null)
{ {
Operator = null; Operator = null;
CreateAccessors();
return; return;
} }
// No need to clear compiled expressions, without a left data model they are already null // No need to clear compiled expressions, without a left data model they are already null
if (LeftDataModel == null) if (LeftPath == null || !LeftPath.IsValid)
{ {
Operator = conditionOperator; Operator = conditionOperator;
return; return;
} }
Type leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!conditionOperator.SupportsType(leftType)) if (!conditionOperator.SupportsType(leftType))
{
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " + throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}"); $"it does not support left side type {leftType.Name}");
}
Operator = conditionOperator; Operator = conditionOperator;
CreateAccessors();
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Evaluate() public override bool Evaluate()
{ {
if (Operator == null || LeftSideAccessor == null || PredicateType != ProfileRightSideType.Static && RightSideAccessor == null) if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false; return false;
// Compare with a static value // Compare with a static value
if (PredicateType == ProfileRightSideType.Static) if (PredicateType == ProfileRightSideType.Static)
{ {
object leftSideValue = LeftSideAccessor(LeftDataModel); object? leftSideValue = LeftPath.GetValue();
if (leftSideValue.GetType().IsValueType && RightStaticValue == null) if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false; return false;
return Operator.Evaluate(leftSideValue, RightStaticValue); return Operator.Evaluate(leftSideValue, RightStaticValue);
} }
// Compare with dynamic values if (RightPath == null || !RightPath.IsValid)
if (PredicateType == ProfileRightSideType.Dynamic) return false;
return Operator.Evaluate(LeftSideAccessor(LeftDataModel), RightSideAccessor(RightDataModel));
return false; // Compare with dynamic values
return Operator.Evaluate(LeftPath.GetValue(), RightPath.GetValue());
}
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -224,68 +203,53 @@ namespace Artemis.Core
return false; return false;
} }
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPropertyPath} {Operator.Description} {RightPropertyPath}";
return $"[Static] {LeftPropertyPath} {Operator.Description} {RightStaticValue}";
}
internal override void Save() internal override void Save()
{ {
Entity.PredicateType = (int) PredicateType; Entity.PredicateType = (int) PredicateType;
Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
Entity.LeftPropertyPath = LeftPropertyPath;
Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; LeftPath?.Save();
Entity.RightPropertyPath = RightPropertyPath; Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; if (Operator != null)
Entity.OperatorType = Operator?.GetType().Name; {
Entity.OperatorPluginGuid = Operator.PluginInfo.Guid;
Entity.OperatorType = Operator.GetType().Name;
}
} }
internal void Initialize() internal void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side // Left side
if (Entity.LeftDataModelGuid != null) if (Entity.LeftPath != null)
{ LeftPath = new DataModelPath(null, Entity.LeftPath);
DataModel dataModel = DataModelStore.Get(Entity.LeftDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
}
// Operator // Operator
if (Entity.OperatorPluginGuid != null) if (Entity.OperatorPluginGuid != null)
{ {
ConditionOperator conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
if (conditionOperator != null) if (conditionOperator != null)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
// Right side dynamic // Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightDataModelGuid != null) if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{ RightPath = new DataModelPath(null, Entity.RightPath);
DataModel dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
// Right side static // Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
{
try try
{ {
if (LeftDataModel != null) if (LeftPath != null && LeftPath.IsValid)
{ {
// Use the left side type so JSON.NET has a better idea what to do // Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftSideType = LeftPath.GetPropertyType()!;
object rightSideValue; object? rightSideValue;
try try
{ {
@ -298,12 +262,12 @@ namespace Artemis.Core
rightSideValue = Activator.CreateInstance(leftSideType); rightSideValue = Activator.CreateInstance(leftSideType);
} }
UpdateRightSide(rightSideValue); UpdateRightSideStatic(rightSideValue);
} }
else else
{ {
// Hope for the best... // Hope for the best...
UpdateRightSide(JsonConvert.DeserializeObject(Entity.RightStaticValue)); UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
} }
} }
catch (JsonReaderException) catch (JsonReaderException)
@ -311,7 +275,6 @@ namespace Artemis.Core
// ignored // ignored
// TODO: Some logging would be nice // TODO: Some logging would be nice
} }
}
} }
internal override DataModelConditionPartEntity GetEntity() internal override DataModelConditionPartEntity GetEntity()
@ -319,119 +282,47 @@ namespace Artemis.Core
return Entity; return Entity;
} }
private void CreateAccessors()
{
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 == ProfileRightSideType.Dynamic && Operator.SupportsRightSide)
CreateDynamicAccessors();
else
CreateStaticExpression();
}
private void ValidateOperator() private void ValidateOperator()
{ {
if (LeftDataModel == null || Operator == null) if (LeftPath == null || !LeftPath.IsValid || Operator == null)
return; return;
Type leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!Operator.SupportsType(leftType)) if (!Operator.SupportsType(leftType))
Operator = null; Operator = null;
} }
private void ValidateRightSide() private void ValidateRightSide()
{ {
Type leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type? leftType = LeftPath?.GetPropertyType();
if (PredicateType == ProfileRightSideType.Dynamic) if (PredicateType == ProfileRightSideType.Dynamic)
{ {
if (RightDataModel == null) if (RightPath == null || !RightPath.IsValid)
return; return;
Type rightSideType = RightDataModel.GetTypeAtPath(RightPropertyPath); Type rightSideType = RightPath.GetPropertyType()!;
if (!leftSideType.IsCastableFrom(rightSideType)) if (leftType != null && !leftType.IsCastableFrom(rightSideType))
UpdateRightSide(null, null); UpdateRightSideDynamic(null);
} }
else else
{ {
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType())) if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType())))
UpdateRightSide(RightStaticValue); UpdateRightSideStatic(RightStaticValue);
else else
UpdateRightSide(null); UpdateRightSideStatic(null);
} }
} }
private void CreateDynamicAccessors()
{
if (LeftDataModel == null || RightDataModel == null || Operator == null)
return;
Expression leftSideAccessor = ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out ParameterExpression leftSideParameter);
Expression rightSideAccessor = ExpressionUtilities.CreateDataModelAccessor(RightDataModel, RightPropertyPath, "right", out ParameterExpression rightSideParameter);
// A conversion may be required if the types differ
// This can cause issues if the DataModelConditionOperator 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);
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, rightSideParameter).Compile();
}
private void CreateStaticExpression()
{
if (LeftDataModel == null || Operator == null)
return;
UnaryExpression leftSideAccessor = Expression.Convert(
ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out ParameterExpression leftSideParameter),
typeof(object)
);
// 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;
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = null;
}
#region Event handlers #region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.LeftDataModelGuid && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (LeftDataModel == e.Registration.DataModel)
{
LeftSideAccessor = null;
LeftDataModel = null;
}
if (RightDataModel == e.Registration.DataModel)
{
RightSideAccessor = null;
RightDataModel = null;
}
}
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{ {
ConditionOperator conditionOperator = e.Registration.ConditionOperator; ConditionOperator conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
{ {
if (e.Registration.ConditionOperator != Operator) if (e.Registration.ConditionOperator != Operator)
return; return;
@ -440,16 +331,5 @@ namespace Artemis.Core
} }
#endregion #endregion
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
} }
} }

View File

@ -0,0 +1,30 @@
using System;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
internal class ListPredicateWrapperDataModel<T> : ListPredicateWrapperDataModel
{
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
}
public abstract class ListPredicateWrapperDataModel : DataModel
{
internal ListPredicateWrapperDataModel()
{
PluginInfo = Constants.CorePluginInfo;
}
[DataModelIgnore]
public object? UntypedValue { get; set; }
public static ListPredicateWrapperDataModel Create(Type type)
{
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
if (instance == null)
throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel<T> for type {type.Name}");
return (ListPredicateWrapperDataModel) instance;
}
}
}

View File

@ -190,7 +190,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the parameter</param> /// <param name="dataModel">The data model of the parameter</param>
/// <param name="path">The path pointing to the parameter inside the data model</param> /// <param name="path">The path pointing to the parameter inside the data model</param>
public void UpdateParameter(DataModel dataModel, string path) public void UpdateParameter(DataModel? dataModel, string? path)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataBindingModifier"); throw new ObjectDisposedException("DataBindingModifier");

View File

@ -14,6 +14,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public class DataModelPath : IStorageModel, IDisposable public class DataModelPath : IStorageModel, IDisposable
{ {
private bool _disposed;
private readonly LinkedList<DataModelPathSegment> _segments; private readonly LinkedList<DataModelPathSegment> _segments;
private Expression<Func<object, object>>? _accessorLambda; private Expression<Func<object, object>>? _accessorLambda;
@ -21,13 +22,11 @@ namespace Artemis.Core
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing directly to the target /// Creates a new instance of the <see cref="DataModelPath" /> class pointing directly to the target
/// </summary> /// </summary>
/// <param name="target">The target at which this path starts</param> /// <param name="target">The target at which this path starts</param>
public DataModelPath(object target) public DataModelPath(DataModel target)
{ {
Target = target ?? throw new ArgumentNullException(nameof(target)); Target = target ?? throw new ArgumentNullException(nameof(target));
Path = ""; Path = "";
Entity = new DataModelPathEntity(); Entity = new DataModelPathEntity();
if (Target is DataModel dataModel)
DataModelGuid = dataModel.PluginInfo.Guid;
_segments = new LinkedList<DataModelPathSegment>(); _segments = new LinkedList<DataModelPathSegment>();
@ -41,13 +40,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="target">The target at which this path starts</param> /// <param name="target">The target at which this path starts</param>
/// <param name="path">A point-separated path</param> /// <param name="path">A point-separated path</param>
public DataModelPath(object target, string path) public DataModelPath(DataModel target, string path)
{ {
Target = target ?? throw new ArgumentNullException(nameof(target)); Target = target ?? throw new ArgumentNullException(nameof(target));
Path = path ?? throw new ArgumentNullException(nameof(path)); Path = path ?? throw new ArgumentNullException(nameof(path));
Entity = new DataModelPathEntity(); Entity = new DataModelPathEntity();
if (Target is DataModel dataModel)
DataModelGuid = dataModel.PluginInfo.Guid;
_segments = new LinkedList<DataModelPathSegment>(); _segments = new LinkedList<DataModelPathSegment>();
@ -56,9 +53,29 @@ namespace Artemis.Core
SubscribeToDataModelStore(); SubscribeToDataModelStore();
} }
internal DataModelPath(object target, DataModelPathEntity entity) /// <summary>
/// Creates a new instance of the <see cref="DataModelPath" /> class based on an existing path
/// </summary>
/// <param name="dataModelPath">The path to base the new instance on</param>
public DataModelPath(DataModelPath dataModelPath)
{ {
Target = target!; if (dataModelPath == null)
throw new ArgumentNullException(nameof(dataModelPath));
Target = dataModelPath.Target;
Path = dataModelPath.Path;
Entity = new DataModelPathEntity();
_segments = new LinkedList<DataModelPathSegment>();
Save();
Initialize();
SubscribeToDataModelStore();
}
internal DataModelPath(DataModel? target, DataModelPathEntity entity)
{
Target = target;
Path = entity.Path; Path = entity.Path;
Entity = entity; Entity = entity;
@ -72,14 +89,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the data model at which this path starts /// Gets the data model at which this path starts
/// </summary> /// </summary>
public object? Target { get; private set; } public DataModel? Target { get; private set; }
internal DataModelPathEntity Entity { get; }
/// <summary> /// <summary>
/// Gets the data model GUID of the <see cref="Target" /> if it is a <see cref="DataModel" /> /// Gets the data model GUID of the <see cref="Target" /> if it is a <see cref="DataModel" />
/// </summary> /// </summary>
public Guid? DataModelGuid { get; private set; } public Guid? DataModelGuid => Target?.PluginInfo.Guid;
/// <summary> /// <summary>
/// Gets the point-separated path associated with this <see cref="DataModelPath" /> /// Gets the point-separated path associated with this <see cref="DataModelPath" />
@ -99,7 +114,10 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a boolean indicating whether this data model path points to a list /// Gets a boolean indicating whether this data model path points to a list
/// </summary> /// </summary>
public bool PointsToList => Segments.LastOrDefault()?.GetPropertyType() != null && typeof(IList).IsAssignableFrom(Segments.LastOrDefault()?.GetPropertyType()); public bool PointsToList => Segments.LastOrDefault()?.GetPropertyType() != null &&
typeof(IList).IsAssignableFrom(Segments.LastOrDefault()?.GetPropertyType());
internal DataModelPathEntity Entity { get; }
internal Func<object, object>? Accessor { get; private set; } internal Func<object, object>? Accessor { get; private set; }
@ -108,6 +126,9 @@ namespace Artemis.Core
/// </summary> /// </summary>
public object? GetValue() public object? GetValue()
{ {
if (_disposed)
throw new ObjectDisposedException("DataModelPath");
if (_accessorLambda == null || Target == null) if (_accessorLambda == null || Target == null)
return null; return null;
@ -123,6 +144,9 @@ namespace Artemis.Core
/// <returns>If static, the property info. If dynamic, <c>null</c></returns> /// <returns>If static, the property info. If dynamic, <c>null</c></returns>
public PropertyInfo? GetPropertyInfo() public PropertyInfo? GetPropertyInfo()
{ {
if (_disposed)
throw new ObjectDisposedException("DataModelPath");
return Segments.LastOrDefault()?.GetPropertyInfo(); return Segments.LastOrDefault()?.GetPropertyInfo();
} }
@ -132,6 +156,9 @@ namespace Artemis.Core
/// <returns>If possible, the property type</returns> /// <returns>If possible, the property type</returns>
public Type? GetPropertyType() public Type? GetPropertyType()
{ {
if (_disposed)
throw new ObjectDisposedException("DataModelPath");
return Segments.LastOrDefault()?.GetPropertyType(); return Segments.LastOrDefault()?.GetPropertyType();
} }
@ -141,6 +168,9 @@ namespace Artemis.Core
/// <returns>If found, the data model property description</returns> /// <returns>If found, the data model property description</returns>
public DataModelPropertyAttribute? GetPropertyDescription() public DataModelPropertyAttribute? GetPropertyDescription()
{ {
if (_disposed)
throw new ObjectDisposedException("DataModelPath");
return Segments.LastOrDefault()?.GetPropertyDescription(); return Segments.LastOrDefault()?.GetPropertyDescription();
} }
@ -177,7 +207,9 @@ namespace Artemis.Core
for (int index = 0; index < segments.Length; index++) for (int index = 0; index < segments.Length; index++)
{ {
string identifier = segments[index]; string identifier = segments[index];
LinkedListNode<DataModelPathSegment> node = _segments.AddLast(new DataModelPathSegment(this, identifier, string.Join('.', segments.Take(index + 1)))); LinkedListNode<DataModelPathSegment> node = _segments.AddLast(
new DataModelPathSegment(this, identifier, string.Join('.', segments.Take(index + 1)))
);
node.Value.Node = node; node.Value.Node = node;
} }
} }
@ -215,32 +247,13 @@ namespace Artemis.Core
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
} }
#region Storage
/// <inheritdoc />
public void Load()
{
Path = Entity.Path;
DataModelGuid = Entity.DataModelGuid;
if (Target == null && Entity.DataModelGuid != null)
Target = DataModelStore.Get(Entity.DataModelGuid.Value);
}
/// <inheritdoc />
public void Save()
{
Entity.Path = Path;
Entity.DataModelGuid = DataModelGuid;
}
#endregion
#region IDisposable #region IDisposable
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
@ -249,11 +262,35 @@ namespace Artemis.Core
#endregion #endregion
#region Storage
/// <inheritdoc />
public void Load()
{
Path = Entity.Path;
if (Target == null && Entity.DataModelGuid != null)
Target = DataModelStore.Get(Entity.DataModelGuid.Value)?.DataModel;
}
/// <inheritdoc />
public void Save()
{
// Do not save an invalid state
if (!IsValid)
return;
Entity.Path = Path;
Entity.DataModelGuid = DataModelGuid;
}
#endregion
#region Event handlers #region Event handlers
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e) private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
{ {
if (e.Registration.DataModel.PluginInfo.Guid != DataModelGuid) if (e.Registration.DataModel.PluginInfo.Guid != Entity.DataModelGuid)
return; return;
Target = e.Registration.DataModel; Target = e.Registration.DataModel;
@ -262,7 +299,7 @@ namespace Artemis.Core
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e) private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
{ {
if (e.Registration.DataModel.PluginInfo.Guid != DataModelGuid) if (e.Registration.DataModel.PluginInfo.Guid != Entity.DataModelGuid)
return; return;
Target = null; Target = null;

View File

@ -137,10 +137,12 @@ namespace Artemis.Core.DataModelExpansions
return value as T; return value as T;
} }
internal bool ContainsPath(string path) internal bool ContainsPath(string? path)
{ {
if (path == null)
return false;
string[] parts = path.Split('.'); string[] parts = path.Split('.');
Type current = GetType(); Type? current = GetType();
foreach (string part in parts) foreach (string part in parts)
{ {
PropertyInfo? property = current?.GetProperty(part); PropertyInfo? property = current?.GetProperty(part);

View File

@ -18,9 +18,9 @@ namespace Artemis.Core
_logger.Warning( _logger.Warning(
exception, exception,
"Failed to deserialize display condition predicate {left} {operator} {right}", "Failed to deserialize display condition predicate {left} {operator} {right}",
dataModelConditionPredicate.Entity.LeftPropertyPath, dataModelConditionPredicate.Entity.LeftPath?.Path,
dataModelConditionPredicate.Entity.OperatorType, dataModelConditionPredicate.Entity.OperatorType,
dataModelConditionPredicate.Entity.RightPropertyPath dataModelConditionPredicate.Entity.RightPath?.Path
); );
} }
@ -29,9 +29,9 @@ namespace Artemis.Core
_logger.Warning( _logger.Warning(
exception, exception,
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
dataModelConditionPredicate.Entity.LeftPropertyPath, dataModelConditionPredicate.Entity.LeftPath?.Path,
dataModelConditionPredicate.Entity.OperatorType, dataModelConditionPredicate.Entity.OperatorType,
dataModelConditionPredicate.Entity.RightPropertyPath dataModelConditionPredicate.Entity.RightPath?.Path
); );
} }

View File

@ -11,9 +11,7 @@ namespace Artemis.Storage.Entities.Profile.Conditions
Children = new List<DataModelConditionPartEntity>(); Children = new List<DataModelConditionPartEntity>();
} }
public Guid? ListDataModelGuid { get; set; } public DataModelPathEntity ListPath { get; set; }
public string ListPropertyPath { get; set; }
public int ListOperator { get; set; } public int ListOperator { get; set; }
} }
} }

View File

@ -6,16 +6,15 @@ namespace Artemis.Storage.Entities.Profile.Conditions
public class DataModelConditionListPredicateEntity : DataModelConditionPartEntity public class DataModelConditionListPredicateEntity : DataModelConditionPartEntity
{ {
public int PredicateType { get; set; } public int PredicateType { get; set; }
public string LeftPropertyPath { get; set; }
public Guid? RightDataModelGuid { get; set; } public DataModelPathEntity LeftPath { get; set; }
public string RightPropertyPath { get; set; } public DataModelPathEntity RightPath { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves // Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; } public string RightStaticValue { get; set; }
public string OperatorType { get; set; } public string OperatorType { get; set; }
public Guid? OperatorPluginGuid { get; set; } public Guid? OperatorPluginGuid { get; set; }
} }
} }

View File

@ -6,16 +6,14 @@ namespace Artemis.Storage.Entities.Profile.Conditions
public class DataModelConditionPredicateEntity : DataModelConditionPartEntity public class DataModelConditionPredicateEntity : DataModelConditionPartEntity
{ {
public int PredicateType { get; set; } public int PredicateType { get; set; }
public Guid? LeftDataModelGuid { get; set; } public DataModelPathEntity LeftPath { get; set; }
public string LeftPropertyPath { get; set; } public DataModelPathEntity RightPath { get; set; }
public Guid? RightDataModelGuid { get; set; }
public string RightPropertyPath { get; set; }
public string OperatorType { get; set; } public string OperatorType { get; set; }
public Guid? OperatorPluginGuid { get; set; } public Guid? OperatorPluginGuid { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves // Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; } public string RightStaticValue { get; set; }
} }
} }

View File

@ -15,16 +15,12 @@ namespace Artemis.UI.Shared
else else
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter); direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
if (direction == Parameters.Normal) if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue))
{ value = null;
if (value == null)
return Visibility.Collapsed;
return Visibility.Visible;
}
if (value == null) if (direction == Parameters.Normal)
return Visibility.Visible; return value == null ? Visibility.Collapsed : Visibility.Visible;
return Visibility.Collapsed; return value == null ? Visibility.Visible : Visibility.Collapsed;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:shared="clr-namespace:Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared"
xmlns:input="clr-namespace:Artemis.UI.Shared.Input" xmlns:input="clr-namespace:Artemis.UI.Shared.Input"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance input:DataModelDynamicViewModel}"> d:DataContext="{d:DesignInstance input:DataModelDynamicViewModel}">
@ -30,7 +31,7 @@
<Button Background="{Binding ButtonBrush}" <Button Background="{Binding ButtonBrush}"
BorderBrush="{Binding ButtonBrush}" BorderBrush="{Binding ButtonBrush}"
Style="{StaticResource DataModelConditionButton}" Style="{StaticResource DataModelConditionButton}"
ToolTip="{Binding SelectedPropertyViewModel.DisplayPath}" ToolTip="{Binding DisplayPath}"
IsEnabled="{Binding IsEnabled}" IsEnabled="{Binding IsEnabled}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Click="PropertyButton_OnClick"> Click="PropertyButton_OnClick">
@ -50,11 +51,17 @@
</ContextMenu> </ContextMenu>
</Button.ContextMenu> </Button.ContextMenu>
<Grid> <Grid>
<TextBlock Text="{Binding SelectedPropertyViewModel.PropertyDescription.Name}" <Grid Visibility="{Binding IsValid,Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
Visibility="{Binding SelectedPropertyViewModel, Converter={StaticResource NullToVisibilityConverter}}" /> <TextBlock Text="{Binding DisplayValue}"
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock FontStyle="Italic"
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
<Run Text="« " /><Run Text="{Binding Placeholder}" /><Run Text=" »" />
</TextBlock>
</Grid>
<TextBlock FontStyle="Italic" <TextBlock FontStyle="Italic"
Visibility="{Binding SelectedPropertyViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}"> Visibility="{Binding IsValid, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<Run Text="« " /><Run Text="{Binding Placeholder}" /><Run Text=" »" /> « Invalid »
</TextBlock> </TextBlock>
</Grid> </Grid>
</Button> </Button>

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Linq;
using System.Timers; using System.Timers;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -19,12 +19,12 @@ namespace Artemis.UI.Shared.Input
private readonly Module _module; private readonly Module _module;
private readonly Timer _updateTimer; private readonly Timer _updateTimer;
private Brush _buttonBrush = new SolidColorBrush(Color.FromRgb(171, 71, 188)); private Brush _buttonBrush = new SolidColorBrush(Color.FromRgb(171, 71, 188));
private DataModelPath _dataModelPath;
private DataModelPropertiesViewModel _dataModelViewModel; private DataModelPropertiesViewModel _dataModelViewModel;
private Type[] _filterTypes;
private bool _isDataModelViewModelOpen; private bool _isDataModelViewModelOpen;
private bool _isEnabled = true; private bool _isEnabled = true;
private string _placeholder = "Select a property"; private string _placeholder = "Select a property";
private DataModelVisualizationViewModel _selectedPropertyViewModel;
private Type[] _filterTypes;
internal DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService) internal DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService)
{ {
@ -81,18 +81,31 @@ namespace Artemis.UI.Shared.Input
set => SetAndNotify(ref _isDataModelViewModelOpen, value); set => SetAndNotify(ref _isDataModelViewModelOpen, value);
} }
public DataModelVisualizationViewModel SelectedPropertyViewModel public DataModelPath DataModelPath
{ {
get => _selectedPropertyViewModel; get => _dataModelPath;
set => SetAndNotify(ref _selectedPropertyViewModel, value); private set
{
if (!SetAndNotify(ref _dataModelPath, value)) return;
NotifyOfPropertyChange(nameof(IsValid));
NotifyOfPropertyChange(nameof(DisplayValue));
NotifyOfPropertyChange(nameof(DisplayPath));
}
} }
public void PopulateSelectedPropertyViewModel(DataModel datamodel, string path) public bool IsValid => DataModelPath?.IsValid ?? true;
public string DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
public string DisplayPath
{ {
if (datamodel == null) get
SelectedPropertyViewModel = null; {
else if (DataModelPath == null)
SelectedPropertyViewModel = DataModelViewModel.GetChildByPath(datamodel.PluginInfo.Guid, path); return "Click to select a property";
if (!DataModelPath.IsValid)
return "Invalid path";
return string.Join(" ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier));
}
} }
public void ChangeDataModel(DataModelPropertiesViewModel dataModel) public void ChangeDataModel(DataModelPropertiesViewModel dataModel)
@ -106,6 +119,12 @@ namespace Artemis.UI.Shared.Input
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
} }
public void ChangeDataModelPath(DataModelPath dataModelPath)
{
DataModelPath?.Dispose();
DataModelPath = dataModelPath != null ? new DataModelPath(dataModelPath) : null;
}
private void Initialize() private void Initialize()
{ {
// Get the data models // Get the data models
@ -132,10 +151,18 @@ namespace Artemis.UI.Shared.Input
if (!(context is DataModelVisualizationViewModel selected)) if (!(context is DataModelVisualizationViewModel selected))
return; return;
SelectedPropertyViewModel = selected; ChangeDataModelPath(selected.DataModelPath);
OnPropertySelected(new DataModelInputDynamicEventArgs(selected)); OnPropertySelected(new DataModelInputDynamicEventArgs(DataModelPath));
} }
public void Dispose()
{
_updateTimer.Stop();
_updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
DataModelPath?.Dispose();
}
#region Events #region Events
@ -147,12 +174,5 @@ namespace Artemis.UI.Shared.Input
} }
#endregion #endregion
public void Dispose()
{
_updateTimer.Stop();
_updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
}
} }
} }

View File

@ -32,13 +32,7 @@
Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}"> Visibility="{Binding InputViewModel, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
<Grid> <Grid>
<StackPanel Visibility="{Binding Value, Converter={StaticResource NullToVisibilityConverter}}" Orientation="Horizontal"> <StackPanel Visibility="{Binding Value, Converter={StaticResource NullToVisibilityConverter}}" Orientation="Horizontal">
<TextBlock FontWeight="Light"
Text="{Binding TargetDescription.Prefix}"
Visibility="{Binding TargetDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}" />
<ContentControl s:View.Model="{Binding DisplayViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" /> <ContentControl s:View.Model="{Binding DisplayViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<TextBlock FontWeight="Light"
Text="{Binding TargetDescription.Affix}"
Visibility="{Binding TargetDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}" />
</StackPanel> </StackPanel>
<TextBlock FontStyle="Italic" Visibility="{Binding Value, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}"> <TextBlock FontStyle="Italic" Visibility="{Binding Value, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">

View File

@ -23,14 +23,15 @@ namespace Artemis.UI.Shared.Input
private object _value; private object _value;
private bool _isEnabled; private bool _isEnabled;
internal DataModelStaticViewModel(Type targetType, IDataModelUIService dataModelUIService) internal DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService)
{ {
_dataModelUIService = dataModelUIService; _dataModelUIService = dataModelUIService;
_rootView = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive); _rootView = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
TargetType = targetType; TargetType = targetType;
TargetDescription = targetDescription;
IsEnabled = TargetType != null; IsEnabled = TargetType != null;
DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), true); DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true);
if (_rootView != null) if (_rootView != null)
{ {
@ -117,7 +118,7 @@ namespace Artemis.UI.Shared.Input
public void UpdateTargetType(Type target) public void UpdateTargetType(Type target)
{ {
TargetType = target; TargetType = target;
DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), true); DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true);
IsEnabled = TargetType != null; IsEnabled = TargetType != null;
// If null, clear the input // If null, clear the input

View File

@ -1,5 +1,6 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
@ -10,11 +11,12 @@ namespace Artemis.UI.Shared
private int _index; private int _index;
private Type _listType; private Type _listType;
public DataModelListPropertiesViewModel(DataModel dataModel, object listItem) : base(null, null, null) public DataModelListPropertiesViewModel(Type listType) : base(null, null, null)
{ {
DataModel = dataModel; DataModel = ListPredicateWrapperDataModel.Create(listType);
ListType = listItem.GetType(); ListType = listType;
DisplayValue = listItem;
IsRootViewModel = false;
} }
public int Index public int Index
@ -35,18 +37,21 @@ namespace Artemis.UI.Shared
set => SetAndNotify(ref _displayValue, value); set => SetAndNotify(ref _displayValue, value);
} }
public DataModelVisualizationViewModel DisplayViewModel => Children.FirstOrDefault();
public override string DisplayPath => null; public override string DisplayPath => null;
public override void Update(IDataModelUIService dataModelUIService) public override void Update(IDataModelUIService dataModelUIService)
{ {
// Display value gets updated by parent, don't do anything if it is null ((ListPredicateWrapperDataModel) DataModel).UntypedValue = DisplayValue;
if (DisplayValue == null)
PopulateProperties(dataModelUIService);
if (DisplayViewModel == null)
return; return;
ListType = DisplayValue.GetType(); if (IsVisualizationExpanded && !DisplayViewModel.IsVisualizationExpanded)
PopulateProperties(dataModelUIService, DisplayValue); DisplayViewModel.IsVisualizationExpanded = IsVisualizationExpanded;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children) DisplayViewModel.Update(dataModelUIService);
dataModelVisualizationViewModel.Update(dataModelUIService);
} }
public override object GetCurrentValue() public override object GetCurrentValue()

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.Core.DataModelExpansions; using Artemis.Core;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
@ -9,19 +9,17 @@ namespace Artemis.UI.Shared
private int _index; private int _index;
private Type _listType; private Type _listType;
public DataModelListPropertyViewModel(DataModel dataModel, object listItem, DataModelDisplayViewModel displayViewModel) : base(null, null, null) public DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel) : base(null, null, null)
{ {
DataModel = dataModel; DataModel = ListPredicateWrapperDataModel.Create(listType);
ListType = listItem.GetType(); ListType = listType;
DisplayValue = listItem;
DisplayViewModel = displayViewModel; DisplayViewModel = displayViewModel;
} }
public DataModelListPropertyViewModel(DataModel dataModel, object listItem) : base(null, null, null) public DataModelListPropertyViewModel(Type listType) : base(null, null, null)
{ {
DataModel = dataModel; DataModel = ListPredicateWrapperDataModel.Create(listType);
ListType = listItem.GetType(); ListType = listType;
DisplayValue = listItem;
} }
public int Index public int Index
@ -47,8 +45,10 @@ namespace Artemis.UI.Shared
if (DisplayValue == null) if (DisplayValue == null)
return; return;
((ListPredicateWrapperDataModel)DataModel).UntypedValue = DisplayValue;
if (DisplayViewModel == null) if (DisplayViewModel == null)
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), true); DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
ListType = DisplayValue.GetType(); ListType = DisplayValue.GetType();
UpdateDisplayParameters(); UpdateDisplayParameters();

View File

@ -11,8 +11,7 @@ namespace Artemis.UI.Shared
{ {
private string _count; private string _count;
private IList _list; private IList _list;
private DataModelVisualizationViewModel _listTypePropertyViewModel;
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
{ {
ListChildren = new BindableCollection<DataModelVisualizationViewModel>(); ListChildren = new BindableCollection<DataModelVisualizationViewModel>();
@ -34,21 +33,19 @@ namespace Artemis.UI.Shared
public DataModelPropertiesViewModel GetListTypeViewModel(IDataModelUIService dataModelUIService) public DataModelPropertiesViewModel GetListTypeViewModel(IDataModelUIService dataModelUIService)
{ {
Type type = DataModelPath.GetPropertyType(); Type listType = DataModelPath.GetPropertyType()?.GenericTypeArguments[0];
if (type == null) if (listType == null)
return null; return null;
// Create a property VM describing the type of the list // Create a property VM describing the type of the list
DataModelVisualizationViewModel viewModel = CreateListChild(dataModelUIService, type.GenericTypeArguments[0]); DataModelVisualizationViewModel viewModel = CreateListChild(dataModelUIService, listType);
viewModel.Update(dataModelUIService);
// Put an empty value into the list type property view model // Put an empty value into the list type property view model
if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel) if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel)
{ {
dataModelListClassViewModel.DisplayValue = Activator.CreateInstance(dataModelListClassViewModel.ListType);
dataModelListClassViewModel.Update(dataModelUIService);
return dataModelListClassViewModel; return dataModelListClassViewModel;
} }
if (viewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel) if (viewModel is DataModelListPropertyViewModel dataModelListPropertyViewModel)
{ {
dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType); dataModelListPropertyViewModel.DisplayValue = Activator.CreateInstance(dataModelListPropertyViewModel.ListType);
@ -70,12 +67,15 @@ namespace Artemis.UI.Shared
return; return;
int index = 0; int index = 0;
foreach (object? item in List) foreach (object item in List)
{ {
if (item == null)
continue;
DataModelVisualizationViewModel child; DataModelVisualizationViewModel child;
if (ListChildren.Count <= index) if (ListChildren.Count <= index)
{ {
child = CreateListChild(dataModelUIService, item); child = CreateListChild(dataModelUIService, item.GetType());
ListChildren.Add(child); ListChildren.Add(child);
} }
else else
@ -102,20 +102,18 @@ namespace Artemis.UI.Shared
Count = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}"; Count = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
} }
protected DataModelVisualizationViewModel CreateListChild(IDataModelUIService dataModelUIService, object listItem) protected DataModelVisualizationViewModel CreateListChild(IDataModelUIService dataModelUIService, Type listType)
{ {
Type listType = listItem.GetType();
// If a display VM was found, prefer to use that in any case // If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType); DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
if (typeViewModel != null) if (typeViewModel != null)
return new DataModelListPropertyViewModel(DataModel, listItem, typeViewModel); return new DataModelListPropertyViewModel(listType, typeViewModel);
// For primitives, create a property view model, it may be null that is fine // For primitives, create a property view model, it may be null that is fine
if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string)) if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string))
return new DataModelListPropertyViewModel(DataModel, listItem); return new DataModelListPropertyViewModel(listType);
// For other value types create a child view model // For other value types create a child view model
if (listType.IsClass || listType.IsStruct()) if (listType.IsClass || listType.IsStruct())
return new DataModelListPropertiesViewModel(DataModel, listItem); return new DataModelListPropertiesViewModel(listType);
return null; return null;
} }

View File

@ -13,7 +13,7 @@ namespace Artemis.UI.Shared
public override void Update(IDataModelUIService dataModelUIService) public override void Update(IDataModelUIService dataModelUIService)
{ {
// Always populate properties // Always populate properties
PopulateProperties(dataModelUIService, null); PopulateProperties(dataModelUIService);
// Only update children if the parent is expanded // Only update children if the parent is expanded
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel) if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)

View File

@ -40,7 +40,7 @@ namespace Artemis.UI.Shared
if (DisplayViewModel == null) if (DisplayViewModel == null)
{ {
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DataModelPath.GetPropertyType(), true); DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DataModelPath.GetPropertyType(), PropertyDescription, true);
DisplayViewModel.PropertyDescription = DataModelPath.GetPropertyDescription(); DisplayViewModel.PropertyDescription = DataModelPath.GetPropertyDescription();
} }

View File

@ -35,7 +35,7 @@ namespace Artemis.UI.Shared
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription; PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription;
} }
public bool IsRootViewModel { get; } public bool IsRootViewModel { get; protected set; }
public DataModelPath DataModelPath { get; } public DataModelPath DataModelPath { get; }
public string Path => DataModelPath?.Path; public string Path => DataModelPath?.Path;
@ -81,7 +81,7 @@ namespace Artemis.UI.Shared
} }
} }
public virtual string DisplayPath => Path?.Replace(".", " "); public virtual string DisplayPath => string.Join(" ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier));
/// <summary> /// <summary>
/// Updates the datamodel and if in an parent, any children /// Updates the datamodel and if in an parent, any children
@ -125,7 +125,7 @@ namespace Artemis.UI.Shared
} }
// If the type couldn't be retrieved either way, assume false // If the type couldn't be retrieved either way, assume false
Type type = DataModelPath.GetPropertyType(); Type type = DataModelPath?.GetPropertyType();
if (type == null) if (type == null)
{ {
IsMatchingFilteredTypes = false; IsMatchingFilteredTypes = false;
@ -146,7 +146,7 @@ namespace Artemis.UI.Shared
return null; return null;
if (propertyPath == null) if (propertyPath == null)
return null; return null;
if (Path.StartsWith(propertyPath, StringComparison.OrdinalIgnoreCase)) if (Path != null && Path.StartsWith(propertyPath, StringComparison.OrdinalIgnoreCase))
return null; return null;
} }
@ -178,18 +178,12 @@ namespace Artemis.UI.Shared
return 0; return 0;
} }
internal void PopulateProperties(IDataModelUIService dataModelUIService, object overrideValue) internal void PopulateProperties(IDataModelUIService dataModelUIService)
{ {
if (IsRootViewModel && overrideValue == null) if (IsRootViewModel)
return; return;
Type modelType; Type modelType = Parent == null || Parent.IsRootViewModel ? DataModel.GetType() : DataModelPath.GetPropertyType();
if (overrideValue != null)
modelType = overrideValue.GetType();
else if (Parent.IsRootViewModel)
modelType = DataModel.GetType();
else
modelType = DataModelPath.GetPropertyType();
// Add missing static children // Add missing static children
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
@ -200,17 +194,13 @@ namespace Artemis.UI.Shared
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null) if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
continue; continue;
DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth(), overrideValue); DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null) if (child != null)
Children.Add(child); Children.Add(child);
} }
// Remove static children that should be hidden // Remove static children that should be hidden
ReadOnlyCollection<PropertyInfo> hiddenProperties; ReadOnlyCollection<PropertyInfo> hiddenProperties = DataModel.GetHiddenProperties();
if (overrideValue != null && overrideValue is DataModel overrideValueDataModel)
hiddenProperties = overrideValueDataModel.GetHiddenProperties();
else
hiddenProperties = DataModel.GetHiddenProperties();
foreach (PropertyInfo hiddenProperty in hiddenProperties) foreach (PropertyInfo hiddenProperty in hiddenProperties)
{ {
string childPath = AppendToPath(hiddenProperty.Name); string childPath = AppendToPath(hiddenProperty.Name);
@ -220,11 +210,7 @@ namespace Artemis.UI.Shared
} }
// Add missing dynamic children // Add missing dynamic children
object value; object value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue();
if (overrideValue != null)
value = overrideValue;
else
value = Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue();
if (value is DataModel dataModel) if (value is DataModel dataModel)
{ {
foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels) foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels)
@ -233,7 +219,7 @@ namespace Artemis.UI.Shared
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue; continue;
DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth(), overrideValue); DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null) if (child != null)
Children.Add(child); Children.Add(child);
} }
@ -245,12 +231,12 @@ namespace Artemis.UI.Shared
Children.RemoveRange(toRemoveDynamic); Children.RemoveRange(toRemoveDynamic);
} }
private DataModelVisualizationViewModel CreateChild(IDataModelUIService dataModelUIService, string path, int depth, object overrideValue) private DataModelVisualizationViewModel CreateChild(IDataModelUIService dataModelUIService, string path, int depth)
{ {
if (depth > MaxDepth) if (depth > MaxDepth)
return null; return null;
DataModelPath dataModelPath = new DataModelPath(overrideValue ?? DataModel, path); DataModelPath dataModelPath = new DataModelPath(DataModel, path);
if (!dataModelPath.IsValid) if (!dataModelPath.IsValid)
return null; return null;
@ -265,7 +251,7 @@ namespace Artemis.UI.Shared
return null; return null;
// If a display VM was found, prefer to use that in any case // If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType); DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription);
if (typeViewModel != null) if (typeViewModel != null)
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth}; return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
// For primitives, create a property view model, it may be null that is fine // For primitives, create a property view model, it may be null that is fine

View File

@ -1,14 +1,15 @@
using System; using System;
using Artemis.Core;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
{ {
public class DataModelInputDynamicEventArgs : EventArgs public class DataModelInputDynamicEventArgs : EventArgs
{ {
public DataModelVisualizationViewModel DataModelVisualizationViewModel { get; } public DataModelPath DataModelPath { get; }
public DataModelInputDynamicEventArgs(DataModelVisualizationViewModel dataModelVisualizationViewModel) public DataModelInputDynamicEventArgs(DataModelPath dataModelPath)
{ {
DataModelVisualizationViewModel = dataModelVisualizationViewModel; DataModelPath = dataModelPath;
} }
} }
} }

View File

@ -159,14 +159,24 @@ namespace Artemis.UI.Shared.Services
} }
} }
public DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, bool fallBackToDefault) public DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault)
{ {
lock (_registeredDataModelDisplays) lock (_registeredDataModelDisplays)
{ {
DataModelDisplayViewModel result;
DataModelVisualizationRegistration match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); DataModelVisualizationRegistration match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
if (match != null) if (match != null)
return (DataModelDisplayViewModel) match.PluginInfo.Kernel.Get(match.ViewModelType); result = (DataModelDisplayViewModel) match.PluginInfo.Kernel.Get(match.ViewModelType);
return !fallBackToDefault ? null : _kernel.Get<DefaultDataModelDisplayViewModel>(); else if (!fallBackToDefault)
result = null;
else
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
if (result != null)
result.PropertyDescription = description;
return result;
} }
} }
@ -199,9 +209,9 @@ namespace Artemis.UI.Shared.Services
return _kernel.Get<DataModelDynamicViewModel>(new ConstructorArgument("module", module)); return _kernel.Get<DataModelDynamicViewModel>(new ConstructorArgument("module", module));
} }
public DataModelStaticViewModel GetStaticInputViewModel(Type targetType) public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription)
{ {
return _kernel.Get<DataModelStaticViewModel>(new ConstructorArgument("targetType", targetType)); return _kernel.Get<DataModelStaticViewModel>(new ConstructorArgument("targetType", targetType), new ConstructorArgument("targetDescription", targetDescription));
} }
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue) private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue)
@ -219,7 +229,7 @@ namespace Artemis.UI.Shared.Services
IParameter[] parameters = new IParameter[] IParameter[] parameters = new IParameter[]
{ {
new ConstructorArgument("description", description), new ConstructorArgument("targetDescription", description),
new ConstructorArgument("initialValue", initialValue) new ConstructorArgument("initialValue", initialValue)
}; };
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.PluginInfo.Kernel.Get(registration.ViewModelType, parameters); DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.PluginInfo.Kernel.Get(registration.ViewModelType, parameters);

View File

@ -19,10 +19,10 @@ namespace Artemis.UI.Shared.Services
void RemoveDataModelInput(DataModelVisualizationRegistration registration); void RemoveDataModelInput(DataModelVisualizationRegistration registration);
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration); void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, bool fallBackToDefault = false); DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false);
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback); DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module); DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module);
DataModelStaticViewModel GetStaticInputViewModel(Type targetType); DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription);
} }
} }

View File

@ -6,9 +6,15 @@
xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}" PreviewTextInput="{s:Action NumberValidationTextBox}" LostFocus="{s:Action Submit}" Width="140"> <TextBox VerticalAlignment="Center"
Text="{Binding InputValue}"
PreviewTextInput="{s:Action NumberValidationTextBox}"
materialDesign:TextFieldAssist.SuffixText="{Binding TargetDescription.Affix}"
LostFocus="{s:Action Submit}"
Width="140">
<b:Interaction.Behaviors> <b:Interaction.Behaviors>
<shared:PutCursorAtEndTextBoxBehavior /> <shared:PutCursorAtEndTextBoxBehavior />
</b:Interaction.Behaviors> </b:Interaction.Behaviors>

View File

@ -6,9 +6,15 @@
xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}" PreviewTextInput="{s:Action NumberValidationTextBox}" LostFocus="{s:Action Submit}" Width="140"> <TextBox VerticalAlignment="Center"
Text="{Binding InputValue}"
PreviewTextInput="{s:Action NumberValidationTextBox}"
materialDesign:TextFieldAssist.SuffixText="{Binding TargetDescription.Suffix}"
LostFocus="{s:Action Submit}"
Width="140">
<b:Interaction.Behaviors> <b:Interaction.Behaviors>
<shared:PutCursorAtEndTextBoxBehavior /> <shared:PutCursorAtEndTextBoxBehavior />
</b:Interaction.Behaviors> </b:Interaction.Behaviors>

View File

@ -109,11 +109,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
// Remove VMs of effects no longer applied on the layer // Remove VMs of effects no longer applied on the layer
List<DataModelConditionViewModel> toRemove = Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList(); Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList());
// Using RemoveRange breaks our lovely animations
foreach (DataModelConditionViewModel DataModelConditionViewModel in toRemove)
Items.Remove(DataModelConditionViewModel);
List<DataModelConditionViewModel> viewModels = new List<DataModelConditionViewModel>();
foreach (DataModelConditionPart childModel in Model.Children) foreach (DataModelConditionPart childModel in Model.Children)
{ {
if (Items.Any(c => c.Model == childModel)) if (Items.Any(c => c.Model == childModel))
@ -122,21 +120,23 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
switch (childModel) switch (childModel)
{ {
case DataModelConditionGroup DataModelConditionGroup: case DataModelConditionGroup DataModelConditionGroup:
Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataModelConditionGroup, IsListGroup)); viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataModelConditionGroup, IsListGroup));
break; break;
case DataModelConditionList DataModelConditionListPredicate: case DataModelConditionList DataModelConditionListPredicate:
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(DataModelConditionListPredicate)); viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(DataModelConditionListPredicate));
break; break;
case DataModelConditionPredicate DataModelConditionPredicate: case DataModelConditionPredicate DataModelConditionPredicate:
if (!IsListGroup) if (!IsListGroup)
Items.Add(_dataModelConditionsVmFactory.DataModelConditionPredicateViewModel(DataModelConditionPredicate)); viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionPredicateViewModel(DataModelConditionPredicate));
break; break;
case DataModelConditionListPredicate DataModelConditionListPredicate: case DataModelConditionListPredicate DataModelConditionListPredicate:
if (IsListGroup) if (IsListGroup)
Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(DataModelConditionListPredicate)); viewModels.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(DataModelConditionListPredicate));
break; break;
} }
} }
if (viewModels.Any())
Items.AddRange(viewModels);
foreach (DataModelConditionViewModel childViewModel in Items) foreach (DataModelConditionViewModel childViewModel in Items)
childViewModel.Update(); childViewModel.Update();

View File

@ -79,18 +79,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public DelegateCommand SelectOperatorCommand { get; } public DelegateCommand SelectOperatorCommand { get; }
public void Dispose()
{
if (!_isPrimitiveList)
{
LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected;
LeftSideSelectionViewModel.Dispose();
}
DisposeRightSideDynamic();
DisposeRightSideStatic();
}
public override void Delete() public override void Delete()
{ {
base.Delete(); base.Delete();
@ -123,30 +111,37 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public override void Update() public override void Update()
{ {
Guid listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListDataModel.PluginInfo.Guid; Guid? listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid;
if (listDataModelGuid == null)
return;
if (!_isPrimitiveList) if (!_isPrimitiveList)
{ {
LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); // Lists use a different color
LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188)); LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188));
LeftSideSelectionViewModel.SelectedPropertyViewModel = LeftSideSelectionViewModel.DataModelViewModel.GetChildByPath( LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
listDataModelGuid, DataModelConditionListPredicate.LeftPropertyPath LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.LeftPath);
);
} }
Type leftSideType = _isPrimitiveList Type leftSideType = _isPrimitiveList
? DataModelConditionListPredicate.DataModelConditionList.ListType ? DataModelConditionListPredicate.DataModelConditionList.ListType
: LeftSideSelectionViewModel.SelectedPropertyViewModel?.DataModelPath?.GetPropertyType(); : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
// Get the supported operators // Get the supported operators
Operators.Clear(); Operators.Clear();
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object)));
if (DataModelConditionListPredicate.Operator == null) if (DataModelConditionListPredicate.Operator == null)
DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object))));
SelectedOperator = DataModelConditionListPredicate.Operator; SelectedOperator = DataModelConditionListPredicate.Operator;
if (SelectedOperator == null || !SelectedOperator.SupportsRightSide)
{
DisposeRightSideStatic();
DisposeRightSideDynamic();
}
// Ensure the right side has the proper VM // Ensure the right side has the proper VM
if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic || ListRightSideType type = DataModelConditionListPredicate.PredicateType;
DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList) if ((type == ListRightSideType.Dynamic || type == ListRightSideType.DynamicList) && SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideStatic(); DisposeRightSideStatic();
if (RightSideSelectionViewModel == null) if (RightSideSelectionViewModel == null)
@ -160,26 +155,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
} }
RightSideSelectionViewModel.FilterTypes = new[] {leftSideType}; RightSideSelectionViewModel.FilterTypes = new[] {leftSideType};
if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic) RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.RightPath);
{
RightSideSelectionViewModel.PopulateSelectedPropertyViewModel(
DataModelConditionListPredicate.RightDataModel,
DataModelConditionListPredicate.RightPropertyPath
);
}
else
{
RightSideSelectionViewModel.SelectedPropertyViewModel = RightSideSelectionViewModel.DataModelViewModel.GetChildByPath(
listDataModelGuid, DataModelConditionListPredicate.RightPropertyPath
);
}
} }
else else if (SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideDynamic(); DisposeRightSideDynamic();
if (RightSideInputViewModel == null) if (RightSideInputViewModel == null)
{ {
RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType); RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription());
RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue; RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue;
RightSideInputViewModel.ButtonBrush = (Brush) Application.Current.FindResource("PrimaryHueMidBrush"); RightSideInputViewModel.ButtonBrush = (Brush) Application.Current.FindResource("PrimaryHueMidBrush");
RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered; RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
@ -192,7 +175,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void ApplyLeftSide() public void ApplyLeftSide()
{ {
DataModelConditionListPredicate.UpdateLeftSide(LeftSideSelectionViewModel.SelectedPropertyViewModel.Path); DataModelConditionListPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
SelectedOperator = DataModelConditionListPredicate.Operator; SelectedOperator = DataModelConditionListPredicate.Operator;
@ -202,16 +185,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void ApplyRightSideDynamic() public void ApplyRightSideDynamic()
{ {
if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic) if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic)
{ DataModelConditionListPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath);
DataModelConditionListPredicate.UpdateRightSideDynamic(
RightSideSelectionViewModel.SelectedPropertyViewModel.DataModel,
RightSideSelectionViewModel.SelectedPropertyViewModel.Path
);
}
else if (DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList) else if (DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList)
{ DataModelConditionListPredicate.UpdateRightSideDynamicList(RightSideSelectionViewModel.DataModelPath);
DataModelConditionListPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.SelectedPropertyViewModel.Path);
}
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -235,10 +211,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
private DataModelVisualizationViewModel GetListDataModel() private DataModelVisualizationViewModel 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); DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true);
DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath( DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath(
DataModelConditionListPredicate.DataModelConditionList.ListDataModel.PluginInfo.Guid, DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid.Value,
DataModelConditionListPredicate.DataModelConditionList.ListPropertyPath DataModelConditionListPredicate.DataModelConditionList.ListPath.Path
); );
return listDataModel.GetListTypeViewModel(_dataModelUIService); return listDataModel.GetListTypeViewModel(_dataModelUIService);
@ -287,5 +266,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
RightSideSelectionViewModel = null; RightSideSelectionViewModel = null;
} }
} }
public void Dispose()
{
if (!_isPrimitiveList)
{
LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected;
LeftSideSelectionViewModel.Dispose();
}
DisposeRightSideDynamic();
DisposeRightSideStatic();
}
} }
} }

View File

@ -96,10 +96,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void ApplyList() public void ApplyList()
{ {
DataModelConditionList.UpdateList( DataModelConditionList.UpdateList(TargetSelectionViewModel.DataModelPath);
TargetSelectionViewModel.SelectedPropertyViewModel.DataModel,
TargetSelectionViewModel.SelectedPropertyViewModel.Path
);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -107,15 +104,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public override void Update() public override void Update()
{ {
TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataModelConditionList.ListDataModel, DataModelConditionList.ListPropertyPath); TargetSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath);
NotifyOfPropertyChange(nameof(SelectedListOperator)); NotifyOfPropertyChange(nameof(SelectedListOperator));
// Remove VMs of effects no longer applied on the layer // Remove VMs of effects no longer applied on the layer
List<DataModelConditionViewModel> toRemove = Items.Where(c => !DataModelConditionList.Children.Contains(c.Model)).ToList(); Items.RemoveRange(Items.Where(c => !DataModelConditionList.Children.Contains(c.Model)).ToList());
// Using RemoveRange breaks our lovely animations
foreach (DataModelConditionViewModel conditionViewModel in toRemove) if (DataModelConditionList.ListPath == null || !DataModelConditionList.ListPath.IsValid)
Items.Remove(conditionViewModel); return;
List<DataModelConditionViewModel> viewModels = new List<DataModelConditionViewModel>();
foreach (DataModelConditionPart childModel in Model.Children) foreach (DataModelConditionPart childModel in Model.Children)
{ {
if (Items.Any(c => c.Model == childModel)) if (Items.Any(c => c.Model == childModel))
@ -125,8 +123,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, true); DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, true);
viewModel.IsRootGroup = true; viewModel.IsRootGroup = true;
Items.Add(viewModel); viewModels.Add(viewModel);
} }
if (viewModels.Any())
Items.AddRange(viewModels);
foreach (DataModelConditionViewModel childViewModel in Items) foreach (DataModelConditionViewModel childViewModel in Items)
childViewModel.Update(); childViewModel.Update();

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract;
using Artemis.UI.Shared; using Artemis.UI.Shared;
@ -116,26 +117,23 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public override void Update() public override void Update()
{ {
LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray();
LeftSideSelectionViewModel.PopulateSelectedPropertyViewModel( LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath);
DataModelConditionPredicate.LeftDataModel,
DataModelConditionPredicate.LeftPropertyPath Type leftSideType = LeftSideSelectionViewModel.DataModelPath?.GetPropertyType();
);
Type leftSideType = LeftSideSelectionViewModel.SelectedPropertyViewModel?.DataModelPath?.GetPropertyType();
// Get the supported operators // Get the supported operators
Operators.Clear(); Operators.Clear();
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object)));
if (DataModelConditionPredicate.Operator == null) if (DataModelConditionPredicate.Operator == null)
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object))));
SelectedOperator = DataModelConditionPredicate.Operator; SelectedOperator = DataModelConditionPredicate.Operator;
if (!SelectedOperator.SupportsRightSide) if (SelectedOperator == null || !SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideStatic(); DisposeRightSideStatic();
DisposeRightSideDynamic(); DisposeRightSideDynamic();
} }
// Ensure the right side has the proper VM // Ensure the right side has the proper VM
Type targetType = LeftSideSelectionViewModel?.SelectedPropertyViewModel?.DataModelPath?.GetPropertyType();
if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic && SelectedOperator.SupportsRightSide) if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic && SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideStatic(); DisposeRightSideStatic();
@ -146,34 +144,28 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected; RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected;
} }
RightSideSelectionViewModel.PopulateSelectedPropertyViewModel( RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.RightPath);
DataModelConditionPredicate.RightDataModel, RightSideSelectionViewModel.FilterTypes = new[] {leftSideType};
DataModelConditionPredicate.RightPropertyPath
);
RightSideSelectionViewModel.FilterTypes = new[] {targetType};
} }
else if (SelectedOperator.SupportsRightSide) else if (SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideDynamic(); DisposeRightSideDynamic();
if (RightSideInputViewModel == null) if (RightSideInputViewModel == null)
{ {
RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(targetType); RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription());
RightSideInputViewModel.ButtonBrush = (Brush) Application.Current.FindResource("PrimaryHueMidBrush"); RightSideInputViewModel.ButtonBrush = (Brush) Application.Current.FindResource("PrimaryHueMidBrush");
RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered; RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered;
} }
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue; RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
if (RightSideInputViewModel.TargetType != targetType) if (RightSideInputViewModel.TargetType != leftSideType)
RightSideInputViewModel.UpdateTargetType(targetType); RightSideInputViewModel.UpdateTargetType(leftSideType);
} }
} }
public void ApplyLeftSide() public void ApplyLeftSide()
{ {
DataModelConditionPredicate.UpdateLeftSide( DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath);
LeftSideSelectionViewModel.SelectedPropertyViewModel.DataModel,
LeftSideSelectionViewModel.SelectedPropertyViewModel.Path
);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
SelectedOperator = DataModelConditionPredicate.Operator; SelectedOperator = DataModelConditionPredicate.Operator;
@ -182,10 +174,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void ApplyRightSideDynamic() public void ApplyRightSideDynamic()
{ {
DataModelConditionPredicate.UpdateRightSide( DataModelConditionPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath);
RightSideSelectionViewModel.SelectedPropertyViewModel.DataModel,
RightSideSelectionViewModel.SelectedPropertyViewModel.Path
);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -193,7 +182,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public void ApplyRightSideStatic(object value) public void ApplyRightSideStatic(object value)
{ {
DataModelConditionPredicate.UpdateRightSide(value); DataModelConditionPredicate.UpdateRightSideStatic(value);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -212,7 +201,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
ApplyLeftSide(); ApplyLeftSide();
} }
private void RightSideOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e) private void RightSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{ {
ApplyRightSideDynamic(); ApplyRightSideDynamic();
} }

View File

@ -27,7 +27,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
ActiveItem.Update(); ActiveItem.Update();
ActiveItem.Updated += ActiveItemOnUpdated; ActiveItem.Updated += ActiveItemOnUpdated;
ValueViewModel = dataModelUIService.GetStaticInputViewModel(typeof(TProperty)); ValueViewModel = dataModelUIService.GetStaticInputViewModel(typeof(TProperty), null);
ValueViewModel.ValueUpdated += ValueViewModelOnValueUpdated; ValueViewModel.ValueUpdated += ValueViewModelOnValueUpdated;
ValueViewModel.Value = DataBindingCondition.Value; ValueViewModel.Value = DataBindingCondition.Value;
} }

View File

@ -42,8 +42,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType)));
EasingViewModels = new BindableCollection<TimelineEasingViewModel>(); EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true); TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true); TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
Initialize(); Initialize();
} }

View File

@ -105,7 +105,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{ {
Modifier.UpdateParameter(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.Path); Modifier.UpdateParameter(e.DataModelPath.Target, e.DataModelPath.Path);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
@ -145,7 +145,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
else else
{ {
DynamicSelectionViewModel = null; DynamicSelectionViewModel = null;
StaticInputViewModel = _dataModelUIService.GetStaticInputViewModel(Modifier.ModifierType.ParameterType ?? sourceType); StaticInputViewModel = _dataModelUIService.GetStaticInputViewModel(Modifier.ModifierType.ParameterType ?? sourceType, null);
if (StaticInputViewModel != null) if (StaticInputViewModel != null)
StaticInputViewModel.ValueUpdated += StaticInputViewModelOnValueUpdated; StaticInputViewModel.ValueUpdated += StaticInputViewModelOnValueUpdated;
} }
@ -166,10 +166,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
SelectedModifierType = Modifier.ModifierType; SelectedModifierType = Modifier.ModifierType;
// Parameter // Parameter
if (DynamicSelectionViewModel != null) throw new NotImplementedException();
DynamicSelectionViewModel.PopulateSelectedPropertyViewModel(Modifier.ParameterDataModel, Modifier.ParameterPropertyPath); // if (DynamicSelectionViewModel != null)
else if (StaticInputViewModel != null) // DynamicSelectionViewModel.PopulateSelectedPropertyViewModel(Modifier.ParameterDataModel, Modifier.ParameterPropertyPath);
StaticInputViewModel.Value = Modifier.ParameterStaticValue; // else if (StaticInputViewModel != null)
// StaticInputViewModel.Value = Modifier.ParameterStaticValue;
} }
private void ExecuteSelectModifierTypeCommand(object context) private void ExecuteSelectModifierTypeCommand(object context)

View File

@ -56,7 +56,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
public void Update() public void Update()
{ {
TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath); throw new NotImplementedException();
// TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath);
TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()}; TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()};
CanAddModifier = DirectDataBinding.SourceDataModel != null; CanAddModifier = DirectDataBinding.SourceDataModel != null;
@ -65,7 +66,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
public object GetTestValue() public object GetTestValue()
{ {
return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); throw new NotImplementedException();
// return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue();
} }
#region IDisposable #region IDisposable
@ -112,7 +114,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{ {
DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.Path); throw new NotImplementedException();
// DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.Path);
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();

View File

@ -117,7 +117,7 @@
<ContentControl Grid.Column="2" s:View.Model="{Binding DisplayViewModel}" FontFamily="Consolas" /> <ContentControl Grid.Column="2" s:View.Model="{Binding DisplayViewModel}" FontFamily="Consolas" />
</Grid> </Grid>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListPropertiesViewModel}" ItemsSource="{Binding Children}"> <HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListPropertiesViewModel}" ItemsSource="{Binding DisplayViewModel.Children}">
<TextBlock> <TextBlock>
<Run>List item [</Run><Run Text="{Binding Index, Mode=OneWay}" /><Run>]</Run> <Run>List item [</Run><Run Text="{Binding Index, Mode=OneWay}" /><Run>]</Run>
</TextBlock> </TextBlock>

View File

@ -50,7 +50,7 @@
&lt;/Entry.SortBy&gt;&#xD; &lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD; &lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD; &lt;Kind Is="Method" /&gt;&#xD;
@ -97,7 +97,7 @@
&lt;/Entry.Match&gt;&#xD; &lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD; &lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD; &lt;Kind Is="Method" /&gt;&#xD;
@ -112,7 +112,7 @@
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD; &lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="Default Pattern"&gt;&#xD; &lt;TypePattern DisplayName="Default Pattern"&gt;&#xD;
&lt;Entry DisplayName="Public Delegates" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Public Delegates"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD; &lt;Access Is="Public" /&gt;&#xD;
@ -123,7 +123,7 @@
&lt;Name /&gt;&#xD; &lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD; &lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public Enums" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Public Enums"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD; &lt;Access Is="Public" /&gt;&#xD;
@ -191,7 +191,7 @@
&lt;Kind Is="Type" /&gt;&#xD; &lt;Kind Is="Type" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD; &lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface Implementations" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Interface Implementations"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD; &lt;Kind Is="Member" /&gt;&#xD;
@ -206,4 +206,10 @@
&lt;/Patterns&gt;</s:String> &lt;/Patterns&gt;</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">ERROR</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">ERROR</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">ERROR</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>