HamBook – Rev 59

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Media;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using HamBook.Properties;
using HamBook.Radios;
using HamBook.Radios.Generic;
using HamBook.Utilities.Serialization;
using Serilog;

namespace HamBook
{
    public partial class MemoryOrganizerForm : Form
    {
        private readonly CancellationTokenSource _cancellationTokenSource;

        private readonly CatAssemblies _catAssemblies;
        private readonly CancellationToken _localCancellationToken;
        private readonly CancellationTokenSource _localCancellationTokenSource;
        private readonly MemoryBanks _memoryBanks;
        private readonly MemoryRadioMode _memoryRadioMode;
        private CancellationToken _cancellationToken;
        private List<DataGridViewRow> _clipboardRows;
        private CancellationTokenSource _readCancellationTokenSource;
        private CancellationTokenSource _readLinkedCancellationTokenSource;
        private Task _readMemoryBanksTask;
        private CancellationTokenSource _writeCancellationTokenSource;
        private CancellationTokenSource _writeLinkedCancellationTokenSource;
        private Task _writeMemoryBanksTask;

        public MemoryOrganizerForm()
        {
            InitializeComponent();

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

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

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

            _memoryBanks = MemoryBanks.Create(Configuration.Radio);
            _memoryRadioMode = MemoryRadioMode.Create(Configuration.Radio);
        }

        private Configuration.Configuration Configuration { get; }

        /// <summary>
        ///     Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && components != null)
            {
                if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();

                components.Dispose();
            }

            base.Dispose(disposing);
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var rows = dataGridView1.Rows.OfType<DataGridViewRow>().OrderBy(row => row.Index).ToList();
            var count = rows.Count;

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

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

                            switch (Configuration.Radio)
                            {
                                case "Yaesu FT-891":
                                    var result = (Radios.Yaesu.FT_891.MemoryChannel)rowProgressSuccess.Data;

                                    rowProgress.Row.Cells["FrequencyColumn"].Value = result.Frequency;
                                    rowProgress.Row.Cells["ClarifierDirectionColumn"].Value =
                                        (char)result.ClarifierDirection;
                                    rowProgress.Row.Cells["ClarifierOffsetColumn"].Value = result.ClarifierOffset;
                                    rowProgress.Row.Cells["ClarColumn"].Value = result.Clar;
                                    rowProgress.Row.Cells["ModeColumn"].Value = result.MemoryRadioMode.Name;
                                    rowProgress.Row.Cells["CtcssColumn"].Value = (string)result.Ctcss;
                                    rowProgress.Row.Cells["PhaseColumn"].Value = (string)result.Phase;
                                    rowProgress.Row.Cells["TagColumn"].Value = result.Tag;
                                    rowProgress.Row.Cells["TextColumn"].Value = result.Text;
                                    rowProgress.Row.Tag = rowProgressSuccess.Data;
                                    break;
                            }

                            toolStripStatusLabel1.Text = $"{Resources.Read_memory_bank} {rowProgress.Index + 1}";
                            break;
                        case DataGridViewRowProgressFailure<int> rowProgressFailure:
                            Log.Error(rowProgressFailure.Exception, $"{Resources.Could_not_read_memory_bank}");
                            dataGridView1.Rows[rowProgressFailure.Row.Index].DefaultCellStyle.BackColor = Color.Red;

                            toolStripStatusLabel1.Text =
                                $"{Resources.Could_not_read_memory_bank} {rowProgress.Index + 1}";
                            break;
                    }

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

            await Task.Run(() => ReadMemoryBanks(rows, progress, _cancellationToken), _cancellationToken);

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

