HamBook – Rev 59

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Media;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using HamBook.Properties;
using HamBook.Radios;
using HamBook.Radios.Generic;
using HamBook.Utilities;
using HamBook.Utilities.Controls;
using HamBook.Utilities.Serialization;
using NetSparkleUpdater;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using RJCP.IO.Ports;
using Serilog;
using PowerState = HamBook.Radios.Generic.PowerState;

namespace HamBook
{
    public partial class Form1 : Form
    {
        private readonly CancellationToken _cancellationToken;
        private readonly CancellationTokenSource _cancellationTokenSource;
        private readonly ScheduledContinuation _changedConfigurationContinuation;

        private readonly ConcurrentDictionary<string, MemoryChannel> _memoryChannelStore =
            new ConcurrentDictionary<string, MemoryChannel>();

        private readonly LogMemorySink _memorySink;
        private readonly SparkleUpdater _sparkle;
        private readonly ScheduledContinuation _squelchScheduledContinuation;
        private AboutForm _aboutForm;
        private BandScan _bandScan;
        private CatAssemblies _catAssemblies;
        private MemoryBanks _memoryBanks;
        private MemoryOrganizerForm _memoryOrganizerForm;
        private MemoryTune _memoryTune;
        private MenuForm _menuForm;
        private ScheduledContinuation _powerScheduledContinuation;
        private SerialPortStream _serialPort;
        private SettingsForm _settingsForm;
        private SpectrogramForm _spectrogramForm;
        private string _storedMemoryChannelLocation;
        private string _storedMemoryChannelTagText;
        private CancellationToken _tagTickerCancellationToken;

        private CancellationTokenSource _tagTickerCancellationTokenSource;
        private Task _tickerTask;
        private volatile bool _tickerTaskRunning;
        private MemoryChannel _tickerTextMemoryChannel;
        private ViewLogsForm _viewLogsForm;

        private Form1()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            _changedConfigurationContinuation = new ScheduledContinuation();

            _squelchScheduledContinuation = new ScheduledContinuation();
            _powerScheduledContinuation = new ScheduledContinuation();
        }

