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
{
///
/// Retrieve the value of a field, for use in expressions.
///
///
///
object? GetFieldValue(string field);
///
/// Retrieve a piece of additional data for a field.
///
/// The field name, which is the variable code.
/// The specific field to be retrieved from the variable.
/// A value, which is specific to the type of and the specific being
/// retrieved.
object? GetFieldData(string fieldName, string dataField);
void SetFieldValue(string field, object? value);
///
/// Set the background colour for a field.
///
void SetFieldColour(string field, Color? colour = null);
}
public class DFLayout
{
public DFLayout()
{
ColumnWidths = new List();
RowHeights = new List();
Elements = new List();
HiddenElements = new List();
Expressions = new Dictionary();
ColourExpressions = new Dictionary();
VariableReferences = new Dictionary>>();
}
public List ColumnWidths { get; }
public List RowHeights { get; }
public List Elements { get; }
public List HiddenElements { get; }
private enum ReferenceType
{
Value,
Colour
}
private Dictionary Expressions;
private Dictionary ColourExpressions;
private Dictionary>> VariableReferences;
public IDFRenderer? Renderer;
public IEnumerable 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? _controls;
/// A type which is a
private Type? GetElementType(string typeName)
{
_controls ??= CoreUtils.TypeList(
AppDomain.CurrentDomain.GetAssemblies(),
x => x.IsClass
&& !x.IsAbstract
&& !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("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>();
VariableReferences[reference] = refs;
}
refs.Add(new Tuple(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();
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();
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 variables)
{
foreach (var field in Elements.Where(x => x is DFLayoutField).Cast())
{
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 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 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 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("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()
{
return GenerateEntityLayout(typeof(T));
}
#endregion
}
}