using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using InABox.Clients; using InABox.Core; using InABox.Wpf; using InABox.Core.Reports; using Syncfusion.Data.Extensions; using System.Diagnostics.CodeAnalysis; using System.Data; using System.Windows.Media; namespace InABox.DynamicGrid; [Caption("Set Default Column Selections")] public class CanSetDefaultColumns : EnabledSecurityDescriptor { } [LibraryInitializer] public static class DynamicGridUtils { private static IEnumerable? _allm2mtypes; private static IEnumerable? _allm2mpages; private static IEnumerable? _allo2mtypes; private static IEnumerable? _allo2mpages; private static IEnumerable? _allcepages; private static IEnumerable? _alleltypes; private static Dictionary> _onetomanypages = new(); private static Dictionary> _manytomanytomanypages = new(); private static Dictionary>> _enclosedlistpages = new(); private static Dictionary> _customeditorpages = new(); // HACK: These are really dumb public static Action? PreviewReport { get; set; } public static Action? PrintMenu { get; set; } public static readonly MainResources Resources = new(); public static void RegisterClasses() { // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name; // AssemblyName assemblyName = new AssemblyName(assyname); // AppDomain appDomain = Thread.GetDomain(); // // String assyFile = String.Format("{0}.dll", assemblyName.Name); // String path = ""; // if (Assembly.GetEntryAssembly() != null) // { // path = Path.Combine( // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) // ); // } // else // { // path = Path.Combine( // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location) // ); // } // // if (!Directory.Exists(path)) // Directory.CreateDirectory(path); // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path); // // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true); // // if (_allm2mtypes == null) // { // _allm2mtypes = CoreUtils.TypeList( // AppDomain.CurrentDomain.GetAssemblies(), // x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)) // ); // } // // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)))); // // foreach (var map in maps) // { // var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))); // Type entity = intf.GenericTypeArguments.First(); // Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity); // TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class); // tbService.SetParent(basetype); // Type final = tbService.CreateType(); // } // // try // { // assemblyBuilder.Save(assyFile); // } // catch (Exception e) // { // Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); // } } #region Pages public static IEnumerable GetManyToManyTypes(Type type) { _allm2mtypes ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)) ); return _allm2mtypes.Where(x => x.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments[0] == type) ); } public static void LoadManyToManyPages(Type type, DynamicEditorPages pages) { if (!_manytomanytomanypages.TryGetValue(type, out var pageTypes)) { pageTypes = new List(); var maps = GetManyToManyTypes(type); foreach (var map in maps) { if (ClientFactory.IsSupported(map)) { _allm2mpages ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) ) ); var subtypes = _allm2mpages.Where( x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) && i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type)) ); if (subtypes.Any()) { pageTypes.Add(subtypes.First()); } else { pageTypes.Add(typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type)); } } } _manytomanytomanypages[type] = pageTypes.ToArray(); } pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!)); } public static IEnumerable GetOneToManyTypes(Type type) { _allo2mtypes ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && x.GetCustomAttribute() == null) ); return _allo2mtypes .Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && i.GenericTypeArguments.Contains(type))) .OrderBy(x => x.EntityName()); } public static void LoadOneToManyPages(Type type, DynamicEditorPages pages) { if (!_onetomanypages.TryGetValue(type, out var pageTypes)) { pageTypes = new List(); var maps = GetOneToManyTypes(type); foreach (var map in maps) { if (ClientFactory.IsSupported(map)) { _allo2mpages ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>) ) ); var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>) && i.GenericTypeArguments.First().Equals(type) && i.GenericTypeArguments.Last().Equals(map) ) ); if (subtypes.Any()) { pageTypes.Add(subtypes.First()); } else { pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map)); } } } _onetomanypages[type] = pageTypes.ToArray(); } pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!)); } public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages) { if (!_customeditorpages.TryGetValue(type, out var pageTypes)) { _allcepages ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) ) ); pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) && i.GenericTypeArguments.First().Equals(type))).ToArray(); _customeditorpages[type] = pageTypes; } pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!)); } public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages) { if (!_enclosedlistpages.TryGetValue(type, out var pageTypes)) { pageTypes = new List>(); foreach (var property in type.GetProperties()) { if (property.PropertyType.GetInterfaces().Contains(typeof(IList))) { var curtype = property.PropertyType; var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault(); while (gentype == null && curtype?.BaseType != null) { curtype = curtype.BaseType; gentype = curtype?.GetGenericArguments().FirstOrDefault(); } if (gentype != null) if (gentype.IsSubclassOf(typeof(BaseObject))) { var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor); if (editor == null || !(editor is NullEditor)) { _alleltypes ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>) ) ); var subtypes = _alleltypes.Where( x => x.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>) && i.GenericTypeArguments.First().Equals(type) && i.GenericTypeArguments.Last().Equals(gentype) ) ); if (subtypes.Any()) { pageTypes.Add(new(subtypes.First(), property)); } else { subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => (i.GenericTypeArguments.LastOrDefault()?.Equals(gentype) == true))); if (subtypes.Any()) { pageTypes.Add(new(subtypes.First().MakeGenericType(type), property)); } else { try { pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property)); } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } } } } } } _enclosedlistpages[type] = pageTypes.ToArray(); } pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!)); } #endregion #region Columns public static Columns LoadEditorColumns(Columns additional) { var result = new Columns().Default( ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties, ColumnType.IncludeEditable); foreach (var col in additional.Items) if (!result.Items.Any(x => string.Equals(x.Property, col.Property))) result.Add(col.Property); foreach (var col in result.Items) { var prop = DatabaseSchema.Property(typeof(T), col.Property); if (prop?.Editor is DataLookupEditor dataLookup) { foreach (var lookupColumn in LookupFactory.DefineLookupFilterColumns(typeof(T), prop.Name).ColumnNames()) { result.Add(lookupColumn); } } } return result; } #endregion #region Editor Values public static Dictionary UpdateEditorValue(BaseObject[] items, string name, object? value) { Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value)); var sw = new Stopwatch(); var changes = new Dictionary(); var props = DatabaseSchema.Properties(items.First().GetType()).ToArray(); foreach (var item in items) { //Dictionary previous = new Dictionary(); var previous = CoreUtils.GetValues(item, props); //if (item.OriginalValues != null) //{ // foreach (var key in item.OriginalValues.Keys) // previous[key] = item.OriginalValues[key]; //} var prop = DatabaseSchema.Property(item.GetType(), name); if (prop is CustomProperty) { if (!item.HasOriginalValue(name)) item.SetOriginalValue(name, item.UserProperties[name]); item.UserProperties[name] = value; } else { if (prop != null) try { var getter = prop.Getter(); var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name); item.OnPropertyChanged(name, oldvalue, value); var setter = prop.Setter(); if (setter != null && value != null) setter.Invoke(item, value); else CoreUtils.SetPropertyValue(item, name, value); } catch (Exception) { Logger.Send(LogType.Error, "", string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value)); } } var current = CoreUtils.GetValues(item, props); CoreUtils.MergeChanges(previous, current, changes); } return changes; } public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary changes) { var results = UpdateEditorValue(items, name, value); foreach (var key in results.Keys) changes[key] = results[key]; } #endregion #region Dynamic Grid Creation public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType) { var type = FindDynamicGrid(gridType, entityType); return (Activator.CreateInstance(type) as IDynamicGrid) ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType)); } public static DynamicGrid CreateDynamicGrid(Type gridType) where TEntity : BaseObject, new() { var type = FindDynamicGrid(gridType, typeof(TEntity)); return (Activator.CreateInstance(type) as DynamicGrid) ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType)); } private static Dictionary _dynamicGrids = new(); public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid) { if (!_dynamicGrids.TryGetValue(gridType, out var grids)) { grids = CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), myType => myType.IsClass && !myType.IsAbstract && !myType.IsGenericType && myType.IsAssignableTo(typeof(IDynamicGrid)) && !myType.IsAssignableTo(typeof(ISpecificGrid)) ).ToArray(); _dynamicGrids[gridType] = grids; } grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray(); var entityGrids = grids.Where(x => { var baseGrid = x.GetSuperclassDefinition(typeof(DynamicGrid<>)); return baseGrid?.GenericTypeArguments[0] == entityType; }).ToList(); var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList(); if (defaults.Count > 0) { if (defaults.Count > 1) { Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}"); } grid = defaults.First(); return true; } grid = entityGrids.FirstOrDefault(); return grid is not null; } public static Type FindDynamicGrid(Type gridType, Type entityType) { if(TryFindDynamicGrid(gridType, entityType, out var grid)) { return grid; } return gridType.MakeGenericType(entityType); } public static Window CreateGridWindow(string title, IDynamicGrid dynamicGrid) { dynamicGrid.Margin = new Thickness(5); var window = new ThemableWindow { Title = title, Content = dynamicGrid }; dynamicGrid.Refresh(true, true); return window; } public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null) { gridType ??= typeof(DynamicGrid<>); var grid = CreateDynamicGrid(gridType, entityType); return CreateGridWindow(title, grid); } public static Window CreateGridWindow(string title) where TEntity : BaseObject where TGrid : IDynamicGrid { return CreateGridWindow(title, typeof(TEntity), typeof(TGrid)); } public static Window CreateGridWindow(string title) where TEntity : BaseObject { return CreateGridWindow(title, typeof(TEntity)); } #endregion #region Editing BaseObject /// /// Edit (using ) a list of s. Use for objects not saved in the database. /// /// /// /// /// /// public static bool EditObjects(T[] items, Func? pageDataHandler = null, bool preloadPages = false, Action>? customiseGrid = null) where T : BaseObject, new() { var grid = new DynamicItemsListGrid(); customiseGrid?.Invoke(grid); return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages); } /// /// Edit (using ) a s. Use for objects not saved in the database. /// /// /// /// /// /// public static bool EditObject(T item, Func? pageDataHandler = null, bool preloadPages = false, Action>? customiseGrid = null) where T : BaseObject, new() { var grid = new DynamicItemsListGrid(); customiseGrid?.Invoke(grid); return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages); } /// /// Edit (using a grid sourced with ) a . /// /// /// /// /// /// /// public static bool EditEntity(T item, Func? pageDataHandler = null, bool preloadPages = false, Action>? customiseGrid = null) where T : Entity, new() { var grid = CreateDynamicGrid(typeof(DynamicGrid<>)); customiseGrid?.Invoke(grid); return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages); } /// /// Edit (using a grid sourced with ) a list of . /// /// /// /// /// /// /// public static bool EditEntities(T[] items, Func? pageDataHandler = null, bool preloadPages = false, Action>? customiseGrid = null) where T : Entity, new() { var grid = CreateDynamicGrid(typeof(DynamicGrid<>)); customiseGrid?.Invoke(grid); return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages); } #endregion #region Drag + Drop public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? ""; /// /// Try to get data dragged from a from a , returning /// if data was present. /// /// public static bool TryGetDropData( DragEventArgs e, [NotNullWhen(true)] out Type? type, [NotNullWhen(true)] out CoreTable? table) { if (e.Data.GetDataPresent(DragFormat)) { var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat; if (data is not null) { table = new CoreTable(); foreach (var column in data.Table.Columns) { if (column is DataColumn dataColumn) { table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType }); } } foreach (var row in data.Table.Rows) { if (row is DataRow dataRow) { var coreRow = table.NewRow(); coreRow.LoadValues(dataRow.ItemArray); table.Rows.Add(coreRow); } } type = data.Entity; return true; } } table = null; type = null; return false; } #endregion #region Style public static Brush SelectionBackground { get; set; } = new SolidColorBrush(Colors.Black); public static Brush SelectionForeground { get; set; } = new SolidColorBrush(Colors.Silver); public static Brush FilterBackground { get; set; } = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9)); #endregion public static void PopulateFormMenu( ItemsControl menu, Guid entityID, Func loadEntity, bool editOnAdd = false, DynamicFormEditWindow.CustomiseDynamicFormEditWindow? customiseEditor = null) where TEntityForm : EntityForm, new() where TEntity : Entity where TEntityLink : IEntityLink, new() { var task = Task.Run(() => { return new Client().Query( new Filter(x => x.Parent.ID).IsEqualTo(entityID), null).Rows.Select(x => x.ToObject()).ToList(); }); var addForm = new MenuItem { Header = "Add Form" }; addForm.Click += (o, e) => { var entity = loadEntity(); var filter = LookupFactory.DefineChildFilter(new TEntity[] { entity }) ?? LookupFactory.DefineLookupFilter(x => x.Form, Array.Empty()); var select = new MultiSelectDialog( filter, LookupFactory.DefineLookupColumns(x => x.Form).Add(x => x.Description), false); if(select.ShowDialog() == true) { var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject(); if(digitalForm is not null) { var form = new TEntityForm { Description = digitalForm.Description }; form.Parent.ID = entityID; form.Form.ID = digitalForm.ID; if (editOnAdd) { if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel, customise: customiseEditor)) { dataModel.Update(null); } } else { new Client().Save(form, "Added by user"); } } }; }; var manageForms = new MenuItem { Header = "Manage Forms..." }; manageForms.Click += (o, e) => { var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" }; var grid = new DynamicEntityFormGrid(loadEntity()); grid.Refresh(true, true); grid.Margin = new Thickness(5); window.Content = grid; window.ShowDialog(); }; menu.Items.Add(addForm); menu.Items.Add(new Separator()); menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false }); menu.Items.Add(new Separator()); menu.Items.Add(manageForms); task.ContinueWith((task) => { var entityForms = task.Result; menu.Items.Clear(); menu.Items.Add(addForm); menu.Items.Add(new Separator()); if (entityForms.Any()) { foreach (var entityForm in entityForms) { var description = entityForm.Description; if (string.IsNullOrWhiteSpace(description)) { description = entityForm.Form.Description; } var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" }; formItem.Click += (o, e) => { if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel, customise: customiseEditor)) { dataModel.Update(null); } }; menu.Items.Add(formItem); } } else { menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false }); } menu.Items.Add(new Separator()); menu.Items.Add(manageForms); }, TaskScheduler.FromCurrentSynchronizationContext()); } }