HamBook – Rev 61

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using HamBook.Properties;
using HamBook.Radios;
using HamBook.Radios.Generic;
using HamBook.Radios.Yaesu.FT_891;
using HamBook.Utilities;
using HamBook.Utilities.Serialization;
using Serilog;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;

namespace HamBook
{
    public partial class MenuForm : Form
    {
        private static ScheduledContinuation _searchTextBoxChangedContinuation;
        private readonly CancellationTokenSource _cancellationTokenSource;

        private readonly CatAssemblies _catAssemblies;
        private readonly CancellationToken _localCancellationToken;
        private readonly CancellationTokenSource _localCancellationTokenSource;
        private CancellationToken _cancellationToken;
        private CancellationTokenSource _readCancellationTokenSource;
        private CancellationTokenSource _readLinkedCancellationTokenSource;
        private Task _readMenuTask;
        private CancellationTokenSource _writeCancellationTokenSource;
        private CancellationTokenSource _writeLinkedCancellationTokenSource;
        private Task _writeMenuTask;
        private readonly Radios.Generic.RadioMenu _radioMenu;

        public MenuForm()
        {
            InitializeComponent();

            _localCancellationTokenSource = new CancellationTokenSource();
            _localCancellationToken = _localCancellationTokenSource.Token;
        }

        public MenuForm(Configuration.Configuration configuration, CatAssemblies catAssemblies,
            CancellationToken cancellationToken) : this()
        {
            Configuration = configuration;
            _catAssemblies = catAssemblies;
            _cancellationToken = cancellationToken;

            _searchTextBoxChangedContinuation = new ScheduledContinuation();
            _radioMenu = Radios.Generic.RadioMenu.Create(Configuration.Radio);

            _cancellationTokenSource =
                CancellationTokenSource.CreateLinkedTokenSource(_localCancellationToken, cancellationToken);
            _cancellationToken = _cancellationTokenSource.Token;
        }

        private Configuration.Configuration Configuration { get; }

        private void AddMenuItem<T>(string menuIndex, string menuFunction, Func<IEnumerable<T>> values,
            T defaultValue = default, int index = -1)
        {
            if (index == -1) index = dataGridView1.Rows.Add();

            dataGridView1.Rows[index].Cells["IndexColumn"].Value = menuIndex;
            dataGridView1.Rows[index].Cells["FunctionColumn"].Value = menuFunction;

            foreach (var value in values.Invoke())
            {
                ((DataGridViewComboBoxCell)dataGridView1.Rows[index].Cells["ValueColumn"]).Items.Add($"{value}");
                ((DataGridViewComboBoxCell)dataGridView1.Rows[index].Cells["ValueColumn"]).Value = $"{defaultValue}";
            }
        }

        private void MenuForm_Load(object sender, EventArgs e)
        {
            Utilities.WindowState.FormTracker.Track(this);

            // Generate columns based on radio type.
            switch (Configuration.Radio)
            {
                case "Yaesu FT-891":
                    dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
                        { Name = "IndexColumn", HeaderText = "Index", ReadOnly = true });
                    dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
                        { Name = "FunctionColumn", HeaderText = "Function", ReadOnly = true });
                    dataGridView1.Columns.Add(new DataGridViewComboBoxColumn
                        { Name = "ValueColumn", HeaderText = "Value" });


                    AddMenuItem("0101", "AGC FAST DELAY", () => { return Mathematics.GenerateRange(20, 4000, 20); },
                        300);

                    AddMenuItem("0102", "AGC MID DELAY", () => { return Mathematics.GenerateRange(20, 4000, 20); },
                        700);

                    AddMenuItem("0103", "AGC SLOW DELAY", () => { return Mathematics.GenerateRange(20, 4000, 20); },
                        3000);

                    AddMenuItem("0201", "LCD CONTRAST", () => { return Mathematics.GenerateRange(1, 15); }, 8);

                    AddMenuItem("0202", "DIMMER BACKLIT", () => { return Mathematics.GenerateRange(1, 15); }, 8);

                    AddMenuItem("0203", "DIMMER LCD", () => { return Mathematics.GenerateRange(1, 15); }, 8);

                    AddMenuItem("0204", "DIMMER TX/BUSY", () => { return Mathematics.GenerateRange(1, 15); }, 8);

                    AddMenuItem("0205", "PEAK HOLD", () => { return new[] { "OFF", "0.5", "1.0", "2.0" }; }, "OFF");

