using Common.AppSettings; using Common.Logging; using Common.Helpers; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MultiTerm.Core.Factories; using System.Collections.ObjectModel; using MultiTerm.Core.Types; using MultiTerm.Protocols.Types; using CommunityToolkit.Mvvm.Messaging; using Common.Messaging; using System.Timers; namespace MultiTerm.Core.ViewModel; public partial class ShellViewModel : ObservableObject, IRecipient { private const string defaultReceiveNewlineSeparatorAppSettingsKey = "DefaultReceiveNewlineSeparator"; private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator"; private const int statusBarMessageDurationMs = 12000; // how long the status bar shall be displayed #region Terminal collection [ObservableProperty] private ObservableCollection terminalViewModels = new(); [ObservableProperty] private ITerminalViewModel? selectedTerminalViewModel; #endregion #region Settings Menu Bar [ObservableProperty] private NewlineSeparatorType defaultReceiveNewlineSeparator = NewlineSeparatorType.None; [ObservableProperty] private NewlineSeparatorType defaultSendNewlineSeparator = NewlineSeparatorType.None; #endregion #region New Terminal Context Menu [ObservableProperty] private TerminalViewType selectedTerminalViewType = TerminalViewType.SendReceive; #endregion #region Status Bar private System.Timers.Timer? statusBarMessageTimer; private const int statusBarMessageTimerIntervalMs = 100; // After x milliseconds the timer shall be called [ObservableProperty] private string statusBarMessage = ""; [ObservableProperty] private MessageImportance statusBarMessageImportance = MessageImportance.Normal; [ObservableProperty] private double statusBarMessageDurationPercentage = 0; #endregion private readonly ITerminalViewModelFactory terminalViewModelFactory; private readonly ILogger logger; private readonly IAppSettingsProvider appSettings; private readonly IMessenger messenger; public ShellViewModel(ITerminalViewModelFactory terminalViewModelFactory, ILogger logger, IAppSettingsProvider appSettings, IMessenger messenger) { this.terminalViewModelFactory = terminalViewModelFactory; this.logger = logger; this.appSettings = appSettings; this.messenger = messenger; // intialize values from app settings this.LoadFromAppSettings(); // register to messages this.messenger.Register(this); } /// /// Forcefully close all s. /// public void Teardown() { // iterate through all terminals foreach (ITerminalViewModel terminal in this.TerminalViewModels) { // forcefully close terminal.ForceClose(); // do not remove from collection, otherwise the collection was changed } } [RelayCommand] private void AppendTerminalWithSelectedViewType(ProtocolType protocolType) { this.logger.LogInfo($"Adding new Terminal with ViewType '{this.SelectedTerminalViewType}' and ProtocolType '{protocolType}'", nameof(ShellViewModel)); this.AppendConfiguredTerminal(this.terminalViewModelFactory.Create(this.SelectedTerminalViewType, protocolType)); } private void AppendConfiguredTerminal(ITerminalViewModel? newTerminal) { // guard null value if(newTerminal == null) { return; } // add to collection and register closing event handler this.TerminalViewModels.Add(newTerminal); newTerminal.ClosingEvent += Terminal_ClosingEvent; // set as selected this.SelectedTerminalViewModel = newTerminal; } private void Terminal_ClosingEvent(object? sender, EventArgs e) { if(sender is not ITerminalViewModel tvm) { throw new ArgumentException($"{nameof(Terminal_ClosingEvent)} failed to convert sender to {nameof(ITerminalViewModel)}"); } this.RemoveTerminal(tvm); } private void RemoveTerminal(ITerminalViewModel terminalToRemove) { // guard null value if(terminalToRemove == null) { return; } this.TerminalViewModels.Remove(terminalToRemove); } #region App Settings handling private void LoadFromAppSettings() { // initialize newline separators from persistent settings this.appSettings.TryReadSettingOrAddDefault(defaultReceiveNewlineSeparatorAppSettingsKey, out string settingValueReceiveNLSep, NewlineSeparatorType.None.ToString()); this.DefaultReceiveNewlineSeparator = EnumHelpers.ParseEnum(settingValueReceiveNLSep); this.appSettings.TryReadSettingOrAddDefault(defaultSendNewlineSeparatorAppSettingsKey, out string settingValueSendNLSep, NewlineSeparatorType.None.ToString()); this.DefaultSendNewlineSeparator = EnumHelpers.ParseEnum(settingValueSendNLSep); } partial void OnDefaultReceiveNewlineSeparatorChanged(NewlineSeparatorType value) { // update setting in app settings this.appSettings.WriteSetting(defaultReceiveNewlineSeparatorAppSettingsKey, value.ToString()); } partial void OnDefaultSendNewlineSeparatorChanged(NewlineSeparatorType value) { // update setting in app settings this.appSettings.WriteSetting(defaultSendNewlineSeparatorAppSettingsKey, value.ToString()); } #endregion #region Messaging handling public void Receive(IUserInterfaceMessage message) { // reset percentage this.StatusBarMessageDurationPercentage = 100; // display message this.StatusBarMessage = message.ToString(); this.StatusBarMessageImportance = message.Importance; // start timer if(this.statusBarMessageTimer == null) { this.statusBarMessageTimer = new System.Timers.Timer(statusBarMessageTimerIntervalMs); this.statusBarMessageTimer.Elapsed += StatusBarMessageTimer_Elapsed; this.statusBarMessageTimer.AutoReset = true; // restart after elapsed this.statusBarMessageTimer.Enabled = true; // start timer } } private void StatusBarMessageTimer_Elapsed(object? sender, ElapsedEventArgs e) { float differencePercentage = (statusBarMessageDurationMs / statusBarMessageTimerIntervalMs) / 100; // if not yet done if(this.StatusBarMessageDurationPercentage - differencePercentage > 0) { this.StatusBarMessageDurationPercentage -= differencePercentage; return; } // clear message and kill timer when finished this.StatusBarMessage = ""; this.StatusBarMessageDurationPercentage = 0; this.statusBarMessageTimer!.Enabled = false; this.statusBarMessageTimer.Dispose(); this.statusBarMessageTimer = null; } #endregion }