#region Copyright and GPL
/*************************************************************************
 * dataMagus - The open source database learning software
 * Copyright (C) 2007 by all authors
 * Authors: Monika Schwitter (monsy@gmx.ch)
 *          Jürg Zgraggen (j.zgraggen@ejaz.net)
 *          HSLU (Hochschule Luzern - Technik+Architektur)
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *************************************************************************/
#endregion
#region Using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using dataMagus.Common;
#endregion

namespace dataMagus.Common.PlugIn.Notation
{
	/// <summary>
	/// This class can be used by notations for elements on the schema.
	/// </summary>
	public partial class ElementControl : UserControl
	{
		#region Constants
		/// <summary>
		/// Defines the default scale factor.
		/// </summary>
        private const float DefaultScaleFactor = 1.0F;
        
        /// <summary>
		/// Defines the default snap size.
        /// </summary>
        private const int DefaultSnapSize = 10;
        
        /// <summary>
		/// Defines the default gap delta.
        /// </summary>
        private const float DefaultGapDelta = 10F;
        
        /// <summary>
		/// Defines the default gap delta for attributes.
        /// </summary>
        private const float DefaultAttributeGapDelta = 5F;
		#endregion
		
		#region Members
		/// <summary>
		/// The element the control is based on.
		/// </summary>
		private IElement _element = null;
		
		/// <summary>
		/// Help variable for counting the on paint events not to get 
		/// into an endless recursion.
		/// </summary>
		private int _onPaintCount = 0;
        
		/// <summary>
		/// Scale factor defines the multiplier for having a bigger view.
		/// <remarks>Is not used at the moment</remarks>
		/// </summary>
		private float _scaleFactor = ElementControl.DefaultScaleFactor;
        
		/// <summary>
		/// Identifier marking the element as selected.
		/// </summary>
		private bool _isSelected = false;
        
		/// <summary>
		/// Identifier defining if the control is in drag mode.
		/// </summary>
		private bool _drag = false;
        
		/// <summary>
		/// Identifier defining if the control has to refresh the view while
		/// beeing in drag mode and moving arround.
		/// </summary>
		private bool _forceRefreshOnDrag = false;
        
		/// <summary>
		/// Start point when beginning the dragging.
		/// </summary>
		private Point _dragStart = Point.Empty;
        
		/// <summary>
		/// Size of how big the snapping will be.
		/// </summary>
		private ushort _snapSize = ElementControl.DefaultSnapSize;
        
		/// <summary>
		/// Backup of the back color.
		/// </summary>
		private Color _BackColorBackup = Color.White;
        
		/// <summary>
		/// Back color when the element is selected.
		/// </summary>
		private Color _selectedBackColor = Color.LightGray;
        #endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Default constructor.
		/// </summary>
		protected ElementControl()
		{
            // constructor used at design time
            this.SetStyle(ControlStyles.SupportsTransparentBackColor
                & ControlStyles.AllPaintingInWmPaint
                & ControlStyles.OptimizedDoubleBuffer
                & ControlStyles.Selectable, true);
            this.BackColor = Color.FromArgb(0, Color.Transparent);
            _BackColorBackup = this.BackColor;
            _selectedBackColor = this.BackColor;
            this.UpdateStyles();
			
			InitializeComponent();
		}
		
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="element">The element the control is based on.</param>
        public ElementControl(IElement element) 
        {
            this._element = element;
            InitializeComponent();
        }
		#endregion
		
		#region Properties
		/// <summary>
		/// The element the control is based on.
		/// </summary>
		public IElement Element 
		{
			get { return this._element; }
			set { this._element = value; }
		}

        /// <summary>
		/// Get the default gap delta.
        /// </summary>
		public virtual float GapDelta 
		{
			get { return DefaultGapDelta; }
		}
		
        /// <summary>
		/// Get the default gap delta for attributes.
        /// </summary>
		public virtual float AttributeGapDelta 
		{
			get { return DefaultAttributeGapDelta; }
		}
        
		/// <summary>
		/// Identifier defining if the control has to refresh the view while
		/// beeing in drag mode and moving arround.
		/// </summary>
		public bool ForceRefreshOnDrag
		{
			get { return _forceRefreshOnDrag; }
			set { _forceRefreshOnDrag = value; }
		}
		
