// 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.Windows.Forms;
using System.Reflection;
using System.ComponentModel;
using System.Security.Permissions;

#endregion

namespace VisualHint.SmartPropertyGrid
{
    /// <summary>
    /// Inplace control shown in the value column when a textbox with a mask is needed.
    /// </summary>
    [ToolboxItem(false)]
    public class PropInPlaceMaskedTextbox : MaskedTextBox, IInPlaceControl
    {
        /// <summary>
        /// An enumerator on the underlying Property.
        /// </summary>
        private PropertyEnumerator _ownerPropertyEnum;

        /// <summary>
        /// Links a Property to this inplace control, so that its value will be displayed inside.
        /// </summary>
        /// <value>An enumerator to the Property to link to this control.</value>
        public PropertyEnumerator OwnerPropertyEnumerator
        {
            set
            {
                _ownerPropertyEnum = (PropertyEnumerator)value.Clone();

                if (_ownerPropertyEnum.Property.Value.IsReadOnly(_ownerPropertyEnum) &&
                    (_ownerPropertyEnum.Property.ParentGrid.ReadOnlyVisual == PropertyGrid.ReadOnlyVisuals.ReadOnlyFeel))
                    ReadOnly = true;
            }

            get { return _ownerPropertyEnum; }
        }

        /// <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;

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

        /// <summary>
        /// CreateParams is overriden to remove the nonclient border.
        /// </summary>
        /// <value>A CreateParams object that contains the required creation parameters when the handle
        /// to the control is created.</value>
        protected override CreateParams CreateParams
        {
            [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
            get
            {
                CreateParams cp = base.CreateParams;

                // The border of the editbox must be removed
                cp.ExStyle &= ~Win32Calls.WS_EX_CLIENTEDGE;

                return cp;
            }
        }

        /// <summary>
        /// Initializes a new instance of the PropInPlaceMaskedTextbox class.
        /// </summary>
        public PropInPlaceMaskedTextbox()
        {
            // Because we want to be free to resize the textbox at will...
            AutoSize = false;
        }

        /// <summary>
        /// Gets or sets the font of the text displayed by the control.
        /// </summary>
        /// <value>The Font object to apply to the text displayed by the control.</value>
        public override System.Drawing.Font Font
        {
            get { return base.Font; }
            set
            {
                base.Font = value;

                // When the Font of the PropertyGrid is assigned to this control, its margins must be recalculated.
                // First they are set corresponding to the font info.
                Win32Calls.SendMessage(Handle, Win32Calls.EM_SETMARGINS,
                    Win32Calls.EC_LEFTMARGIN | Win32Calls.EC_RIGHTMARGIN,
                    Win32Calls.MakeLong(Win32Calls.EC_USEFONTINFO, Win32Calls.EC_USEFONTINFO));

                // Then 2 pixels are added to the left margin.
                int adjustedMargin = 2 + Win32Calls.LoWord(Win32Calls.SendMessage(Handle, Win32Calls.EM_GETMARGINS, 0, 0));
                Win32Calls.SendMessage(Handle, Win32Calls.EM_SETMARGINS,
                    Win32Calls.EC_LEFTMARGIN | Win32Calls.EC_RIGHTMARGIN,
                    Win32Calls.MakeLong(adjustedMargin, 0));

                // This makes a pleasant reading of the content of the PropertyGrid.
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnGotFocus(System.EventArgs e)
        {
            if ((_wmFocusMissingArg == null) || (_ownerPropertyEnum.Property.ParentGrid.Contains(_wmFocusMissingArg) == false))
                _ownerPropertyEnum.Property.ParentGrid.Invalidate(true);

            if (_ownerPropertyEnum.Property.ParentGrid.ClickThroughInProgress)
                Select(0, 0);
            else
                SelectAll();

            base.OnGotFocus(new FocusEventArgs(_wmFocusMissingArg, false));
        }

        /// <summary>
        /// 
        /// </summary>
        public void ValidateTextInternal()
        {
            PropertyValue value = _ownerPropertyEnum.Property.Value;
            if (value != null)
            {
                // Store the previous value
                string oldValue = value.GetStringValue();

                // Assign the new value based on the edit content
                if (Text != oldValue)
                {
                    if (MaskCompleted)
                    {
                        _ownerPropertyEnum.Property.Value.PreviousValue = _ownerPropertyEnum.Property.Value.GetValue();

                        string oldMask = Mask;
                        Win32Calls.SendMessage(Handle, Win32Calls.WM_SETREDRAW, 0, 0);
                        Mask = "";
                        value.SetValue(Text);
                        Mask = oldMask;
                        Win32Calls.SendMessage(Handle, Win32Calls.WM_SETREDRAW, 1, 0);
                    }

                    // Notify the properties control if the value has really changed
                    if (value.GetStringValue() != oldValue)
                        _ownerPropertyEnum.Property.ParentGrid.NotifyPropertyChanged(new PropertyChangedEventArgs(_ownerPropertyEnum));

                    // We have to update the edit control in case the value was refused
                    Text = value.GetStringValue();

                    Parent.Invalidate();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostFocus(System.EventArgs e)
        {
            if (Parent.GetType().GetMethod("ValidateTextInternal", BindingFlags.Instance | BindingFlags.NonPublic) == null)
                ValidateTextInternal();

            Select(0, 0);
            ScrollToCaret();

            base.OnLostFocus(new FocusEventArgs(_wmFocusMissingArg, _leaveKey));

            _leaveKey = false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyData"></param>
        /// <returns></returns>
        [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
        protected override bool ProcessDialogKey(Keys keyData)
        {
            Keys key = (keyData & Keys.KeyCode);

            if (key == Keys.Return)
            {
                _leaveKey = true;
                Parent.Focus();
                return true;
            }
            else if (key == Keys.Escape)
            {
                // Restore the old string in the edit box
                PropertyValue value = _ownerPropertyEnum.Property.Value;
                if (value != null)
                    Text = value.DisplayString;

                _leaveKey = true;
                Parent.Focus();
                return true;
            }

            return base.ProcessDialogKey(keyData);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="m"></param>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == Win32Calls.WM_SETFOCUS)
            {
                // The window that lose the focus is stored for future reference
                // in OnGotFocus. if the handle is in another application, the stored
                // value will be null.
                _wmFocusMissingArg = FromHandle(m.WParam);
            }
            else if (m.Msg == Win32Calls.WM_KILLFOCUS)
            {
                // The window that gained the focus is stored for future reference
                // in OnLostFocus. if the handle is in another application, the stored
                // value will be null.
                _wmFocusMissingArg = FromHandle(m.WParam);
            }
            else if (m.Msg == Win32Calls.WM_ERASEBKGND)
            {
                // If I don't intercept this message, the control flickers when it is multiline
                return;
            }

            base.WndProc(ref m);
        }

        public void OnF4()
        {
            Focus();
        }

        public bool OnParentKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                Clear();
                Focus();

                return true;
            }

            return false;
        }

        public bool RealtimeChange
        {
            set { }
            get { return true; }
        }

        public void RepositionChildren() { }

        public bool GoToNextPropertyOnTab(bool forward)
        {
            return true;
        }

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