123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Globalization;
- using System.Linq;
- using System.Text;
- using System.Xml.Linq;
- using Expressive;
- using InABox.Clients;
- namespace InABox.Core
- {
- public interface IDFRenderer
- {
- /// <summary>
- /// Retrieve the value of a field, for use in expressions.
- /// </summary>
- /// <param name="field"></param>
- /// <returns></returns>
- object? GetFieldValue(string field);
- /// <summary>
- /// Retrieve a piece of additional data for a field.
- /// </summary>
- /// <param name="fieldName">The field name, which is the variable code.</param>
- /// <param name="dataField">The specific field to be retrieved from the variable.</param>
- /// <returns>A value, which is specific to the type of <paramref name="fieldName"/> and the specific <paramref name="dataField"/> being
- /// retrieved.</returns>
- object? GetFieldData(string fieldName, string dataField);
- void SetFieldValue(string field, object? value);
- /// <summary>
- /// Set the background colour for a field.
- /// </summary>
- void SetFieldColour(string field, Color? colour = null);
- }
- public class DFLayout
- {
- public DFLayout()
- {
- ColumnWidths = new List<string>();
- RowHeights = new List<string>();
- Elements = new List<DFLayoutControl>();
- HiddenElements = new List<DFLayoutControl>();
- Expressions = new Dictionary<string, CoreExpression>();
- ColourExpressions = new Dictionary<string, CoreExpression>();
- VariableReferences = new Dictionary<string, List<Tuple<ReferenceType, string>>>();
- }
- public List<string> ColumnWidths { get; }
- public List<string> RowHeights { get; }
- public List<DFLayoutControl> Elements { get; }
- public List<DFLayoutControl> HiddenElements { get; }
- private enum ReferenceType
- {
- Value,
- Colour
- }
- private Dictionary<string, CoreExpression> Expressions;
- private Dictionary<string, CoreExpression> ColourExpressions;
- private Dictionary<string, List<Tuple<ReferenceType, string>>> VariableReferences;
- public IDFRenderer? Renderer;
- public IEnumerable<DFLayoutControl> GetElements(bool includeHidden = false)
- {
- foreach (var element in Elements)
- {
- yield return element;
- }
- if (includeHidden)
- {
- foreach (var element in HiddenElements)
- {
- yield return element;
- }
- }
- }
- public string SaveLayout()
- {
- var sb = new StringBuilder();
- foreach (var column in ColumnWidths)
- sb.AppendFormat("C {0}\n", column);
- foreach (var row in RowHeights)
- sb.AppendFormat("R {0}\n", row);
- foreach (var element in Elements)
- sb.AppendFormat("E {0} {1}\n", element.GetType().EntityName(), element.SaveToString());
- var result = sb.ToString();
- return result;
- }
- private static Dictionary<string, Type>? _controls;
- /// <returns>A type which is a <see cref="DFLayoutControl"/></returns>
- private Type? GetElementType(string typeName)
- {
- _controls ??= CoreUtils.Entities.Where(x => x.IsClass && !x.IsGenericType && typeof(DFLayoutControl).IsAssignableFrom(x)).ToDictionary(
- x => x.EntityName(),
- x => x);
- return _controls.GetValueOrDefault(typeName);
- }
- private static bool IsHidden(DFLayoutControl element)
- => element is DFLayoutField field && field.GetPropertyValue<bool>("Hidden");
- public void LoadLayout(string layout)
- {
- ColumnWidths.Clear();
- RowHeights.Clear();
- Elements.Clear();
- var lines = layout.Split('\n');
- foreach (var line in lines)
- if (line.StartsWith("C "))
- {
- ColumnWidths.Add(line.Substring(2));
- }
- else if (line.StartsWith("R "))
- {
- RowHeights.Add(line.Substring(2));
- }
- else if (line.StartsWith("E ") || line.StartsWith("O "))
- {
- var typename = line.Split(' ').Skip(1).FirstOrDefault()
- ?.Replace("InABox.Core.Design", "InABox.Core.DFLayout")
- ?.Replace("DFLayoutChoiceField", "DFLayoutOptionField");
- if (!string.IsNullOrWhiteSpace(typename))
- {
- var type = GetElementType(typename);
- if(type != null)
- {
- var element = (Activator.CreateInstance(type) as DFLayoutControl)!;
- var json = string.Join(" ", line.Split(' ').Skip(2));
- element.LoadFromString(json);
- //Serialization.DeserializeInto(json, element);
- if(IsHidden(element))
- {
- HiddenElements.Add(element);
- }
- else
- {
- Elements.Add(element);
- }
- }
- else
- {
- Logger.Send(LogType.Error, ClientFactory.UserID, $"{typename} is not the name of any concrete DFLayoutControls!");
- }
- }
- }
- //else if (line.StartsWith("O "))
- //{
- // String typename = line.Split(' ').Skip(1).FirstOrDefault()?.Replace("PRSDesktop", "InABox.Core");
- // if (!String.IsNullOrWhiteSpace(typename))
- // {
- // Type type = Type.GetType(typename);
- // DesignControl element = Activator.CreateInstance(type) as DesignControl;
- // if (element != null)
- // {
- // String json = String.Join(" ", line.Split(' ').Skip(2));
- // element.LoadFromString(json);
- // //CoreUtils.DeserializeInto(json, element);
- // }
- // Elements.Add(element);
- // }
- //}
- // Invalid Line Hmmm..
- if (!ColumnWidths.Any())
- ColumnWidths.AddRange(new[] { "*", "Auto" });
- if (!RowHeights.Any())
- RowHeights.AddRange(new[] { "Auto" });
- }
- private void AddVariableReference(string reference, string fieldName, ReferenceType referenceType)
- {
- if (reference.Contains('.'))
- reference = reference.Split('.')[0];
- if(!VariableReferences.TryGetValue(reference, out var refs))
- {
- refs = new List<Tuple<ReferenceType, string>>();
- VariableReferences[reference] = refs;
- }
- refs.Add(new Tuple<ReferenceType, string>(referenceType, fieldName));
- }
- private object? GetFieldValue(string field)
- {
- if (field.Contains('.'))
- {
- var parts = field.Split('.');
- return Renderer?.GetFieldData(parts[0], string.Join('.', parts.Skip(1)));
- }
- else
- {
- return Renderer?.GetFieldValue(field);
- }
- }
- private void EvaluateValueExpression(string name)
- {
- var expression = Expressions[name];
- var values = new Dictionary<string, object?>();
- foreach (var field in expression.ReferencedVariables)
- {
- values[field] = GetFieldValue(field);
- }
- var oldValue = Renderer?.GetFieldValue(name);
- try
- {
- var value = expression?.Evaluate(values);
- if(value != oldValue)
- {
- Renderer?.SetFieldValue(name, value);
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Expression field '{name}': {CoreUtils.FormatException(e)}");
- }
- }
- private void EvaluateColourExpression(string name)
- {
- var expression = ColourExpressions[name];
- var values = new Dictionary<string, object?>();
- foreach (var field in expression.ReferencedVariables)
- {
- values[field] = GetFieldValue(field);
- }
- try
- {
- var colour = expression?.Evaluate(values);
- Renderer?.SetFieldColour(name, DFLayoutUtils.ConvertObjectToColour(colour));
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Expression field '{name}': {CoreUtils.FormatException(e)}");
- }
- }
- private void LoadExpression(string fieldName, string? expressionStr, ReferenceType referenceType)
- {
- if (string.IsNullOrWhiteSpace(expressionStr))
- return;
- var expression = new CoreExpression(expressionStr);
- foreach (var reference in expression.ReferencedVariables)
- {
- AddVariableReference(reference, fieldName, referenceType);
- }
- switch (referenceType)
- {
- case ReferenceType.Value:
- Expressions[fieldName] = expression;
- break;
- case ReferenceType.Colour:
- ColourExpressions[fieldName] = expression;
- break;
- }
- }
- public void LoadVariable(DigitalFormVariable variable, DFLayoutField field)
- {
- var properties = variable.LoadProperties(field);
- LoadExpression(field.Name, properties?.Expression, ReferenceType.Value);
- LoadExpression(field.Name, properties?.ColourExpression, ReferenceType.Colour);
- }
- public void LoadVariables(IEnumerable<DigitalFormVariable> variables)
- {
- foreach (var field in Elements.Where(x => x is DFLayoutField).Cast<DFLayoutField>())
- {
- var variable = variables.FirstOrDefault(x => string.Equals(x.Code, field.Name));
- if (variable != null)
- {
- LoadVariable(variable, field);
- }
- }
- }
- public static DFLayout FromLayoutString(string layoutString)
- {
- var layout = new DFLayout();
- layout.LoadLayout(layoutString);
- return layout;
- }
- #region Expression Fields
- public void ChangeField(string fieldName)
- {
- if (!VariableReferences.TryGetValue(fieldName, out var refs)) return;
- foreach(var (refType, refName) in refs)
- {
- switch (refType)
- {
- case ReferenceType.Value:
- EvaluateValueExpression(refName);
- break;
- case ReferenceType.Colour:
- EvaluateColourExpression(refName);
- break;
- }
- }
- }
- public void EvaluateExpressions()
- {
- foreach(var name in Expressions.Keys)
- {
- EvaluateValueExpression(name);
- }
- foreach(var name in ColourExpressions.Keys)
- {
- EvaluateColourExpression(name);
- }
- }
- #endregion
- #region Auto-generated Layouts
- public static string GetLayoutFieldDefaultHeight(DFLayoutField field)
- {
- if (field is DFLayoutSignaturePad || field is DFLayoutMultiSignaturePad)
- return "200";
- return "Auto";
- }
- public static DFLayoutField? GenerateLayoutFieldFromVariable(DigitalFormVariable variable)
- {
- DFLayoutField? field = Activator.CreateInstance(variable.FieldType()) as DFLayoutField;
- if(field == null)
- {
- return null;
- }
- field.Name = variable.Code;
- return field;
- }
- public static DFLayout GenerateAutoDesktopLayout(
- IList<DigitalFormVariable> variables)
- {
- var layout = new DFLayout();
- layout.ColumnWidths.Add("Auto");
- layout.ColumnWidths.Add("Auto");
- layout.ColumnWidths.Add("*");
- int row = 1;
- var group = "";
- foreach(var variable in variables)
- {
-
- if (!String.IsNullOrWhiteSpace(variable.Group) && !String.Equals(variable.Group, group))
- {
- layout.RowHeights.Add("Auto");
- var header = new DFLayoutHeader()
- { Header = variable.Group, Row = row, Column = 1, ColumnSpan = 3, Collapsed = !String.IsNullOrWhiteSpace(group) };
- layout.Elements.Add(header);
- group = variable.Group;
- row++;
- }
-
- var rowHeight = "Auto";
- var rowNum = new DFLayoutLabel { Caption = row.ToString(), Row = row, Column = 1 };
- var label = new DFLayoutLabel { Caption = variable.Code, Row = row, Column = 2 };
- layout.Elements.Add(rowNum);
- layout.Elements.Add(label);
- var field = GenerateLayoutFieldFromVariable(variable);
- if(field != null)
- {
- field.Row = row;
- field.Column = 3;
- layout.Elements.Add(field);
- rowHeight = GetLayoutFieldDefaultHeight(field);
- }
- layout.RowHeights.Add(rowHeight);
- ++row;
- }
- return layout;
- }
- public static DFLayout GenerateAutoMobileLayout(
- IList<DigitalFormVariable> variables)
- {
- var layout = new DFLayout();
- layout.ColumnWidths.Add("Auto");
- layout.ColumnWidths.Add("*");
- var row = 1;
- var i = 0;
- var group = "";
- foreach(var variable in variables)
- {
- if (!String.IsNullOrWhiteSpace(variable.Group) && !String.Equals(variable.Group, group))
- {
- layout.RowHeights.Add("Auto");
- var header = new DFLayoutHeader()
- { Header = variable.Group, Row = row, Column = 1, ColumnSpan = 2, Collapsed = !String.IsNullOrWhiteSpace(group) };
- layout.Elements.Add(header);
- group = variable.Group;
- row++;
- }
-
- var rowHeight = "Auto";
- layout.RowHeights.Add("Auto");
- var rowNum = new DFLayoutLabel { Caption = i + 1 + ".", Row = row, Column = 1 };
- var label = new DFLayoutLabel { Caption = variable.Code, Row = row, Column = 2 };
- layout.Elements.Add(rowNum);
- layout.Elements.Add(label);
- var field = GenerateLayoutFieldFromVariable(variable);
- if(field != null)
- {
- field.Row = row + 1;
- field.Column = 1;
- field.ColumnSpan = 2;
- layout.Elements.Add(field);
- rowHeight = GetLayoutFieldDefaultHeight(field);
- }
- layout.RowHeights.Add(rowHeight);
- row += 2;
- ++i;
- }
- return layout;
- }
- public static DFLayout GenerateAutoLayout(DFLayoutType type, IList<DigitalFormVariable> variables)
- {
- return type switch
- {
- DFLayoutType.Mobile => GenerateAutoDesktopLayout(variables),
- _ => GenerateAutoDesktopLayout(variables),
- };
- }
- public static DFLayoutField? GenerateLayoutFieldFromEditor(BaseEditor editor)
- {
- // TODO: Finish
- switch (editor)
- {
- case CheckBoxEditor _:
- var newField = new DFLayoutBooleanField();
- newField.Properties.Type = DesignBooleanFieldType.Checkbox;
- return newField;
- case CheckListEditor _:
- // TODO: At this point, it seems CheckListEditor is unused.
- throw new NotImplementedException();
- case UniqueCodeEditor _:
- case CodeEditor _:
- return new DFLayoutCodeField();
- /* Not implemented because we don't like it.
- case PopupEditor v:*/
- case CodePopupEditor codePopupEditor:
- // TODO: Let's look at this later. For now, using a lookup.
- var newLookupFieldPopup = new DFLayoutLookupField();
- newLookupFieldPopup.Properties.LookupType = codePopupEditor.Type.EntityName();
- return newLookupFieldPopup;
- case ColorEditor _:
- return new DFLayoutColorField();
- case CurrencyEditor _:
- // TODO: Make this a specialised editor
- return new DFLayoutDoubleField();
- case DateEditor _:
- return new DFLayoutDateField();
- case DateTimeEditor _:
- return new DFLayoutDateTimeField();
- case DoubleEditor _:
- return new DFLayoutDoubleField();
- case DurationEditor _:
- return new DFLayoutTimeField();
- case EmbeddedImageEditor _:
- return new DFLayoutEmbeddedImage();
- case FileNameEditor _:
- case FolderEditor _:
- // Unimplemented because these editors only apply to properties for server engine configuration; it
- // doesn't make sense to store filenames in the database, and hence no entity will ever try to be saved
- // with a property with these editors.
- throw new NotImplementedException("This has intentionally been left unimplemented.");
- case IntegerEditor _:
- return new DFLayoutIntegerField();
- case ComboLookupEditor _:
- case EnumLookupEditor _:
- var newComboLookupField = new DFLayoutOptionField();
- var comboValuesTable = (editor as StaticLookupEditor)!.Values(typeof(object), "Key");
- newComboLookupField.Properties.Options = string.Join(",", comboValuesTable.ExtractValues<string>("Key"));
- return newComboLookupField;
- case LookupEditor lookupEditor:
- var newLookupField = new DFLayoutLookupField();
- newLookupField.Properties.LookupType = lookupEditor.Type.EntityName();
- return newLookupField;
- case ImageDocumentEditor _:
- case MiscellaneousDocumentEditor _:
- case VectorDocumentEditor _:
- case PDFDocumentEditor _:
- var newDocField = new DFLayoutDocumentField();
- newDocField.Properties.FileMask = (editor as BaseDocumentEditor)!.FileMask;
- return newDocField;
- case NotesEditor _:
- return new DFLayoutNotesField();
- case NullEditor _:
- return null;
- case PasswordEditor _:
- return new DFLayoutPasswordField();
- case PINEditor _:
- var newPINField = new DFLayoutPINField();
- newPINField.Properties.Length = ClientFactory.PINLength;
- return newPINField;
- // TODO: Implement JSON editors and RichText editors.
- case JsonEditor _:
- case MemoEditor _:
- case RichTextEditor _:
- case ButtonEditor _:
- case ScriptEditor _:
- return new DFLayoutTextField();
-
- case TextBoxEditor _:
- return new DFLayoutStringField();
- case TimestampEditor _:
- return new DFLayoutTimeStampField();
- case TimeOfDayEditor _:
- return new DFLayoutTimeField();
- case URLEditor _:
- return new DFLayoutURLField();
- }
- return null;
- }
- public static DFLayout GenerateEntityLayout(Type entityType)
- {
- var layout = new DFLayout();
- layout.ColumnWidths.Add("Auto");
- layout.ColumnWidths.Add("*");
- var properties = DatabaseSchema.Properties(entityType);
- var Row = 1;
- foreach (var property in properties)
- {
- var editor = EditorUtils.GetPropertyEditor(entityType, property);
- if (editor != null && !(editor is NullEditor) && editor.Editable.EditorVisible())
- {
- var field = GenerateLayoutFieldFromEditor(editor);
- if (field != null)
- {
- var label = new DFLayoutLabel { Caption = editor.Caption };
- label.Row = Row;
- label.Column = 1;
- field.Row = Row;
- field.Column = 2;
- field.Name = property.Name;
- layout.Elements.Add(label);
- layout.Elements.Add(field);
- layout.RowHeights.Add("Auto");
- Row++;
- }
- }
- }
- return layout;
- }
- public static DFLayout GenerateEntityLayout<T>()
- {
- return GenerateEntityLayout(typeof(T));
- }
- #endregion
- }
- }
|