555 lines
18 KiB
C#
555 lines
18 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Periodically flushes buffered log messages into RichTextBox.
|
|
///
|
|
/// Runs on the UI thread because WinForms Timer executes on UI thread.
|
|
/// Processes messages in batches to reduce UI overhead.
|
|
/// </summary>
|
|
/// Plynule pridavanie do mema
|
|
/*private void UiLogFlushTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
if (IsDisposed || !IsHandleCreated)
|
|
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;
|
|
|
|
rtbMainLog.SuspendLayout();
|
|
|
|
try
|
|
{
|
|
foreach (string msg in messages)
|
|
{
|
|
AppendLogMessage(msg);
|
|
}
|
|
|
|
const int maxTextLength = 200000;
|
|
|
|
if (rtbMainLog.TextLength > maxTextLength)
|
|
{
|
|
rtbMainLog.Select(0, rtbMainLog.TextLength - maxTextLength);
|
|
rtbMainLog.SelectedText = "";
|
|
}
|
|
|
|
rtbMainLog.SelectionStart = rtbMainLog.TextLength;
|
|
rtbMainLog.ScrollToCaret();
|
|
}
|
|
finally
|
|
{
|
|
rtbMainLog.ResumeLayout();
|
|
}
|
|
}*/
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switches currently displayed GCI view inside the host panel.
|
|
/// </summary>
|
|
private void button2_Click(object sender, EventArgs e)
|
|
{
|
|
SwitchGciView(
|
|
"CombinedInterfaceView",
|
|
new CombinedInterfaceView(this, _bridge));
|
|
}
|
|
|
|
private void button1_Click_1(object sender, EventArgs e)
|
|
{
|
|
SwitchGciView(
|
|
"StorageAction",
|
|
new UniDataStorageReaderInterfaceView(this, _bridge));
|
|
}
|
|
|
|
#region BUTTONS
|
|
|
|
private void btnMetersAction_Click(object sender, EventArgs e)
|
|
{
|
|
SwitchGciView(
|
|
"SlotsMetersAction",
|
|
new GenesisCordonelInterfaceView(this, _bridge, AddSlotRow, SaveSlots));
|
|
}
|
|
|
|
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.");
|
|
|
|
using (FrmCordonelPreadjustmentUI frm = new FrmCordonelPreadjustmentUI())
|
|
{
|
|
frm.ShowDialog(DialogOwner);
|
|
}
|
|
|
|
Logger.Trace("FORM: Preadjustment closed.");*/
|
|
}
|
|
|
|
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.");
|
|
}
|
|
#endregion
|
|
|
|
#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 void btnMeterInit_Click_1(object sender, EventArgs e)
|
|
{
|
|
SwitchGciView(
|
|
"ConfigurationView",
|
|
new ConfigurationView(this));
|
|
}
|
|
}
|
|
} |