ExpressionEditorWindow.xaml.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading.Tasks;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Data;
  13. using System.Windows.Documents;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Imaging;
  17. using System.Windows.Shapes;
  18. namespace InABox.DynamicGrid;
  19. public class ExpressionEditorVariable(string variableName, string display, List<ExpressionEditorVariable> children)
  20. {
  21. public string VariableName { get; } = variableName;
  22. public string Display { get; } = display;
  23. public List<ExpressionEditorVariable> ChildVariables { get; } = children;
  24. public bool HasChildren => ChildVariables.Count > 0;
  25. }
  26. public class ExpressionEditorVariableTemplateItemStyleSelector : StyleSelector
  27. {
  28. public override Style? SelectStyle(object item, DependencyObject container)
  29. {
  30. if(item is ExpressionEditorVariable variable && container is FrameworkElement element && variable.HasChildren)
  31. {
  32. return element.FindResource("variableTemplateItemStyle") as Style;
  33. }
  34. return base.SelectStyle(item, container);
  35. }
  36. }
  37. public class ExpressionEditorVariableTemplateSelector : DataTemplateSelector
  38. {
  39. public override DataTemplate? SelectTemplate(object item, DependencyObject container)
  40. {
  41. if(item is ExpressionEditorVariable variable && container is FrameworkElement element)
  42. {
  43. if (variable.HasChildren)
  44. {
  45. return element.FindResource("VariableExpanderTemplate") as DataTemplate;
  46. }
  47. else
  48. {
  49. return element.FindResource("VariableTemplate") as DataTemplate;
  50. }
  51. }
  52. return base.SelectTemplate(item, container);
  53. }
  54. }
  55. /// <summary>
  56. /// Interaction logic for ExpressionEditorWindow.xaml
  57. /// </summary>
  58. public partial class ExpressionEditorWindow : Window
  59. {
  60. public static List<Tuple<string, List<FunctionTemplate>>> FunctionTemplates = new();
  61. public IEnumerable<ExpressionEditorVariable> Variables { get; private set; }
  62. public string Expression
  63. {
  64. get => Editor.Text;
  65. set => Editor.Text = value;
  66. }
  67. private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
  68. {
  69. var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
  70. if(groupList is null)
  71. {
  72. groupList = new List<FunctionTemplate>();
  73. FunctionTemplates.Add(new(group, groupList));
  74. }
  75. groupList.Add(new FunctionTemplate
  76. {
  77. Name = name,
  78. Parameters = parameters,
  79. Description = description ?? ""
  80. });
  81. }
  82. private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
  83. => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
  84. {
  85. var parts = x.Split(' ');
  86. if (parts.Length == 1)
  87. {
  88. return new FunctionParameter(parts[0], "");
  89. }
  90. return new FunctionParameter(parts[1], parts[0]);
  91. }).ToList<IFunctionParameter>());
  92. private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
  93. => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
  94. {
  95. var parts = x.Split(' ');
  96. if (parts.Length == 1)
  97. {
  98. return new FunctionParameter(parts[0], "");
  99. }
  100. return new FunctionParameter(parts[1], parts[0]);
  101. }).ToList<IFunctionParameter>());
  102. static ExpressionEditorWindow()
  103. {
  104. RegisterFunctionTemplate("Math", "Abs", "double x");
  105. RegisterFunctionTemplate("Math", "Acos", "double x");
  106. RegisterFunctionTemplate("Math", "Asin", "double x");
  107. RegisterFunctionTemplate("Math", "Atan", "double x");
  108. RegisterFunctionTemplate("Math", "Ceiling", "double x");
  109. RegisterFunctionTemplate("Math", "Cos", "double x");
  110. RegisterFunctionTemplate("Math", "Count", "...");
  111. RegisterFunctionTemplate("Math", "Exp", "double exp");
  112. RegisterFunctionTemplate("Math", "Floor", "double x");
  113. RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
  114. RegisterFunctionTemplate("Math", "Log10", "double x");
  115. RegisterFunctionTemplate("Math", "Log", "double x", "double base");
  116. RegisterFunctionTemplate("Math", "Max", "double x", "double y");
  117. RegisterFunctionTemplate("Math", "Min", "double x", "double y");
  118. RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
  119. RegisterFunctionTemplate("Math", "Random");
  120. RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
  121. RegisterFunctionTemplate("Math", "Sign", "double x");
  122. RegisterFunctionTemplate("Math", "Sin", "double x");
  123. RegisterFunctionTemplate("Math", "Sqrt", "double x");
  124. RegisterFunctionTemplate("Math", "Tan", "double x");
  125. RegisterFunctionTemplate("Math", "Truncate", "double x");
  126. RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
  127. RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
  128. RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
  129. RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
  130. RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
  131. RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
  132. RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
  133. RegisterFunctionTemplate("String", "Contains", "string text", "str value");
  134. RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
  135. RegisterFunctionTemplate("String", "Length", "string value");
  136. RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
  137. RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
  138. RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
  139. RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
  140. RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
  141. RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
  142. RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
  143. RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
  144. RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
  145. RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
  146. RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
  147. RegisterFunctionTemplate("Date", "YearOf", "date date");
  148. RegisterFunctionTemplate("Date", "MonthOf", "date date");
  149. RegisterFunctionTemplate("Date", "DayOf", "date date");
  150. RegisterFunctionTemplate("Date", "HourOf", "date date");
  151. RegisterFunctionTemplate("Date", "MinuteOf", "date date");
  152. RegisterFunctionTemplate("Date", "SecondOf", "date date");
  153. RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
  154. RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
  155. RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
  156. RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
  157. RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
  158. RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
  159. RegisterFunctionTemplate("Conversion", "Date", "value");
  160. RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
  161. RegisterFunctionTemplate("Conversion", "Decimal", "value");
  162. RegisterFunctionTemplate("Conversion", "Double", "value");
  163. RegisterFunctionTemplate("Conversion", "Integer", "value");
  164. RegisterFunctionTemplate("Conversion", "Long", "value");
  165. RegisterFunctionTemplate("Conversion", "String", "value");
  166. foreach(var function in CoreExpression.Functions)
  167. {
  168. RegisterFunctionTemplateDesc(
  169. function.Group,
  170. function.Name,
  171. function.Description,
  172. function.Parameters);
  173. }
  174. }
  175. public ExpressionEditorWindow(IEnumerable<string> variables)
  176. {
  177. var parentColumns = new Dictionary<string, ExpressionEditorVariable>();
  178. var items = new List<ExpressionEditorVariable>();
  179. foreach (var variable in variables)
  180. {
  181. var display = variable;
  182. var lastDot = variable.LastIndexOf('.');
  183. if(lastDot != -1)
  184. {
  185. display = variable[(lastDot + 1)..];
  186. }
  187. var item = new ExpressionEditorVariable(variable, display, []);
  188. var props = variable.Split('.');
  189. var vars = items;
  190. string? parent = null;
  191. for (int i = 0; i < props.Length - 1; ++i)
  192. {
  193. if (parent is null)
  194. {
  195. parent = props[i];
  196. }
  197. else
  198. {
  199. parent = $"{parent}.{props[i]}";
  200. }
  201. if(!parentColumns.TryGetValue(parent, out var parentVar))
  202. {
  203. parentVar = new ExpressionEditorVariable(parent, props[i], []);
  204. parentColumns.Add(parent, parentVar);
  205. vars.Add(parentVar);
  206. }
  207. vars = parentVar.ChildVariables;
  208. }
  209. vars.Add(item);
  210. }
  211. SortVariables(items);
  212. Variables = items;
  213. InitializeComponent();
  214. }
  215. private void SortVariables(List<ExpressionEditorVariable> vars)
  216. {
  217. vars.Sort((a, b) =>
  218. {
  219. if (a.HasChildren && !b.HasChildren)
  220. {
  221. return -1;
  222. }
  223. else if (!a.HasChildren && b.HasChildren)
  224. {
  225. return 1;
  226. }
  227. return a.Display.CompareTo(b.Display);
  228. });
  229. foreach(var v in vars)
  230. {
  231. SortVariables(v.ChildVariables);
  232. }
  233. }
  234. private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
  235. {
  236. Editor.Focus();
  237. }
  238. private void InsertText(string text)
  239. {
  240. var newCaret = Editor.CaretIndex + text.Length;
  241. Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
  242. Editor.CaretIndex = newCaret;
  243. }
  244. private void AppendText(string text)
  245. {
  246. var oldCaret = Editor.CaretIndex;
  247. Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
  248. Editor.CaretIndex = oldCaret;
  249. }
  250. private void DeleteText(int start, int length)
  251. {
  252. var oldCaret = Editor.CaretIndex;
  253. Editor.Text = Editor.Text.Remove(start, length);
  254. if(oldCaret >= start && oldCaret < start + length)
  255. {
  256. Editor.CaretIndex = start;
  257. }
  258. else
  259. {
  260. Editor.CaretIndex = oldCaret - length;
  261. }
  262. }
  263. private static Regex tagRegex = new("\\{\\S+\\}");
  264. private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);
  265. private bool DeletePlaceholder()
  266. {
  267. if (Editor.CaretIndex >= Editor.Text.Length) return false;
  268. var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
  269. if (tagStart == -1) return false;
  270. var tagMatch = tagRegex.Match(Editor.Text, tagStart);
  271. if (!tagMatch.Success) return false;
  272. var startIndex = tagMatch.Index;
  273. if (startIndex != tagStart) return false;
  274. var length = tagMatch.Length;
  275. if (Editor.CaretIndex >= startIndex + length) return false;
  276. DeleteText(startIndex, length);
  277. return true;
  278. }
  279. private void JumpPlaceholderBackwards()
  280. {
  281. if (Editor.CaretIndex == 0) return;
  282. var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
  283. if (!tagMatch.Success) return;
  284. Editor.CaretIndex = tagMatch.Index;
  285. }
  286. private void JumpPlaceholder(bool allowStart = false)
  287. {
  288. if (Editor.CaretIndex >= Editor.Text.Length) return;
  289. var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;
  290. var tagMatch = tagRegex.Match(Editor.Text, start);
  291. if (!tagMatch.Success) return;
  292. Editor.CaretIndex = tagMatch.Index;
  293. }
  294. private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
  295. {
  296. if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;
  297. var placeholder = DeletePlaceholder();
  298. InsertText($"{template.Name}(");
  299. AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
  300. if (placeholder)
  301. JumpPlaceholder(true);
  302. }
  303. private void Variable_Click(object sender, MouseButtonEventArgs e)
  304. {
  305. if ((sender as FrameworkElement)!.Tag is not string str) return;
  306. DeletePlaceholder();
  307. InsertText($"[{str}]");
  308. }
  309. private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
  310. {
  311. e.Handled = true;
  312. }
  313. private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
  314. {
  315. DeletePlaceholder();
  316. }
  317. private void Editor_KeyDown(object sender, KeyEventArgs e)
  318. {
  319. if(e.Key == Key.Tab)
  320. {
  321. if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
  322. {
  323. JumpPlaceholderBackwards();
  324. }
  325. else
  326. {
  327. JumpPlaceholder();
  328. }
  329. e.Handled = true;
  330. }
  331. }
  332. private void OK_Click(object sender, RoutedEventArgs e)
  333. {
  334. DialogResult = true;
  335. }
  336. }