Spring – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using NetSparkleUpdater;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using QRCoder;
using Spring.About;
using Spring.Charging;
using Spring.Discharging;
using Spring.Editing;
using Spring.HUD;
using Spring.MouseKeyboard;
using Spring.Properties;
using Spring.Serialization;
using Spring.Serialization.Combos;
using Spring.Serialization.Configuration;
using Spring.Settings;
using Spring.Utilities;
using Spring.Utilities.Collections.RangeTree;
using SpringCombos;

namespace Spring.Main
{
    public partial class MainForm : Form
    {
#region Public Events & Delegates

        public event EventHandler<EventArgs> SpringKeyButtonClicked;

        public event EventHandler<EventArgs> SpringSlowButtonClicked;

        public event EventHandler<EventArgs> SpringSpeedButtonClicked;

        public event EventHandler<SpeedbarValueChangedEventArgs> SpeedbarValueChanged;

        public event EventHandler<ConfigurationRestoredEventArgs> ConfigurationRestored;

#endregion

#region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private Configuration.Configuration Configuration { get; set; }

        private OSD OSD { get; set; }

        private Key Key { get; set; }

        private KeyWatch WatchKey { get; set; }

        private Charge Charge { get; set; }

        private Discharge Discharge { get; set; }

        private IKeyboardMouseEvents KeyboardMouseEvents { get; }

        private EditorForm EditorForm { get; set; }

        private SettingsForm SettingsForm { get; set; }

        private BufferBlock<ComboQueueItem> ComboQueue { get; }

        private ActionBlock<ComboQueueItem> ComboQueueAction { get; }

        private static QRCodeGenerator QrGenerator { get; set; }

        private readonly IDisposable _comboQueueLink;

        private RangeTreeAction<TimeSpan, int> _rangeTree;
        private readonly SparkleUpdater _sparkle;

        #endregion

#region Constructors, Destructors and Finalizers

        public MainForm()
        {
            InitializeComponent();
            Utilities.WindowState.FormTracker.Track(this);

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

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

            Directory.CreateDirectory(Constants.UserApplicationDirectory);

            QrGenerator = new QRCodeGenerator();

            ComboQueue =
                new BufferBlock<ComboQueueItem>(new DataflowBlockOptions
                                                { BoundedCapacity = 4, EnsureOrdered = true });

            ComboQueueAction = new ActionBlock<ComboQueueItem>(DoComboQueueAction,
                new ExecutionDataflowBlockOptions { EnsureOrdered = true, BoundedCapacity = 4 });

            _comboQueueLink = ComboQueue.LinkTo(ComboQueueAction);

            toolStripStatusLabel1.Text = Strings.Welcome_to_Spring;

            ConfigurationRestored += MainForm_ConfigurationRestored;

            KeyboardMouseEvents = Hook.GlobalEvents();
        }

        /// <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)
            {
                ConfigurationRestored -= MainForm_ConfigurationRestored;

                _comboQueueLink?.Dispose();
                Discharge?.Dispose();
                Charge?.Dispose();
                WatchKey?.Dispose();
                Key?.Dispose();

                components.Dispose();
            }

            OSD?.Dispose();
            OSD = null;

            base.Dispose(disposing);
        }

#endregion

#region Event Handlers

        private void ComboQueuePictureBox_Click(object sender, EventArgs e)
        {
            var pictureBox = (PictureBox) sender;

            Charge.Load((Combos) pictureBox.Tag);
        }

