Spring – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using Spring.Main;
using Spring.Properties;

namespace Spring.MouseKeyboard
{
    public class Key : IDisposable
    {
#region Public Events & Delegates

        public event EventHandler<KeyComboConfiguredEventArgs> KeyComboConfigured;

        public event EventHandler<KeySlowConfiguredEventArgs> KeySlowConfigured;

        public event EventHandler<KeySpeedConfiguredEventArgs> KeySpeedConfigured;

        public event EventHandler<KeyMessageEventArgs> KeyMessage;

#endregion

#region Public Enums, Properties and Fields

        public bool IsConfigured => _isConfigured;

        public bool IsSpeedConfigured => _isSpeedConfigured;

        public bool IsSlowConfigured => _isSlowConfigured;

        public ConcurrentQueue<KeyAction> KeyCombo { get; private set; }

        public ConcurrentQueue<KeyAction> SpeedCombo { get; private set; }

        public ConcurrentQueue<KeyAction> SlowCombo { get; private set; }

        public BindingType CurrentBindingType { get; set; }

#endregion

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

        private MainForm Form { get; }

        private IKeyboardMouseEvents KeyboardMouseEvents { get; }

        private Configuration.Configuration Configuration { get; set; }

        private bool _disposing;

        private volatile bool _isConfigured;

        private volatile bool _isSlowConfigured;

        private volatile bool _isSpeedConfigured;

        private BufferBlock<KeyAction> _keyCombo;

        private BufferBlock<KeyAction> _slowCombo;

        private BufferBlock<KeyAction> _speedCombo;

#endregion

#region Constructors, Destructors and Finalizers

        public Key()
        {
            _keyCombo = new BufferBlock<KeyAction>();
            _speedCombo = new BufferBlock<KeyAction>();
            _slowCombo = new BufferBlock<KeyAction>();
        }

        public Key(MainForm form,
                   Configuration.Configuration springConfiguration,
                   IKeyboardMouseEvents keyboardMouseEvents) : this()
        {
            Form = form;
            KeyboardMouseEvents = keyboardMouseEvents;
            Configuration = springConfiguration;

            Configuration.PropertyChanged += Configuration_PropertyChanged;

            Form.BindKeyButton.Click += BindKeyButton_Click;

            // Bind speed and slow buttons.
            Form.BindSlowButton.Click += BindSlowButton_Click;
            Form.BindSpeedButton.Click += BindSpeedButton_Click;
        }

        public void Dispose()
        {
            if (_disposing)
            {
                return;
            }

            _disposing = true;

            Configuration.PropertyChanged -= Configuration_PropertyChanged;

            Form.BindKeyButton.Click -= BindKeyButton_Click;

            // Bind speed and slow buttons.
            Form.BindSlowButton.Click -= BindSlowButton_Click;
            Form.BindSpeedButton.Click -= BindSpeedButton_Click;
        }

#endregion

#region Event Handlers

        private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Configuration = (Configuration.Configuration) sender;
        }

        private void BindSpeedButton_Click(object sender, EventArgs e)
        {
            if (_isSpeedConfigured)
            {
                CurrentBindingType = BindingType.Speed;

                _isSpeedConfigured = false;

                Configuration.BoundSpeedButton.Clear();

                Interlocked.Exchange(ref _speedCombo, new BufferBlock<KeyAction>());

                KeyMessage?.Invoke(this, new KeyMessageEventArgs(Strings.Keys_unbound));

                return;
            }

            CurrentBindingType = BindingType.Speed;

            _isSpeedConfigured = false;

            Configuration.BoundSpeedButton.Clear();

            Interlocked.Exchange(ref _speedCombo, new BufferBlock<KeyAction>());

            Start();
        }

        private void BindSlowButton_Click(object sender, EventArgs e)
        {
            if (_isSlowConfigured)
            {
                CurrentBindingType = BindingType.Slow;

                _isSlowConfigured = false;

                Configuration.BoundSlowButton.Clear();

                Interlocked.Exchange(ref _slowCombo, new BufferBlock<KeyAction>());

                KeyMessage?.Invoke(this, new KeyMessageEventArgs(Strings.Keys_unbound));

                return;
            }

            CurrentBindingType = BindingType.Slow;

            _isSlowConfigured = false;

            Configuration.BoundSlowButton.Clear();

            Interlocked.Exchange(ref _slowCombo, new BufferBlock<KeyAction>());

            Start();
        }

