using IBKRTrader.Core.Database.Migrations;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modularity;
using IBKRTrader.Core.Settings;
using IBKRTrader.Core.Workers;
using IBKRTrader.UI;
using Microsoft.Extensions.DependencyInjection;
namespace IBKRTrader;
///
/// Launcher – das Basis-Fenster (Shell). Zeigt je registrierter View einen Button, führt beim Start
/// Migrationen aus, startet Module und die WorkerEngine, und trägt das gemeinsame Fenster-Menü.
/// Die inhaltlichen Ansichten (Logs, Settings, Workers, Module) sind eigenständige Fenster.
///
public sealed class LauncherForm : Form
{
private readonly ShellUiHost _uiHost;
private readonly IServiceProvider _services;
private readonly LoggingService _logger;
private readonly WorkerEngine _workerEngine;
private readonly IReadOnlyList _modules;
private readonly Dictionary _viewButtons = new(StringComparer.OrdinalIgnoreCase);
private readonly FlowLayoutPanel _buttonPanel = new()
{
Dock = DockStyle.Fill, Padding = new Padding(16), AutoScroll = true
};
private readonly ToolStripStatusLabel _status = new("Start...");
public LauncherForm(ShellUiHost uiHost, IServiceProvider services)
{
_uiHost = uiHost;
_services = services;
_logger = services.GetRequiredService();
_workerEngine = services.GetRequiredService();
_modules = services.GetServices().ToList();
Text = "IBKRTrader — Launcher";
Width = 720;
Height = 540;
StartPosition = FormStartPosition.CenterScreen;
_uiHost.SetMainWindow(this);
BuildUi();
_uiHost.OpenStateChanged += UpdateButtonStates;
}
private void BuildUi()
{
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new Size(24, 24) };
WindowMenu.Wire(menu, _uiHost, null);
foreach (var view in _uiHost.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
{
var id = view.Id;
var btn = new Button
{
Text = view.Title, Width = 210, Height = 48, Margin = new Padding(8),
TextAlign = ContentAlignment.MiddleLeft, Image = view.Icon,
ImageAlign = ContentAlignment.MiddleLeft, TextImageRelation = TextImageRelation.ImageBeforeText
};
btn.Click += (_, _) => _uiHost.OpenView(id);
_viewButtons[id] = btn;
_buttonPanel.Controls.Add(btn);
}
var statusStrip = new StatusStrip();
statusStrip.Items.Add(_status);
// Reihenfolge: Fill-Panel zuerst, dann Bottom, dann Top (WinForms Dock-Stacking).
Controls.Add(_buttonPanel);
Controls.Add(statusStrip);
Controls.Add(menu);
MainMenuStrip = menu;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_ = StartupAsync();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// Auch das Schließen-X läuft über die Sicherheitsabfrage.
if (!_uiHost.ShutdownConfirmed)
{
e.Cancel = true;
BeginInvoke((Action)(() => _uiHost.RequestShutdown()));
return;
}
_uiHost.CloseAllViews();
try
{
foreach (var module in _modules)
module.StopAsync(default).GetAwaiter().GetResult();
}
catch { /* Shutdown darf nicht am Modul scheitern */ }
_workerEngine.StopAllAsync().GetAwaiter().GetResult();
base.OnFormClosing(e);
}
private async Task StartupAsync()
{
_logger.Info("Core", "=== IBKRTrader startet ===");
_logger.Info("Core", $"Version: 1.0.0 | .NET {Environment.Version}");
// Log-Level aus Settings.
var levelStr = _services.GetRequiredService().Settings.Logging.Level;
if (Enum.TryParse(levelStr, true, out var level))
_logger.SetMinLevel(level);
SetStatus("Migrationen...");
try
{
await _services.GetRequiredService().RunAsync();
await _services.GetRequiredService().RunAsync();
}
catch (Exception ex)
{
_logger.Error("Core", "Core-Datenbankfehler beim Start.", ex);
}
foreach (var module in _modules)
{
try { await module.StartAsync(default); }
catch (Exception ex) { _logger.Error(module.Name, $"{module.Name}: Start fehlgeschlagen.", ex); }
}
await _workerEngine.StartAllAsync();
_logger.Info("Core", "IBKRTrader bereit.");
SetStatus("Bereit");
UpdateButtonStates();
}
private void UpdateButtonStates()
{
if (IsDisposed) return;
if (InvokeRequired) { BeginInvoke((Action)UpdateButtonStates); return; }
foreach (var (id, btn) in _viewButtons)
btn.Font = new Font(btn.Font, _uiHost.IsOpen(id) ? FontStyle.Bold : FontStyle.Regular);
}
private void SetStatus(string text)
{
if (IsDisposed) return;
if (InvokeRequired) { BeginInvoke((Action)(() => SetStatus(text))); return; }
_status.Text = $"Status: {text} | {DateTime.Now:HH:mm:ss}";
}
}