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

#endregion

namespace VisualHint.SmartPropertyGrid
{
    /// <summary>
    /// Inplace control shown in the value column when a button (with textbox or not) is needed.
    /// </summary>
    [ToolboxItem(false)]
    public class PropInPlaceButton : Control, IInPlaceControl
    {
        protected PropInPlaceTextbox mEdit = null;

        private bool _dontTransferFocus = false;

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

        protected PropertyEnumerator mOwnerPropertyEnum;

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

                if (mEdit != null)
                {
                    mEdit.OwnerPropertyEnumerator = value;
                    mEdit.Text = Text;
                }
            }

            get { return mOwnerPropertyEnum; }
        }

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

        private bool _pushed = false;

        private bool _mouseOver = 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 PropInPlaceButton(bool editable)
        {
            if (editable)
            {
                mEdit = new PropInPlaceTextbox(false);
                mEdit.LostFocus += new EventHandler(OnChildLostFocus);
                mEdit.Parent = this;
                mEdit.Text = Text;
                mEdit.TabStop = false;
            }

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

            Cursor = Cursors.Arrow;
        }

        private string _buttonText = "...";

        public string ButtonText
        {
            set { _buttonText = value; RepositionChildren(); }
            get { return _buttonText; }
        }

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

        protected int GetButtonWidth(Graphics graphics)
        {
            if (ReadOnly)
                return 0;

            Graphics graphics2 = (graphics != null ? graphics : CreateGraphics());
            int width = Size.Round(graphics2.MeasureString(_buttonText, Font)).Width + 6;

            if (graphics == null)
                graphics2.Dispose();

            return width;
        }

        protected Rectangle GetButtonRect(Graphics graphics)
        {
            int buttonWidth = GetButtonWidth(graphics);
            return new Rectangle(Width - buttonWidth, 0, buttonWidth, Height);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // Draw the background

            mOwnerPropertyEnum.Property.ParentGrid.DrawManager.DrawPropertyValueBackground(e.Graphics,
                ClientRectangle, mOwnerPropertyEnum);

            // Draw the arrows

            Rectangle buttonRect = GetButtonRect(e.Graphics);

            if (!ReadOnly)
            {
                if (Application.RenderWithVisualStyles)
                {
                    VisualStyleElement vsElement;
                    if (_pushed)
                    {
                        if (_mouseOver)
                            vsElement = VisualStyleElement.Button.PushButton.Pressed;
                        else
                            vsElement = VisualStyleElement.Button.PushButton.Hot;
                    }
                    else
                    {
                        if (_mouseOver)
                            vsElement = VisualStyleElement.Button.PushButton.Hot;
                        else
                            vsElement = VisualStyleElement.Button.PushButton.Normal;
                    }

                    VisualStyleRenderer renderer1 = new VisualStyleRenderer(vsElement);
                    renderer1.DrawBackground(e.Graphics, buttonRect);
//                    renderer1.DrawText(e.Graphics, buttonRect, _buttonText);
                    Win32Calls.DrawText(e.Graphics, _buttonText, ref buttonRect, Font,
                        OwnerPropertyEnumerator.Property.Value.ForeColor, Win32Calls.DT_SINGLELINE |
                        Win32Calls.DT_VCENTER | Win32Calls.DT_CENTER);
                }
                else
                {
                    ControlPaint.DrawButton(e.Graphics, buttonRect, (_pushed && _mouseOver) ? ButtonState.Pushed : ButtonState.Normal);
                    StringFormat stringFormat = (StringFormat)StringFormat.GenericDefault.Clone();
                    stringFormat.Alignment = StringAlignment.Center;
                    stringFormat.LineAlignment = StringAlignment.Center;
                    Brush brush = new SolidBrush(Color.Black);
                    e.Graphics.DrawString(_buttonText, Font, brush, buttonRect, stringFormat);
                    brush.Dispose();
                }

                if (Focused)
                {
                    Rectangle focusRect = buttonRect;
                    focusRect.Inflate(-3, -3);
                    ControlPaint.DrawFocusRectangle(e.Graphics, focusRect);
                }
            }

            // Draw the value
            if (mEdit == null)
            {
                Rectangle valueRect = ClientRectangle;
                valueRect.Width -= buttonRect.Width + 1;

                Color valueColor;
                if (mOwnerPropertyEnum.Property.Enabled == false)
                    valueColor = SystemColors.GrayText;
                else
                    valueColor = mOwnerPropertyEnum.Property.Value.ForeColor;

                mOwnerPropertyEnum.Property.Value.DrawValue(e.Graphics, valueRect, valueColor, mOwnerPropertyEnum, null);
            }

            base.OnPaint(e);
        }

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

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

                    int buttonWidth = GetButtonWidth(null);
                    Rectangle editRect = new Rectangle(leftPos, 0, clientRect.Width - buttonWidth - 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)
                        mEdit.Visible = false;
                    else
                    {
                        mEdit.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;

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

            Invalidate(false);

//            mEdit.Location = new Point(0, 0);
//          mEdit.Size = new Size(ClientRectangle.Width - SystemInformation.VerticalScrollBarWidth, ClientRectangle.Height);
        }

        protected override void OnMove(EventArgs e)
        {
            base.OnMove(e);
            RepositionChildren();
        }

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

            RepositionChildren();
        }

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

            if (mEdit != null)
            {
                if (_dontTransferFocus == false)
                {
                    if (e.FocusControl != mEdit)
                        mEdit.Focus();
                    else if (e.FocusControl == mEdit)
                        Parent.Focus();
                }
            }
            else
                Invalidate();
        }

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

            if (mEdit == null)
            {
                Invalidate();

                PropertyValue value = mOwnerPropertyEnum.Property.Value;
                if (value != null)
                {
                    // Store the previous value
                    string oldValue = value.GetStringValue();

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

                    // Assign the new value based on the edit content
                    value.SetValue(Text);

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

                    Parent.Invalidate();
                }
            }
        }

        protected override void OnLostFocus(EventArgs e)
        {
            _pushed = false;

            Invalidate();

            base.OnLostFocus(new FocusEventArgs(_wmFocusMissingArg, _leaveKey));
            _leaveKey = false;
        }

        void OnChildLostFocus(object source, EventArgs e)
        {
            _leaveKey = (e as FocusEventArgs).LeaveKey;
            if (_leaveKey)
                Parent.Focus();
            else
                base.OnLostFocus(e);
        }

        [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 override void OnKeyPress(KeyPressEventArgs e)
        {
            if ((mEdit != null) && (Focused == false))
                Win32Calls.PostMessage(mEdit.Handle, Win32Calls.WM_CHAR, e.KeyChar, 0);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Graphics graphics = CreateGraphics();
                if (GetButtonRect(graphics).Contains(e.Location))
                {
                    _pushed = true;
                    Capture = true;
                    Invalidate(false);
                }
                graphics.Dispose();

                _dontTransferFocus = true;
                Focus();
                _dontTransferFocus = false;
            }

            base.OnMouseDown(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            _mouseOver = false;
            Invalidate(false);

            base.OnMouseLeave(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            bool oldMouseOver = _mouseOver;
            Graphics graphics = CreateGraphics();
            _mouseOver = GetButtonRect(graphics).Contains(e.Location);
            graphics.Dispose();
            if (oldMouseOver != _mouseOver)
                Invalidate(false);

            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (_pushed && (e.Button == MouseButtons.Left))
            {
                Capture = false;
                _pushed = false;
                _mouseOver = false;
                Invalidate(false);

                Graphics graphics = CreateGraphics();
                if (GetButtonRect(graphics).Contains(e.Location))
                {
                    // Maybe the user typed something in the editbox that has not been validated yet
                    if (mEdit != null)
                        mEdit.ValidateTextInternal();

                    RunButton();
                }
                graphics.Dispose();
            }

            base.OnMouseUp(e);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if ((e.KeyData == Keys.Return) || (e.KeyData == Keys.Space))
            {
                _mouseOver = true;
                _pushed = true;
                Invalidate(false);
            }
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            if ((e.KeyData == Keys.Return) || (e.KeyData == Keys.Space))
            {
                _mouseOver = false;
                _pushed = false;
                Invalidate(false);

                RunButton();
            }
        }

        protected override bool IsInputKey(Keys keyData)
        {
            if (keyData == Keys.Return)
                return true;

            return base.IsInputKey(keyData);
        }

        [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
        protected override bool ProcessDialogKey(Keys keyData)
        {
            Keys key = (keyData & Keys.KeyCode);

            if (key == Keys.Escape)
            {
                _pushed = false;

                _leaveKey = true;
                Parent.Focus();
                return true;
            }
            else if (key == Keys.Tab)
            {
                if ((mEdit != null) && !ReadOnly)
                {
                    bool forward = ((keyData & Keys.Shift) == Keys.None);

                    if (forward && mEdit.Focused)
                    {
                        if (((mOwnerPropertyEnum.Property.ParentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.ArrowKeys) != 0) ||
                            (((mOwnerPropertyEnum.Property.ParentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.TabKey) != 0) &&
                            ((mOwnerPropertyEnum.Property.ParentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.TabKeyInSubControls) != 0)))
                        {
                            _dontTransferFocus = true;
                            Focus();
                            Invalidate(false);
                            _dontTransferFocus = false;
                            return true;
                        }
                    }
                    else if (!forward && mEdit.Focused)
                    {
                        if ((mOwnerPropertyEnum.Property.ParentGrid.NavigationKeyMode & PropertyGrid.NavigationKeyModes.ArrowKeys) != 0)
                        {
                            Parent.Focus();
                            return true;
                        }
                    }
                    else if (!forward && Focused)
                    {
                        mEdit.Focus();
                        mEdit.SelectAll();
                        Invalidate(false);
                        return true;
                    }
                }
            }

            return base.ProcessDialogKey(keyData);
        }

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

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

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

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

        protected virtual void RunButton()
        {
            Parent.Focus();

            PropertyButtonClickedEventArgs eventArgs = new PropertyButtonClickedEventArgs(mOwnerPropertyEnum);
            mOwnerPropertyEnum.Property.Value.PreviousValue = mOwnerPropertyEnum.Property.Value.GetValue();

            mOwnerPropertyEnum.Property.ParentGrid.OnPropertyButtonClicked(eventArgs);

            if (eventArgs.PropertyChanged)
            {
                if (mEdit != null)
                    mEdit.Text = mOwnerPropertyEnum.Property.Value.DisplayString;

                mOwnerPropertyEnum.Property.ParentGrid.NotifyPropertyChanged(new PropertyChangedEventArgs(mOwnerPropertyEnum));
            }
        }

        public bool OnForbidClickThrough(Point pt)
        {
            Graphics graphics = CreateGraphics();
            bool result = GetButtonRect(graphics).Contains(pt);
            graphics.Dispose();

            return result;
        }

        public void OnF4()
        {
            Focus();
        }

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

            return false;
        }

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

        public bool GoToNextPropertyOnTab(bool forward)
        {
            if (forward)
            {
                if ((mEdit == null) || ReadOnly)
                    return true;
                else
                    return Focused;
            }
            else
            {
                if (mEdit == null)
                    return true;
                else
                    return mEdit.Focused;
            }
        }

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