                    AddMenuItem("0206", "ZIN LED", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0207", "POP-UP MENU", () => { return new[] { "UPPER", "LOWER" }; }, "LOWER");

                    AddMenuItem("0301", "DVS RX OUT LVL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0302", "DVS TX OUT LVL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0401", "KEYER TYPE",
                        () =>
                        {
                            return new[] { "OFF", "BUG", "ELEKEY-A", "ELEKEY-B", "ELEKEY-Y", "ACS" };
                        }, "ELEKEY-B");

                    AddMenuItem("0402", "KEYER DOT/DASH", () => { return new[] { "NOR", "KEY" }; }, "NOR");

                    AddMenuItem("0403", "CW WEIGHT", () =>
                    {
                        var value = new List<string>();
                        for (var i = 2.0; i <= 4.5; i += 0.1)
                        {
                            var v = $"{(decimal)i:f1}";
                            value.Add(v);
                        }

                        return value;
                    }, "3.0");

                    AddMenuItem("0404", "BEACON INTERVAL",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(1, 690).Select(i => i.ToString()));
                        },
                        "OFF");

                    AddMenuItem("0405", "NUMBER STYLE",
                        () => { return new[] { "1290", "AUNO", "AUNT", "A2NO", "A2NT", "12NO", "12NT" }; }, "1290");

                    AddMenuItem("0406", "CONTEST NUMBER", () => { return Mathematics.GenerateRange(0, 9999); }, 1);

                    AddMenuItem("0407", "CW MEMORY 1", () => { return new[] { "TEXT", "MESSAGE" }; }, "TEXT");

                    AddMenuItem("0408", "CW MEMORY 2", () => { return new[] { "TEXT", "MESSAGE" }; }, "TEXT");

                    AddMenuItem("0409", "CW MEMORY 3", () => { return new[] { "TEXT", "MESSAGE" }; }, "TEXT");

                    AddMenuItem("0410", "CW MEMORY 4", () => { return new[] { "TEXT", "MESSAGE" }; }, "TEXT");

                    AddMenuItem("0411", "CW MEMORY 5", () => { return new[] { "TEXT", "MESSAGE" }; }, "TEXT");

                    AddMenuItem("0501", "NB WIDTH", () => { return new[] { 1, 3, 10 }; }, 3);

                    AddMenuItem("0502", "NB REJECTION", () => { return new[] { 10, 30, 50 }; }, 30);

                    AddMenuItem("0503", "NB LEVEL", () => { return Mathematics.GenerateRange(0, 10); }, 5);

                    AddMenuItem("0504", "BEEP LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 30);

                    AddMenuItem("0505", "RF/SQL VR", () => { return new[] { "RF", "SQL" }; }, "RF");

                    AddMenuItem("0506", "CAT RATE", () => { return new[] { 4800, 9600, 19200, 38400 }; }, 9600);

                    AddMenuItem("0507", "CAT TOT", () => { return new[] { 10, 100, 1000, 3000 }; }, 1000);

                    AddMenuItem("0508", "CAT RTS", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0509", "MEM GROUP", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0510", "FM SETTING", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0511", "REC SETTING", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0512", "ATAS SETTING", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    AddMenuItem("0513", "QUICK SPL FREQ", () => { return Mathematics.GenerateRange(-20, 20); }, 5);

                    AddMenuItem("0514", "TX TOT",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(1, 30).Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("0515", "MIC SCAN", () => { return new[] { "DISABLE", "ENABLE" }; }, "ENABLE");

                    AddMenuItem("0516", "MIC SCAN RESUME", () => { return new[] { "PAUSE", "TIME" }; }, "TIME");

                    AddMenuItem("0517", "REF FREQ ADJ", () => { return Mathematics.GenerateRange(-25, 25); });

                    AddMenuItem("0518", "CLAR SELECT", () => { return new[] { "RX", "TX", "TRX" }; }, "RX");

                    AddMenuItem("0519", "APO", () => { return new[] { "OFF", "1", "2", "4", "6", "8", "10", "12" }; },
                        "RX");

                    AddMenuItem("0520", "FAN CONTROL", () => { return new[] { "NORMAL", "CONTEST" }; }, "NORMAL");

                    AddMenuItem("0601", "AM LCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 1000, 50)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("0602", "AM LCUT SLOPE", () => { return new[] { 6, 18 }; }, 6);

                    AddMenuItem("0603", "AM HCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 4000, 50)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("0604", "AM HCUT SLOPE", () => { return new[] { 6, 18 }; }, 6);

                    AddMenuItem("0605", "AM MIC SELECT", () => { return new[] { "MIC", "REAR" }; }, "MIC");

                    AddMenuItem("0606", "AM OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0607", "AM PTT SELECT", () => { return new[] { "DAKY", "RTS", "DTR" }; }, "DAKY");

                    AddMenuItem("0701", "CW LCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 1000, 50)
                                .Select(i => i.ToString()));
                        }, "250");

                    AddMenuItem("0702", "CW LCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("0703", "CW HCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 4000, 50)
                                .Select(i => i.ToString()));
                        }, "1200");

