DFLayout.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Expressive;
  6. using InABox.Clients;
  7. namespace InABox.Core
  8. {
  9. public interface IDFRenderer
  10. {
  11. object? GetFieldValue(string field);
  12. void SetFieldValue(string field, object? value);
  13. }
  14. public class DFLayout
  15. {
  16. public DFLayout()
  17. {
  18. ColumnWidths = new List<string>();
  19. RowHeights = new List<string>();
  20. Elements = new List<DFLayoutControl>();
  21. Expressions = new Dictionary<string, CoreExpression>();
  22. VariableReferences = new Dictionary<string, List<string>>();
  23. }
  24. public List<string> ColumnWidths { get; }
  25. public List<string> RowHeights { get; }
  26. public List<DFLayoutControl> Elements { get; }
  27. private Dictionary<string, CoreExpression> Expressions;
  28. private Dictionary<string, List<string>> VariableReferences;
  29. public IDFRenderer? Renderer;
  30. public string SaveLayout()
  31. {
  32. var sb = new StringBuilder();
  33. foreach (var column in ColumnWidths)
  34. sb.AppendFormat("C {0}\n", column);
  35. foreach (var row in RowHeights)
  36. sb.AppendFormat("R {0}\n", row);
  37. foreach (var element in Elements)
  38. sb.AppendFormat("E {0} {1}\n", element.GetType().EntityName(), element.SaveToString());
  39. var result = sb.ToString();
  40. return result;
  41. }
  42. private static Dictionary<string, Type>? _controls;
  43. /// <returns>A type which is a <see cref="DFLayoutControl"/></returns>
  44. private Type? GetElementType(string typeName)
  45. {
  46. _controls ??= CoreUtils.TypeList(
  47. AppDomain.CurrentDomain.GetAssemblies(),
  48. x => x.IsClass
  49. && !x.IsAbstract
  50. && !x.IsGenericType
  51. && typeof(DFLayoutControl).IsAssignableFrom(x)
  52. ).ToDictionary(
  53. x => x.EntityName(),
  54. x => x);
  55. return _controls.GetValueOrDefault(typeName);
  56. }
  57. public void LoadLayout(string layout)
  58. {
  59. ColumnWidths.Clear();
  60. RowHeights.Clear();
  61. Elements.Clear();
  62. var lines = layout.Split('\n');
  63. foreach (var line in lines)
  64. if (line.StartsWith("C "))
  65. {
  66. ColumnWidths.Add(line.Substring(2));
  67. }
  68. else if (line.StartsWith("R "))
  69. {
  70. RowHeights.Add(line.Substring(2));
  71. }
  72. else if (line.StartsWith("E ") || line.StartsWith("O "))
  73. {
  74. var typename = line.Split(' ').Skip(1).FirstOrDefault()
  75. ?.Replace("InABox.Core.Design", "InABox.Core.DFLayout")
  76. ?.Replace("DFLayoutChoiceField", "DFLayoutOptionField");
  77. if (!string.IsNullOrWhiteSpace(typename))
  78. {
  79. var type = GetElementType(typename);
  80. if(type != null)
  81. {
  82. var element = (Activator.CreateInstance(type) as DFLayoutControl)!;
  83. var json = string.Join(" ", line.Split(' ').Skip(2));
  84. element.LoadFromString(json);
  85. //Serialization.DeserializeInto(json, element);
  86. Elements.Add(element);
  87. }
  88. else
  89. {
  90. Logger.Send(LogType.Error, ClientFactory.UserID, $"{typename} is not the name of any concrete DFLayoutControls!");
  91. }
  92. }
  93. }
  94. //else if (line.StartsWith("O "))
  95. //{
  96. // String typename = line.Split(' ').Skip(1).FirstOrDefault()?.Replace("PRSDesktop", "InABox.Core");
  97. // if (!String.IsNullOrWhiteSpace(typename))
  98. // {
  99. // Type type = Type.GetType(typename);
  100. // DesignControl element = Activator.CreateInstance(type) as DesignControl;
  101. // if (element != null)
  102. // {
  103. // String json = String.Join(" ", line.Split(' ').Skip(2));
  104. // element.LoadFromString(json);
  105. // //CoreUtils.DeserializeInto(json, element);
  106. // }
  107. // Elements.Add(element);
  108. // }
  109. //}
  110. // Invalid Line Hmmm..
  111. if (!ColumnWidths.Any())
  112. ColumnWidths.AddRange(new[] { "*", "Auto" });
  113. if (!RowHeights.Any())
  114. RowHeights.AddRange(new[] { "Auto" });
  115. }
  116. private void AddVariableReference(string reference, string fieldName)
  117. {
  118. if(!VariableReferences.TryGetValue(reference, out var refs))
  119. {
  120. refs = new List<string>();
  121. VariableReferences[reference] = refs;
  122. }
  123. refs.Add(fieldName);
  124. }
  125. private void EvaluateExpression(string name)
  126. {
  127. var expression = Expressions[name];
  128. var values = new Dictionary<string, object?>();
  129. foreach (var field in expression.ReferencedVariables)
  130. {
  131. values[field] = Renderer?.GetFieldValue(field);
  132. }
  133. var oldValue = Renderer?.GetFieldValue(name);
  134. try
  135. {
  136. var value = expression?.Evaluate(values);
  137. if(value != oldValue)
  138. {
  139. Renderer?.SetFieldValue(name, value);
  140. }
  141. }
  142. catch (Exception e)
  143. {
  144. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Expression field '{name}': {CoreUtils.FormatException(e)}");
  145. }
  146. }
  147. public void LoadVariable(DigitalFormVariable variable, DFLayoutField field)
  148. {
  149. var properties = variable.LoadProperties(field);
  150. if (!string.IsNullOrWhiteSpace(properties.Expression))
  151. {
  152. var expression = new CoreExpression(properties.Expression);
  153. foreach (var reference in expression.ReferencedVariables)
  154. {
  155. AddVariableReference(reference, field.Name);
  156. }
  157. Expressions[field.Name] = expression;
  158. }
  159. }
  160. public void LoadVariables(IEnumerable<DigitalFormVariable> variables)
  161. {
  162. foreach (var field in Elements.Where(x => x is DFLayoutField).Cast<DFLayoutField>())
  163. {
  164. var variable = variables.FirstOrDefault(x => string.Equals(x.Code, field.Name));
  165. if (variable != null)
  166. {
  167. LoadVariable(variable, field);
  168. }
  169. }
  170. }
  171. public static DFLayout FromLayoutString(string layoutString)
  172. {
  173. var layout = new DFLayout();
  174. layout.LoadLayout(layoutString);
  175. return layout;
  176. }
  177. #region Expression Fields
  178. public void ChangeField(string fieldName)
  179. {
  180. if (!VariableReferences.TryGetValue(fieldName, out var refs)) return;
  181. foreach(var reference in refs)
  182. {
  183. EvaluateExpression(reference);
  184. }
  185. }
  186. public void EvaluateExpressions()
  187. {
  188. foreach(var name in Expressions.Keys)
  189. {
  190. EvaluateExpression(name);
  191. }
  192. }
  193. #endregion
  194. #region Auto-generated Layouts
  195. public static DFLayoutField? GenerateLayoutFieldFromVariable(DigitalFormVariable variable)
  196. {
  197. DFLayoutField? field = Activator.CreateInstance(variable.FieldType()) as DFLayoutField;
  198. if(field == null)
  199. {
  200. return null;
  201. }
  202. field.Name = variable.Code;
  203. return field;
  204. }
  205. public static DFLayout GenerateAutoDesktopLayout(
  206. IEnumerable<DigitalFormVariable> variables)
  207. {
  208. var layout = new DFLayout();
  209. layout.ColumnWidths.Add("*");
  210. layout.ColumnWidths.Add("Auto");
  211. layout.ColumnWidths.Add("Auto");
  212. for (var i = 0; i < variables.Count(); i++)
  213. {
  214. var variable = variables.ElementAt(i);
  215. layout.RowHeights.Add("Auto");
  216. var rowNum = new DFLayoutLabel { Caption = (i + 1).ToString(), Row = i + 1, Column = 1 };
  217. var label = new DFLayoutLabel { Caption = variable.Code, Row = i + 1, Column = 2 };
  218. layout.Elements.Add(rowNum);
  219. layout.Elements.Add(label);
  220. var field = GenerateLayoutFieldFromVariable(variable);
  221. if(field != null)
  222. {
  223. field.Row = i + 1;
  224. field.Column = 3;
  225. layout.Elements.Add(field);
  226. }
  227. }
  228. return layout;
  229. }
  230. public static DFLayout GenerateAutoMobileLayout(
  231. IEnumerable<DigitalFormVariable> variables)
  232. {
  233. var layout = new DFLayout();
  234. layout.ColumnWidths.Add("Auto");
  235. layout.ColumnWidths.Add("*");
  236. var row = 1;
  237. for (var i = 0; i < variables.Count(); i++)
  238. {
  239. var variable = variables.ElementAt(i);
  240. layout.RowHeights.Add("Auto");
  241. layout.RowHeights.Add("Auto");
  242. var rowNum = new DFLayoutLabel { Caption = i + 1 + ".", Row = row, Column = 1 };
  243. var label = new DFLayoutLabel { Caption = variable.Code, Row = row, Column = 2 };
  244. layout.Elements.Add(rowNum);
  245. layout.Elements.Add(label);
  246. var field = GenerateLayoutFieldFromVariable(variable);
  247. if(field != null)
  248. {
  249. field.Row = row + 1;
  250. field.Column = 1;
  251. field.ColumnSpan = 2;
  252. layout.Elements.Add(field);
  253. }
  254. row += 2;
  255. }
  256. return layout;
  257. }
  258. public static DFLayout GenerateAutoLayout(DFLayoutType type, IEnumerable<DigitalFormVariable> variables)
  259. {
  260. switch (type)
  261. {
  262. case DFLayoutType.Mobile:
  263. return GenerateAutoDesktopLayout(variables);
  264. case DFLayoutType.Desktop:
  265. default:
  266. return GenerateAutoDesktopLayout(variables);
  267. }
  268. }
  269. public static DFLayoutField GenerateLayoutFieldFromEditor(BaseEditor editor)
  270. {
  271. // TODO: Finish
  272. switch (editor)
  273. {
  274. case CheckBoxEditor _:
  275. var newField = new DFLayoutBooleanField();
  276. newField.Properties.Type = DesignBooleanFieldType.Checkbox;
  277. return newField;
  278. case CheckListEditor _:
  279. // TODO: At this point, it seems CheckListEditor is unused.
  280. throw new NotImplementedException();
  281. case UniqueCodeEditor _:
  282. case CodeEditor _:
  283. return new DFLayoutCodeField();
  284. /* Not implemented because we don't like it.
  285. case PopupEditor v:*/
  286. case CodePopupEditor codePopupEditor:
  287. // TODO: Let's look at this later. For now, using a lookup.
  288. var newLookupFieldPopup = new DFLayoutLookupField();
  289. newLookupFieldPopup.Properties.LookupType = codePopupEditor.Type.EntityName();
  290. return newLookupFieldPopup;
  291. case ColorEditor _:
  292. return new DFLayoutColorField();
  293. case CurrencyEditor _:
  294. // TODO: Make this a specialised editor
  295. return new DFLayoutDoubleField();
  296. case DateEditor _:
  297. return new DFLayoutDateField();
  298. case DateTimeEditor _:
  299. return new DFLayoutDateTimeField();
  300. case DoubleEditor _:
  301. return new DFLayoutDoubleField();
  302. case DurationEditor _:
  303. return new DFLayoutTimeField();
  304. case EmbeddedImageEditor _:
  305. return new DFLayoutEmbeddedImage();
  306. case FileNameEditor _:
  307. case FolderEditor _:
  308. // Unimplemented because these editors only apply to properties for server engine configuration; it
  309. // doesn't make sense to store filenames in the database, and hence no entity will ever try to be saved
  310. // with a property with these editors.
  311. throw new NotImplementedException("This has intentionally been left unimplemented.");
  312. case IntegerEditor _:
  313. return new DFLayoutIntegerField();
  314. case ComboLookupEditor _:
  315. case EnumLookupEditor _:
  316. var newComboLookupField = new DFLayoutOptionField();
  317. var comboValuesTable = (editor as StaticLookupEditor).Values("Key");
  318. newComboLookupField.Properties.Options = string.Join(",", comboValuesTable.ExtractValues<string>("Key"));
  319. return newComboLookupField;
  320. case LookupEditor lookupEditor:
  321. var newLookupField = new DFLayoutLookupField();
  322. newLookupField.Properties.LookupType = lookupEditor.Type.EntityName();
  323. return newLookupField;
  324. case ImageDocumentEditor _:
  325. case MiscellaneousDocumentEditor _:
  326. case VectorDocumentEditor _:
  327. case PDFDocumentEditor _:
  328. var newDocField = new DFLayoutDocumentField();
  329. newDocField.Properties.FileMask = (editor as BaseDocumentEditor).FileMask;
  330. return newDocField;
  331. case NotesEditor _:
  332. return new DFLayoutNotesField();
  333. case NullEditor _:
  334. return null;
  335. case PasswordEditor _:
  336. return new DFLayoutPasswordField();
  337. case PINEditor _:
  338. var newPINField = new DFLayoutPINField();
  339. newPINField.Properties.Length = ClientFactory.PINLength;
  340. return newPINField;
  341. // TODO: Implement JSON editors and RichText editors.
  342. case JsonEditor _:
  343. case MemoEditor _:
  344. case RichTextEditor _:
  345. case ScriptEditor _:
  346. return new DFLayoutTextField();
  347. case TextBoxEditor _:
  348. return new DFLayoutStringField();
  349. case TimestampEditor _:
  350. return new DFLayoutTimeStampField();
  351. case TimeOfDayEditor _:
  352. return new DFLayoutTimeField();
  353. case URLEditor _:
  354. return new DFLayoutURLField();
  355. }
  356. return null;
  357. }
  358. public static DFLayout GenerateEntityLayout(Type entityType)
  359. {
  360. var layout = new DFLayout();
  361. layout.ColumnWidths.Add("Auto");
  362. layout.ColumnWidths.Add("*");
  363. var properties = DatabaseSchema.Properties(entityType);
  364. var Row = 1;
  365. foreach (var property in properties)
  366. {
  367. var editor = EditorUtils.GetPropertyEditor(entityType, property);
  368. if (editor != null && !(editor is NullEditor) && editor.Editable != Editable.Hidden)
  369. {
  370. var field = GenerateLayoutFieldFromEditor(editor);
  371. if (field != null)
  372. {
  373. var label = new DFLayoutLabel { Caption = editor.Caption };
  374. label.Row = Row;
  375. label.Column = 1;
  376. field.Row = Row;
  377. field.Column = 2;
  378. field.Name = property.Name;
  379. layout.Elements.Add(label);
  380. layout.Elements.Add(field);
  381. layout.RowHeights.Add("Auto");
  382. Row++;
  383. }
  384. }
  385. }
  386. return layout;
  387. }
  388. public static DFLayout GenerateEntityLayout<T>()
  389. {
  390. return GenerateEntityLayout(typeof(T));
  391. }
  392. #endregion
  393. }
  394. }