        private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var aboutForm = new AboutForm();
            aboutForm.Show();
        }

        private void TutorialToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new Tutorial.Tutorial(this, KeyboardMouseEvents).Show();
        }

        private async void MainForm_ConfigurationRestored(object sender, ConfigurationRestoredEventArgs e)
        {
            await Initialize(e.Combos);
        }

        private void SpeedBar_ValueChanged(object sender, EventArgs e)
        {
            var trackBar = (TrackBar) sender;
            SpeedbarValueChanged?.Invoke(sender, new SpeedbarValueChangedEventArgs(trackBar.Value));
        }

        private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void BindSpeedButton_Click(object sender, EventArgs e)
        {
            SpringSpeedButtonClicked?.Invoke(this, e);
        }

        private void BindSlowButton_Click(object sender, EventArgs e)
        {
            SpringSlowButtonClicked?.Invoke(this, e);
        }

        private void BindKeyButton_Click(object sender, EventArgs e)
        {
            SpringKeyButtonClicked?.Invoke(this, e);
        }

        private async void SaveFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            switch (await Charge.Combos.Serialize(saveFileDialog1.FileName))
            {
                case SerializationSuccess<Combos> serializationSuccess:
                    toolStripStatusLabel1.Text = $"{Strings.Combos_wrote_successfully}";

                    Charge.StartCharge(serializationSuccess.Result);

                    Configuration.Spring.Store = saveFileDialog1.FileName;

                    if (!Configuration.Spring.History.Contains(saveFileDialog1.FileName))
                    {
                        Configuration.Spring.History.Add(saveFileDialog1.FileName);
                    }

                    break;

                case SerializationFailure serializationFailed:
                    toolStripStatusLabel1.Text =
                        $"{Strings.Failed_to_write_combos}: {serializationFailed.Exception.Message}";

                    break;
            }
        }

        private async void OpenFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            var fileInfo = new FileInfo(openFileDialog1.FileName);

            var combos = await LoadCombos(fileInfo);

            if (combos == null)
            {
                return;
            }

            Charge.StartCharge(combos);

            Configuration.Spring.Store = openFileDialog1.FileName;

            if (!Configuration.Spring.History.Contains(openFileDialog1.FileName))
            {
                Configuration.Spring.History.Add(openFileDialog1.FileName);
            }
        }

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

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

        private async void MainForm_Load(object sender, EventArgs e)
        {
            var configurationFileInfo = new FileInfo(Constants.ConfigurationFilePath);

            // Load configuration.
            switch (ConfigurationSerialization.Deserialize(configurationFileInfo))
            {
                case SerializationSuccess<Configuration.Configuration> serializationSuccess:
                    Configuration = serializationSuccess.Result;
                    toolStripStatusLabel1.Text = $"{Strings.Read_configuration_successfully}";

                    break;

                case SerializationFailure serializationFailed:
                    toolStripStatusLabel1.Text =
                        $"{Strings.Failed_to_read_configuration}: {serializationFailed.Exception.Message}";

                    // Attempt to load default configuration
                    var fileInfo = new FileInfo("Configuration.xml.default");

                    switch (ConfigurationSerialization.Deserialize(fileInfo))
                    {
                        case SerializationSuccess<Configuration.Configuration> serializationSuccess:
                            toolStripStatusLabel1.Text =
                                $"{Strings.Read_default_configuration_successfully}";

                            Configuration = serializationSuccess.Result;

                            break;

                        case SerializationFailure defaultSerializationFailure:
                            toolStripStatusLabel1.Text =
                                $"{Strings.Failed_to_read_default_configuration}: {defaultSerializationFailure.Exception.Message}";

                            Configuration = new Configuration.Configuration();

                            break;
                    }

                    break;
            }

            // Load combos.
            if (string.IsNullOrEmpty(Configuration.Spring.Store))
            {
                ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(new Combos()));

                return;
            }

            var storeFileInfo = new FileInfo(Configuration.Spring.Store);

            var combos = await LoadCombos(storeFileInfo);

            if (combos == null)
            {
                ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(new Combos()));

                return;
            }

            await ComboQueue.SendAsync(new ComboQueueItem(comboQueuePictureBox1, combos));

            ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(combos));
        }

        private void WatchKey_SpeedAdjusted(object sender, KeySpeedAdjustedEventArgs e)
        {
            this.InvokeIfRequired(form =>
                {
                    switch (e.Multiplier)
                    {
                        case -1:
                            form.SpeedBar.Value = Math.Max(form.SpeedBar.Value - 1, form.SpeedBar.Minimum);

                            break;

                        case 1:
                            form.SpeedBar.Value = Math.Min(form.SpeedBar.Value + 1, form.SpeedBar.Maximum);

                            break;
                    }
                });
        }

        private void Key_KeyMessage(object sender, KeyMessageEventArgs e)
        {
            this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = e.Message; });
        }

        private void Loading_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            _rangeTree = new RangeTreeAction<TimeSpan, int>
                         {
                             {
                                 TimeSpan.FromSeconds(0),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 ActionNoCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 ActionNoCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
                                                      Configuration.Spring.Charge.ChargeSeconds),
                                 ActionCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
                                                      Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.MaxValue, ActionNoCharge
                             }
                         };
        }

        private void WatchKey_KeyUp(object sender, KeyUpEventArgs e)
        {
            OSD.Text = OSD.Symbols.KeyReleased();
        }

        private void Charge_ChargeLoaded(object sender, ChargeLoadedEventArgs e)
        {
            this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_charged; });

            if (e.Combos == null)
            {
                return;
            }

            switch (e.Combos.ComboRepeat)
            {
                case 0:
                    OSD.Text = OSD.Symbols.ChargeStop(Resources.Infinity);

                    break;

                default:
                    OSD.Text = OSD.Symbols.ChargeStop(e.Combos.ComboRepeat.ToString());

                    break;
            }
        }

        private async void ChargeChanged(object sender, ChargeChangedEventArgs e)
        {
            this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_charged; });

            if (e.Combos == null)
            {
                return;
            }

            var combos = Charge.Combos;

            await ComboQueue.SendAsync(new ComboQueueItem(comboQueuePictureBox1, combos));

            switch (e.Combos.ComboRepeat)
            {
                case 0:
                    OSD.Text = OSD.Symbols.ChargeStop(Resources.Infinity);

                    break;

                default:
                    OSD.Text = OSD.Symbols.ChargeStop(e.Combos.ComboRepeat.ToString());

                    break;
            }
        }

        private void ChargeStart(object sender, ChargeStartEventArgs e)
        {
            if (Charge.Combos == null)
            {
                return;
            }

            this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_is_charging; });

            OSD.Text = OSD.Symbols.ChargeStart();
        }

        private void Discharge_DischargeStart(object sender, DischargeStartEventArgs e)
        {
            this.InvokeIfRequired(form =>
                {
                    form.toolStripStatusLabel1.Text = Strings.Spring_is_discharging;
                    form.toolStripProgressBar1.Value = 0;

                    switch (e.Repeat)
                    {
                        case 0:
                            form.label5.Text = Resources.Infinity;
                            form.toolStripProgressBar1.Visible = false;

                            OSD.Text = OSD.Symbols.DischargeProgress(Resources.Infinity);

                            break;

                        default:
                            form.label5.Text = e.Repeat.ToString();
                            form.toolStripProgressBar1.Visible = true;

                            OSD.Text = OSD.Symbols.DischargeProgress(e.Repeat.ToString());

                            break;
                    }
                });
        }

        private void Discharge_DischargeStop(object sender, DischargeStopEventArgs e)
        {
            OSD.Text = string.Empty;

            this.InvokeIfRequired(form =>
                {
                    form.toolStripStatusLabel1.Text = Strings.Spring_discharge_cancelled;
                    form.toolStripProgressBar1.Visible = false;

                    switch (e.Repeat)
                    {
                        case 0:
                            form.label5.Text = Resources.Infinity;

                            OSD.Text = OSD.Symbols.DischargeStop(Resources.Infinity);

                            break;

                        default:
                            form.label5.Text = e.Repeat.ToString();

                            OSD.Text = OSD.Symbols.DischargeStop(e.Repeat.ToString());

                            break;
                    }
                });
        }

        private void Discharge_DischargeProgress(object sender, DischargeProgressEventArgs e)
        {
            this.InvokeIfRequired(form =>
                {
                    switch (e.Repeat)
                    {
                        case 0:
                            break;

                        default:
                            var progress = (int) (100f - 100f * e.Current / (e.Total * e.Repeat));

                            if (progress < 0 || progress > 100)
                            {
                                break;
                            }

                            form.toolStripProgressBar1.Value = progress;

                            var repeats = (int) (1f * e.Current / e.Total);

                            form.label5.Text = $@"{repeats}";

                            OSD.Text = OSD.Symbols.DischargeProgress(repeats.ToString());

                            break;
                    }
                });
        }

        private void WatchKey_KeyDown(object sender, KeyDownEventArgs e)
        {
            _rangeTree.Query(e.Elapsed);
        }

        private async void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Save combos.
            await SaveCombos(Configuration.Spring.Store);

            // Save configuration file.
            switch (await Configuration.Serialize(Constants.ConfigurationFilePath))
            {
                case SerializationSuccess _:
                    toolStripStatusLabel1.Text = $"{Strings.Wrote_configuration_successfully}";

                    break;

                case SerializationFailure serializationFailed:
                    toolStripStatusLabel1.Text =
                        $"{Strings.Failed_to_write_configuration}: {serializationFailed.Exception.Message}";

                    break;
            }
        }

        private void EditorButton_Click(object sender, EventArgs e)
        {
            if (EditorForm != null)
            {
                return;
            }

            EditorForm = new EditorForm(Configuration, Charge.Combos);
            EditorForm.Closing += EditorForm_Closing;
            EditorForm.Edited += EditorForm_Edited;
            EditorForm.Show();
        }

        private void EditorForm_Edited(object sender, EditEventArgs e)
        {
            switch (e)
            {
                case EditSuccessEventArgs editSuccessEventArgs:
                    Charge.StartCharge(editSuccessEventArgs.Combos);

                    break;

                case EditFailureEventArgs editFailureEventArgs:
                    break;
            }
        }

        private void EditorForm_Closing(object sender, CancelEventArgs e)
        {
            if (EditorForm == null)
            {
                return;
            }

            EditorForm.Closing -= EditorForm_Closing;
            EditorForm.Edited -= EditorForm_Edited;
            EditorForm.Dispose();
            EditorForm = null;

            toolStripStatusLabel1.Text = $"{Strings.Combos_read_successfully}";
        }

        private void SettingsButton_Click(object sender, EventArgs e)
        {
            if (SettingsForm != null)
            {
                return;
            }

            SettingsForm = new SettingsForm(Configuration);
            SettingsForm.FormClosing += SettingsForm_FormClosing;
            SettingsForm.Show();
        }

        private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (SettingsForm == null)
            {
                return;
            }

            SettingsForm.FormClosing -= SettingsForm_FormClosing;
            SettingsForm.Dispose();
            SettingsForm = null;
        }

        private void History_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            RecreateHistoryMenu(e.NewItems.OfType<string>());
        }

        private void Spring_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var springHistory = ((Configuration.Spring) sender).History;

            RecreateHistoryMenu(springHistory);
        }

        private async void HistoryToolStripMenu_Click(object sender, EventArgs e)
        {
            var selectedFile = ((ToolStripMenuItem) sender).Text;

            if (string.IsNullOrEmpty(selectedFile))
            {
                return;
            }

            var fileInfo = new FileInfo(selectedFile);

            var combos = await LoadCombos(fileInfo);

            if (combos == null)
            {
                return;
            }

            Charge.StartCharge(combos);

            Configuration.Spring.Store = openFileDialog1.FileName;

            if (!Configuration.Spring.History.Contains(openFileDialog1.FileName))
            {
                Configuration.Spring.History.Add(openFileDialog1.FileName);
            }
        }

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

            MessageBox.Show("No updates available at this time.", "Spring", MessageBoxButtons.OK,
                MessageBoxIcon.Asterisk,
                MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
        }