                    AddMenuItem("0704", "CW HCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("0705", "CW OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0706", "CW AUTO MODE", () => { return new[] { "OFF", "50M", "ON" }; }, "OFF");

                    AddMenuItem("0707", "CW BFO", () => { return new[] { "USB", "LSB", "AUTO" }; }, "USB");

                    AddMenuItem("0708", "CW BK-IN TYPE", () => { return new[] { "SEMI", "FULL" }; }, "SEMI");

                    AddMenuItem("0709", "CW BK-IN DELAY", () => { return Mathematics.GenerateRange(30, 3000, 10); },
                        200);

                    AddMenuItem("0710", "CW WAVE SHAPE", () => { return new[] { string.Empty, "2", "4" }; }, "4");

                    AddMenuItem("0711", "CW FREQ DISPLAY", () => { return new[] { "FREQ", "PITCH" }; }, "PITCH");

                    AddMenuItem("0712", "PC KEYING", () => { return new[] { "OFF", "DAKY", "RTS", "DTR" }; }, "OFF");

                    AddMenuItem("0713", "QSK DELAY TIME", () => { return new[] { 15, 20, 25, 30 }; }, 15);

                    AddMenuItem("0801", "DATA MODE", () => { return new[] { "PSK", "OTHERS" }; }, "OTHERS");

                    AddMenuItem("0802", "PSK TONE", () => { return new[] { 1000, 1500, 2000 }; }, 1000);

                    AddMenuItem("0803", "OTHER DISP", () => { return Mathematics.GenerateRange(-3000, 3000, 10); },
                        1000);

                    AddMenuItem("0804", "OTHER SHIFT", () => { return Mathematics.GenerateRange(-3000, 3000, 10); },
                        1500);

                    AddMenuItem("0805", "DATA LCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 1000, 50)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("0806", "DATA LCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("0807", "DATA HCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 4000, 50)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("0808", "DATA HCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("0809", "DATA IN SELECT", () => { return new[] { "MIC", "REAR" }; }, "REAR");

                    AddMenuItem("0810", "DATA PTT SELECT", () => { return new[] { "DAKY", "RTS", "DTR" }; }, "DAKY");

                    AddMenuItem("0811", "DATA OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0812", "DATA BFO", () => { return new[] { "USB", "LSB" }; }, "USB");

                    AddMenuItem("0901", "FM MIC SELECT", () => { return new[] { "MIC", "REAR" }; }, "MIC");

                    AddMenuItem("0902", "FM OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("0903", "PKT PTT SELECT", () => { return new[] { "DAKY", "RTS", "DTR" }; }, "DAKY");

                    AddMenuItem("0904", "RPT SHIFT 28MHz", () => { return Mathematics.GenerateRange(0, 1000, 10); },
                        100);

                    AddMenuItem("0905", "RPT SHIFT 50MHz", () => { return Mathematics.GenerateRange(0, 4000, 10); },
                        100);

                    AddMenuItem("0906", "DCS POLARITY",
                        () => { return new[] { "Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv" }; }, "Tn-Rn");

                    AddMenuItem("1001", "RTTY LCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 1000, 50)
                                .Select(i => i.ToString()));
                        }, "300");

                    AddMenuItem("1002", "RTTY LCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("1003", "RTTY HCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 4000, 50)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("1004", "RTTY HCUT SLOPE", () => { return new[] { 6, 18 }; }, 18);

                    AddMenuItem("1005", "RTTY SHIFT PORT", () => { return new[] { "SHIFT", "DTR", "RTS" }; }, "SHIFT");

                    AddMenuItem("1006", "RTTY POLARITY-R", () => { return new[] { "NOR", "REV" }; }, "NOR");

                    AddMenuItem("1007", "RTTY POLARITY-T", () => { return new[] { "NOR", "REV" }; }, "NOR");

                    AddMenuItem("1008", "RTTY OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1009", "RTTY SHIFT FREQ", () => { return new[] { 170, 200, 425, 850 }; }, 170);

                    AddMenuItem("1010", "RTTY MARK FREQ", () => { return new[] { 1275, 2125 }; }, 2125);

                    AddMenuItem("1011", "RTTY BFO", () => { return new[] { "USB", "LSB" }; }, "LSB");

                    AddMenuItem("1101", "SSB LCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 1000, 50)
                                .Select(i => i.ToString()));
                        }, "100");

                    AddMenuItem("1102", "SSB LCUT SLOPE", () => { return new[] { 6, 18 }; }, 6);

