// This is a part of the Smart PropertyGrid Library
// Copyright (C) 2001-2006 VisualHint
// All rights reserved.
//
// This source code can be used, distributed or modified
// only under terms and conditions
// of the accompanying license agreement.

#region Using directives

using System;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Security.Permissions;

#endregion

namespace VisualHint.SmartPropertyGrid
{
    /// <summary>
    /// Inplace control shown in the value column when a trackbar (with textbox or not) is needed.
    /// </summary>
    [ToolboxItem(false)]
    public class PropInPlaceTrackbar : Control, IInPlaceControl
    {
        private PropertyGrid _parentGrid;

        private PropertyEnumerator _ownerPropertyEnum;

        public PropertyEnumerator OwnerPropertyEnumerator
        {
            set
            {
                _ownerPropertyEnum = (PropertyEnumerator)value.Clone();

                _parentGrid = _ownerPropertyEnum.Property.ParentGrid;

                if (_edit != null)
                {
                    _edit.OwnerPropertyEnumerator = value;
                    _edit.Text = _ownerPropertyEnum.Property.Value.DisplayString;
                }

                if (!ReadOnly)
                {
                    if (_trackbar == null)
                    {
                        _trackbar = new InternalTrackBar();
                        if (_edit != null)
                            _trackbar.TabStop = false;
                        _trackbar.Parent = this;
                        _trackbar.Scroll += new EventHandler(OnScroll);
                        _trackbar.LostFocus += new EventHandler(OnChildLostFocus);
                    }

                    _trackbar.OwnerPropertyEnumerator = _ownerPropertyEnum;

                    int trackValue = (int)_ownerPropertyEnum.Property.Value.GetValue();
                    try
                    {
                        _trackbar.Value = trackValue;
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        if (trackValue < _trackbar.Minimum)
                            _trackbar.Value = _trackbar.Minimum;
                        else
                            _trackbar.Value = _trackbar.Maximum;
                    }

                    ActualText = _trackbar.Value.ToString();
                    ValidateTextInternal(false);
                }
                else
                {
                    ActualText = _ownerPropertyEnum.Property.Value.GetValue().ToString();
                    ValidateTextInternal(false);
                }
            }

            get { return _ownerPropertyEnum; }
        }

        /// <summary>
        /// True when the focus is lost because of typing Return or Escape, false otherwise.
        /// </summary>
        private bool _leaveKey = false;

        /// <summary>
        /// Because Control.OnLostFocus and Control.OnGotFocus doesn't provide a reference to the
        /// window that lost or gained the focus, this variable will contain this missing information.
        /// It is initialized in WndProc when processing WM_FILLFOCUS and WM_SETFOCUS messages.
        /// It is also embedded in a FocusEventArgs instance for event listeners.
        /// </summary>
        private Control _wmFocusMissingArg;

        public override Font Font
        {
            get { return base.Font; }
            set
            {
                base.Font = value;
                if (_edit != null)
                    _edit.Font = value;
            }
        }

        public override Color BackColor
        {
            get { return base.BackColor; }
            set
            {
                base.BackColor = value;

                if (_edit != null)
                    _edit.BackColor = value;
            }
        }

        public override Color ForeColor
        {
            get { return base.ForeColor; }
            set
            {
                base.ForeColor = value;

                if (_edit != null)
                    _edit.ForeColor = value;
            }
        }

        private PropInPlaceTextbox _edit = null;

        private InternalTrackBar _trackbar = null;

        /// <summary>
        /// Gives access to the actual internal TrackBar used by this inplace control.
        /// </summary>
        public TrackBar TrackBar
        {
            get { return _trackbar; }
        }

        private bool _realtimeChange = false;

        public bool RealtimeChange
        {
            set { _realtimeChange = value; }
            get { return _realtimeChange; }
        }

        public bool ReadOnly
        {
            get { return _ownerPropertyEnum.Property.Value.IsReadOnly(_ownerPropertyEnum); }
        }

        public int LargeChange
        {
            get { return _trackbar.LargeChange; }
            set { _trackbar.LargeChange = value; }
        }

        public int SmallChange
        {
            get { return _trackbar.SmallChange; }
            set { _trackbar.SmallChange = value; }
        }

        public PropInPlaceTrackbar(bool editable)
        {
            TabStop = false;

            if (editable)
            {
                _edit = new PropInPlaceTextbox(false);
                _edit.Parent = this;
                _edit.LostFocus += new EventHandler(OnChildLostFocus);
            }

            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer | ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);

            Cursor = Cursors.Arrow;
        }

