using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Drawing.Extended;

namespace WheelchairControl
{
    /// <summary>
    /// GroupBox control that provides functionality to 
    /// allow it to be collapsed.
    /// </summary>
    /// <remarks>
    /// Please contact: aaronreginald@yahoo.com for the most 
    /// recent implementations of
    /// this class. 
    /// For further info, please see:
    /// http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=12835
    /// </remarks>
    [ToolboxBitmap(typeof(CollapsibleGroupBox))]
    public class CollapsibleGroupBox : GroupBox
    {
        #region Fields

        private Color m_borderColor = Color.FromArgb(208, 208, 191);
        private Color m_textColor = Color.Black;
        private Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11);
        private bool m_collapsed = false;
        private bool m_bResizingFromCollapse = false;

        private const int m_collapsedHeight = 20;
        private Size m_FullSize = Size.Empty;
        private System.ComponentModel.Container components = null;

        #endregion

        #region Events & Delegates

        /// <summary>
        /// Fired when the Collapse Toggle button is pressed.
        /// </summary>
        public delegate void CollapseBoxClickedEventHandler(object sender);

        /// <summary>
        /// Fired when control is clicked.
        /// </summary>
        public event CollapseBoxClickedEventHandler CollapseBoxClickedEvent;

        #endregion

        #region Construct & Dispose

        /// <summary>
        /// Default constructor.
        /// </summary>
        public CollapsibleGroupBox()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();
        }

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #endregion

        #region Component Designer generated code
        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        #endregion

        #region Public Properties

        /// <summary>
        /// Collapsible groupbox full height.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int FullHeight
        {
            get { return m_FullSize.Height; }
        }

        /// <summary>
        /// Property to determine whether collapsible groupbox is collapsed.
        /// </summary>
        [DefaultValue(false), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Description("Determines if object is initially collapsed.")]
        public bool IsCollapsed
        {
            get { return m_collapsed; }
            set
            {
                if (value != m_collapsed)
                {
                    m_collapsed = value;

                    if (!value)
                        // Expand
                        this.Size = m_FullSize;
                    else
                    {
                        // Collapse
                        m_bResizingFromCollapse = true;
                        this.Height = m_collapsedHeight;
                        m_bResizingFromCollapse = false;
                    }

                    foreach (Control c in Controls)
                        c.Visible = !value;

                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Collapsible groupbox collapsed height.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int CollapsedHeight
        {
            get { return m_collapsedHeight; }
        }

        #endregion

        #region Private Properties

        Image MinusImage
        {
            get { return WheelchairControl.Properties.Resources.minus; }
        }

        Image PlusImage
        {
            get { return WheelchairControl.Properties.Resources.plus; }
        }

        #endregion

        #region Overrides

        /// <summary>
        /// OnMouseUp method override.
        /// </summary>
        /// <param name="e">Mouse event arguments.</param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (m_toggleRect.Contains(new Point(e.X, e.Y)))
                ToggleCollapsed();
            else
                base.OnMouseUp(e);
        }

        /// <summary>
        /// OnPaint method override.
        /// </summary>
        /// <param name="e">Paint event arguments.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            HandleResize();
            //base.OnPaint(e);
            DrawGroupBox(e.Graphics);
            DrawToggleButton(e.Graphics);
        }

        #endregion

        #region Implimentation

        void DrawGroupBox(Graphics g)
        {
            // Get windows to draw the GroupBox
            Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 6, ClientRectangle.Width, ClientRectangle.Height - 6);
            
            // Text Formating positioning & Size
            StringFormat sf = new StringFormat();
            int i_textPos = (bounds.X + 8) + m_toggleRect.Width + 2;
            int i_textSize = (int)g.MeasureString(Text, this.Font).Width;
            i_textSize = i_textSize < 1 ? 1 : i_textSize;
            int i_endPos = i_textPos + i_textSize + 1;

            // Draw the Border
            ExtendedGraphics eg = new ExtendedGraphics(g);
            using (Pen p = new Pen(m_borderColor))
                eg.DrawRoundRectangle(p, 0, 6, Width - 1, Height - 7, 5F);

            // Draw a line to cover the GroupBox border where the text will sit
            g.FillRectangle(SystemBrushes.Control, 8, 2, i_endPos - 8, 12);

            // Draw the GroupBox text
            using (SolidBrush drawBrush = new SolidBrush(m_textColor))
                g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
        }

        void DrawToggleButton(Graphics g)
        {
            if (IsCollapsed)
                g.DrawImage(PlusImage, m_toggleRect);
            else
                g.DrawImage(MinusImage, m_toggleRect);
        }

        void ToggleCollapsed()
        {
            IsCollapsed = !IsCollapsed;

            if (CollapseBoxClickedEvent != null)
                CollapseBoxClickedEvent(this);
        }

        void HandleResize()
        {
            if (!m_bResizingFromCollapse && !m_collapsed)
                m_FullSize = this.Size;
        }

        #endregion
    }
}

