diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index f6226c541..67ac8df30 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -28,7 +28,7 @@ namespace Artemis.Core public static readonly PluginInfo CorePluginInfo = new PluginInfo {Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core"}; /// - /// A read-only collection containing all primitive number types + /// A read-only collection containing all primitive numeric types /// public static IReadOnlyCollection NumberTypes = new List { @@ -44,5 +44,30 @@ namespace Artemis.Core typeof(double), typeof(decimal) }; + + /// + /// A read-only collection containing all primitive integral numeric types + /// + public static IReadOnlyCollection IntegralNumberTypes = new List + { + typeof(sbyte), + typeof(byte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong) + }; + + /// + /// A read-only collection containing all primitive floating-point numeric types + /// + public static IReadOnlyCollection FloatNumberTypes = new List + { + typeof(float), + typeof(double), + typeof(decimal) + }; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs index e8eeb4ee5..dba5e36f8 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs @@ -34,14 +34,6 @@ namespace Artemis.Core.Models.Profile.Conditions /// public abstract string Icon { get; } - /// - /// Creates a binary expression comparing two types - /// - /// The type of parameter passed to the left side of the expression - /// The type of parameter passed to the right side of the expression - /// - public abstract BinaryExpression CreateExpression(Type leftSideType, Type rightSideType); - internal void Register(PluginInfo pluginInfo, IDataModelService dataModelService) { if (_registered) @@ -78,5 +70,13 @@ namespace Artemis.Core.Models.Profile.Conditions return true; return CompatibleTypes.Any(t => t.IsCastableFrom(type)); } + + /// + /// Creates a binary expression comparing two types + /// + /// The parameter on the left side of the expression + /// The parameter on the right side of the expression + /// + public abstract BinaryExpression CreateExpression(Expression leftSide, Expression rightSide); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 3d54a64fc..b260f3449 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -1,5 +1,9 @@ using System; +using System.Linq; +using System.Linq.Expressions; using Artemis.Core.Models.Profile.Conditions.Abstract; +using Artemis.Core.Plugins.Abstract.DataModels; +using Artemis.Core.Services.Interfaces; namespace Artemis.Core.Models.Profile.Conditions { @@ -10,10 +14,88 @@ namespace Artemis.Core.Models.Profile.Conditions public Guid LeftDataModelGuid { get; set; } public string LeftPropertyPath { get; set; } - + public Guid RightDataModelGuid { get; set; } public string RightPropertyPath { get; set; } + + // TODO: Implement type-checking or perhaps convert it here public object RightStaticValue { get; set; } + + public Expression> DynamicConditionLambda { get; private set; } + public Func CompiledDynamicConditionLambda { get; private set; } + public Expression> StaticConditionLambda { get; private set; } + public Func CompiledStaticConditionLambda { get; private set; } + + public void CreateExpression(IDataModelService dataModelService) + { + if (PredicateType == PredicateType.Dynamic) + CreateDynamicExpression(dataModelService); + else + CreateStaticExpression(dataModelService); + } + + private void CreateDynamicExpression(IDataModelService dataModelService) + { + if (LeftDataModelGuid == Guid.Empty || string.IsNullOrWhiteSpace(LeftPropertyPath)) + return; + if (RightDataModelGuid == Guid.Empty || string.IsNullOrWhiteSpace(RightPropertyPath)) + return; + + var leftDataModel = dataModelService.GetPluginDataModelByGuid(LeftDataModelGuid); + if (leftDataModel == null) + return; + + var rightDataModel = dataModelService.GetPluginDataModelByGuid(RightDataModelGuid); + if (rightDataModel == null) + return; + + var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel"); + var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate( + Expression.Convert(leftSideParameter, leftDataModel.GetType()), // Cast to the appropriate type + Expression.Property + ); + var rightSideParameter = Expression.Parameter(typeof(DataModel), "rightDataModel"); + var rightSideAccessor = RightPropertyPath.Split('.').Aggregate( + Expression.Convert(rightSideParameter, rightDataModel.GetType()), // Cast to the appropriate type + Expression.Property + ); + + // A conversion may be required if the types differ + // This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here + if (rightSideAccessor.Type != leftSideAccessor.Type) + rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); + + var dynamicConditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor); + + DynamicConditionLambda = Expression.Lambda>(dynamicConditionExpression, leftSideParameter, rightSideParameter); + CompiledDynamicConditionLambda = DynamicConditionLambda.Compile(); + } + + private void CreateStaticExpression(IDataModelService dataModelService) + { + if (LeftDataModelGuid == Guid.Empty || string.IsNullOrWhiteSpace(LeftPropertyPath)) + return; + + var leftDataModel = dataModelService.GetPluginDataModelByGuid(LeftDataModelGuid); + if (leftDataModel == null) + return; + + var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel"); + var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate( + Expression.Convert(leftSideParameter, leftDataModel.GetType()), // Cast to the appropriate type + Expression.Property + ); + + // 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; + + var rightSideConstant = Expression.Constant(RightStaticValue); + var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant); + + StaticConditionLambda = Expression.Lambda>(conditionExpression, leftSideParameter); + CompiledStaticConditionLambda = StaticConditionLambda.Compile(); + } } public enum PredicateType diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs index 9f2b3cee9..4868ad996 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Equals"; public override string Icon => "Equal"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.Equal(leftSideParameter, rightSideParameter); + return Expression.Equal(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs index a8fa3adb4..b8618a2b6 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Is greater than"; public override string Icon => "GreaterThan"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.GreaterThan(leftSideParameter, rightSideParameter); + return Expression.GreaterThan(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs index 9b428c0cf..d4955ee34 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Is greater than or equal to"; public override string Icon => "GreaterThanOrEqual"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.GreaterThanOrEqual(leftSideParameter, rightSideParameter); + return Expression.GreaterThanOrEqual(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs index 2a9ba1310..fc083f400 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Is less than"; public override string Icon => "LessThan"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.LessThan(leftSideParameter, rightSideParameter); + return Expression.LessThan(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs index d01fe5d1b..ebc9dd871 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Is less than or equal to"; public override string Icon => "LessThanOrEqual"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.LessThanOrEqual(leftSideParameter, rightSideParameter); + return Expression.LessThanOrEqual(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs index 00d83e516..f4dc78c71 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs @@ -11,11 +11,9 @@ namespace Artemis.Core.Models.Profile.Conditions.Operators public override string Description => "Does not equal"; public override string Icon => "NotEqualVariant"; - public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) { - var leftSideParameter = Expression.Parameter(leftSideType, "a"); - var rightSideParameter = Expression.Parameter(rightSideType, "b"); - return Expression.NotEqual(leftSideParameter, rightSideParameter); + return Expression.NotEqual(leftSide, rightSide); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/DataModelService.cs b/src/Artemis.Core/Services/DataModelService.cs index 548257500..60b8b48c2 100644 --- a/src/Artemis.Core/Services/DataModelService.cs +++ b/src/Artemis.Core/Services/DataModelService.cs @@ -93,6 +93,15 @@ namespace Artemis.Core.Services return null; } + public DataModel GetPluginDataModelByGuid(Guid pluginGuid) + { + var pluginInfo = _pluginService.GetAllPluginInfo().FirstOrDefault(i => i.Guid == pluginGuid); + if (pluginInfo == null || !pluginInfo.Enabled) + return null; + + return GetPluginDataModel(pluginInfo.Instance); + } + public bool GetPluginExtendsDataModel(Plugin plugin) { if (plugin is Module module) diff --git a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs index 24b6d5ab4..e6190ba40 100644 --- a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs @@ -31,6 +31,12 @@ namespace Artemis.Core.Services.Interfaces /// Should be a module with a data model or a data model expansion DataModel GetPluginDataModel(Plugin plugin); + /// + /// If found, returns the data model of the provided plugin + /// + /// Should be a module with a data model or a data model expansion + DataModel GetPluginDataModelByGuid(Guid pluginGuid); + /// /// Determines whether the given plugin expands the main data model /// diff --git a/src/Artemis.Storage/Entities/Profile/DisplayConditionPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/DisplayConditionPredicateEntity.cs new file mode 100644 index 000000000..eb4e70d0c --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DisplayConditionPredicateEntity.cs @@ -0,0 +1,16 @@ +using System; + +namespace Artemis.Storage.Entities.Profile +{ + public class DisplayConditionPredicateEntity + { + public Guid LeftDataModelGuid { get; set; } + public string LeftPropertyPath { get; set; } + + public Guid RightDataModelGuid { get; set; } + public string RightPropertyPath { get; set; } + + // Stored as a string to be able to control serialization and deserialization ourselves + public string RightStaticValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml index 7e3811d08..a1944d9a1 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml @@ -49,6 +49,7 @@ LostFocus="InputLostFocus" KeyDown="InputKeyDown" Visibility="Collapsed" - RequestBringIntoView="Input_OnRequestBringIntoView" /> + RequestBringIntoView="Input_OnRequestBringIntoView" + PreviewTextInput="Input_PreviewTextInput"/> \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs index 7d2ba3c10..f1bd0b456 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs @@ -1,6 +1,8 @@ using System; using System.ComponentModel; +using System.Globalization; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -81,7 +83,7 @@ namespace Artemis.UI.Shared.Controls private void InputMouseDown(object sender, MouseButtonEventArgs e) { e.Handled = true; - + _startValue = Value; ((IInputElement) sender).CaptureMouse(); _mouseDragStartPoint = e.GetPosition((IInputElement) sender); @@ -109,7 +111,7 @@ namespace Artemis.UI.Shared.Controls return; e.Handled = true; - + if (!_calledDragStarted) { OnDragStarted(); @@ -162,6 +164,13 @@ namespace Artemis.UI.Shared.Controls e.Handled = true; } + private void Input_PreviewTextInput(object sender, TextCompositionEventArgs e) + { + var seperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + var regex = new Regex("^[" + seperator + "][0-9]+$|^[0-9]*[" + seperator + "]{0,1}[0-9]*$"); + e.Handled = !regex.IsMatch(e.Text); + } + private static decimal UltimateRoundingFunction(decimal amountToRound, decimal nearstOf, decimal fairness) { return Math.Floor(amountToRound / nearstOf + fairness) * nearstOf; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs index fd4fe4b38..24931edc3 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs @@ -46,24 +46,6 @@ namespace Artemis.UI.Shared.DataModelVisualization OnCancel(); UpdateCallback(InputValue, false); } - - /// - /// May be called to convert an object to one of the types defined in CompatibleConversionTypes - /// - /// The object to convert, will always be of a type contained in CompatibleConversionTypes - /// The converted value - protected virtual T ConvertToSupportedType(object source) - { - return default; - } - - internal override object InternalConvertToSupportedType(object source) - { - if (CompatibleConversionTypes != null && CompatibleConversionTypes.Contains(source.GetType())) - return ConvertToSupportedType(source); - - throw new ArtemisSharedUIException($"Cannot convert source of type {source.GetType().Name} because the data model input view model does not support it."); - } } /// @@ -80,7 +62,7 @@ namespace Artemis.UI.Shared.DataModelVisualization internal Action UpdateCallback { get; set; } /// - /// Gets the types this input view model can support through type conversion + /// Gets the types this input view model can support through type conversion. This list is defined when registering the view model. /// internal IReadOnlyCollection CompatibleConversionTypes { get; set; } @@ -124,7 +106,5 @@ namespace Artemis.UI.Shared.DataModelVisualization protected virtual void OnCancel() { } - - internal abstract object InternalConvertToSupportedType(object source); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs index af9ba6a06..6eadcd763 100644 --- a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs @@ -64,8 +64,10 @@ namespace Artemis.UI.Shared.Services return _dataModelService.GetPluginExtendsDataModel(plugin); } - public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes) where T : DataModelInputViewModel + public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel { + if (compatibleConversionTypes == null) + compatibleConversionTypes = new List(); var viewModelType = typeof(T); lock (_registeredDataModelEditors) { @@ -158,7 +160,12 @@ namespace Artemis.UI.Shared.Services { lock (_registeredDataModelEditors) { + // Prefer a VM that natively supports the type var match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType); + // Fall back on a VM that supports the type through conversion + if (match == null) + match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes.Contains(propertyType)); + if (match != null) { var viewModel = InstantiateDataModelInputViewModel(match, description, initialValue); @@ -173,8 +180,14 @@ namespace Artemis.UI.Shared.Services private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue) { // The view models expecting value types shouldn't be given null, avoid that - if (initialValue == null && registration.SupportedType.IsValueType) - initialValue = Activator.CreateInstance(registration.SupportedType); + if (registration.SupportedType.IsValueType) + { + if (initialValue == null) + initialValue = Activator.CreateInstance(registration.SupportedType); + } + // This assumes the type can be converted, that has been checked when the VM was created + if (initialValue != null && initialValue.GetType() != registration.SupportedType) + initialValue = Convert.ChangeType(initialValue, registration.SupportedType); var parameters = new IParameter[] { diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 06062b3bb..be17d3cbb 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Windows; +using System.Windows.Documents; +using System.Windows.Markup; using System.Windows.Threading; using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Ninject; @@ -34,8 +37,6 @@ namespace Artemis.UI protected override void Launch() { - var test = new DisplayCondition(); - StartupArguments = Args.ToList(); var logger = Kernel.Get(); @@ -52,6 +53,9 @@ namespace Artemis.UI throw; } + // Ensure WPF uses the right culture in binding converters + FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); + // Create and bind the root view, this is a tray icon so don't show it with the window manager Execute.OnUIThread(() => viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel)); diff --git a/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputViewModel.cs b/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputViewModel.cs index e93d731c4..6a73466b5 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputViewModel.cs +++ b/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputViewModel.cs @@ -1,8 +1,8 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; using System.Windows.Input; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.UI.Shared.DataModelVisualization; -using FluentValidation.TestHelper; namespace Artemis.UI.DataModelVisualization.Input { @@ -14,7 +14,8 @@ namespace Artemis.UI.DataModelVisualization.Input public void NumberValidationTextBox(object sender, TextCompositionEventArgs e) { - var regex = new Regex("^[.|,][0-9]+$|^[0-9]*[.|,]{0,1}[0-9]*$"); + var seperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + var regex = new Regex("^["+ seperator + "][0-9]+$|^[0-9]*["+ seperator + "]{0,1}[0-9]*$"); e.Handled = !regex.IsMatch(e.Text); } } diff --git a/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputViewModel.cs b/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputViewModel.cs index 170244d4f..ccd780489 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputViewModel.cs +++ b/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputViewModel.cs @@ -1,5 +1,4 @@ -using System; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using System.Windows.Input; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.UI.Shared.DataModelVisualization; @@ -17,10 +16,5 @@ namespace Artemis.UI.DataModelVisualization.Input var regex = new Regex("[^0-9]+"); e.Handled = regex.IsMatch(e.Text); } - - protected override int ConvertToSupportedType(object source) - { - return Convert.ToInt32(source); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index c21bb75b6..4ff37d6c9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -33,12 +33,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private List _supportedInputTypes; - public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, - DisplayConditionViewModel parent, - IProfileEditorService profileEditorService, - IDataModelVisualizationService dataModelVisualizationService, - IDataModelService dataModelService, - IEventAggregator eventAggregator) + public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent, IProfileEditorService profileEditorService, + IDataModelVisualizationService dataModelVisualizationService, IDataModelService dataModelService, IEventAggregator eventAggregator) : base(displayConditionPredicate, parent) { _profileEditorService = profileEditorService; @@ -54,9 +50,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions } public DisplayConditionPredicate DisplayConditionPredicate => (DisplayConditionPredicate) Model; - public DelegateCommand SelectLeftPropertyCommand { get; } - public DelegateCommand SelectRightPropertyCommand { get; } - public DelegateCommand SelectOperatorCommand { get; } public bool ShowRightSidePropertySelection => DisplayConditionPredicate.PredicateType == PredicateType.Dynamic; public bool CanActivateRightSideInputViewModel => SelectedLeftSideProperty?.PropertyInfo != null; @@ -108,26 +101,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions set => SetAndNotify(ref _operators, value); } - public void Handle(MainWindowKeyEvent message) - { - if (RightSideInputViewModel == null) - return; - - if (!message.KeyDown && message.EventArgs.Key == Key.Escape) - RightSideInputViewModel.Cancel(); - if (!message.KeyDown && message.EventArgs.Key == Key.Enter) - RightSideInputViewModel.Submit(); - } - - public void Handle(MainWindowMouseEvent message) - { - if (RightSideInputViewModel == null) - return; - - if (message.Sender is FrameworkElement frameworkElement && !frameworkElement.IsDescendantOf(RightSideInputViewModel.View)) - RightSideInputViewModel.Submit(); - } - public void Initialize() { Task.Run(() => @@ -142,7 +115,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions } // Determine which types are currently supported - _supportedInputTypes = _dataModelVisualizationService.RegisteredDataModelEditors.Select(e => e.SupportedType).ToList(); + var editors = _dataModelVisualizationService.RegisteredDataModelEditors; + _supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); + _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); IsInitialized = true; Update(); @@ -164,38 +139,51 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions : null; var leftSideType = SelectedLeftSideProperty?.PropertyInfo?.PropertyType; - if (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic) - { - // Right side may only select properties matching the left side - if (SelectedLeftSideProperty != null) - RightSideDataModel.ApplyTypeFilter(true, leftSideType); - else - RightSideDataModel.ApplyTypeFilter(true); - - // Determine the right side property - if (DisplayConditionPredicate.RightPropertyPath != null) - { - // Ensure the right side property still matches the left side type, else set it to null - var selectedProperty = RightSideDataModel.GetChildByPath(DisplayConditionPredicate.RightDataModelGuid, DisplayConditionPredicate.RightPropertyPath); - SelectedRightSideProperty = selectedProperty.IsMatchingFilteredTypes ? selectedProperty : null; - } - else - SelectedRightSideProperty = null; - } - else - { - if (DisplayConditionPredicate.RightStaticValue != null && DisplayConditionPredicate.RightStaticValue.GetType() != leftSideType) - DisplayConditionPredicate.RightStaticValue = null; - } - // Get the supported operators Operators = _dataModelService.GetCompatibleConditionOperators(leftSideType); if (DisplayConditionPredicate.Operator == null || !DisplayConditionPredicate.Operator.SupportsType(leftSideType)) DisplayConditionPredicate.Operator = Operators.FirstOrDefault(o => o.SupportsType(leftSideType)); + if (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic) + UpdateRightSideDynamic(leftSideType); + else + UpdateRightSideStatic(leftSideType); + + DisplayConditionPredicate.CreateExpression(_dataModelService); NotifyOfPropertyChange(nameof(DisplayConditionPredicate)); } + #region Dynamic input + + private void UpdateRightSideDynamic(Type leftSideType) + { + // Right side may only select properties matching the left side + if (SelectedLeftSideProperty != null) + RightSideDataModel.ApplyTypeFilter(true, leftSideType); + else + RightSideDataModel.ApplyTypeFilter(true); + + // Determine the right side property + if (DisplayConditionPredicate.RightPropertyPath != null) + { + // Ensure the right side property still matches the left side type, else set it to null + var selectedProperty = RightSideDataModel.GetChildByPath(DisplayConditionPredicate.RightDataModelGuid, DisplayConditionPredicate.RightPropertyPath); + SelectedRightSideProperty = selectedProperty.IsMatchingFilteredTypes ? selectedProperty : null; + } + else + SelectedRightSideProperty = null; + } + + #endregion + + #region Static input + + private void UpdateRightSideStatic(Type leftSideType) + { + if (DisplayConditionPredicate.RightStaticValue != null && DisplayConditionPredicate.RightStaticValue.GetType() != leftSideType) + DisplayConditionPredicate.RightStaticValue = null; + } + public void ActivateRightSideInputViewModel() { if (SelectedLeftSideProperty?.PropertyInfo == null) @@ -215,7 +203,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { if (isSubmitted) { - DisplayConditionPredicate.RightStaticValue = value; + var leftSideType = SelectedLeftSideProperty?.PropertyInfo?.PropertyType; + + if (value != null && value.GetType() != leftSideType) + DisplayConditionPredicate.RightStaticValue = Convert.ChangeType(value, leftSideType); + else + DisplayConditionPredicate.RightStaticValue = value; Update(); } @@ -224,6 +217,34 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions _eventAggregator.Unsubscribe(this); } + public void Handle(MainWindowKeyEvent message) + { + if (RightSideInputViewModel == null) + return; + + if (!message.KeyDown && message.EventArgs.Key == Key.Escape) + RightSideInputViewModel.Cancel(); + if (!message.KeyDown && message.EventArgs.Key == Key.Enter) + RightSideInputViewModel.Submit(); + } + + public void Handle(MainWindowMouseEvent message) + { + if (RightSideInputViewModel == null) + return; + + if (message.Sender is FrameworkElement frameworkElement && !frameworkElement.IsDescendantOf(RightSideInputViewModel.View)) + RightSideInputViewModel.Submit(); + } + + #endregion + + #region Commands + + public DelegateCommand SelectLeftPropertyCommand { get; } + public DelegateCommand SelectRightPropertyCommand { get; } + public DelegateCommand SelectOperatorCommand { get; } + private void ExecuteSelectLeftProperty(object context) { if (!(context is DataModelVisualizationViewModel vm)) @@ -252,5 +273,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions DisplayConditionPredicate.Operator = displayConditionOperator; Update(); } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 878dcc776..b6cc80620 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -37,9 +37,9 @@ namespace Artemis.UI.Services if (_registeredBuiltInDataModelInputs) return; - _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.NumberTypes); - _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.NumberTypes); - _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.NumberTypes); + _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, null); + _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.IntegralNumberTypes); + _dataModelVisualizationService.RegisterDataModelInput(Constants.CorePluginInfo, Constants.FloatNumberTypes); _registeredBuiltInDataModelInputs = true; }