tbf/TBF/Rig/BridgeComponents/GciBridge/UI/MainView.cs

514 lines
16 KiB
C#

using GenesisCordonelInterface.API;
using GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Xylem.Common.Utils.Logging;
using TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge;
using System.Runtime.InteropServices;
namespace TBF.Rig.BridgeComponents.GciBridge.UI
{
/// <summary>
/// Main WinForms UserControl for the GCI Bridge application.
///
/// Responsibilities:
/// - Hosts and switches child UI views
/// - Manages slot configuration panels
/// - Displays global application logs
/// - Buffers log messages to keep UI responsive
/// - Connects UI with GCI bridge APIs
/// </summary>
public partial class MainView : UserControl
{
#region DECLARATION
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface");
private static readonly Regex LogLevelRegex = new Regex(@"\|\s*(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s*\|", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public InterfaceOutsideToGCI _gciApi;
public InterfaceGCIToLaatzen _laatzenApi;
public MainForm _mainform;
public GciBridge _bridge;
public Debug.MeterBatchConfigPanel _batchPanel;
public event Action<List<PublicModels.MeterBatchDebugStatus>> MeterBatchStatusChanged;// object status from place of his location
private const int WM_SETREDRAW = 0x000B;
[DllImport("user32.dll")] //faster ritchbox redrawing
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Thread-safe UI log buffering infrastructure.
///
/// Incoming log messages may arrive from multiple worker threads.
/// Messages are queued and periodically flushed to RichTextBox
/// by a UI timer to prevent UI freezes during parallel operations.
/// </summary>
private readonly object _uiLogLock = new object();
private readonly Queue<string> _pendingUiLogs = new Queue<string>();
private readonly Timer _uiLogFlushTimer = new Timer();
#endregion
public MainView(GciBridge bridge, MainForm mainform)
{
_mainform = mainform;
_bridge = bridge;
_gciApi = bridge.gciExternalInterface;
_laatzenApi = _bridge.gciExternalInterface._innerMeterAPI;
InitializeComponent();
InitializeDebugPanels();
//InitializeWorkerDebugPanel();
UiLogBus.MessageReceived += UiLogBus_MessageReceived; // Nlog messages incoming
rtbMainLog.BackColor = Color.Black;
rtbMainLog.ForeColor = Color.Gainsboro;
rtbMainLog.Font = new Font("Consolas", 9f);
rtbMainLog.ReadOnly = true;
rtbMainLog.HideSelection = false;
//UI htread safe
_uiLogFlushTimer.Interval = 250;
_uiLogFlushTimer.Tick += UiLogFlushTimer_Tick;
_uiLogFlushTimer.Start();
preadjustmentButton.Enabled = true;
groupBox2.Enabled = true;
}
private void UiLogFlushTimer_Tick(object sender, EventArgs e)
{
if (IsDisposed || !IsHandleCreated || rtbMainLog == null || rtbMainLog.IsDisposed)
return;
List<string> messages = new List<string>();
lock (_uiLogLock)
{
while (_pendingUiLogs.Count > 0 && messages.Count < 500)
{
messages.Add(_pendingUiLogs.Dequeue());
}
}
if (messages.Count == 0)
return;
string batchText = string.Join(Environment.NewLine, messages) + Environment.NewLine;
SendMessage(rtbMainLog.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
try
{
int batchStart = rtbMainLog.TextLength;
rtbMainLog.SelectionStart = batchStart;
rtbMainLog.SelectionLength = 0;
rtbMainLog.SelectionColor = Color.Gainsboro;
rtbMainLog.AppendText(batchText);
// Optional: re-apply line highlighting after bulk insertion
//ApplyHighlightingToBatch(batchText, batchStart);
const int maxTextLength = 200000;
if (rtbMainLog.TextLength > maxTextLength)
{
rtbMainLog.Select(0, rtbMainLog.TextLength - maxTextLength);
rtbMainLog.SelectedText = "";
}
rtbMainLog.SelectionStart = rtbMainLog.TextLength;
rtbMainLog.ScrollToCaret();
}
finally
{
SendMessage(rtbMainLog.Handle, WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
rtbMainLog.Invalidate();
}
}
private void ApplyHighlightingToBatch(string batchText, int batchStart)
{
string[] lines = batchText.Replace("\r\n", "\n").Split('\n');
int offset = 0;
foreach (string line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
{
Match m = LogLevelRegex.Match(line);
if (m.Success)
{
rtbMainLog.SelectionStart = batchStart + offset + m.Index;
rtbMainLog.SelectionLength = m.Length;
rtbMainLog.SelectionColor = GetLogLevelColor(m.Groups[1].Value);
}
HighlightKeywordsInLine(line, batchStart + offset);
}
offset += line.Length + Environment.NewLine.Length;
}
}
/// <summary>
/// Initializes debug/configuration panels hosted inside MainView.
/// </summary>
private void InitializeDebugPanels()
{
_batchPanel = new Debug.MeterBatchConfigPanel(_mainform, this)
{
Dock = DockStyle.Fill
};
pnlSlotConfig.Controls.Add(_batchPanel);
pnlWorkerDebug.Controls.Add(new Debug.WorkerDebugPanel(_mainform, this)
{
Dock = DockStyle.Fill
});
}
public void AddSlotRow()
{
_batchPanel?.AddEmptySlotRow();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
BeginInvoke(new Action(() =>
{
SetSafeSplitterDistance(splitWorkArea, 420);
}));
}
/// <summary>
/// Safely adjusts SplitContainer distance while respecting
/// minimum panel sizes and current control dimensions.
/// </summary>
private void SetSafeSplitterDistance(SplitContainer split, int desired)
{
int width = split.ClientSize.Width;
int min1 = split.Panel1MinSize;
int min2 = split.Panel2MinSize;
int splitter = split.SplitterWidth;
int max = width - min2 - splitter;
if (width <= min1 + min2 + splitter)
return;
if (desired < min1)
desired = min1;
if (desired > max)
desired = max;
split.SplitterDistance = desired;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
UiLogBus.MessageReceived -= UiLogBus_MessageReceived;
_uiLogFlushTimer.Stop();
_uiLogFlushTimer.Tick -= UiLogFlushTimer_Tick;
_uiLogFlushTimer.Dispose();
}
base.Dispose(disposing);
}
#region GLOBAL LOGGING to memo in this view
/// <summary>
/// Receives global log messages from UiLogBus.
///
/// This method may be called from background threads.
/// Messages are only queued here and later processed by UI timer.
/// </summary>
void UiLogBus_MessageReceived(string loggerName, string msg)
{
if (loggerName != "GciBridge" && loggerName != "GenesisCordonelInterface")
return;
if (IsDisposed || !IsHandleCreated)
return;
lock (_uiLogLock)
{
_pendingUiLogs.Enqueue(msg);
while (_pendingUiLogs.Count > 1000)
_pendingUiLogs.Dequeue();
}
}
/// <summary>
/// Splits multiline log messages and appends each line separately.
/// </summary>
private void AppendLogMessage(string msg)
{
string[] lines = msg.Replace("\r\n", "\n").Split('\n');
foreach (string originalLine in lines)
{
if (string.IsNullOrWhiteSpace(originalLine))
continue;
AppendStyledLine(originalLine);
}
}
/// <summary>
/// Appends a single styled log line into RichTextBox.
///
/// Performs syntax highlighting for:
/// - log levels
/// - important keywords
/// </summary>
private void AppendStyledLine(string line)
{
if (rtbMainLog == null || rtbMainLog.IsDisposed)
return;
int start = rtbMainLog.TextLength;
rtbMainLog.SelectionStart = start;
rtbMainLog.SelectionLength = 0;
rtbMainLog.SelectionColor = Color.Gainsboro;
rtbMainLog.AppendText(line + Environment.NewLine);
Match m = LogLevelRegex.Match(line);
if (m.Success)
{
rtbMainLog.SelectionStart = start + m.Index;
rtbMainLog.SelectionLength = m.Length;
rtbMainLog.SelectionColor = GetLogLevelColor(m.Groups[1].Value);
}
HighlightKeywordsInLine(line, start);
}
/// <summary>
/// Returns color associated with a log level.
/// </summary>
private Color GetLogLevelColor(string level)
{
switch (level.Trim().ToUpperInvariant())
{
case "TRACE": return Color.Gray;
case "DEBUG": return Color.DeepSkyBlue;
case "INFO": return Color.LimeGreen;
case "WARN": return Color.Orange;
case "ERROR": return Color.Red;
case "FATAL": return Color.Magenta;
default: return Color.Gainsboro;
}
}
private static readonly Tuple<Color, string[]>[] KeywordGroups =
{
Tuple.Create(Color.DeepSkyBlue, new[] { "REQUEST" }),
Tuple.Create(Color.Lime, new[] { "RESPONSE" }),
Tuple.Create(Color.Gold, new[] { "READ-REGISTER-SESSION", "UI-CLICK" })
};
/// <summary>
/// Highlights important keywords inside a log line.
/// </summary>
private void HighlightKeywordsInLine(string line, int lineStartIndex)
{
foreach (var group in KeywordGroups)
{
foreach (var keyword in group.Item2)
{
int index = 0;
while ((index = line.IndexOf(keyword, index, StringComparison.Ordinal)) >= 0)
{
rtbMainLog.SelectionStart = lineStartIndex + index;
rtbMainLog.SelectionLength = keyword.Length;
rtbMainLog.SelectionColor = group.Item1;
index += keyword.Length;
}
}
}
}
#endregion
private IWin32Window DialogOwner
{
get
{
Form owner = FindForm();
return owner ?? (IWin32Window)this;
}
}
private void ShowGciView(Control view)
{
pnlGciViewHost.Controls.Clear();
view.Dock = DockStyle.Fill;
pnlGciViewHost.Controls.Add(view);
}
/// <summary>
/// Saves current slot configuration from the grid into backend storage.
/// </summary>
public void SaveSlots()
{
var data = _batchPanel.GetGridData();
_laatzenApi.SaveSlotSetup(data);
}
#region BUTTONS
private void btnSetup_Click(object sender, EventArgs e)
{
/*Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Setup open");
using (FrmSetup frm = new FrmSetup())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Setup closed.");*/
}
private void btnRegisterStore_Click(object sender, EventArgs e)
{
/*Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Register Store open.");
using (FrmRegisterStore frm = new FrmRegisterStore())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Register Store closed.");*/
}
private void btnPulseSetup_Click(object sender, EventArgs e)
{
/*Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Pulse Setup open.");
using (FrmConfigurations frm = new FrmConfigurations())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Pulse Setup closed.");*/
}
private void preadjustmentButton_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Preadjustment open.");
_laatzenApi.ShowPreadjustmentForm(DialogOwner);
Logger.Trace("FORM: Preadjustment shown.");
}
private void button1_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: GciBridge GUI open.");
using (var frm = new FrmGCIAPI(_gciApi))
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: GciBridge closed.");
}
private void SwitchGciView(string name, Control view)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace($"FORM: GciBridge VIEW -> {name} OPEN");
pnlGciViewHost.Controls.Clear();
view.Dock = DockStyle.Fill;
pnlGciViewHost.Controls.Add(view);
view.BringToFront();
Logger.Trace($"FORM: GciBridge VIEW -> {name} LOADED");
}
private void btnMeterInit_Click(object sender, EventArgs e)
{
SwitchGciView(
"MeterInit",
new MeterInitView(_gciApi, AddSlotRow, SaveSlots));
}
/// <summary>
/// Clears the main UI log window.
/// </summary>
public void ClearLog()
{
rtbMainLog.Clear();
Logger.Trace("Log cleared.");
}
/// <summary>
/// Switches currently displayed GCI view inside the host panel.
/// </summary>
private void button2_Click(object sender, EventArgs e)
{
SwitchGciView(
"CombinedActionsView",
new CombinedActionsView(this, _bridge));
}
private void button1_Click_1(object sender, EventArgs e)
{
SwitchGciView(
"UniDataSorageActionsView",
new UniDataSorageActionsView(this, _bridge));
}
private void btnMetersAction_Click(object sender, EventArgs e)
{
SwitchGciView(
"SlotsComPortsRegistersActionsView",
new SlotsComPortsRegistersActionsView(this, _bridge, AddSlotRow, SaveSlots));
}
private void btnMeterInit_Click_1(object sender, EventArgs e)
{
SwitchGciView(
"ConfigurationView",
new ConfigurationView(this));
}
private void preadjustmentActionsViewButton_Click(object sender, EventArgs e)
{
SwitchGciView(
"PreadjustmentActionsView",
new PreadjustmentActionsView(this, _bridge, AddSlotRow, SaveSlots));
}
}
#endregion
}