                    AddMenuItem("1103", "SSB HCUT FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 4000, 50)
                                .Select(i => i.ToString()));
                        }, "3000");

                    AddMenuItem("1104", "SSB HCUT SLOPE", () => { return new[] { 6, 18 }; }, 6);

                    AddMenuItem("1105", "SSB MIC SELECT", () => { return new[] { "MIC", "REAR" }; }, "REAR");

                    AddMenuItem("1106", "RTTY OUT LEVEL", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1107", "SSB BFO", () => { return new[] { "USB", "LSB", "AUTO" }; }, "USB");

                    AddMenuItem("1108", "SSB PTT SELECT", () => { return new[] { "DAKY", "RTS", "DTR" }; }, "DAKY");

                    AddMenuItem("1109", "SSB TX BPF",
                        () => { return new[] { "100-3000", "100-2900", "200-2800", "300-2700", "400-2600" }; },
                        "300-2700");

                    AddMenuItem("1201", "APF WIDTH", () => { return new[] { "NARROW", "MEDIUM", "WIDE" }; }, "MEDIUM");

                    AddMenuItem("1202", "CONTOUR LEVEL", () => { return Mathematics.GenerateRange(-40, 20); }, -15);

                    AddMenuItem("1203", "CONTOUR WIDTH", () => { return Mathematics.GenerateRange(1, 11); }, 10);

                    AddMenuItem("1204", "IF NOTCH WIDTH", () => { return new[] { "NARROW", "WIDE" }; }, "WIDE");

                    AddMenuItem("1301", "SCP START CYCLE", () => { return new[] { "OFF", "3", "5", "10" }; }, "OFF");

                    AddMenuItem("1302", "SCP SPAN FREQ", () => { return new[] { 37.5, 75, 150, 375, 750 }; }, 750);

                    AddMenuItem("1401", "QUICK DIAL", () => { return new[] { 50, 100, 500 }; }, 500);

                    AddMenuItem("1402", "SSB DIAL STEP", () => { return new[] { 2, 5, 10 }; }, 10);

                    AddMenuItem("1403", "AM DIAL STEP", () => { return new[] { 10, 100 }; }, 10);

                    AddMenuItem("1404", "FM DIAL STEP", () => { return new[] { 10, 100 }; }, 100);

                    AddMenuItem("1405", "DIAL STEP", () => { return new[] { 2, 5, 10 }; }, 5);

                    AddMenuItem("1406", "AM CH STEP", () => { return new[] { 2.5, 5, 2.9, 10, 12.5, 25 }; }, 5);

                    AddMenuItem("1407", "FM CH STEP", () => { return new[] { 5, 6.25, 10, 12.5, 15, 20, 25 }; }, 5);

                    AddMenuItem("1501", "EQ1 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 700, 100)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("1502", "EQ1 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); }, 5);

                    AddMenuItem("1503", "EQ1 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 10);

                    AddMenuItem("1504", "EQ2 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 1500, 100)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("1505", "EQ2 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); }, 5);

                    AddMenuItem("1506", "EQ2 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 10);

                    AddMenuItem("1507", "EQ3 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(1500, 3200, 100)
                                .Select(i => i.ToString()));
                        }, "OFF");

                    AddMenuItem("1508", "EQ3 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); }, 5);

                    AddMenuItem("1509", "EQ3 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 10);

                    AddMenuItem("1510", "P-EQ1 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(100, 700, 100)
                                .Select(i => i.ToString()));
                        }, "200");

                    AddMenuItem("1511", "P-EQ1 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); });

                    AddMenuItem("1512", "P-EQ1 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 2);

                    AddMenuItem("1513", "P-EQ2 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(700, 1500, 100)
                                .Select(i => i.ToString()));
                        }, "800");

                    AddMenuItem("1514", "P-EQ2 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); });

                    AddMenuItem("1515", "P-EQ2 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 1);

                    AddMenuItem("1516", "P-EQ3 FREQ",
                        () =>
                        {
                            return new[] { "OFF" }.Concat(Mathematics.GenerateRange(1500, 3200, 100)
                                .Select(i => i.ToString()));
                        }, "2100");

                    AddMenuItem("1517", "P-EQ3 LEVEL", () => { return Mathematics.GenerateRange(-20, 10); });

                    AddMenuItem("1518", "P-EQ3 BWTH", () => { return Mathematics.GenerateRange(1, 10); }, 1);

                    AddMenuItem("1601", "HF SSB PWR", () => { return Mathematics.GenerateRange(5, 100); }, 100);

                    AddMenuItem("1602", "HF AM PWR", () => { return Mathematics.GenerateRange(5, 40); }, 40);

                    AddMenuItem("1603", "HF PWR", () => { return Mathematics.GenerateRange(5, 100); }, 100);

                    AddMenuItem("1604", "50M SSB PWR", () => { return Mathematics.GenerateRange(5, 100); }, 100);

                    AddMenuItem("1605", "50M AM PWR", () => { return Mathematics.GenerateRange(5, 40); }, 40);

                    AddMenuItem("1606", "50M PWR", () => { return Mathematics.GenerateRange(5, 100); }, 100);

                    AddMenuItem("1607", "SSB MIC GAIN", () => { return Mathematics.GenerateRange(5, 100); }, 50);

                    AddMenuItem("1608", "AM MIC GAIN", () => { return Mathematics.GenerateRange(5, 100); }, 50);

                    AddMenuItem("1609", "FM MIC GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1610", "DATA MIC GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1611", "SSB DATA GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1612", "AM DATA GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1613", "FM DATA GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1614", "DATA DATA GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1615", "TUNER SELECT", () => { return new[] { "OFF", "EXTERNAL", "ATAS", "LAMP" }; },
                        "OFF");

                    AddMenuItem("1616", "VOX SELECT", () => { return new[] { "MIC", "DATA" }; }, "MIC");

                    AddMenuItem("1617", "VOX GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1618", "VOX DELAY", () => { return Mathematics.GenerateRange(30, 3000, 10); }, 1000);

                    AddMenuItem("1619", "ANTI VOX GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1620", "DATA VOX GAIN", () => { return Mathematics.GenerateRange(0, 100); }, 50);

                    AddMenuItem("1621", "DATA VOX DELAY", () => { return Mathematics.GenerateRange(30, 3000); }, 100);

                    AddMenuItem("1622", "ANTI DVOX GAIN", () => { return Mathematics.GenerateRange(0, 100); });

                    AddMenuItem("1623", "EMERGENCY FREQ", () => { return new[] { "DISABLE", "ENABLE" }; }, "DISABLE");

                    break;
            }

            _readCancellationTokenSource = new CancellationTokenSource();
            _readLinkedCancellationTokenSource =
                CancellationTokenSource.CreateLinkedTokenSource(new[]
                    { _cancellationTokenSource.Token, _cancellationToken });

            _readMenuTask = ReadMenu(_readCancellationTokenSource.Token);
        }

        private async Task WriteMenu(CancellationToken cancellationToken)
        {
            var rows = GetAllDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var list = rows.ToList();

            var count = list.Count;

            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = count;
            toolStripProgressBar1.Value = toolStripProgressBar1.Minimum;

            var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
            {
                try
                {
                    switch (rowProgress)
                    {
                        case DataGridViewRowProgressSuccess<int> rowProgressSuccess:
                            dataGridView1.Rows[rowProgressSuccess.Row.Index].DefaultCellStyle.BackColor =
                                DefaultBackColor;
                            toolStripStatusLabel1.Text = $"{Resources.Wrote_menu} {rowProgressSuccess.Data:0000}";
                            break;
                        case DataGridViewRowProgressFailure<int> rowProgressFailure:
                            Log.Error(rowProgressFailure.Exception, $"{Resources.Could_not_read_menu}");
                            dataGridView1.Rows[rowProgressFailure.Row.Index].DefaultCellStyle.BackColor = Color.Red;

                            toolStripStatusLabel1.Text =
                                $"{Resources.Could_not_write_menu} {rowProgressFailure.Data:0000}";
                            break;
                    }

                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Unexpected_error_while_reading_memory_bank);
                }
            });

            await Task.Run(() => WriteMenu(list, progress, cancellationToken), cancellationToken);

            if (!_cancellationToken.IsCancellationRequested)
            {
                toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
                toolStripStatusLabel1.Text = "Done.";
            }
        }

        private async Task WriteMenu(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
            CancellationToken cancellationToken)
        {
            var count = rows.Count;

            for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
            {
                if (!int.TryParse($"{rows[i].Cells["IndexColumn"].Value}", out var menu)) continue;

                try
                {
                    var value = $"{rows[i].Cells["ValueColumn"].Value}";

                    var parameter = _radioMenu.MenuToCAT(menu, value);
                    
                    await _catAssemblies.CatWriteAsync<int>("EX", new object[] { menu, parameter }, cancellationToken);

                    progress.Report(new DataGridViewRowProgressSuccess<int>(rows[i], i, menu));
                }
                catch (UnexpectedRadioResponseException exception)
                {
                    progress.Report(new DataGridViewRowProgressFailure<int>(rows[i], i, exception, menu));
                }
                catch (Exception exception)
                {
                    progress.Report(new DataGridViewRowProgressFailure<int>(rows[i], i, exception, menu));
                }
            }
        }

        private async Task ReadMenu(CancellationToken cancellationToken)
        {
            var rows = GetAllDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var list = rows.ToList();

            var count = list.Count;

            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = count;
            toolStripProgressBar1.Value = toolStripProgressBar1.Minimum;

            var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
            {
                try
                {
                    switch (rowProgress)
                    {
                        case DataGridViewRowProgressSuccess<int> rowProgressSuccess:
                            dataGridView1.Rows[rowProgressSuccess.Row.Index].DefaultCellStyle.BackColor =
                                DefaultBackColor;

                            if (!int.TryParse(
                                    $"{dataGridView1.Rows[rowProgressSuccess.Row.Index].Cells["IndexColumn"].Value}",
                                    out var menu)) throw new ArgumentException(Resources.Invalid_menu_item);

                            switch (Configuration.Radio)
                            {
                                case "Yaesu FT-891":
                                    switch (menu)
                                    {
                                        case 403:
                                            var v403 = $"{rowProgressSuccess.Data}".ToCharArray();
                                            dataGridView1.Rows[rowProgressSuccess.Row.Index].Cells["ValueColumn"]
                                                .Value = $"{v403[0]}.{v403[1]}";
                                            break;
                                        case 205:
                                        case 206:
                                        case 207:
                                        case 401:
                                        case 402:
                                        case 404:
                                        case 405:
                                        case 407:
                                        case 408:
                                        case 409:
                                        case 410:
                                        case 411:
                                        case 501:
                                        case 502:
                                        case 505:
                                        case 506:
                                        case 507:
                                        case 508:
                                        case 509:
                                        case 510:
                                        case 511:
                                        case 512:
                                        case 514:
                                        case 515:
                                        case 516:
                                        case 518:
                                        case 519:
                                        case 520:
                                        case 601:
                                        case 602:
                                        case 603:
                                        case 605:
                                        case 607:
                                        case 701:
                                        case 702:
                                        case 703:
                                        case 704:
                                        case 706:
                                        case 707:
                                        case 708:
                                        case 710:
                                        case 711:
                                        case 712:
                                        case 713:
                                        case 801:
                                        case 802:
                                        case 805:
                                        case 806:
                                        case 807:
                                        case 808:
                                        case 809:
                                        case 810:
                                        case 811:
                                        case 812:
                                        case 901:
                                        case 902:
                                        case 903:
                                        case 906:
                                        case 1001:
                                        case 1002:
                                        case 1003:
                                        case 1004:
                                        case 1005:
                                        case 1006:
                                        case 1007:
                                        case 1009:
                                        case 1010:
                                        case 1011:
                                        case 1101:
                                        case 1102:
                                        case 1103:
                                        case 1104:
                                        case 1105:
                                        case 1107:
                                        case 1108:
                                        case 1109:
                                        case 1201:
                                        case 1204:
                                        case 1301:
                                        case 1302:
                                        case 1401:
                                        case 1402:
                                        case 1403:
                                        case 1404:
                                        case 1405:
                                        case 1406:
                                        case 1407:
                                        case 1501:
                                        case 1504:
                                        case 1507:
                                        case 1510:
                                        case 1513:
                                        case 1516:
                                        case 1615:
                                        case 1616:
                                        case 1623:
                                            var value = (dataGridView1.Rows[rowProgressSuccess.Row.Index]
                                                    .Cells["ValueColumn"] as DataGridViewComboBoxCell)
                                                .Items[rowProgressSuccess.Data];
                                            dataGridView1.Rows[rowProgressSuccess.Row.Index].Cells["ValueColumn"]
                                                .Value = $"{value}";
                                            break;
                                        default:
                                            dataGridView1.Rows[rowProgressSuccess.Row.Index].Cells["ValueColumn"]
                                                .Value = $"{rowProgressSuccess.Data}";
                                            break;
                                    }

                                    break;
                            }

                            toolStripStatusLabel1.Text = $"{Resources.Read_menu} {menu:0000}";
                            break;
                        case DataGridViewRowProgressFailure<int> rowProgressFailure:
                            Log.Error(rowProgressFailure.Exception, $"{Resources.Could_not_read_menu}");
                            dataGridView1.Rows[rowProgressFailure.Row.Index].DefaultCellStyle.BackColor = Color.Red;

                            toolStripStatusLabel1.Text =
                                $"{Resources.Could_not_read_menu} {rowProgressFailure.Data:0000}";
                            break;
                    }

                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Unexpected_error_while_reading_memory_bank);
                }
            });

            await Task.Run(() => ReadMenu(list, progress, cancellationToken), cancellationToken);

            if (!_cancellationToken.IsCancellationRequested)
                try
                {
                    toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
                    toolStripStatusLabel1.Text = "Done.";
                }
                catch (NullReferenceException)
                {
                    // toolStripProgressBar1 disposed on close
                }
        }

        private async Task ReadMenu(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
            CancellationToken cancellationToken)
        {
            var count = rows.Count;

            for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
            {
                if (!int.TryParse($"{rows[i].Cells["IndexColumn"].Value}", out var menu)) continue;

                try
                {
                    var result = await _catAssemblies.CatReadAsync<int>("EX", new object[] { menu }, cancellationToken);

                    progress.Report(new DataGridViewRowProgressSuccess<int>(rows[i], i, result));
                }
                catch (UnexpectedRadioResponseException exception)
                {
                    progress.Report(new DataGridViewRowProgressFailure<int>(rows[i], i, exception, menu));
                }
                catch (Exception exception)
                {
                    progress.Report(new DataGridViewRowProgressFailure<int>(rows[i], i, exception, menu));
                }
            }
        }

        //Mix two colors
        //Example: Steps=10 & Position=4 makes Color2 mix 40% into Color1
        /// <summary>
        ///     Mix two colors.
        /// </summary>
        /// <param name="color1"></param>
        /// <param name="color2"></param>
        /// <param name="steps"></param>
        /// <param name="position"></param>
        /// <example>Steps=10 & Positon=4 makes Color2 mix 40% into Color1</example>
        /// <remarks>https://stackoverflow.com/questions/38337849/transparent-selectionbackcolor-for-datagridview-cell</remarks>
        /// <returns></returns>
        public static Color MixColor(Color color1, Color color2, int steps, int position)
        {
            if (position <= 0 || steps <= 1) return color1;
            if (position >= steps) return color2;
            return Color.FromArgb(
                color1.R + (color2.R - color1.R) / steps * position,
                color1.G + (color2.G - color1.G) / steps * position,
                color1.B + (color2.B - color1.B) / steps * position
            );
        }

        private static IEnumerable<DataGridViewRow> GetSelectedDataGridViewRows(DataGridView dataGridView)
        {
            return dataGridView.SelectedRows.OfType<DataGridViewRow>().OrderBy(row => row.Index);
        }

        private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
        }

        private void DataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            var dataGridView = (DataGridView)sender;

            dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
        }

        private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
        }

        private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
        }

        private void DataGridView1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
        {
        }

        private void DataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
        }

        private void DataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            // @(-.-)@ -(o.o)- @(o_o)@
        }

        private static List<DataGridViewRow> GetAllDataGridViewRows(DataGridView dataGridView)
        {
            return dataGridView.Rows.OfType<DataGridViewRow>().ToList();
        }

        private void TextBox1_TextChanged(object sender, EventArgs e)
        {
            _searchTextBoxChangedContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
            {
                textBox1.InvokeIfRequired(textBox =>
                {
                    var search = textBox.Text;

                    dataGridView1.InvokeIfRequired(dataGridView =>
                    {
                        foreach (var row in GetAllDataGridViewRows(dataGridView))
                        {
                            if (row.Cells["FunctionColumn"].Value == null) continue;

                            switch (((string)row.Cells["FunctionColumn"].Value).IndexOf(search,
                                        StringComparison.OrdinalIgnoreCase))
                            {
                                case -1:
                                    row.Visible = false;
                                    break;
                                default:
                                    row.Visible = true;
                                    break;
                            }
                        }
                    });
                });
            }, _cancellationToken);
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            if (_readLinkedCancellationTokenSource != null)
            {
                _readLinkedCancellationTokenSource.Cancel();
                _readLinkedCancellationTokenSource = null;
            }

            if (_readMenuTask != null)
            {
                await _readMenuTask;
                _readMenuTask = null;
            }

            _readCancellationTokenSource = new CancellationTokenSource();
            _readLinkedCancellationTokenSource =
                CancellationTokenSource.CreateLinkedTokenSource(new[]
                    { _cancellationTokenSource.Token, _cancellationToken });

            _readMenuTask = ReadMenu(_readLinkedCancellationTokenSource.Token);
        }

        private void DataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
        {
            var dataGridView = sender as DataGridView;
            foreach (DataGridViewCell cell in dataGridView.Rows[e.RowIndex].Cells)
            {
                if (cell.Selected == false) continue;
                var bgColorCell = Color.White;
                if (cell.Style.BackColor != Color.Empty)
                    bgColorCell = cell.Style.BackColor;
                else if (cell.InheritedStyle.BackColor != Color.Empty) bgColorCell = cell.InheritedStyle.BackColor;
                cell.Style.SelectionBackColor = MixColor(bgColorCell, Color.FromArgb(0, 150, 255), 10, 4);
            }
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            if (_writeLinkedCancellationTokenSource != null)
            {
                _writeLinkedCancellationTokenSource.Cancel();
                _writeLinkedCancellationTokenSource = null;
            }

            if (_writeMenuTask != null)
            {
                await _writeMenuTask;
                _writeMenuTask = null;
            }

            _writeCancellationTokenSource = new CancellationTokenSource();
            _writeLinkedCancellationTokenSource =
                CancellationTokenSource.CreateLinkedTokenSource(new[]
                    { _cancellationTokenSource.Token, _cancellationToken });

            _writeMenuTask = WriteMenu(_writeLinkedCancellationTokenSource.Token);
        }

        private void exportToolStripMenuItem_Click(object sender, EventArgs e)
        {
            saveFileDialog1.ShowDialog();
        }

        private async void saveFileDialog1_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (e.Cancel)
            {
                return;
            }

            var rows = GetAllDataGridViewRows(dataGridView1);
            var count = rows.Count;
            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = count;
            toolStripProgressBar1.Value = toolStripProgressBar1.Minimum;

            var dataGridViewRows = new ConcurrentQueue<DataGridViewRow>();
            var dataGridViewRowsTaskCompletionSource = new TaskCompletionSource<object>();
            var radioMenuTaskCompletionSource = new TaskCompletionSource<object>();

            var radioMenu = new RadioMenu();

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

                try
                {
                    if (!dataGridViewRows.TryDequeue(out var row))
                    {
                        Application.Idle -= IdleHandler;
                        
                        toolStripStatusLabel1.Text = "Done.";

                        radioMenuTaskCompletionSource.TrySetResult(new { });
                        return;
                    }

                    if (!int.TryParse($"{row.Cells["IndexColumn"].Value}", out var index))
                    {
                        return;
                    }

                    var value = $"{row.Cells["ValueColumn"].Value}";

                    var radioMenuItem = new RadioMenuItem
                    {
                        Index = index,
                        Value = value
                    };

                    radioMenu.RadioMenuItem.Add(radioMenuItem);

                    toolStripStatusLabel1.Text = $"Exported menu {row.Index:0000}";

                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Could_not_update_data_grid_view);
                }
            }

            Application.Idle += IdleHandler;
            try
            {
                foreach (var row in rows)
                {
                    dataGridViewRows.Enqueue(row);
                }

                dataGridViewRowsTaskCompletionSource.TrySetResult(new { });
            }
            catch (Exception exception)
            {
                Application.Idle -= IdleHandler;

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

            await radioMenuTaskCompletionSource.Task;

            var fileName = saveFileDialog1.FileName;
            switch (await Serialization.Serialize(radioMenu, fileName, "RadioMenu",
                        "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
                        _localCancellationToken))
            {
                case SerializationSuccess<RadioMenu> radioMenuSerializationSuccess:
                    Log.Information(Resources.Radio_menu_serialized_successfully);
                    break;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception.Message, Resources.Radio_menu_failed_to_serialize);
                    break;
            }
        }

        private void importToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
        }

        private async void openFileDialog1_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (e.Cancel)
            {
                return;
            }

            RadioMenu radioMenu = null;

            var fileName = openFileDialog1.FileName;
            var deserializationResult =
                await Serialization.Deserialize<RadioMenu>(fileName,
                    "urn:hambook-radiomenu-schema", "RadioMenu.xsd",
                    _localCancellationToken);

            switch (deserializationResult)
            {
                case SerializationSuccess<RadioMenu> serializationSuccess:
                    Log.Information(Resources.Deserialized_radio_menu);
                    radioMenu = serializationSuccess.Result;
                    break;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception, Resources.Failed_to_deserialize_radio_menu);
                    return;
            }

            var rows = GetAllDataGridViewRows(dataGridView1);
            var count = rows.Count;
            var menuIndexToRow = new Dictionary<int, DataGridViewRow>();
            foreach (var row in rows)
            {
                if (!int.TryParse($"{row.Cells["IndexColumn"].Value}", out var index))
                {
                    continue;
                }

                menuIndexToRow.Add(index, row);
            }

            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = count;
            toolStripProgressBar1.Value = toolStripProgressBar1.Minimum;

            var radioMenuItems = new ConcurrentQueue<RadioMenuItem>();
            var radioMenuItemsTaskCompletionSource = new TaskCompletionSource<object>();

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

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

                        dataGridView1.Sort(dataGridView1.Columns["IndexColumn"], ListSortDirection.Ascending);
                        toolStripStatusLabel1.Text = "Done.";

                        return;
                    }

                    menuIndexToRow[radioMenuItem.Index].Cells["ValueColumn"].Value = radioMenuItem.Value;
                    toolStripStatusLabel1.Text = $"Imported menu {radioMenuItem.Index:0000}";

                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Could_not_update_data_grid_view);
                }
            }

            Application.Idle += IdleHandler;
            try
            {
                foreach (var radioMenuItem in radioMenu.RadioMenuItem)
                {
                    radioMenuItems.Enqueue(radioMenuItem);
                }

                radioMenuItemsTaskCompletionSource.TrySetResult(new { });
            }
            catch (Exception exception)
            {
                Application.Idle -= IdleHandler;

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

Generated by GNU Enscript 1.6.5.90.