using IBKRTrader.Core.Modularity;
namespace IBKRTrader.UI;
///
/// Verwaltet die registrierten Fenster-Views: öffnet sie als eigenständige Forms, hält je View
/// höchstens eine Instanz offen und holt ein bereits offenes Fenster wieder in den Vordergrund.
/// Meldet Änderungen am Offen-Status (für die Button-Markierung im Launcher).
/// Vorbild: PolytraderSharp ShellUiHost.
///
public sealed class ShellUiHost : IModuleUiHost
{
private readonly List _views = new();
private readonly Dictionary _open = new(StringComparer.OrdinalIgnoreCase);
private Form? _mainWindow;
/// True, sobald das Beenden bestätigt wurde (der Launcher wertet das in FormClosing aus).
public bool ShutdownConfirmed { get; private set; }
public event Action? OpenStateChanged;
public IReadOnlyList Views => _views;
public void RegisterView(ModuleView view) => _views.Add(view);
/// Setzt das Hauptfenster (Launcher) – Ziel für .
public void SetMainWindow(Form main) => _mainWindow = main;
public bool IsOpen(string viewId) =>
_open.TryGetValue(viewId, out var form) && !form.IsDisposed;
public void ActivateMain()
{
if (_mainWindow is null || _mainWindow.IsDisposed) return;
if (_mainWindow.WindowState == FormWindowState.Minimized)
_mainWindow.WindowState = FormWindowState.Normal;
_mainWindow.BringToFront();
_mainWindow.Activate();
}
/// Sicherheitsabfrage; bei Bestätigung wird die Message-Loop beendet.
public void RequestShutdown()
{
if (ShutdownConfirmed) return;
var owner = _mainWindow is { IsDisposed: false } ? _mainWindow : null;
var result = MessageBox.Show(owner,
"IBKRTrader wirklich beenden? Laufende Worker/Services werden gestoppt.",
"Beenden", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
if (result != DialogResult.OK) return;
ShutdownConfirmed = true;
Application.Exit();
}
public void OpenView(string viewId)
{
var view = _views.FirstOrDefault(v => v.Id == viewId);
if (view is not null) OpenView(view);
}
private void OpenView(ModuleView view)
{
if (_open.TryGetValue(view.Id, out var existing) && !existing.IsDisposed)
{
if (existing.WindowState == FormWindowState.Minimized)
existing.WindowState = FormWindowState.Normal;
existing.BringToFront();
existing.Activate();
return;
}
// Der Core-Contract ist toolkit-neutral (CreateView liefert object). Ein anderer Typ als
// Form ist ein Programmierfehler und soll laut scheitern, nicht still ein leeres Fenster geben.
var form = (Form)view.CreateView();
if (string.IsNullOrEmpty(form.Text) || form.Text == form.Name)
form.Text = view.Title;
// Gemeinsames „Fenster"-Menü in jedes Fenster injizieren (identische Shell-Chrome).
if (form.MainMenuStrip is null)
AttachWindowMenu(form, view.Id);
_open[view.Id] = form;
form.FormClosed += (_, _) =>
{
_open.Remove(view.Id);
OpenStateChanged?.Invoke();
};
form.Show();
OpenStateChanged?.Invoke();
}
private void AttachWindowMenu(Form form, string currentViewId)
{
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new Size(24, 24) };
WindowMenu.Wire(menu, this, currentViewId);
form.Controls.Add(menu);
form.MainMenuStrip = menu;
}
/// Schließt alle offenen View-Fenster (beim Herunterfahren).
public void CloseAllViews()
{
foreach (var form in _open.Values.ToList())
if (!form.IsDisposed)
form.Close();
_open.Clear();
}
}