context: // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  // Copyright (c) 2004-2005 Novell, Inc.
  //
  // Authors:
  //	Jordi Mas i Hernandez, jordi@ximian.com
  //	Chris Toshok  <toshok@ximian.com>
  //	Everaldo Canuto  <ecanuto@novell.com>
  //
  // Based on work by:
  //	Daniel Carrera, dcarrera@math.toronto.edu (stubbed out)
  //	Jaak Simm (jaaksimm@firm.ee) (stubbed out)
  //
  
  using System.ComponentModel;
  using System.Collections;
  using System.Drawing;
  using System.Drawing.Drawing2D;
  using System.Runtime.InteropServices;
  using System.Windows.Forms.Theming;
  
  namespace System.Windows.Forms
  {
  	[DefaultEvent("LinkClicked")]
  	[ClassInterface (ClassInterfaceType.AutoDispatch)]
  	[ComVisible (true)]
  	[ToolboxItem ("System.Windows.Forms.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)]
  	public class LinkLabel : Label, IButtonControl
  	{
  		/* Encapsulates a piece of text (regular or link)*/
  		internal class Piece
  		{
  			public string		text;
  			public int		start;
  			public int		length;
  			public LinkLabel.Link	link;	// Empty link indicates regular text
  			public Region           region;
  
  			public Piece (int start, int length, string text, Link link)
  			{
  				this.start = start;
  				this.length = length;
  				this.text = text;
  				this.link = link;
  			}
  		}
  
  		private Color active_link_color;
  		private Color disabled_link_color;
  		private Color link_color;
  		private Color visited_color;
  		private LinkArea link_area;
  		private LinkBehavior link_behavior;
  		private LinkCollection link_collection;
  		private ArrayList links = new ArrayList();
  		internal Link[] sorted_links;
  		private bool link_visited;
  		internal Piece[] pieces;
  		private Cursor override_cursor;
  		private DialogResult dialog_result;
  
  		private Link active_link;
  		private Link hovered_link;
  		/* this is an index instead of a Link because we have
  		 * to search through sorted links for the new one */
  		private int focused_index;
  
  		#region Events
  		static object LinkClickedEvent = new object ();
  
  		public event LinkLabelLinkClickedEventHandler LinkClicked {
  			add { Events.AddHandler (LinkClickedEvent, value); }
  			remove { Events.RemoveHandler (LinkClickedEvent, value); }
  		}
  
  		[Browsable (true)]
  		[EditorBrowsable (EditorBrowsableState.Always)]
  		public new event EventHandler TabStopChanged {
  			add { base.TabStopChanged += value; }
  			remove { base.TabStopChanged -= value; }
  		}
  		#endregion // Events
  
  		public LinkLabel ()
  		{
  			LinkArea = new LinkArea (0, -1);
  			link_behavior = LinkBehavior.SystemDefault;
  			link_visited = false;
  			pieces = null;
  			focused_index = -1;
  
  			string_format.FormatFlags |= StringFormatFlags.NoClip;
  			
  			ActiveLinkColor = Color.Red;
  			DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
  			LinkColor = Color.FromArgb (255, 0, 0, 255);
  			VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
  			SetStyle (ControlStyles.Selectable, false);
  			SetStyle (ControlStyles.ResizeRedraw | 
  				ControlStyles.UserPaint | 
  				ControlStyles.AllPaintingInWmPaint |
  				ControlStyles.SupportsTransparentBackColor | 
  				ControlStyles.Opaque |
  				ControlStyles.OptimizedDoubleBuffer
  				, true);
  			CreateLinkPieces ();
  		}
  
  		#region Public Properties
  
  		public Color ActiveLinkColor {
  			get { return active_link_color; }
  			set {
  				if (active_link_color == value)
  					return;
  
  				active_link_color = value;
  				Invalidate ();
  			}
  		}
  
  		public Color DisabledLinkColor {
  
  			get { return disabled_link_color; }
  			set {
  				if (disabled_link_color == value)
  					return;
  
  				disabled_link_color = value;
  				Invalidate ();
  			}
  		}
  
  		public Color LinkColor {
  			get { return link_color; }
  			set {
  				if (link_color == value)
  					return;
  
  				link_color = value;
  				Invalidate ();
  			}
  		}
  
  		public Color VisitedLinkColor {
  			get { return visited_color;}
  			set {
  				if (visited_color == value)
  					return;
  
  				visited_color = value;
  				Invalidate ();
  			}
  		}
  
  		[Localizable (true)]
  		[Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]				
  		[RefreshProperties (RefreshProperties.Repaint)]
  		public LinkArea LinkArea {
  			get { return link_area;}
  			set {
  
  				if (value.Start <0 || value.Length < -1)
  					throw new ArgumentException ();
  
  				Links.Clear ();
  
  				if (!value.IsEmpty) {
  					Links.Add (value.Start, value.Length);
  
  					link_area = value;
  					Invalidate ();
  				}
  			}
  		}
  				
  		[DefaultValue (LinkBehavior.SystemDefault)]
  		public LinkBehavior LinkBehavior {
  
  			get { return link_behavior;}
  			set {
  				if (link_behavior == value)
  					return;
  
  				link_behavior = value;
  				Invalidate ();
  			}
  		}
  	
  		[Browsable (false)]
  		[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
  		public LinkLabel.LinkCollection Links {
  			get {
  				if (link_collection == null)
  					link_collection = new LinkCollection (this);
  				return link_collection;
  			}
  		}
  
  		[DefaultValue (false)]
  		public bool LinkVisited {
  			get { return link_visited;}
  			set {
  				if (link_visited == value)
  					return;
  
  				link_visited = value;
  				Invalidate ();
  			}
  		}
  		
  		protected Cursor OverrideCursor {
  			get {
  				if (override_cursor == null)
  					override_cursor = Cursors.Hand;
  				return override_cursor;
  			}
  			set { override_cursor = value; }
  		}
  
  		[RefreshProperties(RefreshProperties.Repaint)]
  		public override string Text {
  			get { return base.Text; }
  			set {
  				if (base.Text == value)
  					return;
  
  				base.Text = value;
  				CreateLinkPieces ();
  			}
  		}
  
  		[Browsable (false)]
  		[EditorBrowsable (EditorBrowsableState.Never)]
  		public new FlatStyle FlatStyle {
  			get { return base.FlatStyle; }
  			set {
  				if (base.FlatStyle == value)
  					return;
  
  				base.FlatStyle = value;
  			}
  		}
  
  		[RefreshProperties (RefreshProperties.Repaint)]
  		public new Padding Padding {
  			get { return base.Padding; }
  			set {
  				if (base.Padding == value)
  					return;
  
  				base.Padding = value;
  				CreateLinkPieces ();
  			}
  		}
  
  		#endregion // Public Properties
  
  		DialogResult IButtonControl.DialogResult {
  			get { return dialog_result; }
  			set { dialog_result = value; }
  		}
  
  
  		void IButtonControl.NotifyDefault (bool value)
  		{
  		}
  
  		void IButtonControl.PerformClick ()
  		{
  		}
  
  		#region Public Methods
  		protected override AccessibleObject CreateAccessibilityInstance ()
  		{
  			return base.CreateAccessibilityInstance();
  		}
  
  		protected override void CreateHandle ()
  		{
  			base.CreateHandle ();
  			CreateLinkPieces ();
  		}
  
  		protected override void OnAutoSizeChanged (EventArgs e)
  		{
  			base.OnAutoSizeChanged (e);
  		}
  
  		protected override void OnEnabledChanged (EventArgs e)
  		{
  			base.OnEnabledChanged (e);
  			Invalidate ();
  		}
  
  		protected override void OnFontChanged (EventArgs e)
  		{
  			base.OnFontChanged (e);
  			CreateLinkPieces ();
  		}
  
  		protected override void OnGotFocus (EventArgs e)
  		{
  			base.OnGotFocus (e);
  			
  			// And yes it can actually be null..... arghh..
  			if (sorted_links == null)
  				return;
  
  			// Set focus to the first enabled link piece
  			if (focused_index == -1) {
  				if ((Control.ModifierKeys & Keys.Shift) == 0) {
  					for (int i = 0; i < sorted_links.Length; i ++) {
  						if (sorted_links[i].Enabled) {
  							focused_index = i;
  							break;
  						}
  					}
  				} else {
  					if (focused_index == -1)
  						focused_index = sorted_links.Length;
  
  					for (int n = focused_index - 1; n >= 0; n--) {
  						if (sorted_links[n].Enabled) {
  							sorted_links[n].Focused = true;
  							focused_index = n;
  							return;
  						}
  					}
  				}
  			}
  
  			if (focused_index != -1)
  				sorted_links[focused_index].Focused = true;
  		}
  
  		protected override void OnKeyDown (KeyEventArgs e)
  		{
  			if (e.KeyCode == Keys.Return) {
  				if (focused_index != -1)
  					OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
  			}
  
  			base.OnKeyDown(e);
  		}
  
  		protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
  		{
  			LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
  			if (eh != null)
  				eh (this, e);
  		}
  
  		protected override void OnLostFocus (EventArgs e)
  		{
  			base.OnLostFocus (e);
  			
  			// Clean focus in link pieces
  			if (focused_index != -1)
  				sorted_links[focused_index].Focused = false;
  		}
  
  		protected override void OnMouseDown (MouseEventArgs e)
  		{
  			if (!Enabled) return;
  
  			base.OnMouseDown (e);
  
  			for (int i = 0; i < sorted_links.Length; i ++) {
  				if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
  					sorted_links[i].Active = true;
  					if (focused_index != -1)
  						sorted_links[focused_index].Focused = false;
  					active_link = sorted_links[i];
  					focused_index = i;
  					sorted_links[focused_index].Focused = true;
  					break;
  				}
  			}
  		}
  
  		protected override void OnMouseLeave(EventArgs e)
  		{
  			if (!Enabled) return;
  			base.OnMouseLeave (e);
  			UpdateHover (null);
  		}
  
  		protected override void OnPaddingChanged (EventArgs e)
  		{
  			base.OnPaddingChanged (e);
  		}
  
  		private void UpdateHover (Link link)
  		{
  			if (link == hovered_link)
  				return;
  
  			if (hovered_link != null)
  				hovered_link.Hovered = false;
  
  			hovered_link = link;
  
  			if (hovered_link != null)
  				hovered_link.Hovered = true;
  
  			Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
  
  			/* XXX this shouldn't be here.  the
  			 * Link.Invalidate machinery should be enough,
  			 * but it seems the piece regions don't
  			 * contain the area with the underline.  this
  			 * can be seen easily when you click on a link
  			 * and the focus rectangle shows up (it's too
  			 * far up), and also the bottom few pixels of
  			 * a linklabel aren't active when it comes to
  			 * hovering */
  			Invalidate ();
  		}
  
  		protected override void OnMouseMove (MouseEventArgs e)
  		{
  			UpdateHover (PointInLink (e.X, e.Y));
  			base.OnMouseMove (e);
  		}
  
  		protected override void OnMouseUp (MouseEventArgs e)
  		{
  			if (!Enabled) return;
  
  			base.OnMouseUp (e);
  
  			if (active_link == null)
  				return;
  
  			Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
  
  			active_link.Active = false;
  			active_link = null;
  
  			if (clicked_link != null)
  				OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link, e.Button));
  		}
  
  		protected override void OnPaint (PaintEventArgs e)
  		{
  			// We need to invoke paintbackground because control is opaque
  			// and can have transparent colors.
  			base.InvokePaintBackground (this, e);
  			
  			ThemeElements.LinkLabelPainter.Draw (e.Graphics, e.ClipRectangle, this);
  			// Do not call base.OnPaint since it's the Label class 
  		}
  
  		protected override void OnPaintBackground (PaintEventArgs e)
  		{
  			base.OnPaintBackground (e);
  		}
  
  		protected override void OnTextAlignChanged (EventArgs e)
  		{
  			CreateLinkPieces ();
  			base.OnTextAlignChanged (e);
  		}
  
  		protected override void OnTextChanged (EventArgs e)
  		{
  			CreateLinkPieces ();
  			base.OnTextChanged (e);
  		}
  		
  		protected Link PointInLink (int x, int y)
  		{
  			for (int i = 0; i < sorted_links.Length; i ++)
  				if (sorted_links[i].Contains (x, y))
  					return sorted_links[i];
  
  			return null;
  		}
  
  		protected override bool ProcessDialogKey (Keys keyData)
  		{
  			if ((keyData & Keys.KeyCode) ==  Keys.Tab) {
  				Select (true, (keyData & Keys.Shift) == 0);
  				return true;
  			}
  			return base.ProcessDialogKey (keyData);
  		}
  
  		protected override void Select (bool directed, bool forward)
  		{
  			if (directed) {
  				if (focused_index != -1) {
  					sorted_links[focused_index].Focused = false;
  					focused_index = -1;
  				}
  
  				if (forward) {
  					for (int n = focused_index + 1; n < sorted_links.Length; n++) {
  						if (sorted_links[n].Enabled) {
  							sorted_links[n].Focused = true;
  							focused_index = n;
  							base.Select (directed, forward);
  							return;
  						}
  					}
  				} else {
  					if (focused_index == -1)
  						focused_index = sorted_links.Length;
  
  					for (int n = focused_index - 1; n >= 0; n--) {
  						if (sorted_links[n].Enabled) {
  							sorted_links[n].Focused = true;
  							focused_index = n;
  							base.Select (directed, forward);
  							return;
  						}
  					}
  				}
  
  				focused_index = -1;
  
  				if (Parent != null)
  					Parent.SelectNextControl (this, forward, false, true, true);
  			}
  		}
  
  		protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
  		{
  			base.SetBoundsCore (x, y, width, height, specified);
  			CreateLinkPieces();
  		}
  
  		protected override void WndProc (ref Message msg)
  		{
  			base.WndProc (ref msg);
  		}
  
  		#endregion //Public Methods
  
  		#region Private Methods
  
  		private ArrayList CreatePiecesFromText (int start, int len, Link link)
  		{
  			ArrayList rv = new ArrayList ();
  
  
gt: 			if (start + len > Text.Length)