using InABox.Core; using NPOI.DDF; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace InABox.DynamicGrid { /// /// Interaction logic for ExpressionEditorWindow.xaml /// public partial class ExpressionEditorWindow : Window { public static List>> FunctionTemplates = new(); public List Variables { get; private set; } public string Expression { get => Editor.Text; set => Editor.Text = value; } private static void RegisterFunctionTemplate(string group, string name, string? description, List parameters) { var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2; if(groupList is null) { groupList = new List(); FunctionTemplates.Add(new(group, groupList)); } groupList.Add(new FunctionTemplate { Name = name, Parameters = parameters, Description = description ?? "" }); } private static void RegisterFunctionTemplate(string group, string name, params string[] parameters) => RegisterFunctionTemplate(group, name, null, parameters.Select(x => { var parts = x.Split(' '); if (parts.Length == 1) { return new FunctionParameter(parts[0], ""); } return new FunctionParameter(parts[1], parts[0]); }).ToList()); private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters) => RegisterFunctionTemplate(group, name, description, parameters.Select(x => { var parts = x.Split(' '); if (parts.Length == 1) { return new FunctionParameter(parts[0], ""); } return new FunctionParameter(parts[1], parts[0]); }).ToList()); static ExpressionEditorWindow() { RegisterFunctionTemplate("Math", "Abs", "double x"); RegisterFunctionTemplate("Math", "Acos", "double x"); RegisterFunctionTemplate("Math", "Asin", "double x"); RegisterFunctionTemplate("Math", "Atan", "double x"); RegisterFunctionTemplate("Math", "Ceiling", "double x"); RegisterFunctionTemplate("Math", "Cos", "double x"); RegisterFunctionTemplate("Math", "Count", "..."); RegisterFunctionTemplate("Math", "Exp", "double exp"); RegisterFunctionTemplate("Math", "Floor", "double x"); RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y"); RegisterFunctionTemplate("Math", "Log10", "double x"); RegisterFunctionTemplate("Math", "Log", "double x", "double base"); RegisterFunctionTemplate("Math", "Max", "double x", "double y"); RegisterFunctionTemplate("Math", "Min", "double x", "double y"); RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp"); RegisterFunctionTemplate("Math", "Random"); RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision"); RegisterFunctionTemplate("Math", "Sign", "double x"); RegisterFunctionTemplate("Math", "Sin", "double x"); RegisterFunctionTemplate("Math", "Sqrt", "double x"); RegisterFunctionTemplate("Math", "Tan", "double x"); RegisterFunctionTemplate("Math", "Truncate", "double x"); RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2"); RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "..."); RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ..."); RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ..."); RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ..."); RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ..."); RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ..."); RegisterFunctionTemplate("String", "Contains", "string text", "str value"); RegisterFunctionTemplate("String", "EndsWith", "string text", "str value"); RegisterFunctionTemplate("String", "Length", "string value"); RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character"); RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character"); RegisterFunctionTemplate("String", "StartsWith", "string text", "string value"); RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length"); RegisterFunctionTemplate("Date", "AddYears", "date date", "int years"); RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months"); RegisterFunctionTemplate("Date", "AddDays", "date date", "int days"); RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs"); RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins"); RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs"); RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms"); RegisterFunctionTemplate("Date", "YearOf", "date date"); RegisterFunctionTemplate("Date", "MonthOf", "date date"); RegisterFunctionTemplate("Date", "DayOf", "date date"); RegisterFunctionTemplate("Date", "HourOf", "date date"); RegisterFunctionTemplate("Date", "MinuteOf", "date date"); RegisterFunctionTemplate("Date", "SecondOf", "date date"); RegisterFunctionTemplate("Date", "MillisecondOf", "date date"); RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date"); RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date"); RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date"); RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date"); RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date"); RegisterFunctionTemplate("Conversion", "Date", "value"); RegisterFunctionTemplate("Conversion", "Date", "value", "string format"); RegisterFunctionTemplate("Conversion", "Decimal", "value"); RegisterFunctionTemplate("Conversion", "Double", "value"); RegisterFunctionTemplate("Conversion", "Integer", "value"); RegisterFunctionTemplate("Conversion", "Long", "value"); RegisterFunctionTemplate("Conversion", "String", "value"); foreach(var function in CoreExpression.Functions) { RegisterFunctionTemplate( function.Group, function.Name, function.Description, function.Parameters.Select(x => new FunctionParameter(x.Name, x.Type)).ToList()); } } public ExpressionEditorWindow(List variables) { Variables = variables; InitializeComponent(); } private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e) { Editor.Focus(); } private void InsertText(string text) { var newCaret = Editor.CaretIndex + text.Length; Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text); Editor.CaretIndex = newCaret; } private void AppendText(string text) { var oldCaret = Editor.CaretIndex; Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text); Editor.CaretIndex = oldCaret; } private void DeleteText(int start, int length) { var oldCaret = Editor.CaretIndex; Editor.Text = Editor.Text.Remove(start, length); if(oldCaret >= start && oldCaret < start + length) { Editor.CaretIndex = start; } else { Editor.CaretIndex = oldCaret - length; } } private static Regex tagRegex = new("\\{\\S+\\}"); private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft); private bool DeletePlaceholder() { if (Editor.CaretIndex >= Editor.Text.Length) return false; var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex); if (tagStart == -1) return false; var tagMatch = tagRegex.Match(Editor.Text, tagStart); if (!tagMatch.Success) return false; var startIndex = tagMatch.Index; if (startIndex != tagStart) return false; var length = tagMatch.Length; if (Editor.CaretIndex >= startIndex + length) return false; DeleteText(startIndex, length); return true; } private void JumpPlaceholderBackwards() { if (Editor.CaretIndex == 0) return; var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1); if (!tagMatch.Success) return; Editor.CaretIndex = tagMatch.Index; } private void JumpPlaceholder(bool allowStart = false) { if (Editor.CaretIndex >= Editor.Text.Length) return; var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1; var tagMatch = tagRegex.Match(Editor.Text, start); if (!tagMatch.Success) return; Editor.CaretIndex = tagMatch.Index; } private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e) { if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return; var placeholder = DeletePlaceholder(); InsertText($"{template.Name}("); AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")"); if (placeholder) JumpPlaceholder(true); } private void Variable_Click(object sender, MouseButtonEventArgs e) { if ((sender as FrameworkElement)!.Tag is not string str) return; DeletePlaceholder(); InsertText($"[{str}]"); } private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { e.Handled = true; } private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e) { DeletePlaceholder(); } private void Editor_KeyDown(object sender, KeyEventArgs e) { if(e.Key == Key.Tab) { if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { JumpPlaceholderBackwards(); } else { JumpPlaceholder(); } e.Handled = true; } } private void OK_Click(object sender, RoutedEventArgs e) { DialogResult = true; } } }