        public Form1(Mutex mutex) : this()
        {
            InitializeComponent();

            _memorySink = new LogMemorySink();

            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink))
                .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
                    rollingInterval: RollingInterval.Day)
                .CreateLogger();

            // Start application update.
            var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
            var icon = Icon.ExtractAssociatedIcon(manifestModuleName);

            _sparkle = new SparkleUpdater("https://hambook.grimore.org/update/appcast.xml",
                new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
            {
                UIFactory = new UIFactory(icon),
                RelaunchAfterUpdate = true,
                SecurityProtocolType = SecurityProtocolType.Tls12
            };
            _sparkle.StartLoop(true, true);
        }

        private Configuration.Configuration Configuration { get; set; }

        public bool MemorySinkEnabled { get; set; }

        private async void Form1_Load(object sender, EventArgs e)
        {
            Configuration = await LoadConfiguration();

            _memoryBanks = MemoryBanks.Create(Configuration.Radio);

            _serialPort = InitializeSerialPort(Configuration);

            _catAssemblies = InitializeAssemblies(_serialPort);

            // Initialize power state.
            try
            {
                switch (await _catAssemblies.CatReadAsync<PowerState>("PS", new object[] { }, _cancellationToken))
                {
                    case PowerState.ON:
                        Log.Information(Resources.Attempting_to_initialize_radio);
                        if (!await InitializeRadio()) return;

                        Log.Information(Resources.Initializing_GUI);
                        break;
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_power_state);
            }

            // Initialize memory banks.
            var memoryBankQueue = new ConcurrentQueue<string>();
            var memoryBankTaskCompletionSource = new TaskCompletionSource<bool>();

            async void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
            {
                await memoryBankTaskCompletionSource.Task;

                try
                {
                    if (!memoryBankQueue.TryDequeue(out var memoryBank))
                    {
                        Application.Idle -= IdleHandler;

                        return;
                    }

                    scrollableToolStripComboBox12.Items.Add(memoryBank);

                    var memoryChannel = MemoryChannel.Create(Configuration.Radio);

                    try
                    {
                        memoryChannel = await _catAssemblies.CatReadAsync<MemoryChannel>("MT",
                            new object[] { memoryBank }, _cancellationToken);

                        scrollableToolStripComboBox5.Items.Add(memoryBank);
                    }
                    catch (Exception exception)
                    {
                        Log.Warning(exception, Resources.Could_not_read_memory_bank);

                        return;
                    }

                    _memoryChannelStore.TryAdd(memoryBank, memoryChannel);
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Could_not_update_data_grid_view);
                }
            }

            Application.Idle += IdleHandler;
            try
            {
                foreach (var memoryBank in _memoryBanks.GetMemoryBanks()) memoryBankQueue.Enqueue(memoryBank);

                memoryBankTaskCompletionSource.TrySetResult(true);
            }
            catch (Exception exception)
            {
                Application.Idle -= IdleHandler;

                Log.Error(exception, Resources.Unable_to_read_memory_banks);
            }

            // Initialize lock state.
            try
            {
                var lockState =
                    await _catAssemblies.CatReadAsync<LockState>("LK", new object[] { }, _cancellationToken);
                switch (lockState)
                {
                    case LockState.OFF:
                        lockToolStripMenuItem.Checked = false;
                        break;
                    case LockState.ON:
                        lockToolStripMenuItem.Checked = true;
                        break;
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_lock_state);
            }
        }

        private async void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_bandScan != null)
            {
                _bandScan.Stop();
                _bandScan = null;
            }

            if (_memoryTune != null)
            {
                _memoryTune.Stop();
                _memoryTune = null;
            }

            // Save configuration on quit.
            await SaveConfiguration();

            Close();
        }

        private void viewLogsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_viewLogsForm != null) return;

            _viewLogsForm = new ViewLogsForm(this, _memorySink, _cancellationToken);
            _viewLogsForm.Closing += ViewLogsForm_Closing;
            _viewLogsForm.Show();
        }

        private void ViewLogsForm_Closing(object sender, CancelEventArgs e)
        {
            if (_viewLogsForm == null) return;

            _viewLogsForm.Closing -= ViewLogsForm_Closing;
            _viewLogsForm.Close();
            _viewLogsForm = null;
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_aboutForm != null) return;

            _aboutForm = new AboutForm();
            _aboutForm.Closing += AboutForm_Closing;
            _aboutForm.Show();
        }

        private void AboutForm_Closing(object sender, CancelEventArgs e)
        {
            if (_aboutForm == null) return;

            _aboutForm.Dispose();
            _aboutForm = null;
        }

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_settingsForm != null) return;

            _settingsForm = new SettingsForm(Configuration, _cancellationToken);
            _settingsForm.Closing += SettingsForm_Closing;
            _settingsForm.Show();
        }

        private void SettingsForm_Closing(object sender, CancelEventArgs e)
        {
            if (_settingsForm == null) return;

            if (_settingsForm.SaveOnClose)
                // Commit the configuration.
                _changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
                    async () =>
                    {
                        await SaveConfiguration();

                        if (_bandScan != null)
                        {
                            _bandScan.Stop();
                            _bandScan = null;
                        }

                        if (_memoryTune != null)
                        {
                            _memoryTune.Stop();
                            _memoryTune = null;
                        }

                        _memoryBanks = MemoryBanks.Create(Configuration.Radio);

                        Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);

                        _serialPort = InitializeSerialPort(Configuration);

                        _catAssemblies = InitializeAssemblies(_serialPort);

                        try
                        {
                            if (await _catAssemblies.CatReadAsync<PowerState>("PS", new object[] { },
                                    _cancellationToken) == PowerState.ON)
                            {
                                Log.Information(Resources.Attempting_to_initialize_radio);
                                if (!await InitializeRadio()) return;
                                Log.Information(Resources.Initializing_GUI);
                            }
                        }
                        catch (Exception exception)
                        {
                            Log.Error(exception, Resources.Failed_to_read_power_state);
                        }
                    }, _cancellationToken);

            _settingsForm.Closing -= SettingsForm_Closing;
            _settingsForm.Dispose();
            _settingsForm = null;
        }

        public async Task SaveConfiguration()
        {
            if (!Directory.Exists(Constants.UserApplicationDirectory))
                Directory.CreateDirectory(Constants.UserApplicationDirectory);

            switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration",
                        "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
                        CancellationToken.None))
            {
                case SerializationSuccess<Configuration.Configuration> configuration:
                    Log.Information(Resources.Configuration_serialized_successfully);
                    break;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception.Message, Resources.Configuration_failed_to_serialize);
                    break;
            }
        }

        public static async Task<Configuration.Configuration> LoadConfiguration()
        {
            if (!Directory.Exists(Constants.UserApplicationDirectory))
                Directory.CreateDirectory(Constants.UserApplicationDirectory);

            var deserializationResult =
                await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
                    Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);

            switch (deserializationResult)
            {
                case SerializationSuccess<Configuration.Configuration> serializationSuccess:
                    return serializationSuccess.Result;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception, Resources.Configuration_failed_to_deserialize);
                    return new Configuration.Configuration();
                default:
                    return new Configuration.Configuration();
            }
        }

        private async Task<bool> InitializeRadio()
        {
            try
            {
                await _catAssemblies.CatWriteAsync<InformationState>("AI", new object[] { InformationState.OFF },
                    _cancellationToken);

                return await _catAssemblies.CatReadAsync<bool>("ID", new object[] { }, _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Unable_to_initialize_radio);

                return false;
            }
        }

        private CatAssemblies InitializeAssemblies(SerialPortStream serialPort)
        {
            if (_catAssemblies != null)
            {
                _catAssemblies.Dispose();
                _catAssemblies = null;
            }

            return new CatAssemblies(serialPort, Configuration.Radio);
        }

        private SerialPortStream InitializeSerialPort(Configuration.Configuration configuration)
        {
            if (_serialPort != null)
            {
                if (_serialPort.IsOpen) _serialPort.Close();

                _serialPort.Dispose();
                _serialPort = null;
            }

            // Set up serial connection.
            var serialPort = new SerialPortStream(configuration.Port, configuration.Speed, configuration.DataBits,
                configuration.Parity, configuration.StopBits);
            serialPort.ReadTimeout = configuration.SerialPortTimeout.Read;
            serialPort.WriteTimeout = configuration.SerialPortTimeout.Write;
            serialPort.Handshake = configuration.Handshake;
            serialPort.Encoding = Encoding.ASCII;

            Log.Information(
                $"{Resources.Initialized_serial_port} {configuration.Port} {configuration.Speed} {configuration.Parity} {configuration.DataBits} {configuration.StopBits}");

            return serialPort;
        }

        private async void updateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Manually check for updates, this will not show a ui
            var result = await _sparkle.CheckForUpdatesQuietly();
            if (result.Status == UpdateStatus.UpdateAvailable)
            {
                // if update(s) are found, then we have to trigger the UI to show it gracefully
                _sparkle.ShowUpdateNeededUI();
                return;
            }

            MessageBox.Show(Resources.No_updates_available_at_this_time, Resources.HamBook, MessageBoxButtons.OK,
                MessageBoxIcon.Asterisk,
                MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
        }

        private async void onToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatSetAsync<PowerState, bool>("PS", new object[] { PowerState.ON },
                    _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_power_state);
            }
        }

        private async void offToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatSetAsync<PowerState, bool>("PS", new object[] { PowerState.OFF },
                    _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_power_state);
            }
        }

        private async void scrollableToolStripComboBox2_MouseWheel(object sender, MouseEventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;
            if (int.TryParse(toolStripComboBox.Text, out var frequency))
            {
                switch (Math.Sign(e.Delta))
                {
                    case -1:
                        frequency = frequency - Configuration.Navigation.FrequencyStep;
                        break;
                    case 1:
                        frequency = frequency + Configuration.Navigation.FrequencyStep;
                        break;
                }

                try
                {
                    using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                               .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                    {
                        if (!await _catAssemblies.CatSetAsync<int, bool>("FB", new object[] { frequency },
                                _cancellationToken))
                            return;
                        toolStripComboBox.Text = $"{frequency}";

                        if (Configuration.Navigation.MouseScrollSound) soundPlayer.Play();
                    }
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_VFO_B_frequency);
                }
            }
        }

        private async void scrollableToolStripComboBox2_KeyPress(object sender, KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case (char)Keys.Enter:
                    var toolStripComboBox = (ScrollableToolStripComboBox)sender;

                    if (int.TryParse(toolStripComboBox.Text, out var frequency))
                        try
                        {
                            using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                                       .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                            {
                                if (await _catAssemblies.CatSetAsync<int, bool>("FB", new object[] { frequency },
                                        _cancellationToken))
                                {
                                    e.Handled = true;

                                    if (Configuration.Navigation.MouseScrollSound) soundPlayer.Play();
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            Log.Error(exception, Resources.Failed_to_set_VFO_B_frequency);
                        }

                    break;
            }
        }

        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            if (_bandScan == null) return;

            _bandScan.Stop();
            _bandScan = null;
        }

        private void toolStripMenuItem38_Click(object sender, EventArgs e)
        {
            if (_memoryTune != null)
            {
                _memoryTune.Stop();
                _memoryTune = null;
            }

            _memoryTune = new MemoryTune(_catAssemblies, _serialPort, Configuration);
            _memoryTune.Start();
        }

        private void stopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_memoryTune == null) return;

            _memoryTune.Stop();
            _memoryTune = null;
        }

        private void scanToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!(sender is ToolStripMenuItem toolStripMenuItem) ||
                !int.TryParse(toolStripMenuItem.Tag.ToString(), out var meters))
                return;

            if (!int.TryParse(scrollableToolStripComboBox3.Text, out var pause)) pause = 5;

            if (!int.TryParse(scrollableToolStripComboBox4.Text, out var step)) step = 5000;

            if (!int.TryParse(scrollableToolStripComboBox6.Text, out var scanDetectPause)) scanDetectPause = 10;

            if (!Configuration.Definitions.TryGetBand(meters, out var band)) return;

            if (_bandScan != null)
            {
                _bandScan.Stop();
                _bandScan = null;
            }

            _bandScan = new BandScan(_catAssemblies, (int)band.Min, (int)band.Max, _serialPort, Configuration);

            if (toolStripMenuItem14.Checked)
            {
                _bandScan.Start(step, pause, scanDetectPause, toolStripMenuItem16.Checked);

                return;
            }

            _bandScan.Start(step, pause, 0, toolStripMenuItem16.Checked);
        }

        private async void modeToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
        {
            try
            {
                var mode = await _catAssemblies.CatReadAsync<RadioMode>("MD", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip => { toolStripComboBox1.Text = mode.Name; });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_radio_mode);
            }

            try
            {
                var fa = await _catAssemblies.CatReadAsync<int>("FA", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox1.Text = $"{fa}";
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_A);
            }

            try
            {
                var fb = await _catAssemblies.CatReadAsync<int>("FB", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox2.Text = $"{fb}";
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_B);
            }

            try
            {
                var mc = await _catAssemblies.CatReadAsync<string>("MC", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox5.Text = mc;

                    if (_memoryChannelStore.TryGetValue(mc, out var memoryChannel))
                        toolStripMenuItem27.Text = memoryChannel.Text;
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_memory_channel);
            }

            try
            {
                var pc = await _catAssemblies.CatReadAsync<int>("PC", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox7.Text = $"{pc}";
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_power_state);
            }

            try
            {
                var sq = await _catAssemblies.CatReadAsync<int>("SQ", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox11.Text = $"{sq}";
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_squelch);
            }

            try
            {
                var st = await _catAssemblies.CatReadAsync<SplitState>("ST", new object[] { }, _cancellationToken);

                contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                {
                    scrollableToolStripComboBox8.Text = $"{(string)st}";
                });
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_split_state);
            }

            try
            {
                var lockState =
                    await _catAssemblies.CatReadAsync<LockState>("LK", new object[] { }, _cancellationToken);
                switch (lockState)
                {
                    case LockState.OFF:
                        lockToolStripMenuItem.Checked = false;
                        break;
                    case LockState.ON:
                        lockToolStripMenuItem.Checked = true;
                        break;
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_lock_state);
            }
        }

        private void spectrogramToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_spectrogramForm != null) return;

            _spectrogramForm = new SpectrogramForm(Configuration, _cancellationToken);
            _spectrogramForm.Closing += SpectrogramForm_Closing;
            _spectrogramForm.Show();
        }

        private void SpectrogramForm_Closing(object sender, CancelEventArgs e)
        {
            if (_spectrogramForm == null) return;

            _spectrogramForm.Dispose();
            _spectrogramForm = null;

            // Commit the configuration.
            _changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
                async () => { await SaveConfiguration(); }, _cancellationToken);
        }

        private void toolStripMenuItem3_Click(object sender, EventArgs e)
        {
            if (_memoryOrganizerForm != null) return;

            _memoryOrganizerForm = new MemoryOrganizerForm(Configuration, _catAssemblies, _cancellationToken);
            _memoryOrganizerForm.Closing += MemoryOrganizerForm_Closing;
            _memoryOrganizerForm.Show();
        }

        private void MemoryOrganizerForm_Closing(object sender, CancelEventArgs e)
        {
            if (_memoryOrganizerForm == null) return;

            _memoryOrganizerForm.Dispose();
            _memoryOrganizerForm = null;
        }

        private async void toolStripMenuItem4_Click(object sender, EventArgs e)
        {
            if (_tickerTaskRunning) return;

            var toolStripTextBox = toolStripTextBox6;

            try
            {
                var result =
                    await _catAssemblies.CatReadAsync<MemoryChannel>("MT", new object[] { "001" }, _cancellationToken);

                _tickerTextMemoryChannel = await _catAssemblies.CatReadAsync<MemoryChannel>("MT",
                    new object[] { $"{result.CurrentLocation}" }, _cancellationToken);

                _storedMemoryChannelTagText = _tickerTextMemoryChannel.Text;
                _storedMemoryChannelLocation = _tickerTextMemoryChannel.CurrentLocation;
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Could_not_read_memory_bank);
            }

            var tickerText = $"{toolStripTextBox.Text,-12}";

            _tagTickerCancellationTokenSource = new CancellationTokenSource();
            _tagTickerCancellationToken = _tagTickerCancellationTokenSource.Token;

            var characterQueue = new Queue<char>(12);
            foreach (var i in Enumerable.Range(0, 12))
            {
                var x = tickerText.ElementAtOrDefault(i);

                if (x == default)
                {
                    characterQueue.Enqueue(' ');
                    continue;
                }

                characterQueue.Enqueue(x);
            }

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            _tickerTask = Task.Run(() => CycleText(characterQueue), _cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }

        private async void toolStripMenuItem5_Click(object sender, EventArgs e)
        {
            if (!_tickerTaskRunning) return;

            _tagTickerCancellationTokenSource.Cancel();
            if (_tickerTask != null)
            {
                await _tickerTask;
                _tickerTask = null;
            }

            try
            {
                _tickerTextMemoryChannel.CurrentLocation = $"{_storedMemoryChannelLocation:000}";
                _tickerTextMemoryChannel.Text = $"{_storedMemoryChannelTagText,-12}";

                var success = await _catAssemblies.CatSetAsync<MemoryChannel, bool>("MT",
                    new object[] { _tickerTextMemoryChannel }, _cancellationToken);
                if (!success) Log.Error(Resources.Error_while_restoring_memory_text);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Error_while_restoring_memory_text);
            }
            finally
            {
                _tickerTaskRunning = false;
            }
        }

        private async Task CycleText(Queue<char> characterQueue)
        {
            _tickerTaskRunning = true;
            try
            {
                do
                {
                    var text = string.Join("", characterQueue.OfType<char>());

                    _tickerTextMemoryChannel.Text = text;

                    await _catAssemblies.CatWriteAsync<MemoryChannel>("MT", new object[] { _tickerTextMemoryChannel },
                        _cancellationToken);

                    var x = characterQueue.Dequeue();
                    characterQueue.Enqueue(x);

                    await Task.Delay(250);
                } while (!_tagTickerCancellationToken.IsCancellationRequested);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Error_while_cycling_text);
            }
        }

        private async void scrollableToolStripComboBox5_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;

            var channel = toolStripComboBox.Text;

            if (!string.IsNullOrEmpty(channel))
                if (_memoryChannelStore.TryGetValue(channel, out var memoryChannel))
                    try
                    {
                        using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                                   .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                        {
                            await _catAssemblies.CatWriteAsync<string>("MC", new object[] { channel },
                                _cancellationToken);

                            scrollableToolStripComboBox1.Text = $"{memoryChannel.Frequency}";

                            toolStripMenuItem27.Text = memoryChannel.Text;

                            soundPlayer.Play();
                        }
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_memory_channel);
                    }
        }

        private async void powerToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
        {
            var toolStripMenuItem = toolStripMenuItem11;

            try
            {
                switch (await _catAssemblies.CatReadAsync<PowerState>("PS", new object[] { }, _cancellationToken))
                {
                    case PowerState.ON:
                        toolStripMenuItem.Text = Resources.On;
                        break;
                    case PowerState.OFF:
                        toolStripMenuItem.Text = Resources.Off;
                        break;
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_power_state);
            }
        }

        private async void toolStripMenuItem17_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNER_ON },
                    _cancellationToken);

                await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNING_START },
                    _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_tuning_current_frequency);
            }
        }

        private async void toolStripMenuItem22_CheckStateChanged(object sender, EventArgs e)
        {
            var toolStripMenuItem = (ToolStripMenuItem)sender;
            try
            {
                if (toolStripMenuItem.Checked)
                {
                    await _catAssemblies.CatWriteAsync<IpoState>("PA", new object[] { IpoState.IPO },
                        _cancellationToken);
                    return;
                }

                await _catAssemblies.CatWriteAsync<IpoState>("PA", new object[] { IpoState.AMP }, _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_setting_IPO);
            }
        }

        private async void toolStripMenuItem23_CheckStateChanged(object sender, EventArgs e)
        {
            var toolStripMenuItem = (ToolStripMenuItem)sender;

            try
            {
                if (toolStripMenuItem.Checked)
                {
                    await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNER_ON },
                        _cancellationToken);

                    return;
                }

                await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNER_OFF },
                    _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_setting_the_tuner_state);
            }
        }

        private async void toolStripMenuItem24_Click(object sender, EventArgs e)
        {
            try
            {
                await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNER_ON },
                    _cancellationToken);

                do
                {
                    await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken);

                    try
                    {
                        var tuneState =
                            await _catAssemblies.CatReadAsync<TunerState>("AC", new object[] { }, _cancellationToken);

                        if (tuneState == TunerState.TUNER_ON) break;
                    }
                    catch (Exception)
                    {
                        // retry
                    }
                } while (!_cancellationToken.IsCancellationRequested);

                await _catAssemblies.CatWriteAsync<TunerState>("AC", new object[] { TunerState.TUNING_START },
                    _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Could_not_start_tuning);
            }
        }

        private void toolStripMenuItem21_DropDownOpening(object sender, EventArgs e)
        {
            Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken).ContinueWith(async task =>
            {
                try
                {
                    var ac = await _catAssemblies.CatReadAsync<TunerState>("AC", new object[] { }, _cancellationToken);

                    contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                    {
                        switch (ac)
                        {
                            case TunerState.TUNING_START:
                            case TunerState.TUNER_ON:
                                toolStripMenuItem23.Checked = true;
                                break;
                            case TunerState.TUNER_OFF:
                                toolStripMenuItem23.Checked = false;
                                break;
                        }
                    });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_read_the_tuner_state);
                }

                try
                {
                    var pa = await _catAssemblies.CatReadAsync<IpoState>("PA", new object[] { }, _cancellationToken);

                    contextMenuStrip1.InvokeIfRequired(contextMenuStrip =>
                    {
                        switch (pa)
                        {
                            case IpoState.AMP:
                                toolStripMenuItem22.Checked = false;
                                break;
                            case IpoState.IPO:
                                toolStripMenuItem22.Checked = true;
                                break;
                        }
                    });
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_read_IPO);
                }
            }, _cancellationToken);
        }

        private async void scrollableToolStripComboBox1_MouseWheel(object sender, MouseEventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;
            if (int.TryParse(toolStripComboBox.Text, out var frequency))
            {
                switch (Math.Sign(e.Delta))
                {
                    case -1:
                        frequency = frequency - Configuration.Navigation.FrequencyStep;
                        break;
                    case 1:
                        frequency = frequency + Configuration.Navigation.FrequencyStep;
                        break;
                }

                try
                {
                    using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                               .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                    {
                        if (!await _catAssemblies.CatSetAsync<int, bool>("FA", new object[] { frequency },
                                _cancellationToken))
                            return;

                        toolStripComboBox.Text = $"{frequency}";

                        if (Configuration.Navigation.MouseScrollSound) soundPlayer.Play();
                    }
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
                }
            }
        }

        private async void scrollableToolStripComboBox1_KeyPress(object sender, KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case (char)Keys.Enter:


                    var toolStripComboBox = (ScrollableToolStripComboBox)sender;

                    if (!int.TryParse(toolStripComboBox.Text, out var frequency)) break;

                    try
                    {
                        using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                                   .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                        {
                            if (await _catAssemblies.CatSetAsync<int, bool>("FA", new object[] { frequency },
                                    _cancellationToken))
                            {
                                e.Handled = true;

                                if (Configuration.Navigation.MouseScrollSound) soundPlayer.Play();
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
                    }

                    break;
            }
        }

        private void scrollableToolStripComboBox11_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;

            _squelchScheduledContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
            {
                contextMenuStrip1.InvokeIfRequired(async contextMenuStrip1 =>
                {
                    if (!int.TryParse(toolStripComboBox.Text, out var squelch)) return;

                    try
                    {
                        await _catAssemblies.CatWriteAsync<int>("SQ", new object[] { squelch }, _cancellationToken);

                        toolStripComboBox.Text = $"{squelch}";

                        Log.Information($"{Resources.Squelch_set} {squelch}");
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_set_squelch);
                    }
                });
            }, _cancellationToken);
        }

        private async void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ToolStripComboBox)sender;

            var radioMode = RadioMode.Create(Configuration.Radio, toolStripComboBox.Text);

            try
            {
                await _catAssemblies.CatSetAsync<RadioMode, bool>("MD", new object[] { radioMode }, _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_radio_mode, radioMode);
            }
        }

        private async void scrollableToolStripComboBox7_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;

            if (int.TryParse(toolStripComboBox.Text, out var amplification))
                try
                {
                    if (await _catAssemblies.CatSetAsync<int, bool>("PC", new object[] { amplification },
                            _cancellationToken))
                    {
                        toolStripComboBox.Text = $"{amplification}";

                        Log.Information($"{Resources.Amplification_set} {amplification}W");
                    }
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Failed_to_set_amplification);
                }
        }

        private async void scrollableToolStripComboBox7_KeyPress(object sender, KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case (char)Keys.Enter:
                    var toolStripComboBox = (ScrollableToolStripComboBox)sender;

                    if (int.TryParse(toolStripComboBox.Text, out var amplification))
                        try
                        {
                            if (await _catAssemblies.CatSetAsync<int, bool>("PC", new object[] { amplification },
                                    _cancellationToken))
                            {
                                toolStripComboBox.Text = $"{amplification}";

                                Log.Information($"{Resources.Amplification_set} {amplification}W");

                                e.Handled = true;
                            }
                        }
                        catch (Exception exception)
                        {
                            Log.Error(exception, Resources.Failed_to_set_amplification);
                        }

                    break;
            }
        }

        private void toolStripMenuItem30_Click(object sender, EventArgs e)
        {
            if (!int.TryParse(scrollableToolStripComboBox9.Text, out var start)
                || !int.TryParse(scrollableToolStripComboBox10.Text, out var stop))
                return;

            if (!int.TryParse(scrollableToolStripComboBox3.Text, out var pause)) pause = 5;

            if (!int.TryParse(scrollableToolStripComboBox4.Text, out var step)) step = 5000;

            if (!int.TryParse(scrollableToolStripComboBox6.Text, out var scanDetectPause)) scanDetectPause = 10;

            if (_bandScan != null)
            {
                _bandScan.Stop();
                _bandScan = null;
            }

            _bandScan = new BandScan(_catAssemblies, start, stop, _serialPort, Configuration);

            if (toolStripMenuItem14.Checked)
            {
                _bandScan.Start(step, pause, scanDetectPause, toolStripMenuItem16.Checked);

                return;
            }

            _bandScan.Start(step, pause, 0, toolStripMenuItem16.Checked);
        }

        private async void toolStripMenuItem32_Click(object sender, EventArgs e)
        {
            var toolStripMenuItem = toolStripMenuItem31;

            try
            {
                if (await _catAssemblies.CatSetAsync<TxState, bool>("TX", new object[] { TxState.ON },
                        _cancellationToken))
                    toolStripMenuItem.Text = Resources.On;
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_PTT_state);
            }
        }

        private async void toolStripMenuItem33_Click(object sender, EventArgs e)
        {
            var toolStripMenuItem = toolStripMenuItem31;

            try
            {
                if (await _catAssemblies.CatSetAsync<TxState, bool>("TX", new object[] { TxState.OFF },
                        _cancellationToken))
                    toolStripMenuItem.Text = Resources.Off;
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_PTT_state);
            }
        }

        private async void toolStripMenuItem26_DropDownOpening(object sender, EventArgs e)
        {
            var toolStripMenuItem = toolStripMenuItem31;

            try
            {
                switch (await _catAssemblies.CatReadAsync<TxState>("TX", new object[] { }, _cancellationToken))
                {
                    case TxState.ON:
                        toolStripMenuItem.Text = Resources.On;
                        break;
                    case TxState.OFF:
                        toolStripMenuItem.Text = Resources.Off;
                        break;
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_PTT_state);
            }
        }

        private async void scrollableToolStripComboBox8_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;

            if (!SplitState.TryParse(toolStripComboBox.Text, out var splitState)) return;

            try
            {
                if (await _catAssemblies.CatSetAsync<int, bool>("ST", new object[] { splitState },
                        _cancellationToken)) Log.Information($"{Resources.Split_state_set} {splitState}W");
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_split_state);
            }
        }

        private async void toolStripMenuItem35_Click(object sender, EventArgs e)
        {
            var scrollableToolStripComboBox = scrollableToolStripComboBox12;

            var channel = scrollableToolStripComboBox.Text;
            if (!string.IsNullOrEmpty(channel))
            {
                if (!_memoryChannelStore.TryGetValue(channel, out var memoryChannel))
                {
                    memoryChannel = MemoryChannel.Create(Configuration.Radio);
                    memoryChannel.CurrentLocation = channel;
                    memoryChannel.MemoryRadioMode =
                        MemoryRadioMode.Create(Configuration.Radio, toolStripComboBox1.Text);
                }

                if (memoryChannel.Tag = !string.IsNullOrEmpty(toolStripTextBox1.Text))
                    memoryChannel.Text = $"{toolStripTextBox1.Text,-12}";

                if (int.TryParse(scrollableToolStripComboBox1.Text, out var frequency))
                {
                    memoryChannel.Frequency = frequency;

                    try
                    {
                        if (await _catAssemblies.CatSetAsync<MemoryChannel, bool>("MT", new object[] { memoryChannel },
                                _cancellationToken)) Log.Information(Resources.Stored_VFO_A_to_memory);
                    }
                    catch (Exception exception)
                    {
                        Log.Error(exception, Resources.Failed_to_save_VFO_A_to_memory);
                    }
                }
            }
        }

        private void scrollableToolStripComboBox12_SelectedIndexChanged(object sender, EventArgs e)
        {
            var toolStripComboBox = (ScrollableToolStripComboBox)sender;

            var channel = toolStripComboBox.Text;
            if (!string.IsNullOrEmpty(channel))
            {
                if (_memoryChannelStore.TryGetValue(channel, out var memoryChannel))
                {
                    toolStripTextBox1.Text = memoryChannel.Text;
                    return;
                }

                toolStripTextBox1.Text = string.Empty;
            }
        }

        private async void toolStripMenuItem20_Click(object sender, EventArgs e)
        {
            try
            {
                var frequency = await _catAssemblies.CatReadAsync<int>("FA", new object[] { }, _cancellationToken);

                await _catAssemblies.CatWriteAsync<int>("FA", new object[] { frequency }, _cancellationToken);

                await _catAssemblies.CatWriteAsync("SV", new object[] { }, _cancellationToken);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Unable_to_swap_VFO_A_and_VFO_B);
            }
        }

        private async void lockToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            var toolStripMenuItem = (ToolStripMenuItem)sender;
            try
            {
                LockState state;
                switch (toolStripMenuItem.Checked)
                {
                    case true:
                        state = LockState.ON;
                        break;
                    default:
                        state = LockState.OFF;
                        break;
                }

                if (!await _catAssemblies.CatSetAsync<LockState, bool>("LK", new object[] { state },
                        _cancellationToken)) Log.Error(Resources.Failed_to_set_lock_state);
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_lock_state);
            }
        }

        private void menuToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_menuForm != null) return;

            _menuForm = new MenuForm(Configuration, _catAssemblies, _cancellationToken);
            _menuForm.Closing += MenuForm_Closing;
            _menuForm.Show();
        }

        private void MenuForm_Closing(object sender, CancelEventArgs e)
        {
            if (_menuForm == null) return;

            _menuForm.Dispose();
            _menuForm = null;
        }
    }
}

Generated by GNU Enscript 1.6.5.90.