// (c) Copyright Microsoft Corporation. 
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved. 


using System.Diagnostics; 
using System.Windows; 
using System.Windows.Input;
using System.Windows.Markup; 
using System.Windows.Media.Animation;
using System.Windows.Automation.Peers;
using System.Windows.Controls; 
using System.Windows.Media;
 
namespace System.Windows.Controls.Primitives 
{
    /// <summary> 
    /// Initializes a new instance of the Thumb class.
    /// </summary>
    [TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)] 
    [TemplateVisualState(Name = VisualStates.StateMouseOver, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StatePressed, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StateDisabled, GroupName = VisualStates.GroupCommon)] 
    [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)] 
    [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)]
    public sealed partial class Thumb : Control 
    {
        #region IsDragging
        /// <summary> 
        /// Gets whether the Thumb control has logical focus and mouse capture
        /// and the left mouse button is pressed.
        /// </summary> 
        public bool IsDragging 
        {
            get { return (bool)GetValue(IsDraggingProperty); } 
            internal set { SetValueImpl(IsDraggingProperty, value); }
        }
 
        /// <summary>
        /// Identifies the IsDragging dependency property.
        /// </summary> 
        public static readonly DependencyProperty IsDraggingProperty = 
            DependencyProperty.RegisterReadOnlyCore(
                "IsDragging", 
                typeof(bool),
                typeof(Thumb),
                new PropertyMetadata(OnIsDraggingPropertyChanged)); 

        /// <summary>
        /// IsDraggingProperty property changed handler. 
        /// </summary> 
        /// <param name="d">RangeBase that changed IsDragging.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnIsDraggingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Thumb thumb = d as Thumb; 
            Debug.Assert(thumb != null);

            thumb.OnDraggingChanged(); 
        } 

        /// <summary> 
        /// This method is invoked when the IsDragging property changes.
        /// </summary>
        private void OnDraggingChanged() 
        {
            UpdateVisualState();
        } 
 
        #endregion IsDragging
 
        #region IsEnabled

        /// <summary> 
        /// Called when the IsEnabled property changes.
        /// </summary>
        /// <param name="e">Property changed args</param> 
        private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) 
        {
            if (!IsEnabled) 
            {
                IsMouseOver = false;
            } 
            UpdateVisualState();
        }
 
        #endregion IsEnabled 

        #region IsFocused 
        /// <summary>
        /// Gets a value that determines whether this element has logical focus.
        /// </summary> 
        /// <remarks>
        /// IsFocused will not be set until OnFocus has been called.  It may not
        /// yet have been set if you check it in your own Focus event handler. 
        /// </remarks> 
        public bool IsFocused
        { 
            get { return (bool)GetValue(IsFocusedProperty); }
            internal set { SetValueImpl(IsFocusedProperty, value); }
        } 

        /// <summary>
        /// Identifies the IsFocused dependency property. 
        /// </summary> 
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterReadOnlyCore( 
                "IsFocused",
                typeof(bool),
                typeof(Thumb), 
                null);
        #endregion IsFocused
 
        #region Events 
        /// <summary>
        /// Identifies the DragStarted routed event. 
        /// </summary>
        public event DragStartedEventHandler DragStarted;
 
        /// <summary>
        /// Identifies the DragDelta routed event.
        /// </summary> 
        public event DragDeltaEventHandler DragDelta; 

        /// <summary> 
        /// Occurs when the Thumb control loses mouse capture.
        /// </summary>
        public event DragCompletedEventHandler DragCompleted; 
        #endregion Events

        #region Constructor 
        /// <summary> 
        /// Initializes a new instance of the Thumb class.
        /// </summary> 
        public Thumb()
        {
            DefaultStyleKey = typeof(Thumb); 
            IsEnabledChanged += OnIsEnabledChanged;
        }
 
        /// <summary> 
        /// Apply a template to the thumb.
        /// </summary> 
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate(); 
            UpdateVisualState(false);
        }
        #endregion Constructor 
 
        #region Mouse Handlers
        /// <summary> 
        /// Handle the MouseLeftButtonDown event.
        /// </summary>
        /// <param name="e">MouseButtonEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        { 
            base.OnMouseLeftButtonDown(e); 
            if (e.Handled)
            { 
                return;
            }
 
            if (!IsDragging && IsEnabled)
            {
                e.Handled = true; 
 
                CaptureMouse();
                IsDragging = true; 

                Debug.Assert(this.Parent is UIElement);
 
                _origin = _previousPosition = e.GetPosition((UIElement)this.Parent);

                // Raise the DragStarted event 
                bool success = false; 
                try
                { 
                    DragStartedEventHandler handler = DragStarted;
                    if (handler != null)
                    { 
                        handler(this, new DragStartedEventArgs(_origin.X, _origin.Y));
                    }
                    success = true; 
                } 
                finally
                { 
                    // Cancel the drag if the DragStarted handler failed
                    if (!success)
                    { 
                        CancelDrag();
                    }
                } 
            } 
        }
 
        /// <summary>
        /// Handle the MouseLeftButtonUp event.
        /// </summary> 
        /// <param name="e">MouseButtonEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) 
        { 
            base.OnMouseLeftButtonUp(e);
            if (e.Handled) 
            {
                return;
            } 

            if (IsDragging && IsEnabled)
            { 
                e.Handled = true; 
                IsDragging = false;
                ReleaseMouseCapture(); 
                RaiseDragCompleted(false);
            }
        } 

        /// <summary>
        /// Handle the MouseEnter event. 
        /// </summary> 
        /// <param name="e">MouseEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            base.OnMouseEnter(e); 

            if (IsEnabled)
            { 
                IsMouseOver = true; 
                UpdateVisualState();
            } 
        }

        /// <summary> 
        /// Handle the MouseLeave event.
        /// </summary>
        /// <param name="e">MouseEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeave(MouseEventArgs e)
        { 
            base.OnMouseLeave(e);

            if (IsEnabled) 
            {
                IsMouseOver = false;
                UpdateVisualState(); 
            } 
        }
 
        /// <summary>
        /// Handle the MouseMove event.
        /// </summary> 
        /// <param name="e">MouseEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseMove(MouseEventArgs e) 
        { 
            base.OnMouseMove(e);
 
            if (IsDragging)
            {
                Debug.Assert(this.Parent is UIElement); 

                Point position = e.GetPosition((UIElement)this.Parent);
 
                if (position != _previousPosition) 
                {
                    // Raise the DragDelta event 
                    DragDeltaEventHandler handler = DragDelta;
                    if (handler != null)
                    { 
                        handler(this, new DragDeltaEventArgs(position.X - _previousPosition.X, position.Y - _previousPosition.Y));
                    }
 
                    _previousPosition = position; 
                }
            } 
        }
        #endregion Mouse Handlers
 
        #region Focus Handlers
        /// <summary>
        /// Handle the GotFocus event. 
        /// </summary> 
        /// <param name="e">RoutedEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e); 
            FocusChanged(HasFocus());
        }
 
        /// <summary> 
        /// Handle the LostFocus event.
        /// </summary> 
        /// <param name="e">RoutedEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnLostFocus(RoutedEventArgs e) 
        {
            base.OnLostFocus(e);
            FocusChanged(HasFocus()); 
        } 

        private void FocusChanged(bool hasFocus) 
        {
            IsFocused = hasFocus;
            UpdateVisualState(); 
        }
        #endregion

        #region AutomationPeer section

        protected override AutomationPeer OnCreateAutomationPeer ()
        {
            return new ThumbAutomationPeer (this);
        }

        #endregion
 
        #region Change State 

        /// <summary> 
        /// Change to the correct visual state for the thumb.
        /// </summary>
        internal void UpdateVisualState() 
        {
            UpdateVisualState(true);
        } 
 
        /// <summary>
        /// Change to the correct visual state for the thumb. 
        /// </summary>
        /// <param name="useTransitions">
        /// true to use transitions when updating the visual state, false to 
        /// snap directly to the new visual state.
        /// </param>
        internal void UpdateVisualState(bool useTransitions) 
        { 
            if (!IsEnabled)
            { 
                GoToState(useTransitions, VisualStates.StateDisabled);
            }
            else if (IsDragging) 
            {
                GoToState(useTransitions, VisualStates.StatePressed);
            } 
            else if (IsMouseOver) 
            {
                GoToState(useTransitions, VisualStates.StateMouseOver); 
            }
            else
            { 
                GoToState(useTransitions, VisualStates.StateNormal);
            }
 
            if (IsFocused && IsEnabled) 
            {
                GoToState(useTransitions, VisualStates.StateFocused); 
            }
            else
            { 
                GoToState(useTransitions, VisualStates.StateUnfocused);
            }
        } 
 
        internal bool GoToState(bool useTransitions, string stateName)
        { 
            Debug.Assert(stateName != null);
            return VisualStateManager.GoToState(this, stateName, useTransitions);
        } 
        #endregion Change State

        private bool HasFocus() 
        { 
            for (DependencyObject doh = FocusManager.GetFocusedElement() as DependencyObject;
                doh != null; 
                doh = VisualTreeHelper.GetParent(doh))
            {
                if (object.ReferenceEquals(doh, this)) 
                    return true;
            }
            return false; 
        } 

        #region Drag Cancel/Complete 
        /// <summary>
        /// Cancel a drag operation if it is currently in progress.
        /// </summary> 
        public void CancelDrag()
        {
            if (IsDragging) 
            { 
                IsDragging = false;
                RaiseDragCompleted(true); 
            }
        }
 
        /// <summary>
        /// Raise the DragCompleted event.
        /// </summary> 
        /// <param name="canceled"> 
        /// A Boolean value that indicates whether the drag operation was
        /// canceled by a call to the CancelDrag method. 
        /// </param>
        private void RaiseDragCompleted(bool canceled)
        { 
            DragCompletedEventHandler handler = DragCompleted;
            if (handler != null)
            { 
                DragCompletedEventArgs args = new DragCompletedEventArgs( 
                    _previousPosition.X - _origin.X,
                    _previousPosition.Y - _origin.Y, 
                    canceled);
                handler(this, args);
            } 
        }
        #endregion Drag Cancel/Complete
 
        #region Member Variables 
        /// <summary>
        /// Whether the mouse is currently over the control 
        /// </summary>
        internal bool IsMouseOver { get; set; }
        /// <summary> 
        /// Origin of the thumb's drag operation.
        /// </summary>
        internal Point _origin; 
        /// <summary> 
        /// Last position of the thumb while during a drag operation.
        /// </summary> 
        internal Point _previousPosition;
        #endregion Member Variables
    } 
}