		/// <summary>
		/// Scale factor defines the multiplier for having a bigger view.
		/// <remarks>Is not used at the moment</remarks>
		/// </summary>
		public virtual float ScaleFactor 
		{
			get { return _scaleFactor; }
			set { _scaleFactor = value; }
		}
        
		/// <summary>
		/// Identifier marking the element as selected.
		/// </summary>
		public virtual bool IsSelected 
		{
			get { return _isSelected; }
			set 
			{
				bool dataChanged = value != _isSelected;
				if (dataChanged)
				{
					if (value)
					{
						// store old backcolor
						this._BackColorBackup = this.BackColor;
						// set selected color
						this.BackColor = this._selectedBackColor;
					}
					else
					{
						// restore
						this.BackColor = _BackColorBackup;
					}
				}
				
				this._isSelected = value;
				
				if (dataChanged && this._isSelected && null != this.Selected)
				{
					this.Selected(this, EventArgs.Empty);
				}
				else if (dataChanged && !this._isSelected && null != this.UnSelected)
				{
					this.UnSelected(this, EventArgs.Empty);
				}
			}
		}
        
		/// <summary>
		/// Size of how big the snapping will be.
		/// </summary>
		public virtual ushort SnapSize 
		{
			get { return _snapSize; }
			set { _snapSize = value; }
		}
        
		/// <summary>
		/// Back color when the element is selected.
		/// </summary>
		public virtual Color SelectedBackColor 
		{
			get { return _selectedBackColor; }
			set { _selectedBackColor = value; }
		}
        
		/// <summary>
        /// Point on the middle of the element.
		/// </summary>
        public virtual Point Middle
        {
            get { return new Point(Location.X + this.Width / 2, Location.Y + this.Height / 2); }
        }

        /// <summary>
        /// Point on the top right of the element.
        /// </summary>
        public virtual Point TopRight
        {
            get { return new Point(this.Location.X + this.Width, this.Location.Y); }
        }

        /// <summary>
        /// Point on the top left of the element.
        /// </summary>
        public virtual Point TopLeft
        {
            get { return this.Location; }
        }

        /// <summary>
        /// Point on the bottom right of the element.
        /// </summary>
        public virtual Point BottomRight
        {
            get { return new Point(this.Location.X + this.Width, this.Location.Y + this.Height); }
        }

        /// <summary>
        /// Point on the bottom left of the element.
        /// </summary>
        public virtual Point BottomLeft
        {
            get { return new Point(this.Location.X, this.Location.Y + this.Height); }
        }

        /// <summary>
        /// Point on the middle bottom of the element.
        /// </summary>
        public virtual Point MiddleBottom
        {
            get { return new Point(this.Location.X + this.Width/2, this.Location.Y + this.Height); }
        }

        /// <summary>
        /// Point on the middle top of the element.
        /// </summary>
        public virtual Point MiddleTop
        {
            get { return new Point(this.Location.X + this.Width/2, this.Location.Y); }
        }

        /// <summary>
        /// Point on the middle right of the element.
        /// </summary>
        public virtual Point MiddleRight
        {
            get { return new Point(this.Location.X + this.Width, this.Location.Y + this.Height/2); }
        }

        /// <summary>
        /// Point on the middle left of the element.
        /// </summary>
        public virtual Point MiddleLeft
        {
            get { return new Point(this.Location.X, this.Location.Y + this.Height/2); }
        }
        #endregion
		
		#region Delegates
		/// <summary>
		/// Handler when the element is moving.
		/// </summary>
    	public delegate void MooovingEventHandler(object sender, MouseEventArgs e);
    	
    	/// <summary>
    	/// Handler when the element is selected.
    	/// </summary>
    	public delegate void SelectedHandler(object sender, EventArgs e);
    	
    	/// <summary>
    	/// Handler when the element is unselected.
    	/// </summary>
    	public delegate void UnSelectedHandler(object sender, EventArgs e);
		#endregion
		
		#region Events
		/// <summary>
		/// Event will be fired when the element is moving.
		/// </summary>
    	public event MooovingEventHandler Moooving;
    	
    	/// <summary>
    	/// Delegate will be fired when the element is selected.
    	/// </summary>
    	public event SelectedHandler Selected;
    	
