ExpressionEditorWindow.xaml.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. using InABox.Core;
  2. using NPOI.DDF;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Linq;
  7. using System.Runtime.CompilerServices;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. using System.Threading.Tasks;
  11. using System.Windows;
  12. using System.Windows.Controls;
  13. using System.Windows.Data;
  14. using System.Windows.Documents;
  15. using System.Windows.Input;
  16. using System.Windows.Media;
  17. using System.Windows.Media.Imaging;
  18. using System.Windows.Shapes;
  19. namespace InABox.DynamicGrid
  20. {
  21. /// <summary>
  22. /// Interaction logic for ExpressionEditorWindow.xaml
  23. /// </summary>
  24. public partial class ExpressionEditorWindow : Window
  25. {
  26. public static List<Tuple<string, List<FunctionTemplate>>> FunctionTemplates = new();
  27. public IEnumerable<string> Variables { get; private set; }
  28. public string Expression
  29. {
  30. get => Editor.Text;
  31. set => Editor.Text = value;
  32. }
  33. private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
  34. {
  35. var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
  36. if(groupList is null)
  37. {
  38. groupList = new List<FunctionTemplate>();
  39. FunctionTemplates.Add(new(group, groupList));
  40. }
  41. groupList.Add(new FunctionTemplate
  42. {
  43. Name = name,
  44. Parameters = parameters,
  45. Description = description ?? ""
  46. });
  47. }
  48. private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
  49. => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
  50. {
  51. var parts = x.Split(' ');
  52. if (parts.Length == 1)
  53. {
  54. return new FunctionParameter(parts[0], "");
  55. }
  56. return new FunctionParameter(parts[1], parts[0]);
  57. }).ToList<IFunctionParameter>());
  58. private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
  59. => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
  60. {
  61. var parts = x.Split(' ');
  62. if (parts.Length == 1)
  63. {
  64. return new FunctionParameter(parts[0], "");
  65. }
  66. return new FunctionParameter(parts[1], parts[0]);
  67. }).ToList<IFunctionParameter>());
  68. static ExpressionEditorWindow()
  69. {
  70. RegisterFunctionTemplate("Math", "Abs", "double x");
  71. RegisterFunctionTemplate("Math", "Acos", "double x");
  72. RegisterFunctionTemplate("Math", "Asin", "double x");
  73. RegisterFunctionTemplate("Math", "Atan", "double x");
  74. RegisterFunctionTemplate("Math", "Ceiling", "double x");
  75. RegisterFunctionTemplate("Math", "Cos", "double x");
  76. RegisterFunctionTemplate("Math", "Count", "...");
  77. RegisterFunctionTemplate("Math", "Exp", "double exp");
  78. RegisterFunctionTemplate("Math", "Floor", "double x");
  79. RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
  80. RegisterFunctionTemplate("Math", "Log10", "double x");
  81. RegisterFunctionTemplate("Math", "Log", "double x", "double base");
  82. RegisterFunctionTemplate("Math", "Max", "double x", "double y");
  83. RegisterFunctionTemplate("Math", "Min", "double x", "double y");
  84. RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
  85. RegisterFunctionTemplate("Math", "Random");
  86. RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
  87. RegisterFunctionTemplate("Math", "Sign", "double x");
  88. RegisterFunctionTemplate("Math", "Sin", "double x");
  89. RegisterFunctionTemplate("Math", "Sqrt", "double x");
  90. RegisterFunctionTemplate("Math", "Tan", "double x");
  91. RegisterFunctionTemplate("Math", "Truncate", "double x");
  92. RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
  93. RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
  94. RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
  95. RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
  96. RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
  97. RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
  98. RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
  99. RegisterFunctionTemplate("String", "Contains", "string text", "str value");
  100. RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
  101. RegisterFunctionTemplate("String", "Length", "string value");
  102. RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
  103. RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
  104. RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
  105. RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
  106. RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
  107. RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
  108. RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
  109. RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
  110. RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
  111. RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
  112. RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
  113. RegisterFunctionTemplate("Date", "YearOf", "date date");
  114. RegisterFunctionTemplate("Date", "MonthOf", "date date");
  115. RegisterFunctionTemplate("Date", "DayOf", "date date");
  116. RegisterFunctionTemplate("Date", "HourOf", "date date");
  117. RegisterFunctionTemplate("Date", "MinuteOf", "date date");
  118. RegisterFunctionTemplate("Date", "SecondOf", "date date");
  119. RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
  120. RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
  121. RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
  122. RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
  123. RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
  124. RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
  125. RegisterFunctionTemplate("Conversion", "Date", "value");
  126. RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
  127. RegisterFunctionTemplate("Conversion", "Decimal", "value");
  128. RegisterFunctionTemplate("Conversion", "Double", "value");
  129. RegisterFunctionTemplate("Conversion", "Integer", "value");
  130. RegisterFunctionTemplate("Conversion", "Long", "value");
  131. RegisterFunctionTemplate("Conversion", "String", "value");
  132. foreach(var function in CoreExpression.Functions)
  133. {
  134. RegisterFunctionTemplate(
  135. function.Group,
  136. function.Name,
  137. function.Description,
  138. function.Parameters.Select(x => new FunctionParameter(x.Name, x.Type)).ToList<IFunctionParameter>());
  139. }
  140. }
  141. public ExpressionEditorWindow(IEnumerable<string> variables)
  142. {
  143. Variables = variables;
  144. InitializeComponent();
  145. }
  146. private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
  147. {
  148. Editor.Focus();
  149. }
  150. private void InsertText(string text)
  151. {
  152. var newCaret = Editor.CaretIndex + text.Length;
  153. Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
  154. Editor.CaretIndex = newCaret;
  155. }
  156. private void AppendText(string text)
  157. {
  158. var oldCaret = Editor.CaretIndex;
  159. Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
  160. Editor.CaretIndex = oldCaret;
  161. }
  162. private void DeleteText(int start, int length)
  163. {
  164. var oldCaret = Editor.CaretIndex;
  165. Editor.Text = Editor.Text.Remove(start, length);
  166. if(oldCaret >= start && oldCaret < start + length)
  167. {
  168. Editor.CaretIndex = start;
  169. }
  170. else
  171. {
  172. Editor.CaretIndex = oldCaret - length;
  173. }
  174. }
  175. private static Regex tagRegex = new("\\{\\S+\\}");
  176. private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);
  177. private bool DeletePlaceholder()
  178. {
  179. if (Editor.CaretIndex >= Editor.Text.Length) return false;
  180. var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
  181. if (tagStart == -1) return false;
  182. var tagMatch = tagRegex.Match(Editor.Text, tagStart);
  183. if (!tagMatch.Success) return false;
  184. var startIndex = tagMatch.Index;
  185. if (startIndex != tagStart) return false;
  186. var length = tagMatch.Length;
  187. if (Editor.CaretIndex >= startIndex + length) return false;
  188. DeleteText(startIndex, length);
  189. return true;
  190. }
  191. private void JumpPlaceholderBackwards()
  192. {
  193. if (Editor.CaretIndex == 0) return;
  194. var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
  195. if (!tagMatch.Success) return;
  196. Editor.CaretIndex = tagMatch.Index;
  197. }
  198. private void JumpPlaceholder(bool allowStart = false)
  199. {
  200. if (Editor.CaretIndex >= Editor.Text.Length) return;
  201. var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;
  202. var tagMatch = tagRegex.Match(Editor.Text, start);
  203. if (!tagMatch.Success) return;
  204. Editor.CaretIndex = tagMatch.Index;
  205. }
  206. private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
  207. {
  208. if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;
  209. var placeholder = DeletePlaceholder();
  210. InsertText($"{template.Name}(");
  211. AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
  212. if (placeholder)
  213. JumpPlaceholder(true);
  214. }
  215. private void Variable_Click(object sender, MouseButtonEventArgs e)
  216. {
  217. if ((sender as FrameworkElement)!.Tag is not string str) return;
  218. DeletePlaceholder();
  219. InsertText($"[{str}]");
  220. }
  221. private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
  222. {
  223. e.Handled = true;
  224. }
  225. private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
  226. {
  227. DeletePlaceholder();
  228. }
  229. private void Editor_KeyDown(object sender, KeyEventArgs e)
  230. {
  231. if(e.Key == Key.Tab)
  232. {
  233. if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
  234. {
  235. JumpPlaceholderBackwards();
  236. }
  237. else
  238. {
  239. JumpPlaceholder();
  240. }
  241. e.Handled = true;
  242. }
  243. }
  244. private void OK_Click(object sender, RoutedEventArgs e)
  245. {
  246. DialogResult = true;
  247. }
  248. }
  249. }