#region << Using Directives >>
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.ComponentModel;
#endregion

namespace Volpe.Cafe.EspressoUI.Charts
{
    //
    // This file contains functionallity supporting mouse tracking over the chart and arrow key navigation.
    //
    partial class Chart
    {

        //----- mouse handlers -----
        /// <summary>
        /// Raises the <see cref="Control.MouseDown"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (this.MouseState.HandleMouseDown(e))
            {
                this.Select();
            }
            else if (this.SelectOnMouseClick)
            {
                base.OnMouseDown(e);
            }
        }
        /// <summary>
        /// Raises the <see cref="Control.MouseMove"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (!this.MouseState.HandleMouseMove(e))
            {
                base.OnMouseMove(e);
            }
        }
        /// <summary>
        /// Raises the <see cref="Control.MouseUp"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (!this.MouseState.HandleMouseUp(e))
            {
                if (e.Button == MouseButtons.Right)
                {
                    if (!this.DisableContextMenu)
                    {   // show context menu
                        //
                        // placeholder for showing the context menu
                        //
                    }
                }
                base.OnMouseUp(e);
            }
        }

        //----- key handlers -----
        /// <summary>
        /// Raises the <see cref="Control.PreviewKeyDown"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
        {
            this.KeyboardState.HandlePreviewKeyDown(e);
            base.OnPreviewKeyDown(e);
        }
        /// <summary>
        /// Raises the <see cref="Control.KeyUp"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnKeyUp(KeyEventArgs e)
        {
            this.KeyboardState.HandleKeyUp(e);
            base.OnKeyUp(e);
        }

        #region /*** MouseTrackingState class and Provided Properties ***/

        //----- ChartBase.MouseTrackingProperties class -----
        /// <summary>
        /// Provides properties for tracking the mouse when a mouse button is in a pressed down state.
        /// </summary>
        [Serializable]
        class MouseTrackingProperties
        {
            internal MouseTrackingProperties(MouseTrackingProperties defaultProperties)
                : this(
                    defaultProperties.TrackHTravel,
                    defaultProperties.TrackVTravel,
                    defaultProperties.MinDistanceTracking,
                    defaultProperties.MinDistanceAction,
                    defaultProperties.Effect) { }
            internal MouseTrackingProperties(bool trackH, bool trackV, int minTracking, float minAction, AnimationEffect effect)
            {
                this.TrackHTravel        = trackH;
                this.TrackVTravel        = trackV;
                this.MinDistanceTracking = minTracking;
                this.MinDistanceAction   = minAction;
                this.Effect              = effect;
            }

            //----- properties -----
            /// <summary>Gets whether the mouse travel in either direction should be tracked when a mouse button is pressed.</summary>
            public bool TrackMouse { get { return this.TrackHTravel || this.TrackVTravel; } }
            /// <summary>Gets or sets whether the horizontal mouse travel should be tracked when a mouse button is pressed.</summary>
            public bool TrackHTravel { get; set; }
            /// <summary>Gets or sets whether the vertical mouse travel should be tracked when a mouse button is pressed.</summary>
            public bool TrackVTravel { get; set; }
            /// <summary>Gets or sets the minimum distance, in pixels, the mouse should travel before tracking begins.</summary>
            public int MinDistanceTracking { get; set; }
            /// <summary>Gets or sets the minimum relative distance, in percent, the mouse should travel before any action in responce to mouse tracking should be activated.</summary>
            public float MinDistanceAction { get; set; }
            /// <summary>Gets or sets the animation effect to display when the mouse travels across the chart while a mouse button is pressed.</summary>
            public AnimationEffect Effect { get; set; }
        }
        //----- ChartBase.MouseTrackingState class -----
        /// <summary>
        /// Provides mouse tracking support.
        /// </summary>
        [Serializable]
        class MouseTrackingState
        {

            #region /*** Events ***/

            /// <summary>Occurs after mouse tracking state changes.</summary>
            public event EventHandler TrackingChanged;
            /// <summary>Occurs after mouse activated state changes.</summary>
            public event EventHandler ActivatedChanged;

            #endregion

            #region /*** Ctors ***/

            internal MouseTrackingState(Chart parent)
            {
                this.Parent = parent;
            }

            #endregion

            #region /*** Methods ***/

            //----- methods for raising events -----
            /// <summary>
            /// Raises the <see cref="TrackingChanged"/> event.
            /// </summary>
            void OnTrackingChanged()
            {
                this.TrackingChanged?.Invoke(this, EventArgs.Empty);
            }
            /// <summary>
            /// Raises the <see cref="ActivatedChanged"/> event.
            /// </summary>
            void OnActivatedChanged()
            {
                this.ActivatedChanged?.Invoke(this, EventArgs.Empty);
            }

            //----- routed methods -----
            /// <summary>
            /// Handles the mouse tracking after a mouse button was pressed down.
            /// </summary>
            /// <param name="e">Contains mouse event args.</param>
            /// <returns>true, if mouse-down event was handled internally; false, to raise the mouse-down event for external processing.</returns>
            public bool HandleMouseDown(MouseEventArgs e)
            {
                // if mouse is already pressed down, we do not want to take any other action until the mouse has been released
                if (!this.IsMouseDown && this.Parent.GridBounds.Contains(e.Location))
                {   // currently only left/right mouse button tracking is supported
                    // > update mouse down state
                    this.IsMouseDown = (e.Button == MouseButtons.Left  && this._lmbProperties.TrackMouse) ||
                                       (e.Button == MouseButtons.Right && this._rmbProperties.TrackMouse);
                    // > reset and update other mouse tracking states
                    if (this.IsMouseDown)
                    {
                        this.IsTracking       = false;
                        this.IsActivated      = false;
                        this.Button           = e          .Button;
                        this.StartLocation    = e          .Location;
                        this.Location         = e          .Location;
                        this.DistanceTraveled = Point      .Empty;
                        this.PercentTraveled  = PointF     .Empty;
                        this.Orientation      = Orientation.Horizontal;
                        // cache properties
                        this.CachedFadeLevel = this.Parent.FadeLevel;
                        // reset animation effects
                        this.Parent.Animation.Reset();
                    }
                }
                // if the mouse is in a pressed down state, we want to suppress any external event processing
                return this.IsMouseDown;
            }
            /// <summary>
            /// Handles the mouse tracking as the mouse moves while a button is in a pressed down state.
            /// </summary>
            /// <param name="e">Contains mouse event args.</param>
            /// <returns>true, if mouse-move event was handled internally; false, to raise the mouse-move event for external processing.</returns>
            public bool HandleMouseMove(MouseEventArgs e)
            {
                // track mouse movement while it is pressed down
                if (this.IsMouseDown)
                {   // update location and travel
                    Chart   chart = this.Parent;
                    GridInfo grid = chart.Grid;
                    int     gridW = grid.Bounds.Width;
                    int     gridH = grid.Bounds.Height;
                    int    deltaX = e.X - this.StartLocation.X;
                    int    deltaY = e.Y - this.StartLocation.Y;
                    float    relX = (float)deltaX / (float)gridW;
                    float    relY = (float)deltaY / (float)gridH;
                    //
                    this.Location         = e.Location;
                    this.DistanceTraveled = new Point(deltaX, deltaY);
                    this.PercentTraveled  = new PointF(relX, relY);

                    // determine travel orientation
                    var properties = this.Properties;
                    if (properties.TrackHTravel && properties.TrackVTravel)
                    {   // if we're tracking travel in both directions, determine orientation based on greater
                        // absolute distance (in pixels) in horizontal and vertical directions the mouse has traveled
                        this.Orientation = (Math.Abs(deltaX) < Math.Abs(deltaY)) ? Orientation.Vertical : Orientation.Horizontal;
                    }
                    else
                    {   // if only tracking in one direction -- set orientation to match
                        this.Orientation = (properties.TrackVTravel) ? Orientation.Vertical : Orientation.Horizontal;
                    }

                    // compute delta distance in the direction of travel
                    int deltaTravel = Math.Abs((this.IsVTravel) ? deltaY : deltaX);
                    float relTravel = Math.Abs((this.IsVTravel) ? relY   : relX  );

                    // update animation
                    AnimationEffects animation = this.Parent.Animation;

                    // determine whether to begin tracking the mouse
                    this.IsTracking |= (deltaTravel >= properties.MinDistanceTracking);
                    // determine whether to activate action in responce to mouse tracking
                    this.IsActivated = (relTravel >= properties.MinDistanceAction);

                    // set up animation effect if tracking
                    if (this.IsTracking)
                    {
                        // calculate mouse direction
                        int direction = (this.IsHTravel) ? (this.PercentTraveled.X < 0 ? 1 : 3) : (this.PercentTraveled.Y < 0 ? 2 : 4);
                        // begin animation effect
                        switch (properties.Effect)
                        {
                            case AnimationEffect.Swipe:
                                chart._fadeLevel = this.CachedFadeLevel * Math.Max(0.25F, 0.75F - relTravel);
                                float swipeX = (this.IsHTravel) ? deltaX / 2F : 0F;
                                float swipeY = (this.IsVTravel) ? deltaY / 2F : 0F;
                                animation.EnableTrackingArrow(direction, this.Location, this.IsActivated);
                                animation.BeginSwipe(new PointF(swipeX, swipeY));
                                break;
                            case AnimationEffect.Fade:
                                animation.EnableTrackingArrow(direction, this.Location, this.IsActivated);
                                animation.BeginFade(this.CachedFadeLevel * Math.Max(0.25F, 0.75F - relTravel));
                                break;
                            case AnimationEffect.Rotate:
                                chart._fadeLevel = this.CachedFadeLevel * 0.5F;
                                animation.BeginRotate(-(this.IsVTravel ? deltaY : deltaX) % 360);
                                break;
                            case AnimationEffect.Rotate3D:
                                chart._fadeLevel = this.CachedFadeLevel * 0.5F;
                                animation.BeginRotate3D(-(this.IsVTravel ? deltaY : deltaX) % 360, (this.IsHTravel) ? RotationAxis.Y : RotationAxis.X);
                                break;
                        }
                    }
                    //if (this.IsActivated)
                    //{
                    //    switch (properties.Effect)
                    //    {
                    //        case AnimationEffect.Swipe:
                    //            float actTravel = (relTravel - properties.MinDistanceAction);
                    //            int actX = (this.IsHTravel) ? (int)(actTravel * gridW * (deltaTravel / deltaX)) : 0;
                    //            int actY = (this.IsVTravel) ? (int)(actTravel * gridH * (deltaTravel / deltaY)) : 0;
                    //            //animation.SwipeDelta = new Point(actX, actY);
                    //            break;
                    //    }
                    //}

                    // invalidate to repaint
                    chart.Invalidate();
                }
                // if the mouse is in a pressed down state, we want to suppress any external event processing
                return this.IsMouseDown;
            }
            /// <summary>
            /// Handles the mouse tracking after a mouse button was released.
            /// </summary>
            /// <param name="e">Contains mouse event args.</param>
            /// <returns>true, if mouse-up event was handled internally; false, to raise the mouse-up event for external processing.</returns>
            public bool HandleMouseUp(MouseEventArgs e)
            {
                bool isHandled = this.IsMouseDown && this.IsTracking;
                if (this.IsMouseDown)
                {   // restore some elements ...
                    this.IsMouseDown = false;
                    // finalize animation
                    var     chart = this.Parent;
                    var animation = chart.Animation;
                    animation.DisableTrackingArrow();
                    //
                    switch (animation.Effect)
                    {
                        case AnimationEffect.Swipe:
                            chart.FadeLevel = this.CachedFadeLevel;
                            if (this.IsActivated)
                            {
                                animation.SwipeOut();
                                this.OnChangeView();
                            }
                            animation.SwipeIn();
                            break;
                        case AnimationEffect.Fade:
                            if (this.IsActivated)
                            {
                                animation.FadeOut();
                                this.OnChangeView();
                            }
                            animation.FadeIn();
                            break;
                        case AnimationEffect.Rotate:
                            animation.RotateNearest();
                            chart.FadeLevel = this.CachedFadeLevel;
                            break;
                        case AnimationEffect.Rotate3D:
                            animation.Rotate3DNearest();
                            chart.FadeLevel = this.CachedFadeLevel;
                            break;
                    }
                }
                // if the mouse is in a pressed down state and it was being tracked, we want to suppress any external event processing
                return isHandled;
            }

            //----- support methods -----
            ChangeViewEventArgs OnChangeView()
            {
                int direction = ((this.IsVTravel ? this.DistanceTraveled.Y : this.DistanceTraveled.X) > 0) ? -1 : 1;
                return this.Parent.OnChangeView(ChangeViewTrigger.Mouse, this.Orientation, direction);
            }

            #endregion

            #region /*** Properties ***/

            //----- provided properties -----
            public MouseTrackingProperties LMBProperties { get { return this._lmbProperties; } }
            public MouseTrackingProperties RMBProperties { get { return this._rmbProperties; } }

            //----- read-only properties (track mouse state) -----
            /// <summary>Gets whether the mouse travel in either direction should be tracked when either left or right mouse button is pressed.</summary>
            public bool TrackMouse { get { return this.LMBProperties.TrackMouse || this.RMBProperties.TrackMouse; } }
            /// <summary>Gets whether a mouse button is in a pressed down state.</summary>
            public bool IsMouseDown { get; private set; }
            /// <summary>Gets whether mouse tracking has begun.</summary>
            public bool IsTracking { get; private set; }
            /// <summary>Gets whether the mouse has traveled the minimum distance required for activating any action in responce to mouse tracking.</summary>
            public bool IsActivated { get; private set; }
            /// <summary>Gets the mouse button that was pressed.</summary>
            public MouseButtons Button { get; private set; }
            /// <summary>Gets whether the left mouse button was pressed.</summary>
            public bool IsLMB { get { return this.Button == MouseButtons.Left; } }
            /// <summary>Gets whether the right mouse button was pressed.</summary>
            public bool IsRMB { get { return this.Button == MouseButtons.Right; } }
            /// <summary>Gets the starting location of the mouse when it was pressed.</summary>
            public Point StartLocation { get; private set; }
            /// <summary>Gets the current location of the mouse.</summary>
            public Point Location { get; private set; }
            /// <summary>Gets the distance in pixels the mouse has traveled since it was pressed.</summary>
            public Point DistanceTraveled { get; private set; }
            /// <summary>Gets the distance the mouse has traveled expressed as a percentage relative to the grid's width and height.</summary>
            public PointF PercentTraveled { get; private set; }
            /// <summary>Gets the primary direction of mouse travel with regard to the grid's width and height.</summary>
            public Orientation Orientation { get; private set; }
            /// <summary>Gets whether the mouse is traveling in mostly horizontal direction.</summary>
            public bool IsHTravel { get { return this.Orientation == Orientation.Horizontal; } }
            /// <summary>Gets whether the mouse is traveling in mostly vertical direction.</summary>
            public bool IsVTravel { get { return this.Orientation == Orientation.Vertical; } }
            //
            /// <summary>Gets the mouse tracking properties associated with the currently pressed mouse button.</summary>
            MouseTrackingProperties Properties { get { return (this.IsMouseDown) ? ((this.IsRMB) ? this.RMBProperties : this.LMBProperties) : null; } }

            //----- cached properties (save on mouse-down and restore on mouse-up) -----
            float CachedFadeLevel { get; set; }

            //----- other properties -----
            /// <summary>Gets the parent of this object.</summary>
            public Chart Parent { get; private set; }

            #endregion

            #region /*** Variables ***/

            // global defaults
            static readonly MouseTrackingProperties LMBDefaults = new MouseTrackingProperties(true, true , 0, 0.25F, AnimationEffect.Swipe );
            static readonly MouseTrackingProperties RMBDefaults = new MouseTrackingProperties(true, false, 4, 0.00F, AnimationEffect.Rotate);

            // runtime
            MouseTrackingProperties _lmbProperties = new MouseTrackingProperties(LMBDefaults);
            MouseTrackingProperties _rmbProperties = new MouseTrackingProperties(RMBDefaults);

            #endregion

        }

        //----- ChartBase.MouseTrackingState instance -----
        /// <summary>
        /// Provides mouse tracking support.
        /// </summary>
        MouseTrackingState MouseState { get; set; }

        //----- ChartBase.MouseTrackingState Provided Properties -----
        #region < Show/Hide MouseState Provided Properties >
        //----- LMB -----
        /// <summary>Gets or sets whether the horizontal mouse travel should be tracked when a left mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(true)]
        [Category("Mouse Tracking")]
        [Description("Indicates whether the horizontal mouse travel should be tracked when a left mouse button is pressed.")]
        public bool LMBTrackHTravel { get { return this.MouseState.LMBProperties.TrackHTravel; } set { this.MouseState.LMBProperties.TrackHTravel = value; } }
        /// <summary>Gets or sets whether the vertical mouse travel should be tracked when a left mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(true)]
        [Category("Mouse Tracking")]
        [Description("Indicates whether the vertical mouse travel should be tracked when a left mouse button is pressed.")]
        public bool LMBTrackVTravel { get { return this.MouseState.LMBProperties.TrackVTravel; } set { this.MouseState.LMBProperties.TrackVTravel = value; } }
        /// <summary>Gets or sets the minimum distance, in pixels, the mouse should travel before tracking begins.</summary>
        [Browsable(true)]
        [DefaultValue(0)]
        [Category("Mouse Tracking")]
        [Description("The minimum distance, in pixels, the mouse should travel before tracking begins.")]
        public int LMBMinDistanceTracking { get { return this.MouseState.LMBProperties.MinDistanceTracking; } set { this.MouseState.LMBProperties.MinDistanceTracking = value; } }
        /// <summary>Gets or sets the minimum relative distance, in percent, the mouse should travel before any action in responce to mouse tracking should be activated.</summary>
        [Browsable(true)]
        [DefaultValue(0.25F)]
        [Category("Mouse Tracking")]
        [Description("The minimum relative distance, in percent, the mouse should travel before any action in responce to mouse tracking should be activated.")]
        public float LMBMinDistanceAction { get { return this.MouseState.LMBProperties.MinDistanceAction; } set { this.MouseState.LMBProperties.MinDistanceAction = value; } }
        /// <summary>Gets or sets the animation effect to display when the mouse travels across the chart while a left mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(AnimationEffect.Swipe)]
        [Category("Mouse Tracking")]
        [Description("The animation effect to display when the mouse travels across the chart while a left mouse button is pressed.")]
        public AnimationEffect LMBEffect { get { return this.MouseState.LMBProperties.Effect; } set { this.MouseState.LMBProperties.Effect = value; } }
        //----- RMB -----
        /// <summary>Gets or sets whether the horizontal mouse travel should be tracked when a right mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(true)]
        [Category("Mouse Tracking")]
        [Description("Indicates whether the horizontal mouse travel should be tracked when a right mouse button is pressed.")]
        public bool RMBTrackHTravel { get { return this.MouseState.RMBProperties.TrackHTravel; } set { this.MouseState.RMBProperties.TrackHTravel = value; } }
        /// <summary>Gets or sets whether the vertical mouse travel should be tracked when a right mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(false)]
        [Category("Mouse Tracking")]
        [Description("Indicates whether the vertical mouse travel should be tracked when a right mouse button is pressed.")]
        public bool RMBTrackVTravel { get { return this.MouseState.RMBProperties.TrackVTravel; } set { this.MouseState.RMBProperties.TrackVTravel = value; } }
        /// <summary>Gets or sets the minimum distance, in pixels, the mouse should travel before tracking begins.</summary>
        [Browsable(true)]
        [DefaultValue(4)]
        [Category("Mouse Tracking")]
        [Description("The minimum distance, in pixels, the mouse should travel before tracking begins.")]
        public int RMBMinDistanceTracking { get { return this.MouseState.RMBProperties.MinDistanceTracking; } set { this.MouseState.RMBProperties.MinDistanceTracking = value; } }
        /// <summary>Gets or sets the minimum relative distance, in percent, the mouse should travel before any action in responce to mouse tracking should be activated.</summary>
        [Browsable(true)]
        [DefaultValue(0.0F)]
        [Category("Mouse Tracking")]
        [Description("The minimum relative distance, in percent, the mouse should travel before any action in responce to mouse tracking should be activated.")]
        public float RMBMinDistanceAction { get { return this.MouseState.RMBProperties.MinDistanceAction; } set { this.MouseState.RMBProperties.MinDistanceAction = value; } }
        /// <summary>Gets or sets the animation effect to display when the mouse travels across the chart while a right mouse button is pressed.</summary>
        [Browsable(true)]
        [DefaultValue(AnimationEffect.Rotate)]
        [Category("Mouse Tracking")]
        [Description("The animation effect to display when the mouse travels across the chart while a right mouse button is pressed.")]
        public AnimationEffect RMBEffect { get { return this.MouseState.RMBProperties.Effect; } set { this.MouseState.RMBProperties.Effect = value; } }
        #endregion

        //----- ChartBase.MouseTrackingState Read-Only Properties -----
        #region < Show/Hide MouseState Read-Only Properties >
        /// <summary>Gets whether a mouse button is in a pressed down state.</summary>
        [Browsable(false)]
        public bool IsMouseDown { get { return this.MouseState.IsMouseDown; } }
        /// <summary>Gets whether mouse tracking has begun.</summary>
        [Browsable(false)]
        public bool IsMouseTracking { get { return this.MouseState.IsTracking; } }
        /// <summary>Gets whether the mouse has traveled the minimum distance required for activating any action in responce to mouse tracking.</summary>
        [Browsable(false)]
        public bool IsMouseActivated { get { return this.MouseState.IsActivated; } }
        /// <summary>Gets the mouse button that was pressed.</summary>
        [Browsable(false)]
        public MouseButtons MouseButtonPressed { get { return this.MouseState.Button; } }
        /// <summary>Gets whether the left mouse button was pressed.</summary>
        [Browsable(false)]
        public bool IsLMBPressed { get { return this.MouseState.IsLMB; } }
        /// <summary>Gets whether the right mouse button was pressed.</summary>
        [Browsable(false)]
        public bool IsRMBPressed { get { return this.MouseState.IsRMB; } }
        /// <summary>Gets the starting location of the mouse when it was pressed.</summary>
        [Browsable(false)]
        public Point MouseStartLocation { get { return this.MouseState.StartLocation; } }
        /// <summary>Gets the current location of the mouse.</summary>
        [Browsable(false)]
        public Point MouseLocation { get { return this.MouseState.Location; } }
        /// <summary>Gets the distance in pixels the mouse has traveled since it was pressed.</summary>
        [Browsable(false)]
        public Point MouseDistanceTraveled { get { return this.MouseState.DistanceTraveled; } }
        /// <summary>Gets the distance the mouse has traveled expressed as a percentage relative to the grid's width and height.</summary>
        [Browsable(false)]
        public PointF MousePercentTraveled { get { return this.MouseState.PercentTraveled; } }
        /// <summary>Gets the primary direction of mouse travel with regard to the grid's width and height.</summary>
        [Browsable(false)]
        public Orientation MouseOrientation { get { return this.MouseState.Orientation; } }
        /// <summary>Gets whether the mouse is traveling in mostly horizontal direction.</summary>
        [Browsable(false)]
        public bool IsMouseHTravel { get { return this.MouseState.IsHTravel; } }
        /// <summary>Gets whether the mouse is traveling in mostly vertical direction.</summary>
        [Browsable(false)]
        public bool IsMouseVTravel { get { return this.MouseState.IsVTravel; } }
        #endregion

        #endregion

        #region /*** KeyboardTrackingState class and Provided Properties ***/

        //----- ChartBase.KeyboardTrackingState class -----
        /// <summary>
        /// Provides keyboard tracking support.
        /// </summary>
        [Serializable]
        class KeyboardTrackingState
        {

            #region /*** Ctors ***/

            internal KeyboardTrackingState(Chart parent)
            {
                this.Parent = parent;
            }

            #endregion

            #region /*** Methods ***/

            /// <summary>
            /// Handles the keyboard tracking when a key is pressed down. This function filters out the keys that are supported for keyboard tracking.
            /// </summary>
            /// <param name="e">Contains key event args.</param>
            /// <returns>true, if preview-key-down event was handled internally; false, to raise the preview-key-down event for external processing.</returns>
            public bool HandlePreviewKeyDown(PreviewKeyDownEventArgs e)
            {
                if (this.TrackArrowKeys)
                {
                    switch (e.KeyCode)
                    {
                        case Keys.Left :
                        case Keys.Right:
                        case Keys.Up   :
                        case Keys.Down : e.IsInputKey = true; return true;
                    }
                }
                return false;
            }
            /// <summary>
            /// Handles the keyboard tracking after a key was released.
            /// </summary>
            /// <param name="e">Contains key event args.</param>
            /// <returns>true, if key-up event was handled internally; false, to raise the key-up event for external processing.</returns>
            public bool HandleKeyUp(KeyEventArgs e)
            {
                if (this.TrackArrowKeys && !this.Parent.IsMouseDown)
                {
                    Orientation orientation = Orientation.Horizontal;
                    int direction = 0, arrowDirection = 0;
                    //
                    switch (e.KeyCode)
                    {
                        case Keys.Left : arrowDirection = 1; direction = -1; break;
                        case Keys.Right: arrowDirection = 3; direction =  1; break;
                        case Keys.Up   : arrowDirection = 2; direction = -1; orientation = Orientation.Vertical; break;
                        case Keys.Down : arrowDirection = 4; direction =  1; orientation = Orientation.Vertical; break;
                    }
                    //
                    if (direction != 0)
                    {
                        AnimationEffects animation = this.Parent.Animation;
                        animation.EnableTrackingArrow(arrowDirection, Point.Empty, true);
                        animation.FadeOut();
                        animation.DisableTrackingArrow();
                        this.Parent.OnChangeView(ChangeViewTrigger.Keyboard, orientation, direction);
                        animation.FadeIn();
                        e.Handled = true;
                    }
                    return true;
                }
                return false;
            }

            #endregion

            #region /*** Properties ***/

            //----- provided properties -----
            internal bool TrackArrowKeys { get { return this._trackArrowKeys; } set { this._trackArrowKeys = value; } }

            //----- other properties -----
            /// <summary>Gets the parent of this object.</summary>
            public Chart Parent { get; private set; }

            #endregion

            #region /*** Variables ***/

            bool _trackArrowKeys = true;

            #endregion

        }

        //----- ChartBase.KeyboardTrackingState instance -----
        /// <summary>
        /// Provides keyboard tracking support.
        /// </summary>
        KeyboardTrackingState KeyboardState { get; set; }

        //----- ChartBase.KeyboardTrackingState Provided Properties -----
        #region < Show/Hide MouseState Provided Properties >
        /// <summary>Gets or sets whether the arrow keys should be tracked when pressed.</summary>
        [Browsable(true)]
        [DefaultValue(true)]
        [Category("Keyboard Tracking")]
        [Description("Indicates whether the arrow keys should be tracked when pressed.")]
        public bool TrackArrowKeys { get { return this.KeyboardState.TrackArrowKeys; } set { this.KeyboardState.TrackArrowKeys = value; } }
        #endregion

        #endregion

    }
}