        protected override void OnPaint(PaintEventArgs pevent)
        {
            _parentGrid.DrawManager.DrawPropertyValueBackground(pevent.Graphics,
                ClientRectangle, _ownerPropertyEnum);

            if (_edit == null)
            {
                Rectangle textRect = new Rectangle(0, 0, GetEditWidth(), ClientRectangle.Height);
                _ownerPropertyEnum.Property.Value.DrawValue(pevent.Graphics, textRect,
                    _ownerPropertyEnum.Property.Value.ForeColor, _ownerPropertyEnum, Text);
            }
        }

        public string ActualText
        {
            set
            {
                if (_edit == null)
                {
                    Text = value;
                    Invalidate();
                }
                else
                    _edit.Text = value;
            }

            get
            {
                if (_edit == null)
                    return Text;
                else
                    return _edit.Text;
            }
        }

        private int GetEditWidth()
        {
            if (_trackbar != null)
            {
                Graphics graphics = CreateGraphics();
                int width1 = Win32Calls.GetTextExtent(graphics, _trackbar.Minimum.ToString(), Font).Width;
                int width2 = Win32Calls.GetTextExtent(graphics, _trackbar.Maximum.ToString(), Font).Width;
                graphics.Dispose();
                if (width1 < width2)
                    width1 = width2;

                return width1 + 2 * PropertyGrid.GlobalTextMargin;
            }
            else
                return ClientRectangle.Width;
        }

        public void RepositionChildren()
        {
            Rectangle clientRect = ClientRectangle;
            if (clientRect.Width > 0)
            {
                if (_edit != null)
                {
                    // Get the margins of the editbox
                    int rawMargins = Win32Calls.LoWord(Win32Calls.SendMessage(_edit.Handle, Win32Calls.EM_GETMARGINS, 0, 0));
                    int margins = Win32Calls.LoWord(rawMargins) + Win32Calls.HiWord(rawMargins);

                    int leftPos = (_ownerPropertyEnum.Property.Value.EditboxLeftMargin == -1 ? 0 :
                        PropertyGrid.GlobalTextMargin - _ownerPropertyEnum.Property.Value.EditboxLeftMargin);

                    Rectangle editRect = new Rectangle(leftPos, 0, GetEditWidth() - leftPos, clientRect.Height);
                    editRect.Y++;
                    editRect.Height--;

                    // The width of the editbox can't be smaller than the margins or the margins would be void and the text misplaced
                    if (editRect.Width <= margins)
                        _edit.Visible = false;
                    else
                    {
                        _edit.Visible = true;

                        Win32Calls.TEXTMETRIC tm = new Win32Calls.TEXTMETRIC();
                        Graphics graphics = CreateGraphics();
                        Win32Calls.GetTextMetrics(graphics, Font, ref tm);
                        graphics.Dispose();
                        int extraHeight = editRect.Height - tm.tmHeight;
                        editRect.Y += extraHeight / 2;
                        editRect.Height -= extraHeight / 2;

                        _edit.Invalidate();
                        _edit.Bounds = editRect;
                    }

                    if (_trackbar != null)
                    {
                        Rectangle trackbarRect = new Rectangle(editRect.Right, 0+2, clientRect.Right - editRect.Right, clientRect.Height-2);
                        if (trackbarRect.Width < 15)
                            _trackbar.Visible = false;
                        else
                        {
                            _trackbar.Visible = true;
                            _trackbar.Bounds = trackbarRect;
                        }
                    }
                }
                else
                {
                    if (_trackbar != null)
                    {
                        int editWidth = GetEditWidth();
                        Rectangle trackbarRect = new Rectangle(editWidth, 0, clientRect.Width - editWidth, clientRect.Height);
                        _trackbar.Bounds = trackbarRect;
                    }
                }
            }
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);