        private void BindKeyButton_Click(object sender, EventArgs e)
        {
            if (_isConfigured)
            {
                CurrentBindingType = BindingType.Combos;

                _isConfigured = false;

                Configuration.BoundButton.Clear();

                _keyCombo.Complete();

                Interlocked.Exchange(ref _keyCombo, new BufferBlock<KeyAction>());

                KeyMessage?.Invoke(this, new KeyMessageEventArgs(Strings.Keys_unbound));

                return;
            }

            CurrentBindingType = BindingType.Combos;

            _isConfigured = false;

            Configuration.BoundButton.Clear();

            _keyCombo.Complete();

            Interlocked.Exchange(ref _keyCombo, new BufferBlock<KeyAction>());

            Start();
        }

        private async void SpringKeyApplicationHook_MouseWheel(object sender, MouseEventArgs e)
        {
            var direction = e.Delta > 0 ? MouseScrollDirection.Up : MouseScrollDirection.Down;

            switch (CurrentBindingType)
            {
                case BindingType.Combos:
                    await _keyCombo.SendAsync(new KeyActionMouseScrollDirection(direction));

                    break;

                case BindingType.Speed:
                    await _speedCombo.SendAsync(new KeyActionMouseScrollDirection(direction));

                    break;

                case BindingType.Slow:
                    await _slowCombo.SendAsync(new KeyActionMouseScrollDirection(direction));

                    break;
            }

            KeyboardMouseEvents.MouseWheel -= SpringKeyApplicationHook_MouseWheel;

            Stop();
        }

        private async void SpringKeyApplicationHook_KeyDown(object sender, KeyEventArgs e)
        {
            e.SuppressKeyPress = true;

            switch (CurrentBindingType)
            {
                case BindingType.Combos:
                    await _keyCombo.SendAsync(new KeyActionKeys(e.KeyCode));

                    break;

                case BindingType.Speed:
                    await _speedCombo.SendAsync(new KeyActionKeys(e.KeyCode));

                    break;

                case BindingType.Slow:
                    await _slowCombo.SendAsync(new KeyActionKeys(e.KeyCode));

                    break;
            }
        }

        private void SpringKeyApplicationHook_KeyUp(object sender, KeyEventArgs e)
        {
            e.SuppressKeyPress = true;

            KeyboardMouseEvents.KeyDown -= SpringKeyApplicationHook_KeyDown;
            KeyboardMouseEvents.KeyUp -= SpringKeyApplicationHook_KeyUp;

            Stop();
        }

#endregion

#region Public Methods

        public async Task Load()
        {
            foreach (var binding in Enum.GetValues(typeof(BindingType))
                                        .OfType<BindingType>())
            {
                switch (binding)
                {
                    case BindingType.Combos:

                        LoadBindingType(Configuration.BoundButton, _keyCombo);

                        break;

                    case BindingType.Slow:
                        LoadBindingType(Configuration.BoundSlowButton, _slowCombo);

                        break;

                    case BindingType.Speed:
                        LoadBindingType(Configuration.BoundSpeedButton, _speedCombo);

                        break;
                }

                BindKeyType(binding);
            }

            // Set up the key combination.
            foreach (var key in Configuration.BoundButton)
            {
                if (Enum.TryParse<MouseScrollDirection>(key, out var mouseScrollDirection))
                {
                    await _keyCombo.SendAsync(new KeyActionMouseScrollDirection(mouseScrollDirection));

                    continue;
                }

                if (Enum.TryParse<Keys>(key, out var springKey))
                {
                    await _keyCombo.SendAsync(new KeyActionKeys(springKey));

                    continue;
                }

                if (Enum.TryParse<MouseButtons>(key, out var springButton))
                {
                    await _keyCombo.SendAsync(new KeyActionMouseButtons(springButton));
                }
            }

            _keyCombo.Complete();

            if (ConfigureBinding(ref _keyCombo, out var keyComboButton, out var keyCombo))
            {
                Configuration.BoundButton = keyComboButton;
                KeyCombo = keyCombo;

                _isConfigured = true;
                KeyComboConfigured?.Invoke(this, new KeyComboConfiguredEventArgs());
            }
        }

#endregion

#region Private Methods

