using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared.DefaultTypes.DataModel.Display; using Artemis.UI.Shared.Input; using Ninject; using Ninject.Parameters; namespace Artemis.UI.Shared.Services { internal class DataModelUIService : IDataModelUIService { private readonly IDataModelService _dataModelService; private readonly IDataModelVmFactory _dataModelVmFactory; private readonly IKernel _kernel; private readonly List _registeredDataModelDisplays; private readonly List _registeredDataModelEditors; public DataModelUIService(IDataModelService dataModelService, IDataModelVmFactory dataModelVmFactory, IKernel kernel) { _dataModelService = dataModelService; _dataModelVmFactory = dataModelVmFactory; _kernel = kernel; _registeredDataModelEditors = new List(); _registeredDataModelDisplays = new List(); RegisteredDataModelEditors = new ReadOnlyCollection(_registeredDataModelEditors); RegisteredDataModelDisplays = new ReadOnlyCollection(_registeredDataModelDisplays); } public IReadOnlyCollection RegisteredDataModelEditors { get; } public IReadOnlyCollection RegisteredDataModelDisplays { get; } public DataModelPropertiesViewModel GetMainDataModelVisualization() { DataModelPropertiesViewModel viewModel = new(null, null, null); foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion).OrderBy(d => d.DataModelDescription.Name)) viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion))); // Update to populate children viewModel.Update(this, null); viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null); return viewModel; } public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization) { List disabledChildren = mainDataModelVisualization.Children .Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled) .ToList(); foreach (DataModelVisualizationViewModel child in disabledChildren) mainDataModelVisualization.Children.Remove(child); foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name)) { if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion)) { mainDataModelVisualization.Children.Add( new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion)) ); } } mainDataModelVisualization.Update(this, null); } public DataModelPropertiesViewModel? GetPluginDataModelVisualization(List modules, bool includeMainDataModel) { DataModelPropertiesViewModel root; // This will contain any modules that are always available if (includeMainDataModel) root = GetMainDataModelVisualization(); else { root = new DataModelPropertiesViewModel(null, null, null); root.UpdateRequested += (sender, args) => root.Update(this, null); } foreach (Module module in modules) { DataModel? dataModel = _dataModelService.GetPluginDataModel(module); if (dataModel == null) continue; root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel))); } if (!root.Children.Any()) return null; // Update to populate children root.Update(this, null); return root; } public DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection? compatibleConversionTypes = null) where T : DataModelInputViewModel { compatibleConversionTypes ??= new List(); Type viewModelType = typeof(T); lock (_registeredDataModelEditors) { Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0]; DataModelVisualizationRegistration? existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { if (existing.Plugin != plugin) throw new ArtemisSharedUIException($"Cannot register data model input for type {supportedType.Name} because an editor was already" + $" registered by {existing.Plugin}"); return existing; } _kernel.Bind(viewModelType).ToSelf(); // Create the registration DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType) { // Apply the compatible conversion types to the registration CompatibleConversionTypes = compatibleConversionTypes }; _registeredDataModelEditors.Add(registration); return registration; } } public DataModelVisualizationRegistration RegisterDataModelDisplay(Plugin plugin) where T : DataModelDisplayViewModel { Type viewModelType = typeof(T); lock (_registeredDataModelDisplays) { Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0]; DataModelVisualizationRegistration? existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { if (existing.Plugin != plugin) throw new ArtemisSharedUIException($"Cannot register data model display for type {supportedType.Name} because an editor was already" + $" registered by {existing.Plugin}"); return existing; } _kernel.Bind(viewModelType).ToSelf(); DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType); _registeredDataModelDisplays.Add(registration); return registration; } } public void RemoveDataModelInput(DataModelVisualizationRegistration registration) { lock (_registeredDataModelEditors) { if (_registeredDataModelEditors.Contains(registration)) { registration.Unsubscribe(); _registeredDataModelEditors.Remove(registration); _kernel.Unbind(registration.ViewModelType); } } } public void RemoveDataModelDisplay(DataModelVisualizationRegistration registration) { lock (_registeredDataModelDisplays) { if (_registeredDataModelDisplays.Contains(registration)) { registration.Unsubscribe(); _registeredDataModelDisplays.Remove(registration); _kernel.Unbind(registration.ViewModelType); } } } public DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault) { lock (_registeredDataModelDisplays) { DataModelDisplayViewModel? result; DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); if (match != null) { // If this ever happens something is likely wrong with the plugin unload detection if (match.Plugin.Kernel == null) throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin"); result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType); } else if (!fallBackToDefault) result = null; else result = _kernel.Get(); if (result != null) result.PropertyDescription = description; return result; } } public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action updateCallback) { lock (_registeredDataModelEditors) { // Prefer a VM that natively supports the type DataModelVisualizationRegistration? 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 != null && d.CompatibleConversionTypes.Contains(propertyType)); // Lastly try getting an enum VM if the provided type is an enum if (match == null && propertyType.IsEnum) match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == typeof(Enum)); if (match != null) { DataModelInputViewModel viewModel = InstantiateDataModelInputViewModel(match, description, initialValue); viewModel.UpdateCallback = updateCallback; return viewModel; } return null; } } public DataModelDynamicViewModel GetDynamicSelectionViewModel(Module? module) { return _dataModelVmFactory.DataModelDynamicViewModel(module == null ? new List() : new List {module}); } public DataModelDynamicViewModel GetDynamicSelectionViewModel(List modules) { return _dataModelVmFactory.DataModelDynamicViewModel(modules); } public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription) { return _dataModelVmFactory.DataModelStaticViewModel(targetType, targetDescription); } private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue) { // 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); IParameter[] parameters = { new ConstructorArgument("targetDescription", description), new ConstructorArgument("initialValue", initialValue) }; // If this ever happens something is likely wrong with the plugin unload detection if (registration.Plugin.Kernel == null) throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin"); DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters); viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes; return viewModel; } } }