DynamicVariableGrid.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using InABox.Core;
  8. using InABox.WPF;
  9. using NPOI.Util.Collections;
  10. namespace InABox.DynamicGrid
  11. {
  12. internal class DynamicVariableGrid : DynamicOneToManyGrid<DigitalForm, DigitalFormVariable>
  13. {
  14. private bool ShowHidden = false;
  15. private Button ShowHiddenButton;
  16. private Button HideButton;
  17. public DynamicVariableGrid()
  18. {
  19. Options.Add(DynamicGridOption.MultiSelect);
  20. ShowHiddenButton = AddButton("Show Hidden", null, ToggleHidden_Click);
  21. HideButton = AddButton("Hide Variable", null, Hide_Click);
  22. HideButton.IsEnabled = false;
  23. HiddenColumns.Add(x => x.Hidden);
  24. }
  25. public DigitalFormVariable? GetVariable(string code)
  26. {
  27. return Items.Where(x => x.Code == code).FirstOrDefault();
  28. }
  29. private bool ShouldHide(CoreRow[] rows)
  30. {
  31. return rows.Any(x => !x.Get<DigitalFormVariable, bool>(x => x.Hidden));
  32. }
  33. private bool Hide_Click(Button btn, CoreRow[] rows)
  34. {
  35. if(rows.Length == 0)
  36. {
  37. MessageBox.Show("No rows selected");
  38. return false;
  39. }
  40. var hide = ShouldHide(rows);
  41. var items = LoadItems(rows);
  42. foreach (var item in items)
  43. {
  44. item.Hidden = hide;
  45. }
  46. if(items.Length > 0)
  47. {
  48. SaveItems(items);
  49. return true;
  50. }
  51. return false;
  52. }
  53. protected override void SelectItems(CoreRow[]? rows)
  54. {
  55. base.SelectItems(rows);
  56. if(rows is null)
  57. {
  58. HideButton.IsEnabled = false;
  59. }
  60. else
  61. {
  62. HideButton.IsEnabled = true;
  63. HideButton.Content = (ShouldHide(rows) ? "Hide Variable" : "Un-hide Variable") + (rows.Length > 1 ? "s" : "");
  64. }
  65. }
  66. private bool ToggleHidden_Click(Button btn, CoreRow[] rows)
  67. {
  68. ShowHidden = !ShowHidden;
  69. ShowHiddenButton.Content = ShowHidden ? "Hide Hidden" : "Show Hidden";
  70. return true;
  71. }
  72. private void CreateMenu(ContextMenu parent, string header, Type type)
  73. {
  74. parent.AddItem(header, null, type, (itemtype) =>
  75. {
  76. if(DynamicVariableUtils.CreateAndEdit(Item, Items, itemtype, out var variable))
  77. {
  78. SaveItem(variable);
  79. Refresh(false, true);
  80. }
  81. });
  82. }
  83. protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
  84. {
  85. var menu = new ContextMenu();
  86. foreach(var fieldType in DFUtils.GetFieldTypes())
  87. {
  88. var caption = fieldType.GetCaption();
  89. if (string.IsNullOrWhiteSpace(caption))
  90. {
  91. caption = CoreUtils.Neatify(fieldType.Name);
  92. }
  93. CreateMenu(menu, caption, fieldType);
  94. }
  95. menu.IsOpen = true;
  96. }
  97. /*protected override DigitalFormVariable LoadItem(CoreRow row)
  98. {
  99. return Items.FirstOrDefault(r => r.ID.Equals(row.Get<DigitalFormVariable, Guid>(c => c.ID)));
  100. }*/
  101. protected override void DoEdit()
  102. {
  103. if (!SelectedRows.Any())
  104. return;
  105. var variable = LoadItem(SelectedRows.First());
  106. var properties = variable.CreateProperties();
  107. if (DynamicVariableUtils.EditProperties(Item, Items, properties.GetType(), properties))
  108. {
  109. variable.SaveProperties(properties);
  110. SaveItem(variable);
  111. Refresh(false, true);
  112. }
  113. }
  114. protected override void DoDelete()
  115. {
  116. var rows = SelectedRows.ToArray();
  117. if (rows.Any())
  118. if (CanDeleteItems(rows))
  119. if (MessageBox.Show("Are you sure you want to delete this variable? This will all cause data associated with this variable to be lost.\n(If you want to just hide the variable, set it to 'Hidden' instead.)", "Confirm Deletion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
  120. {
  121. DeleteItems(rows);
  122. SelectedRows = Array.Empty<CoreRow>();
  123. DoChanged();
  124. Refresh(false, true);
  125. SelectItems(null);
  126. }
  127. }
  128. protected override bool FilterRecord(CoreRow row)
  129. {
  130. return ShowHidden || !row.Get<DigitalFormVariable, bool>(x => x.Hidden);
  131. }
  132. }
  133. public static class DynamicVariableUtils
  134. {
  135. public static bool CreateAndEdit(
  136. DigitalForm form, IList<DigitalFormVariable> variables,
  137. Type fieldType,
  138. [NotNullWhen(true)] out DigitalFormVariable? variable)
  139. {
  140. var fieldBaseType = fieldType.GetSuperclassDefinition(typeof(DFLayoutField<>));
  141. if (fieldBaseType != null)
  142. {
  143. var propertiesType = fieldBaseType.GetGenericArguments()[0];
  144. var properties = (Activator.CreateInstance(propertiesType) as DFLayoutFieldProperties)!;
  145. if (DynamicVariableUtils.EditProperties(form, variables, propertiesType, properties))
  146. {
  147. variable = new DigitalFormVariable();
  148. variable.SaveProperties(fieldType, properties);
  149. return true;
  150. }
  151. }
  152. variable = null;
  153. return false;
  154. }
  155. public static bool EditProperties(DigitalForm form, IList<DigitalFormVariable> variables, Type type, DFLayoutFieldProperties item)
  156. {
  157. var editor = new DynamicEditorForm(type);
  158. if (item is DFLayoutLookupFieldProperties)
  159. {
  160. editor.OnFormCustomiseEditor += (sender, items, column, editor) => LookupEditor_OnFormCustomiseEditor(sender, variables, items, column, editor);
  161. editor.OnEditorValueChanged += (sender, name, value) =>
  162. {
  163. var result = DynamicGridUtils.UpdateEditorValue(new[] { item }, name, value);
  164. if (name == "LookupType")
  165. {
  166. var grid = (sender as DynamicEditorGrid)!;
  167. var edit = grid.FindEditor("Filter");
  168. if (edit is FilterEditorControl filter)
  169. {
  170. filter.FilterType = value is string str ?
  171. CoreUtils.GetEntityOrNull(str) :
  172. null;
  173. }
  174. var propertiesEditor = grid.FindEditor(nameof(DFLayoutLookupFieldProperties.AdditionalProperties));
  175. if (propertiesEditor is MultiLookupEditorControl multi && multi.EditorDefinition is MultiLookupEditor combo)
  176. {
  177. combo.Clear();
  178. multi.Configure();
  179. }
  180. }
  181. return new();
  182. };
  183. }
  184. else
  185. {
  186. editor.OnFormCustomiseEditor += (sender, items, column, editor) => Editor_OnFormCustomiseEditor(sender, variables, column, editor);
  187. }
  188. editor.OnDefineLookups += o =>
  189. {
  190. var def = (o.EditorDefinition as ILookupEditor)!;
  191. var colname = o.ColumnName;
  192. // Nope, there is nothing dodgy about this at all
  193. // I am not breaking any rules by passing in the QA Form instance, rather than the Field instance
  194. // so that I can get access to the "AppliesTo" property, and thus the list of properties that can be updated
  195. // Nothing to see here, I promise!
  196. CoreTable? values;
  197. if (o.ColumnName == "Property")
  198. {
  199. values = def.Values(colname, new object[] { form });
  200. }
  201. else
  202. {
  203. values = def.Values(colname, new object[] { item });
  204. }
  205. o.LoadLookups(values);
  206. };
  207. var thisVariable = variables.Where(x => x.Code == item.Code).ToList();
  208. editor.OnValidateData += (sender, items) =>
  209. {
  210. var errors = new List<string>();
  211. foreach(var item in items.Cast<DFLayoutFieldProperties>())
  212. {
  213. if (string.IsNullOrWhiteSpace(item.Code))
  214. {
  215. errors.Add("[Code] may not be blank!");
  216. }
  217. else
  218. {
  219. var codeVars = variables.Where(x => x.Code == item.Code).ToList();
  220. if(codeVars.Count > 1)
  221. {
  222. errors.Add($"Duplicate code [{item.Code}]");
  223. }
  224. else if(codeVars.Count == 1)
  225. {
  226. if (!thisVariable.Contains(codeVars.First()))
  227. {
  228. errors.Add($"There is already a variable with code [{item.Code}]");
  229. }
  230. }
  231. }
  232. }
  233. return errors;
  234. };
  235. editor.Items = new BaseObject[] { item };
  236. return editor.ShowDialog() == true;
  237. }
  238. private static void Editor_OnFormCustomiseEditor(IDynamicEditorForm sender, IList<DigitalFormVariable> vars, DynamicGridColumn column, BaseEditor editor)
  239. {
  240. if ((column.ColumnName == "Expression" || column.ColumnName == "ColourExpression") && editor is ExpressionEditor exp)
  241. {
  242. var variables = new List<string>();
  243. foreach (var variable in vars)
  244. {
  245. //variables.Add(variable.Code);
  246. foreach (var col in variable.GetVariableColumns())
  247. {
  248. variables.Add(col.ColumnName);
  249. }
  250. }
  251. variables.Sort();
  252. exp.VariableNames = variables;
  253. }
  254. }
  255. private static void LookupEditor_OnFormCustomiseEditor(IDynamicEditorForm sender, IList<DigitalFormVariable> vars, object[] items, DynamicGridColumn column, BaseEditor editor)
  256. {
  257. if (column.ColumnName == "Filter" && editor is FilterEditor fe)
  258. {
  259. var properties = (items[0] as DFLayoutLookupFieldProperties)!;
  260. var lookupType = properties.LookupType;
  261. var entityType = CoreUtils.GetEntityOrNull(lookupType);
  262. fe.Type = entityType;
  263. }
  264. Editor_OnFormCustomiseEditor(sender, vars, column, editor);
  265. }
  266. }
  267. }