        private static void LoadBindingType(IEnumerable<string> keys, ITargetBlock<KeyAction> store)
        {
            foreach (var key in keys)
            {
                if (Enum.TryParse<MouseScrollDirection>(key, out var mouseScrollDirection))
                {
                    store.Post(new KeyActionMouseScrollDirection(mouseScrollDirection));

                    continue;
                }

                if (Enum.TryParse<Keys>(key, out var springKey))
                {
                    store.Post(new KeyActionKeys(springKey));

                    continue;
                }

                if (Enum.TryParse<MouseButtons>(key, out var springButton))
                {
                    store.Post(new KeyActionMouseButtons(springButton));
                }
            }

            store.Complete();
        }

        private void Start()
        {
            KeyMessage?.Invoke(this, new KeyMessageEventArgs(Strings.Binding_keys));

            KeyboardMouseEvents.KeyUp += SpringKeyApplicationHook_KeyUp;
            KeyboardMouseEvents.KeyDown += SpringKeyApplicationHook_KeyDown;
            KeyboardMouseEvents.MouseWheel += SpringKeyApplicationHook_MouseWheel;
        }

        private void Stop()
        {
            KeyboardMouseEvents.KeyUp -= SpringKeyApplicationHook_KeyUp;
            KeyboardMouseEvents.KeyDown -= SpringKeyApplicationHook_KeyDown;
            KeyboardMouseEvents.MouseWheel -= SpringKeyApplicationHook_MouseWheel;

            BindKeyType(CurrentBindingType);

            CurrentBindingType = BindingType.None;

            KeyMessage?.Invoke(this, new KeyMessageEventArgs(Strings.Keys_bound));
        }

        private void BindKeyType(BindingType currentBindingType)
        {
            switch (currentBindingType)
            {
                case BindingType.Slow:
                    if (!ConfigureBinding(ref _slowCombo, out var slowComboButton, out var slowCombo))
                    {
                        return;
                    }

                    Configuration.BoundSlowButton = slowComboButton;
                    SlowCombo = slowCombo;

                    _isSlowConfigured = true;
                    KeySlowConfigured?.Invoke(this, new KeySlowConfiguredEventArgs());

                    break;

                case BindingType.Speed:
                    if (!ConfigureBinding(ref _speedCombo, out var speedComboButton, out var speedCombo))
                    {
                        return;
                    }

                    Configuration.BoundSpeedButton = speedComboButton;
                    SpeedCombo = speedCombo;

                    _isSpeedConfigured = true;
                    KeySpeedConfigured?.Invoke(this, new KeySpeedConfiguredEventArgs());

                    break;

                case BindingType.Combos:
                    if (!ConfigureBinding(ref _keyCombo, out var keyComboButton, out var keyCombo))
                    {
                        return;
                    }

                    Configuration.BoundButton = keyComboButton;
                    KeyCombo = keyCombo;

                    _isConfigured = true;
                    KeyComboConfigured?.Invoke(this, new KeyComboConfiguredEventArgs());

                    break;
            }
        }

        private static bool ConfigureBinding(ref BufferBlock<KeyAction> bufferBlock,
                                             out List<string> keys,
                                             out ConcurrentQueue<KeyAction> queue)
        {
            if (!bufferBlock.TryReceiveAll(out var keyComboKeys))
            {
                queue = null;
                keys = null;

                return false;
            }

            var uniqueKeyComboKeys = keyComboKeys.Distinct();

            var comboKeys = uniqueKeyComboKeys as KeyAction[] ?? uniqueKeyComboKeys.ToArray();

            keys = new List<string>();

            foreach (var key in comboKeys)
            {
                switch (key)
                {
                    case KeyActionKeys keyActionKeys:
                        keys.Add(keyActionKeys.Keys.ToString());

                        break;
                    case KeyActionMouseButtons keyActionMouseButtons:
                        keys.Add(keyActionMouseButtons.MouseButtons.ToString());

                        break;
                    case KeyActionMouseScrollDirection keyActionMouseScrollDirection:
                        keys.Add(keyActionMouseScrollDirection.Direction.ToString());

                        break;
                }
            }

            queue = new ConcurrentQueue<KeyAction>(comboKeys);

            return true;
        }

#endregion
    }
}