#endregion

#region Private Methods

        private async void DoComboQueueAction(ComboQueueItem springComboQueueItem)
        {
            var serialization = await springComboQueueItem.Combos.Serialize();

            switch (serialization)
            {
                case SerializationSuccess<Combos> serializationSuccess:
                    var comboText = serializationSuccess.Text;

                    var qrCodeData = QrGenerator.CreateQrCode(Helpers.Sha1Hash(comboText),
                        QRCodeGenerator.ECCLevel.L,
                        false,
                        false,
                        QRCodeGenerator.EciMode.Default,
                        1);

                    var qrCode = new QRCode(qrCodeData);
                    var qrCodeImage = qrCode.GetGraphic(20);
                    var image = Helpers.Crop(qrCodeImage);

                    // Set the QR code hash and the combos.
                    comboQueuePictureBox4.BackgroundImage = comboQueuePictureBox3.BackgroundImage;
                    comboQueuePictureBox4.Tag = comboQueuePictureBox3.Tag;
                    comboQueuePictureBox3.BackgroundImage = comboQueuePictureBox2.BackgroundImage;
                    comboQueuePictureBox3.Tag = comboQueuePictureBox2.Tag;
                    comboQueuePictureBox2.BackgroundImage = comboQueuePictureBox1.BackgroundImage;
                    comboQueuePictureBox2.Tag = comboQueuePictureBox1.Tag;
                    comboQueuePictureBox1.BackgroundImage = image;
                    comboQueuePictureBox1.Tag = serializationSuccess.Result;

                    break;

                case SerializationFailure serializationFailure:
                    break;
            }
        }

        private async Task<Combos> LoadCombos(FileInfo file)
        {
            switch (await ComboSerialization.Deserialize(file, 0))
            {
                case SerializationSuccess<Combos> serializationSuccess:
                    toolStripStatusLabel1.Text = $"{Strings.Combos_read_successfully}";

                    return serializationSuccess.Result;

                case SerializationFailure deserializationFailure:
                    toolStripStatusLabel1.Text =
                        $"{Strings.Failed_to_read_combos}: {deserializationFailure.Exception.Message}";

                    break;
            }

            return null;
        }

        /// <summary>
        ///     Serialize combos to file.
        /// </summary>
        /// <param name="file">the file to which the combos will be serialized</param>
        /// <returns>an awaitable task</returns>
        private async Task SaveCombos(string file)
        {
            switch (await Charge.Combos.Serialize(file))
            {
                case SerializationSuccess<Combos> serializationSuccess:
                    toolStripStatusLabel1.Text = $"{Strings.Combos_wrote_successfully}";

                    break;

                case SerializationFailure deserializationFailure:
                    toolStripStatusLabel1.Text =
                        $"{Strings.Failed_to_write_combos}: {deserializationFailure.Exception.Message}";

                    break;
            }
        }

        private void ActionCharge(TimeSpan x)
        {
            this.InvokeIfRequired(form =>
                {
                    switch (x.Seconds - Configuration.Spring.Charge.ChargeSeconds)
                    {
                        case 0:
                            form.label5.Text = Resources.Infinity;
                            OSD.Text = OSD.Symbols.KeyDown(Resources.Infinity);

                            break;

                        default:
                            var comboText = (x.Seconds - Configuration.Spring.Charge.ChargeSeconds).ToString();

                            form.label5.Text = comboText;

                            OSD.Text = OSD.Symbols.KeyDown(comboText);

                            break;
                    }
                });
        }

        private void ActionNoCharge(TimeSpan x)
        {
            this.InvokeIfRequired(form =>
                {
                    switch (x.Seconds < Configuration.Spring.Charge.ChargeSeconds)
                    {
                        case true:
                            form.label5.Text = string.Empty;
                            OSD.Text = OSD.Symbols.KeyPressed();

                            break;

                        default:
                            form.label5.Text = Resources.Infinity;
                            OSD.Text = OSD.Symbols.KeyDown(Resources.Infinity);

                            break;
                    }
                });
        }

        private async Task Initialize(Combos springCombos)
        {
            Key = new Key(this, Configuration, KeyboardMouseEvents);
            Key.KeyMessage += Key_KeyMessage;
            WatchKey = new KeyWatch(KeyboardMouseEvents, Key);
            WatchKey.SpeedAdjusted += WatchKey_SpeedAdjusted;
            WatchKey.KeyDown += WatchKey_KeyDown;
            WatchKey.KeyUp += WatchKey_KeyUp;

            Charge = new Charge(springCombos, Configuration, KeyboardMouseEvents, Key, WatchKey);
            Charge.ChargeStart += ChargeStart;
            Charge.ChargeChanged += ChargeChanged;
            Charge.ChargeLoaded += Charge_ChargeLoaded;

            Discharge = new Discharge(this, Configuration, Key, WatchKey, Charge, SpeedBar.Value);
            Discharge.SpringDischargeStart += Discharge_DischargeStart;
            Discharge.SpringDischargeStop += Discharge_DischargeStop;
            Discharge.SpringDischargeProgress += Discharge_DischargeProgress;

            Configuration.Spring.Charge.PropertyChanged += Loading_PropertyChanged;
            Configuration.Spring.PropertyChanged += Spring_PropertyChanged;
            Configuration.Spring.History.CollectionChanged += History_CollectionChanged;

            OSD = new OSD(Configuration, KeyboardMouseEvents);

            if (OSD.Configuration.HUD.Enable)
            {
                OSD.Text = OSD.Symbols.HUDShown();
            }

            _rangeTree = new RangeTreeAction<TimeSpan, int>
                         {
                             {
                                 TimeSpan.FromSeconds(0),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 ActionNoCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 ActionNoCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
                                                      Configuration.Spring.Charge.ChargeSeconds),
                                 ActionCharge
                             },
                             {
                                 TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
                                                      Configuration.Spring.Charge.ChargeSeconds),
                                 TimeSpan.MaxValue, ActionNoCharge
                             }
                         };

            RecreateHistoryMenu(Configuration.Spring.History);

            await Key.Load();
        }

        private void RecreateHistoryMenu(IEnumerable<string> springHistory)
        {
            var history = springHistory.ToList();

            foreach (var toolStripMenuItem in
                historyToolStripMenuItem.DropDownItems.OfType<ToolStripMenuItem>())
            {
                toolStripMenuItem.Click -= HistoryToolStripMenu_Click;

                history.Add(toolStripMenuItem.Text);
            }

            historyToolStripMenuItem.DropDownItems.Clear();

            foreach (var item in history.Distinct())
            {
                if (string.IsNullOrEmpty(item))
                {
                    continue;
                }

                historyToolStripMenuItem.DropDownItems.Add(item, null, HistoryToolStripMenu_Click);
            }
        }

#endregion
    }
}