using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace AppDiagnostic { public sealed class ColorRuleEditorForm : Form { private readonly List rules; private readonly ListBox ruleList = new ListBox(); private readonly TextBox patternTextBox = new TextBox(); private readonly CheckBox enabledCheckBox = new CheckBox(); private readonly CheckBox boldCheckBox = new CheckBox(); private readonly CheckBox backgroundCheckBox = new CheckBox(); private readonly Button foregroundButton = new Button(); private readonly Button backgroundButton = new Button(); private readonly RichTextBox previewBox = new RichTextBox(); private readonly Font previewBoldFont; private bool loadingRule; public ColorRuleEditorForm(IEnumerable sourceRules) { rules = sourceRules.Select(rule => rule.Clone()).ToList(); InitializeWindow(); previewBoldFont = new Font(previewBox.Font, FontStyle.Bold); RefreshRuleList(0); RefreshPreview(); } public List Rules => rules.Select(rule => rule.Clone()).ToList(); protected override void Dispose(bool disposing) { if (disposing) { previewBoldFont.Dispose(); } base.Dispose(disposing); } private void InitializeWindow() { Text = "Log color rules"; Icon = Properties.Resources.TBF_icon; StartPosition = FormStartPosition.CenterParent; MinimumSize = new Size(760, 500); ClientSize = new Size(860, 560); var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3, Padding = new Padding(10) }; root.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 145F)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 42F)); var split = new SplitContainer { Dock = DockStyle.Fill, SplitterDistance = 280 }; split.Panel1.Padding = new Padding(0, 0, 8, 0); split.Panel2.Padding = new Padding(8, 0, 0, 0); ruleList.Dock = DockStyle.Fill; ruleList.IntegralHeight = false; ruleList.SelectedIndexChanged += RuleList_SelectedIndexChanged; var listButtons = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 36, FlowDirection = FlowDirection.LeftToRight }; listButtons.Controls.Add(CreateButton("Add", AddRule_Click)); listButtons.Controls.Add(CreateButton("Delete", DeleteRule_Click)); listButtons.Controls.Add(CreateButton("Up", MoveUp_Click)); listButtons.Controls.Add(CreateButton("Down", MoveDown_Click)); split.Panel1.Controls.Add(ruleList); split.Panel1.Controls.Add(listButtons); var editor = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, ColumnCount = 2, Padding = new Padding(4) }; editor.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F)); editor.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); enabledCheckBox.Text = "Rule enabled"; enabledCheckBox.AutoSize = true; enabledCheckBox.CheckedChanged += EditorValueChanged; AddEditorRow(editor, "State", enabledCheckBox); patternTextBox.Dock = DockStyle.Fill; patternTextBox.TextChanged += EditorValueChanged; AddEditorRow(editor, "Substring", patternTextBox); foregroundButton.Text = "Choose text color..."; foregroundButton.AutoSize = true; foregroundButton.Click += ForegroundButton_Click; AddEditorRow(editor, "Text color", foregroundButton); var backgroundPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true }; backgroundCheckBox.Text = "Use background"; backgroundCheckBox.AutoSize = true; backgroundCheckBox.CheckedChanged += EditorValueChanged; backgroundButton.Text = "Choose..."; backgroundButton.AutoSize = true; backgroundButton.Click += BackgroundButton_Click; backgroundPanel.Controls.Add(backgroundCheckBox); backgroundPanel.Controls.Add(backgroundButton); AddEditorRow(editor, "Background", backgroundPanel); boldCheckBox.Text = "Bold text"; boldCheckBox.AutoSize = true; boldCheckBox.CheckedChanged += EditorValueChanged; AddEditorRow(editor, "Font", boldCheckBox); var resetButton = CreateButton("Restore default rules", RestoreDefaults_Click); resetButton.AutoSize = true; AddEditorRow(editor, string.Empty, resetButton); split.Panel2.Controls.Add(editor); previewBox.Dock = DockStyle.Fill; previewBox.ReadOnly = true; previewBox.WordWrap = true; previewBox.Font = new Font("Consolas", 9F); previewBox.Text = "2026-09-10 14:25:31.042|DEBUG|TBF[14280]|TBF.UI.MainWnd|Device configuration loaded.\n2026-09-10 14:25:31.384|INFO |TBF[14280]|TBF.Rig.StateMachine|Test bench started.\n2026-09-10 14:25:32.018|WARN |TBF[14280]|TBF.Device|Pressure is approaching the limit.\n2026-09-10 14:25:32.611|ERROR|TBF[14280]|TBF.Device|Communication failed."; var previewGroup = new GroupBox { Text = "Live preview", Dock = DockStyle.Fill, Padding = new Padding(8) }; previewGroup.Controls.Add(previewBox); var footer = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft, Padding = new Padding(0, 8, 0, 0) }; var okButton = new Button { Text = "OK", DialogResult = DialogResult.OK, AutoSize = true }; var cancelButton = new Button { Text = "Cancel", DialogResult = DialogResult.Cancel, AutoSize = true }; footer.Controls.Add(okButton); footer.Controls.Add(cancelButton); AcceptButton = okButton; CancelButton = cancelButton; root.Controls.Add(split, 0, 0); root.Controls.Add(previewGroup, 0, 1); root.Controls.Add(footer, 0, 2); Controls.Add(root); } private static Button CreateButton(string text, EventHandler clickHandler) { var button = new Button { Text = text, AutoSize = true }; button.Click += clickHandler; return button; } private static void AddEditorRow(TableLayoutPanel panel, string labelText, Control control) { int row = panel.RowCount++; panel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); var label = new Label { Text = labelText, AutoSize = true, Anchor = AnchorStyles.Left, Margin = new Padding(3, 8, 3, 3) }; control.Margin = new Padding(3, 5, 3, 5); panel.Controls.Add(label, 0, row); panel.Controls.Add(control, 1, row); } private void RefreshRuleList(int selectedIndex) { ruleList.BeginUpdate(); ruleList.Items.Clear(); foreach (LogColorRule rule in rules) { ruleList.Items.Add(rule); } ruleList.EndUpdate(); if (rules.Count > 0) { ruleList.SelectedIndex = Math.Max(0, Math.Min(selectedIndex, rules.Count - 1)); } } private void RuleList_SelectedIndexChanged(object sender, EventArgs e) { LogColorRule rule = ruleList.SelectedItem as LogColorRule; if (rule == null) { return; } loadingRule = true; enabledCheckBox.Checked = rule.Enabled; patternTextBox.Text = rule.Pattern; foregroundButton.BackColor = rule.ForeColor; backgroundCheckBox.Checked = rule.UseBackColor; backgroundButton.Enabled = rule.UseBackColor; backgroundButton.BackColor = rule.UseBackColor ? rule.BackColor : SystemColors.Control; boldCheckBox.Checked = rule.Bold; loadingRule = false; } private void EditorValueChanged(object sender, EventArgs e) { if (loadingRule) { return; } LogColorRule rule = ruleList.SelectedItem as LogColorRule; if (rule == null) { return; } rule.Enabled = enabledCheckBox.Checked; rule.Pattern = patternTextBox.Text; rule.UseBackColor = backgroundCheckBox.Checked; rule.Bold = boldCheckBox.Checked; backgroundButton.Enabled = rule.UseBackColor; int index = ruleList.SelectedIndex; RefreshRuleList(index); RefreshPreview(); } private void ForegroundButton_Click(object sender, EventArgs e) { LogColorRule rule = ruleList.SelectedItem as LogColorRule; if (rule == null) { return; } using (var dialog = new ColorDialog { Color = rule.ForeColor, FullOpen = true }) { if (dialog.ShowDialog(this) != DialogResult.OK) { return; } rule.ForeColorArgb = dialog.Color.ToArgb(); foregroundButton.BackColor = dialog.Color; RefreshPreview(); } } private void BackgroundButton_Click(object sender, EventArgs e) { LogColorRule rule = ruleList.SelectedItem as LogColorRule; if (rule == null) { return; } Color initialColor = rule.UseBackColor ? rule.BackColor : SystemColors.Window; using (var dialog = new ColorDialog { Color = initialColor, FullOpen = true }) { if (dialog.ShowDialog(this) != DialogResult.OK) { return; } rule.BackColorArgb = dialog.Color.ToArgb(); rule.UseBackColor = true; backgroundCheckBox.Checked = true; backgroundButton.BackColor = dialog.Color; RefreshPreview(); } } private void AddRule_Click(object sender, EventArgs e) { rules.Add(new LogColorRule { Pattern = "NEW SUBSTRING", ForeColorArgb = Color.MediumPurple.ToArgb() }); RefreshRuleList(rules.Count - 1); patternTextBox.Focus(); patternTextBox.SelectAll(); } private void DeleteRule_Click(object sender, EventArgs e) { int index = ruleList.SelectedIndex; if (index < 0) { return; } rules.RemoveAt(index); RefreshRuleList(Math.Min(index, rules.Count - 1)); RefreshPreview(); } private void MoveUp_Click(object sender, EventArgs e) { MoveSelectedRule(-1); } private void MoveDown_Click(object sender, EventArgs e) { MoveSelectedRule(1); } private void MoveSelectedRule(int offset) { int index = ruleList.SelectedIndex; int newIndex = index + offset; if (index < 0 || newIndex < 0 || newIndex >= rules.Count) { return; } LogColorRule rule = rules[index]; rules.RemoveAt(index); rules.Insert(newIndex, rule); RefreshRuleList(newIndex); RefreshPreview(); } private void RestoreDefaults_Click(object sender, EventArgs e) { rules.Clear(); rules.AddRange(LogColorRule.CreateDefaults()); RefreshRuleList(0); RefreshPreview(); } private void RefreshPreview() { int selectionStart = previewBox.SelectionStart; int selectionLength = previewBox.SelectionLength; string text = previewBox.Text; LogColorizer.Apply(previewBox, 0, text.Length, text, rules, previewBox.Font, previewBoldFont); previewBox.Select(Math.Min(selectionStart, previewBox.TextLength), Math.Min(selectionLength, Math.Max(0, previewBox.TextLength - selectionStart))); } } }