    	/// <summary>
    	/// Delegate will be fired when the element is unselected.
    	/// </summary>
    	public event UnSelectedHandler UnSelected;
		#endregion
		
		#region Methodes
		/// <summary>
		/// Raised when painting the control.
		/// </summary>
		/// <param name="e">PaintEventArgs</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            // call base
            base.OnPaint(e);
            
            // used for sizing
            if (_onPaintCount == 0)
            {
				_onPaintCount++;
				if (this.AutoSize)
				{
					this.Size = this.CalcControlSize();
				}
				_onPaintCount--;
            }
        }
		
        /// <summary>
		/// Raised when pressing the mouse down button on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            // call base
            base.OnMouseDown(e);

            this.IsSelected = true;
            _dragStart = e.Location;
            _drag = true;
        }

        /// <summary>
		/// Raised when the mouse moves on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // call base
            base.OnMouseMove(e);

            // check drag mode
            if (_drag)
            {
                int deltaX = e.X - _dragStart.X;
                int deltaY = e.Y - _dragStart.Y;
                
                // check if in snap-mode
                if (_snapSize != 0)
                {
	                // calculate snap point
	                deltaX = deltaX - (deltaX % _snapSize);
	                deltaY = deltaY - (deltaY % _snapSize);
                }

                // check for locations out of bounds and set new location
                if (this.Location.X + deltaX < 0)
                {
                	deltaX = -this.Location.X;
                }
                if (this.Location.Y + deltaY < 0)
                {
                	deltaY = -this.Location.Y;
                }
                // move the control and set the new location on the element
                this.Location = new Point(this.Location.X + deltaX, this.Location.Y + deltaY);
                
                // fire moooving event to inform others
                if (Moooving != null)
                {
                	MouseEventArgs arg = new MouseEventArgs(e.Button, e.Clicks, deltaX, deltaY, e.Delta);
                	Moooving(this, arg);
                }

	            // check for parent control
	            if (this.Parent != null)
	            {
		            // tell the parent to invalidate the region around the 
		            // current element
		            this.Parent.Invalidate();
		            
		            // update if forced
		            if (this.ForceRefreshOnDrag)
		            {
	            		this.Parent.Update();
		            }
	            }
            }
        }

        /// <summary>
		/// Raised when pressing the mouse up button on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            // call base
            base.OnMouseUp(e);

            this.IsSelected = false;
            _drag = false;
        }

        /// <summary>
        /// Draws the whole control into a graphics context.
        /// </summary>
        /// <param name="context">Graphics context.</param>
		public virtual void DrawControl(Graphics context)
		{
			return;
		}

		/// <summary>
		/// Refreshes the control size and redraws the schema if available.
		/// </summary>
		protected virtual void RefreshSizeAndSchema()
        {
        	// set new size
        	this.Size = this.CalcControlSize();

        	// force refresh own control
        	this.Refresh();
        	
        	// redraw it
        	this.RedrawSchema();
        }
        
		/// <summary>
		/// Redraws the schema if available.
		/// </summary>
        protected virtual void RedrawSchema()
        {
        	// try to invalidate schema
        	if (null != this.ParentForm)
        	{
        		// let the parent refresh the links on it
        		this.ParentForm.Invalidate(true);
        		this.ParentForm.Update();
        	}
        }
        
        /// <summary>
        /// Calculate the whole size of the control.
        /// </summary>
        /// <returns>Size of the control.</returns>
		public virtual Size CalcControlSize()
		{
            return new Size(this.Width, this.Height);
		}
		
		/// <summary>
		/// Handles the AutoSizeChanged event.
		/// </summary>
		/// <param name="sender">Sender object.</param>
		/// <param name="e">EventArgs</param>
		public void ElementControlAutoSizeChanged(object sender, EventArgs e)
		{
			if (this.AutoSize)
			{
				this.Size = this.CalcControlSize();
			}
		}
		
		/// <summary>
		/// Handles the ControlResize event.
		/// </summary>
		/// <param name="sender">Sender object.</param>
		/// <param name="e">EventArgs</param>
		public void ElementControlResize(object sender, EventArgs e)
		{
			if (this.AutoSize)
			{
				this.Size = this.CalcControlSize();
			}
		}
		#endregion
	}
}