        private async void button2_Click(object sender, EventArgs e)
        {
            var rows = dataGridView1.Rows.OfType<DataGridViewRow>().OrderBy(row => row.Index).ToList();
            var count = rows.Count;

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

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

                            if (success)
                            {
                                toolStripStatusLabel1.Text =
                                    $"{Resources.Wrote_memory_bank} {rowProgress.Index + 1}";
                                toolStripProgressBar1.Increment(1);
                                statusStrip1.Update();
                                return;
                            }

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

                    toolStripStatusLabel1.Text =
                        $"{Resources.Could_not_write_memory_bank} {rowProgress.Index + 1}";
                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Unexpected_error_while_writing_memory_bank);
                }
            });

            await Task.Run(() => WriteMemoryBanks(rows, progress, _cancellationToken), _cancellationToken);

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

        private void MemoryOrganizerForm_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 = "LocationColumn", HeaderText = "Location", ReadOnly = true });
                    dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
                        { Name = "FrequencyColumn", HeaderText = "Frequency" });
                    var clarifierDropDownColumn = new DataGridViewComboBoxColumn
                        { Name = "ClarifierDirectionColumn", HeaderText = "Clarifier" };
                    clarifierDropDownColumn.Items.AddRange(
                        "+",
                        "-"
                    );
                    dataGridView1.Columns.Add(clarifierDropDownColumn);
                    dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
                        { Name = "ClarifierOffsetColumn", HeaderText = "Offset" });
                    dataGridView1.Columns.Add(new DataGridViewCheckBoxColumn
                        { Name = "ClarColumn", HeaderText = "Clar" });
                    var modeComboBoxColumn = new DataGridViewComboBoxColumn
                        { Name = "ModeColumn", HeaderText = "Mode" };
                    foreach (var name in _memoryRadioMode.Names)
                        modeComboBoxColumn.Items.Add(
                            name
                        );

                    dataGridView1.Columns.Add(modeComboBoxColumn);
                    var ctcssComboBoxColumn = new DataGridViewComboBoxColumn
                        { Name = "CtcssColumn", HeaderText = "CTCSS" };
                    ctcssComboBoxColumn.Items.AddRange(
                        "Off",
                        "Enc/Dec",
                        "Enc"
                    );
                    dataGridView1.Columns.Add(ctcssComboBoxColumn);
                    var phaseComboBoxColumn = new DataGridViewComboBoxColumn
                        { Name = "PhaseColumn", HeaderText = "Phase" };
                    phaseComboBoxColumn.Items.AddRange(
                        "Simplex",
                        "Plus Shift",
                        "Minus Shift"
                    );
                    dataGridView1.Columns.Add(phaseComboBoxColumn);
                    dataGridView1.Columns.Add(new DataGridViewCheckBoxColumn
                        { Name = "TagColumn", HeaderText = "Tag" });
                    dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
                    {
                        Name = "TextColumn", HeaderText = "Text",
                        MaxInputLength = Radios.Yaesu.FT_891.Constants.MaxTagCharacters
                    });
                    break;
            }

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

            var memoryBankQueue = new ConcurrentQueue<string>();
            var memoryBankAddRowsTaskCompletionSource = new TaskCompletionSource<object>();

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

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

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

                        return;
                    }

                    var index = dataGridView1.Rows.Add();

                    switch (Configuration.Radio)
                    {
                        case "Yaesu FT-891":
                            dataGridView1.Rows[index].Cells["LocationColumn"].Value = memoryBank;
                            dataGridView1.Rows[index].Cells["FrequencyColumn"].Value = default;
                            dataGridView1.Rows[index].Cells["ClarifierDirectionColumn"].Value = "+";
                            dataGridView1.Rows[index].Cells["ClarifierOffsetColumn"].Value = 0;
                            dataGridView1.Rows[index].Cells["ClarColumn"].Value = default;
                            dataGridView1.Rows[index].Cells["ModeColumn"].Value = default;
                            dataGridView1.Rows[index].Cells["CtcssColumn"].Value = "Off";
                            dataGridView1.Rows[index].Cells["PhaseColumn"].Value = "Simplex";
                            dataGridView1.Rows[index].Cells["TagColumn"].Value = default;
                            dataGridView1.Rows[index].Cells["TextColumn"].Value = default;
                            break;
                    }

                    toolStripStatusLabel1.Text = $"{Resources.Created_memory_bank} {memoryBank}";
                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                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);

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

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

        private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
            var dataGridView = (DataGridView)sender;

            if (dataGridView.CurrentCell is DataGridViewCheckBoxCell)
                dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
        }

        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)
        {
            var dataGridView = (DataGridView)sender;

            if (e.RowIndex == -1 || e.ColumnIndex == -1) return;

            switch (dataGridView.Columns[e.ColumnIndex].Name)
            {
                case "EnableColumn":
                    //ProcessEnable(dataGridView.Rows[e.RowIndex]);
                    break;
            }
        }

        private void DataGridView1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
        {
            var dataGridView = (DataGridView)sender;

            if (e.RowIndex == -1 || e.ColumnIndex == -1 ||
                !(dataGridView.Columns[e.ColumnIndex] is DataGridViewCheckBoxColumn))
                return;

            dataGridView.EndEdit();
        }

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

            if (e.RowIndex == -1 || e.ColumnIndex == -1 ||
                !(dataGridView.Columns[e.ColumnIndex] is DataGridViewCheckBoxColumn))
                return;

            dataGridView.EndEdit();
        }

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

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

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

            for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
                try
                {
                    var location = $"{rows[i].Cells["LocationColumn"].Value}";

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

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

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

            for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
                try
                {
                    var success = false;

                    switch (Configuration.Radio)
                    {
                        case "Yaesu FT-891":
                            var memoryChannel =
                                (Radios.Yaesu.FT_891.MemoryChannel)MemoryChannel.Create(Configuration.Radio);

                            memoryChannel.CurrentLocation = $"{rows[i].Cells["LocationColumn"].Value}";
                            memoryChannel.Frequency = int.Parse($"{rows[i].Cells["FrequencyColumn"].Value}");
                            memoryChannel.ClarifierDirection =
                                char.Parse($"{rows[i].Cells["ClarifierDirectionColumn"].Value}");
                            memoryChannel.ClarifierOffset =
                                int.Parse($"{rows[i].Cells["ClarifierOffsetColumn"].Value}");
                            memoryChannel.Clar = Convert.ToBoolean(rows[i].Cells["ClarColumn"].Value);
                            memoryChannel.MemoryRadioMode = MemoryRadioMode.Create(Configuration.Radio,
                                $"{rows[i].Cells["ModeColumn"].Value}");
                            memoryChannel.Ctcss = new Ctcss($"{rows[i].Cells["CtcssColumn"].Value}");
                            memoryChannel.Phase = new Phase($"{rows[i].Cells["PhaseColumn"].Value}");
                            memoryChannel.Tag = Convert.ToBoolean(rows[i].Cells["TagColumn"].Value);
                            memoryChannel.Text = $"{rows[i].Cells["TextColumn"].Value,-12}";

                            success = await _catAssemblies.CatSetAsync<MemoryChannel, bool>("MT",
                                new object[] { memoryChannel }, cancellationToken);
                            break;
                    }

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

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

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

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

            var list = new List<MemoryChannelIndexed>();
            foreach (var row in dataGridView1.Rows.OfType<DataGridViewRow>().OrderBy(row => row.Index))
                if (row.Tag is MemoryChannel memoryChannel)
                {
                    var memoryChannelOrganizerBanks = new MemoryChannelIndexed(row.Index, memoryChannel);
                    list.Add(memoryChannelOrganizerBanks);
                }

            var memoryBanks = list.ToArray();

            var fileName = saveFileDialog1.FileName;
            switch (await Serialization.Serialize(memoryBanks, fileName, "MemoryChannelOrganizerBank",
                        "<!ATTLIST MemoryChannelOrganizerBank xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
                        CancellationToken.None))
            {
                case SerializationSuccess<MemoryChannelIndexed[]> configuration:
                    Log.Information(Resources.Serialized_memory_banks);
                    break;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception.Message, Resources.Failed_to_serialize_memory_banks);
                    break;
            }
        }

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

            MemoryChannelIndexed[] memoryBanks = null;

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

            switch (deserializationResult)
            {
                case SerializationSuccess<MemoryChannelIndexed[]> serializationSuccess:
                    Log.Information(Resources.Deserialized_memory_banks);
                    memoryBanks = serializationSuccess.Result;
                    break;
                case SerializationFailure serializationFailure:
                    Log.Warning(serializationFailure.Exception, Resources.Failed_to_deserialize_memory_banks);
                    return;
            }

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

            var memoryBankQueue = new ConcurrentQueue<MemoryChannelIndexed>();
            var snapshotsQueuedTaskCompletionSource = new TaskCompletionSource<object>();

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

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

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

                        return;
                    }

                    switch (Configuration.Radio)
                    {
                        case "Yaesu FT-891":
                            var memoryChannel = (Radios.Yaesu.FT_891.MemoryChannel)memoryBank.MemoryChannel;

                            dataGridView1.Rows[memoryBank.Index].Cells["FrequencyColumn"].Value =
                                memoryBank.MemoryChannel.Frequency;
                            dataGridView1.Rows[memoryBank.Index].Cells["ClarifierDirectionColumn"].Value =
                                (char)memoryChannel.ClarifierDirection;
                            dataGridView1.Rows[memoryBank.Index].Cells["ClarifierOffsetColumn"].Value =
                                memoryChannel.ClarifierOffset;
                            dataGridView1.Rows[memoryBank.Index].Cells["ClarColumn"].Value = memoryChannel.Clar;
                            dataGridView1.Rows[memoryBank.Index].Cells["ModeColumn"].Value =
                                memoryBank.MemoryChannel.MemoryRadioMode.Name;
                            dataGridView1.Rows[memoryBank.Index].Cells["CtcssColumn"].Value =
                                (string)memoryChannel.Ctcss;
                            dataGridView1.Rows[memoryBank.Index].Cells["PhaseColumn"].Value =
                                (string)memoryChannel.Phase;
                            dataGridView1.Rows[memoryBank.Index].Cells["TagColumn"].Value =
                                memoryBank.MemoryChannel.Tag;
                            dataGridView1.Rows[memoryBank.Index].Cells["TextColumn"].Value =
                                memoryBank.MemoryChannel.Text;
                            dataGridView1.Rows[memoryBank.Index].Tag = memoryBank.MemoryChannel;
                            break;
                    }

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

            Application.Idle += IdleHandler;
            try
            {
                foreach (var memoryChannel in memoryBanks) memoryBankQueue.Enqueue(memoryChannel);

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

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

        private void MemoryOrganizerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();
        }

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

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

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

            _readMemoryBanksTask = ReadMemoryBanks(_readLinkedCancellationTokenSource.Token);
        }

        private async Task ReadMemoryBanks(CancellationToken cancellationToken)
        {
            var rows = GetSelectedDataGridViewRows(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<MemoryChannel> rowProgressSuccess:
                            dataGridView1.Rows[rowProgressSuccess.Row.Index].DefaultCellStyle.BackColor =
                                DefaultBackColor;

                            switch (rowProgressSuccess.Data)
                            {
                                case Radios.Yaesu.FT_891.MemoryChannel memoryChannel:
                                    rowProgress.Row.Cells["FrequencyColumn"].Value = memoryChannel.Frequency;
                                    rowProgress.Row.Cells["ClarifierDirectionColumn"].Value =
                                        (char)memoryChannel.ClarifierDirection;
                                    rowProgress.Row.Cells["ClarifierOffsetColumn"].Value =
                                        memoryChannel.ClarifierOffset;
                                    rowProgress.Row.Cells["ClarColumn"].Value = memoryChannel.Clar;
                                    rowProgress.Row.Cells["ModeColumn"].Value = memoryChannel.MemoryRadioMode.Name;
                                    rowProgress.Row.Cells["CtcssColumn"].Value = (string)memoryChannel.Ctcss;
                                    rowProgress.Row.Cells["PhaseColumn"].Value = (string)memoryChannel.Phase;
                                    rowProgress.Row.Cells["TagColumn"].Value = memoryChannel.Tag;
                                    rowProgress.Row.Cells["TextColumn"].Value = memoryChannel.Text;
                                    rowProgress.Row.Tag = rowProgressSuccess.Data;
                                    break;
                            }

                            toolStripStatusLabel1.Text = $"{Resources.Read_memory_bank} {rowProgress.Index + 1}";
                            break;
                        case DataGridViewRowProgressFailure<int> rowProgressFailure:
                            Log.Error(rowProgressFailure.Exception, $"{Resources.Could_not_read_memory_bank}");
                            dataGridView1.Rows[rowProgressFailure.Row.Index].DefaultCellStyle.BackColor = Color.Red;

                            toolStripStatusLabel1.Text =
                                $"{Resources.Could_not_read_memory_bank} {rowProgress.Index + 1}";
                            break;
                    }

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

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

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

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

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

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

            _writeMemoryBanksTask = WriteMemoryBanks(_writeLinkedCancellationTokenSource.Token);
        }

        private async Task WriteMemoryBanks(CancellationToken cancellationToken)
        {
            var rows = GetSelectedDataGridViewRows(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<bool> rowProgressSuccess:
                            dataGridView1.Rows[rowProgressSuccess.Row.Index].DefaultCellStyle.BackColor =
                                DefaultBackColor;
                            var success = rowProgressSuccess.Data;

                            if (success)
                            {
                                toolStripStatusLabel1.Text =
                                    $"{Resources.Wrote_memory_bank} {rowProgress.Index + 1}";
                                toolStripProgressBar1.Increment(1);
                                statusStrip1.Update();

                                return;
                            }

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

                    toolStripStatusLabel1.Text =
                        $"{Resources.Could_not_write_memory_bank} {rowProgress.Index + 1}";
                    toolStripProgressBar1.Increment(1);
                    statusStrip1.Update();
                }
                catch (Exception exception)
                {
                    Log.Error(exception, Resources.Unexpected_error_while_writing_memory_bank);
                }
            });

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

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

        private async void readFromVFOAToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var rows = GetSelectedDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var list = rows.ToList();

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

                    row.Cells["FrequencyColumn"].Value = $"{fa}";
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_A);
            }
        }

        private async void readFromVFOBToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var rows = GetSelectedDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var list = rows.ToList();

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

                    row.Cells["FrequencyColumn"].Value = $"{fb}";
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_read_VFO_B);
            }
        }

        private async void memoryChannelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var rows = GetSelectedDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var row = rows.First();

            try
            {
                using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                           .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                {
                    await _catAssemblies.CatWriteAsync<string>("MC", new[] { row.Cells["LocationColumn"].Value },
                        _cancellationToken);

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

        private async void frequencyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var rows = GetSelectedDataGridViewRows(dataGridView1);

            if (!rows.Any()) return;

            var row = rows.First();

            try
            {
                using (var soundPlayer = new SoundPlayer(Assembly.GetExecutingAssembly()
                           .GetManifestResourceStream("HamBook.Effects.pot.wav")))
                {
                    if (await _catAssemblies.CatSetAsync<int, bool>("FA", new[] { row.Cells["FrequencyColumn"].Value },
                            _cancellationToken))
                        if (Configuration.Navigation.MouseScrollSound)
                            soundPlayer.Play();
                }
            }
            catch (Exception exception)
            {
                Log.Error(exception, Resources.Failed_to_set_VFO_A_frequency);
            }
        }

        private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
        {
            var dataGridView = (DataGridView)sender;

            var hitTest = dataGridView.HitTest(e.X, e.Y);

            switch (e.Button)
            {
                case MouseButtons.Right:
                    if (GetSelectedDataGridViewRows(dataGridView).Count() > 1) return;

                    dataGridView.ClearSelection();
                    if (hitTest.RowIndex != -1) dataGridView.Rows[hitTest.RowIndex].Selected = true;
                    break;
            }
        }

        private IEnumerable<DataGridViewRow> CopySelectedRows(DataGridView dataGridView)
        {
            foreach (var row in GetSelectedDataGridViewRows(dataGridView))
            {
                var clone = row.Clone() as DataGridViewRow;

                for (var i = 0; i < row.Cells.Count; ++i)
                {
                    if (row.Cells[i].ReadOnly) continue;

                    clone.Cells[i].Value = row.Cells[i].Value;
                }

                yield return clone;
            }
        }

        private void DeleteSelectedRows(DataGridView dataGridView)
        {
            foreach (var row in GetSelectedDataGridViewRows(dataGridView))
                for (var i = 0; i < row.Cells.Count; ++i)
                {
                    if (row.Cells[i].ReadOnly) continue;

                    row.Cells[i].Value = default;
                }
        }


        private void PasteSelectedRows(DataGridView dataGridView, IEnumerable<DataGridViewRow> pasteRows)
        {
            var rows = pasteRows.ToList();
            if (rows.Count == 0) return;

            foreach (var row in GetSelectedDataGridViewRows(dataGridView))
            {
                if (rows.Count == 0) return;

                var paste = rows[0];
                for (var i = 0; i < paste.Cells.Count; ++i)
                {
                    if (row.Cells[i].ReadOnly) continue;
                    row.Cells[i].Value = paste.Cells[i].Value;
                }

                rows.RemoveAt(0);
            }
        }

        private IEnumerable<DataGridViewRow> CutSelectedRows(DataGridView dataGridView)
        {
            foreach (var row in GetSelectedDataGridViewRows(dataGridView))
            {
                var clone = row.Clone() as DataGridViewRow;

                for (var i = 0; i < row.Cells.Count; ++i)
                {
                    if (row.Cells[i].ReadOnly) continue;

                    clone.Cells[i].Value = row.Cells[i].Value;

                    row.Cells[i].Value = default;
                }

                yield return clone;
            }
        }

        private void cutToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            _clipboardRows = new List<DataGridViewRow>();
            foreach (var row in CutSelectedRows(dataGridView1)) _clipboardRows.Add(row);
        }

        private void copyToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            _clipboardRows = new List<DataGridViewRow>();
            foreach (var row in CopySelectedRows(dataGridView1)) _clipboardRows.Add(row);
        }

        private void pasteToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            PasteSelectedRows(dataGridView1, _clipboardRows);
        }

        private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DeleteSelectedRows(dataGridView1);
        }

        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);
            }
        }

        //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 void DataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
        {
            switch (Configuration.Radio)
            {
                case "Yaesu FT-891":
                    e.Row.Cells["LocationColumn"].Value = default;
                    e.Row.Cells["FrequencyColumn"].Value = default;
                    e.Row.Cells["ClarifierDirectionColumn"].Value = "+";
                    e.Row.Cells["ClarifierOffsetColumn"].Value = 0;
                    e.Row.Cells["ClarColumn"].Value = default;
                    e.Row.Cells["ModeColumn"].Value = default;
                    e.Row.Cells["CtcssColumn"].Value = "Off";
                    e.Row.Cells["PhaseColumn"].Value = "Simplex";
                    e.Row.Cells["TagColumn"].Value = default;
                    e.Row.Cells["TextColumn"].Value = default;
                    break;
            }
        }
    }
}

Generated by GNU Enscript 1.6.5.90.