            RepositionChildren();
        }

        void OnScroll(object sender, EventArgs e)
        {
            ActualText = _trackbar.Value.ToString();
            ValidateTextInternal(false);
        }

        protected void ValidateTextInternal(bool forceUpdate)
        {
            PropertyValue value = _ownerPropertyEnum.Property.Value;

            if (ActualText != value.DisplayString)
            {
                // Store the previous value
                string oldValue = value.GetStringValue();

                _ownerPropertyEnum.Property.Value.PreviousValue = _ownerPropertyEnum.Property.Value.GetValue();

                if (_realtimeChange || forceUpdate)
                {
                    if (_edit != null)
                        _edit.ValidateTextInternal();
                    else
                        value.SetValue(ActualText);
                }

                Invalidate();

                if ((oldValue != value.GetStringValue()) && (_edit == null))
                {
                    _parentGrid.NotifyPropertyChanged(new PropertyChangedEventArgs(_ownerPropertyEnum));
                    Parent.Invalidate();
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == Win32Calls.WM_SETFOCUS)
                OnWmSetFocus(new FocusEventArgs(FromHandle(m.WParam), false));
            else if (m.Msg == Win32Calls.WM_KILLFOCUS)
                OnWmKillFocus(new FocusEventArgs(FromHandle(m.WParam), false));

            base.WndProc(ref m);
        }

        protected virtual void OnWmSetFocus(FocusEventArgs e)
        {
            if (_edit != null)
            {
                if (e.FocusControl != _edit)
                    _edit.Focus();
                else
                    Parent.Focus();
            }
            else if ((_trackbar != null) && (e.FocusControl != _trackbar))
                _trackbar.Focus();
            else
                Parent.Focus();
        }

        protected virtual void OnWmKillFocus(FocusEventArgs e)
        {
            _wmFocusMissingArg = e.FocusControl;
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(new FocusEventArgs(_wmFocusMissingArg, _leaveKey));

            _leaveKey = false;
        }

        void OnChildLostFocus(object sender, EventArgs e)
        {
            if (_trackbar != null)
            {
                if (sender == _edit)
                {
                    // First, check if the value fits in the allowed range
                    int trackValue = (int)_ownerPropertyEnum.Property.Value.GetActualValue(ActualText);

                    try
                    {
                        _trackbar.Value = trackValue;
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                    }
                }

                if ((_realtimeChange == false) || (sender == _edit))
                    ValidateTextInternal(true);
            }

            _leaveKey = (e as FocusEventArgs).LeaveKey;
            if (_leaveKey)
                Parent.Focus();
            else
                base.OnLostFocus(e);
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            if ((_edit != null) && (Focused == false))
                Win32Calls.PostMessage(_edit.Handle, Win32Calls.WM_CHAR, e.KeyChar, 0);
            else
                base.OnKeyPress(e);
        }

        [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
        protected override bool ProcessDialogKey(Keys keyData)
        {
            Keys key = (keyData & Keys.KeyCode);
            if (key == Keys.Tab)
            {
                if (_edit != null)
                {
                    bool forward = ((keyData & Keys.Shift) == Keys.None);
                    if (forward && _edit.Focused)
                    {
                        if (((_parentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.TabKey) == 0) ||
                            ((_parentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.TabKeyInSubControls) != 0))
                        {
                            _trackbar.Focus();
                            return true;
                        }
                    }
                    else if (!forward && _trackbar.Focused)
                    {
                        _edit.Focus();
                        _edit.SelectAll();
                        Invalidate(false);
                        return true;
                    }
                }
            }

            return base.ProcessDialogKey(keyData);
        }

        protected override bool ProcessKeyPreview(ref Message m)
        {
            if (m.Msg == Win32Calls.WM_KEYDOWN)
            {
                Keys key = (Keys)m.WParam;
                if ((_edit != null) && _edit.Focused)
                {
                    int value = _trackbar.Value;
                    if (key == Keys.Down)
                        value = _trackbar.Value - _trackbar.SmallChange;
                    else if (key == Keys.Up)
                        value = _trackbar.Value + _trackbar.SmallChange;
                    else if (key == Keys.PageDown)
                        value = _trackbar.Value - _trackbar.LargeChange;
                    else if (key == Keys.PageUp)
                        value = _trackbar.Value + _trackbar.LargeChange;

                    if (value != _trackbar.Value)
                    {
                        if (value > _trackbar.Maximum)
                            value = _trackbar.Maximum;
                        else if (value < _trackbar.Minimum)
                            value = _trackbar.Minimum;

                        _trackbar.Value = value;

                        ActualText = value.ToString();
                        _edit.SelectAll();
                        ValidateTextInternal(false);
                        
                        return true;
                    }
                }
            }

            return base.ProcessKeyPreview(ref m);
        }

        public void OnF4()
        {
            Focus();
        }

        public bool OnParentKeyDown(KeyEventArgs e)
        {
            if ((_edit != null) && _edit.OnParentKeyDown(e))
                return true;

            return false;
        }

        public bool OnForbidClickThrough(Point pt)
        {
            if (_trackbar == null)
                return false;

            return _trackbar.Bounds.Contains(pt);
        }

        public void DoubleClickFromParent(Point pt) { }

        public bool GoToNextPropertyOnTab(bool forward)
        {
            if (forward)
                return _trackbar.Focused;
            else
            {
                if (_edit == null)
                    return _trackbar.Focused;
                else
                    return _edit.Focused;
            }
        }

        public virtual bool ScrollValueOnDoubleClick(bool forward, bool force)
        {
            return false;
        }
    }
}
