Newer
Older
EARS-LINUX / program / SettingWindow.cs
using System;
using Gtk;
using System.Collections.Generic;
using UI = Gtk.Builder.ObjectAttribute;

public class SettingsWindow : Window
{
    [UI] private Label      ComPortLabel     = null;
    [UI] private ComboBoxText ComPortComboBox = null;
    [UI] private Button     ConnectButton    = null;
    [UI] private Button     AutoConnectButton = null;
    [UI] private Button     RefreshButton    = null;
    [UI] private Button     BackButton       = null;
    [UI] private Label      StatusLabel      = null;
    [UI] private Label      LogLabel         = null;
    [UI] private TextView   LogtextBox       = null;

    private readonly Dictionary<string, Button> _buttonsById;
    private readonly Dictionary<string, Label> _labelsById;
    internal Language _Language;

    private const int MaxLogLines = 500;

    private readonly ArduinoConnection _arduino;
    private string[] _ports = new string[0];

    /// <summary>Raised after the window hides itself, so the caller can re-show the main window.</summary>
    public event EventHandler BackRequested;

    public SettingsWindow(ArduinoConnection arduino)
        : this(CreateBuilder(), arduino) { }

    private static Builder CreateBuilder()
    {
        var builder = new Builder();
        builder.AddFromFile("homepage.glade");
        return builder;
    }

    private SettingsWindow(Builder builder, ArduinoConnection arduino)
        : base(builder.GetObject("SettingWindow").Handle)
    {
        builder.Autoconnect(this);
        _arduino = arduino;

        Title = "Settings";
        ComPortLabel.Text = "COM Port";
        LogLabel.Text = "Log";
        LogtextBox.Editable = false;
        LogtextBox.WrapMode = WrapMode.WordChar;

        RefreshButton.Clicked     += (s, e) => RefreshPorts();
        ConnectButton.Clicked     += OnConnectClicked;
        AutoConnectButton.Clicked += OnAutoConnectClicked;
        BackButton.Clicked        += OnBackClicked;

        DeleteEvent += (o, a) => { a.RetVal = true; OnBackClicked(o, EventArgs.Empty); };

        _arduino.Log               += AppendLog;
        _arduino.LineReceived      += OnLineReceived;
        _arduino.ConnectionChanged += OnConnectionChanged;

         _buttonsById = new Dictionary<string, Button>
         {
            { "ConnectButton", ConnectButton },
            { "AutoConnectButton", AutoConnectButton },
            { "RefreshButton", RefreshButton },
            { "BackButton",   BackButton   },
         };

        // CSS ID selectors (#Option1Button etc.) need the widget Name set explicitly
        foreach (var kvp in _buttonsById)
            kvp.Value.Name = kvp.Key;
        
        _Language = new Language(_buttonsById);
        _Language.ApplyLanguages(Language.LanguageSwitchValue); 

        RefreshPorts();
        OnConnectionChanged(_arduino.IsOpen);
    }

    // ---------- UI actions ----------

    private void RefreshPorts()
    {
        ComPortComboBox.RemoveAll();
        _ports = ArduinoConnection.ListPorts();

        foreach (string p in _ports)
            ComPortComboBox.AppendText(p);

        if (_ports.Length > 0)
        {
            ComPortComboBox.Active = 0;
            AppendLog($"{_ports.Length} port(s) found");
        }
        else
        {
            AppendLog("No serial ports found");
        }
    }

    private void OnConnectClicked(object sender, EventArgs e)
    {
        if (_arduino.IsOpen)
        {
            _arduino.Disconnect();
            return;
        }

        string port = ComPortComboBox.ActiveText;
        if (string.IsNullOrEmpty(port))
        {
            AppendLog("Select a COM port first");
            return;
        }
        _arduino.Connect(port);
    }

    private async void OnAutoConnectClicked(object sender, EventArgs e)
    {
        AutoConnectButton.Sensitive = false;
        ConnectButton.Sensitive = false;
        AppendLog("Scanning ports for device...");

        string found = await _arduino.DetectPortAsync();

        if (found != null)
        {
            RefreshPorts();
            SelectPort(found);
            _arduino.Connect(found);
        }
        else
        {
            AppendLog("Device not found — use manual connection");
        }

        AutoConnectButton.Sensitive = true;
        ConnectButton.Sensitive = true;
    }

    private void OnBackClicked(object sender, EventArgs e)
    {
        Hide();
        BackRequested?.Invoke(this, EventArgs.Empty);
    }

    private void SelectPort(string portName)
    {
        for (int i = 0; i < _ports.Length; i++)
        {
            if (_ports[i] == portName)
            {
                ComPortComboBox.Active = i;
                return;
            }
        }
    }

    // ---------- device events ----------

    private void OnConnectionChanged(bool connected)
    {
        ConnectButton.Label = connected ? "Disconnect" : "Connect";
        StatusLabel.Text = connected
            ? $"Connected ({_arduino.PortName})"
            : "Disconnected";

        ComPortComboBox.Sensitive = !connected;
        RefreshButton.Sensitive   = !connected;

        StyleContext ctx = StatusLabel.StyleContext;
        ctx.RemoveClass(connected ? "status-off" : "status-on");
        ctx.AddClass(connected ? "status-on" : "status-off");
    }

    private void OnLineReceived(string line)
    {
        // Same filtering Form2.cs applied before parsing.
        if (line.StartsWith("Send Status:")) return;
        if (line.StartsWith("\u2713") || line.StartsWith("\u2717")) return;

        if (line.Contains("ESPNOW_CONNECTED"))    { AppendLog("[ESP-NOW] Peer connected");    return; }
        if (line.Contains("ESPNOW_DISCONNECTED")) { AppendLog("[ESP-NOW] Peer disconnected"); return; }

        AppendLog(line);
    }

    // ---------- log ----------

    public void AppendLog(string message)
    {
        TextBuffer buf = LogtextBox.Buffer;

        TextIter end = buf.EndIter;
        buf.Insert(ref end, $"[{DateTime.Now:HH:mm:ss.fff}] {message}\n");

        if (buf.LineCount > MaxLogLines)
        {
            TextIter start = buf.StartIter;
            TextIter cut = buf.GetIterAtLine(buf.LineCount - MaxLogLines);
            buf.Delete(ref start, ref cut);
        }

        TextMark mark = buf.CreateMark(null, buf.EndIter, false);
        LogtextBox.ScrollToMark(mark, 0, false, 0, 0);
        buf.DeleteMark(mark);
    }
}