// A simple extension to the Graphics class for extended 
// graphic routines, such, 
// as for creating rounded rectangles. 
// Because, Graphics class is an abstract class, 
// that is why it can not be inherited. Although, 
// I have provided a simple constructor 
// that builds the ExtendedGraphics object around a 
// previously created Graphics object. 
// Author: Aaron Reginald <aaronreginald@yahoo.com>
// URL: http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=12835
namespace System.Drawing.Extended
{

    /// <summary>
    /// Inherited child for the class Graphics encapsulating 
    /// additional functionality for curves and rounded rectangles. 
    /// </summary> 
    public class ExtendedGraphics
    {

        private Graphics mGraphics;

        /// <summary>
        /// Graphics property getter / setter.
        /// </summary>
        public Graphics Graphics
        {
            get { return this.mGraphics; }
            set { this.mGraphics = value; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="graphics"></param>
        public ExtendedGraphics(Graphics graphics)
        {
            this.Graphics = graphics;
        }


        #region Fills a Rounded Rectangle with integers.

        /// <summary>
        /// Misc method to draw collapsible groupbox's area.
        /// </summary>
        /// <param name="brush">Brush.</param>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <param name="width">Rectangle width.</param>
        /// <param name="height">Rectangle height.</param>
        /// <param name="radius">Corners' radius.</param>
        public void FillRoundRectangle(System.Drawing.Brush brush,
            int x, int y,
            int width, int height, int radius)
        {

            float fx = Convert.ToSingle(x);
            float fy = Convert.ToSingle(y);
            float fwidth = Convert.ToSingle(width);
            float fheight = Convert.ToSingle(height);
            float fradius = Convert.ToSingle(radius);
            this.FillRoundRectangle(brush, fx, fy,
                fwidth, fheight, fradius);

        }
        #endregion

        #region Fills a Rounded Rectangle with continuous numbers.

        /// <summary>
        /// Misc method to draw collapsible groupbox's area.
        /// </summary>
        /// <param name="brush">Brush.</param>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <param name="width">Rectangle width.</param>
        /// <param name="height">Rectangle height.</param>
        /// <param name="radius">Corners' radius.</param>
        public void FillRoundRectangle(System.Drawing.Brush brush,
            float x, float y,
            float width, float height, float radius)
        {
            RectangleF rectangle = new RectangleF(x, y, width, height);
            GraphicsPath path = this.GetRoundedRect(rectangle, radius);
            this.Graphics.FillPath(brush, path);
        }
        #endregion

        #region Draws a Rounded Rectangle border with integers.

        /// <summary>
        /// Misc method to draw collapsible groupbox's area.
        /// </summary>
        /// <param name="pen">Pen.</param>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <param name="width">Rectangle width.</param>
        /// <param name="height">Rectangle height.</param>
        /// <param name="radius">Corners' radius.</param>
        public void DrawRoundRectangle(System.Drawing.Pen pen, int x, int y,
            int width, int height, int radius)
        {
            float fx = Convert.ToSingle(x);
            float fy = Convert.ToSingle(y);
            float fwidth = Convert.ToSingle(width);
            float fheight = Convert.ToSingle(height);
            float fradius = Convert.ToSingle(radius);
            this.DrawRoundRectangle(pen, fx, fy, fwidth, fheight, fradius);
        }
        #endregion

        #region Draws a Rounded Rectangle border with continuous numbers.

        /// <summary>
        /// Misc method to draw collapsible groupbox's area.
        /// </summary>
        /// <param name="pen">Pen.</param>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <param name="width">Rectangle width.</param>
        /// <param name="height">Rectangle height.</param>
        /// <param name="radius">Corners' radius.</param>
        public void DrawRoundRectangle(System.Drawing.Pen pen,
            float x, float y,
            float width, float height, float radius)
        {
            RectangleF rectangle = new RectangleF(x, y, width, height);
            GraphicsPath path = this.GetRoundedRect(rectangle, radius);
            this.Graphics.DrawPath(pen, path);
        }
        #endregion

        #region Get the desired Rounded Rectangle path.
        private GraphicsPath GetRoundedRect(RectangleF baseRect,
            float radius)
        {
            // if corner radius is less than or equal to zero, 
            // return the original rectangle 
            if (radius <= 0.0F)
            {
                GraphicsPath mPath = new GraphicsPath();
                mPath.AddRectangle(baseRect);
                mPath.CloseFigure();
                return mPath;
            }

            // if the corner radius is greater than or equal to 
            // half the width, or height (whichever is shorter) 
            // then return a capsule instead of a lozenge 
            if (radius >= (Math.Min(baseRect.Width, baseRect.Height)) / 2.0)
                return GetCapsule(baseRect);

            // create the arc for the rectangle sides and declare 
            // a graphics path object for the drawing 
            float diameter = radius * 2.0F;
            SizeF sizeF = new SizeF(diameter, diameter);
            RectangleF arc = new RectangleF(baseRect.Location, sizeF);
            GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();

            // top left arc 
            path.AddArc(arc, 180, 90);

            // top right arc 
            arc.X = baseRect.Right - diameter;
            path.AddArc(arc, 270, 90);

            // bottom right arc 
            arc.Y = baseRect.Bottom - diameter;
            path.AddArc(arc, 0, 90);

            // bottom left arc
            arc.X = baseRect.Left;
            path.AddArc(arc, 90, 90);

            path.CloseFigure();
            return path;
        }
        #endregion

        #region Gets the desired Capsular path.
        private GraphicsPath GetCapsule(RectangleF baseRect)
        {
            float diameter;
            RectangleF arc;
            GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
            try
            {
                if (baseRect.Width > baseRect.Height)
                {
                    // return horizontal capsule 
                    diameter = baseRect.Height;
                    SizeF sizeF = new SizeF(diameter, diameter);
                    arc = new RectangleF(baseRect.Location, sizeF);
                    path.AddArc(arc, 90, 180);
                    arc.X = baseRect.Right - diameter;
                    path.AddArc(arc, 270, 180);
                }
                else if (baseRect.Width < baseRect.Height)
                {
                    // return vertical capsule 
                    diameter = baseRect.Width;
                    SizeF sizeF = new SizeF(diameter, diameter);
                    arc = new RectangleF(baseRect.Location, sizeF);
                    path.AddArc(arc, 180, 180);
                    arc.Y = baseRect.Bottom - diameter;
                    path.AddArc(arc, 0, 180);
                }
                else
                {
                    // return circle 
                    path.AddEllipse(baseRect);
                }
            }
            catch (Exception)
            {
                path.AddEllipse(baseRect);
            }
            finally
            {
                path.CloseFigure();
            }
            return path;
        }
        #endregion
    }
}