1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2005 Novell, Inc.
23 // Jordi Mas i Hernandez, jordi@ximian.com
24 // Chris Toshok <toshok@ximian.com>
25 // Everaldo Canuto <ecanuto@novell.com>
28 // Daniel Carrera, dcarrera@math.toronto.edu (stubbed out)
29 // Jaak Simm (jaaksimm@firm.ee) (stubbed out)
32 using System.ComponentModel;
33 using System.Collections;
35 using System.Drawing.Drawing2D;
36 using System.Runtime.InteropServices;
37 using System.Windows.Forms.Theming;
39 namespace System.Windows.Forms
41 [DefaultEvent("LinkClicked")]
42 [ClassInterface (ClassInterfaceType.AutoDispatch)]
44 [ToolboxItem ("System.Windows.Forms.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)]
45 public class LinkLabel : Label, IButtonControl
47 /* Encapsulates a piece of text (regular or link)*/
53 public LinkLabel.Link link; // Empty link indicates regular text
56 public Piece (int start, int length, string text, Link link)
65 private Color active_link_color;
66 private Color disabled_link_color;
67 private Color link_color;
68 private Color visited_color;
69 private LinkArea link_area;
70 private LinkBehavior link_behavior;
71 private LinkCollection link_collection;
72 private ArrayList links = new ArrayList();
73 internal Link[] sorted_links;
74 private bool link_visited;
75 internal Piece[] pieces;
76 private Cursor override_cursor;
77 private DialogResult dialog_result;
79 private Link active_link;
80 private Link hovered_link;
81 /* this is an index instead of a Link because we have
82 * to search through sorted links for the new one */
83 private int focused_index;
86 static object LinkClickedEvent = new object ();
88 public event LinkLabelLinkClickedEventHandler LinkClicked {
89 add { Events.AddHandler (LinkClickedEvent, value); }
90 remove { Events.RemoveHandler (LinkClickedEvent, value); }
94 [EditorBrowsable (EditorBrowsableState.Always)]
95 public new event EventHandler TabStopChanged {
96 add { base.TabStopChanged += value; }
97 remove { base.TabStopChanged -= value; }
103 LinkArea = new LinkArea (0, -1);
104 link_behavior = LinkBehavior.SystemDefault;
105 link_visited = false;
109 string_format.FormatFlags |= StringFormatFlags.NoClip;
111 ActiveLinkColor = Color.Red;
112 DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
113 LinkColor = Color.FromArgb (255, 0, 0, 255);
114 VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
115 SetStyle (ControlStyles.Selectable, false);
116 SetStyle (ControlStyles.ResizeRedraw |
117 ControlStyles.UserPaint |
118 ControlStyles.AllPaintingInWmPaint |
119 ControlStyles.SupportsTransparentBackColor |
120 ControlStyles.Opaque |
121 ControlStyles.OptimizedDoubleBuffer
126 #region Public Properties
128 public Color ActiveLinkColor {
129 get { return active_link_color; }
131 if (active_link_color == value)
134 active_link_color = value;
139 public Color DisabledLinkColor {
141 get { return disabled_link_color; }
143 if (disabled_link_color == value)
146 disabled_link_color = value;
151 public Color LinkColor {
152 get { return link_color; }
154 if (link_color == value)
162 public Color VisitedLinkColor {
163 get { return visited_color;}
165 if (visited_color == value)
168 visited_color = value;
174 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
175 [RefreshProperties (RefreshProperties.Repaint)]
176 public LinkArea LinkArea {
177 get { return link_area;}
180 if (value.Start <0 || value.Length < -1)
181 throw new ArgumentException ();
185 if (!value.IsEmpty) {
186 Links.Add (value.Start, value.Length);
194 [DefaultValue (LinkBehavior.SystemDefault)]
195 public LinkBehavior LinkBehavior {
197 get { return link_behavior;}
199 if (link_behavior == value)
202 link_behavior = value;
208 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
209 public LinkLabel.LinkCollection Links {
211 if (link_collection == null)
212 link_collection = new LinkCollection (this);
213 return link_collection;
217 [DefaultValue (false)]
218 public bool LinkVisited {
219 get { return link_visited;}
221 if (link_visited == value)
224 link_visited = value;
229 protected Cursor OverrideCursor {
231 if (override_cursor == null)
232 override_cursor = Cursors.Hand;
233 return override_cursor;
235 set { override_cursor = value; }
238 [RefreshProperties(RefreshProperties.Repaint)]
239 public override string Text {
240 get { return base.Text; }
242 if (base.Text == value)
251 [EditorBrowsable (EditorBrowsableState.Never)]
252 public new FlatStyle FlatStyle {
253 get { return base.FlatStyle; }
255 if (base.FlatStyle == value)
258 base.FlatStyle = value;
262 [RefreshProperties (RefreshProperties.Repaint)]
263 public new Padding Padding {
264 get { return base.Padding; }
266 if (base.Padding == value)
269 base.Padding = value;
274 #endregion // Public Properties
276 DialogResult IButtonControl.DialogResult {
277 get { return dialog_result; }
278 set { dialog_result = value; }
282 void IButtonControl.NotifyDefault (bool value)
286 void IButtonControl.PerformClick ()
290 #region Public Methods
291 protected override AccessibleObject CreateAccessibilityInstance ()
293 return base.CreateAccessibilityInstance();
296 protected override void CreateHandle ()
298 base.CreateHandle ();
302 protected override void OnAutoSizeChanged (EventArgs e)
304 base.OnAutoSizeChanged (e);
307 protected override void OnEnabledChanged (EventArgs e)
309 base.OnEnabledChanged (e);
313 protected override void OnFontChanged (EventArgs e)
315 base.OnFontChanged (e);
319 protected override void OnGotFocus (EventArgs e)
323 // And yes it can actually be null..... arghh..
324 if (sorted_links == null)
327 // Set focus to the first enabled link piece
328 if (focused_index == -1) {
329 if ((Control.ModifierKeys & Keys.Shift) == 0) {
330 for (int i = 0; i < sorted_links.Length; i ++) {
331 if (sorted_links[i].Enabled) {
337 if (focused_index == -1)
338 focused_index = sorted_links.Length;
340 for (int n = focused_index - 1; n >= 0; n--) {
341 if (sorted_links[n].Enabled) {
342 sorted_links[n].Focused = true;
350 if (focused_index != -1)
351 sorted_links[focused_index].Focused = true;
354 protected override void OnKeyDown (KeyEventArgs e)
356 if (e.KeyCode == Keys.Return) {
357 if (focused_index != -1)
358 OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
364 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
366 LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
371 protected override void OnLostFocus (EventArgs e)
373 base.OnLostFocus (e);
375 // Clean focus in link pieces
376 if (focused_index != -1)
377 sorted_links[focused_index].Focused = false;
380 protected override void OnMouseDown (MouseEventArgs e)
382 if (!Enabled) return;
384 base.OnMouseDown (e);
386 for (int i = 0; i < sorted_links.Length; i ++) {
387 if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
388 sorted_links[i].Active = true;
389 if (focused_index != -1)
390 sorted_links[focused_index].Focused = false;
391 active_link = sorted_links[i];
393 sorted_links[focused_index].Focused = true;
399 protected override void OnMouseLeave(EventArgs e)
401 if (!Enabled) return;
402 base.OnMouseLeave (e);
406 protected override void OnPaddingChanged (EventArgs e)
408 base.OnPaddingChanged (e);
411 private void UpdateHover (Link link)
413 if (link == hovered_link)
416 if (hovered_link != null)
417 hovered_link.Hovered = false;
421 if (hovered_link != null)
422 hovered_link.Hovered = true;
424 Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
426 /* XXX this shouldn't be here. the
427 * Link.Invalidate machinery should be enough,
428 * but it seems the piece regions don't
429 * contain the area with the underline. this
430 * can be seen easily when you click on a link
431 * and the focus rectangle shows up (it's too
432 * far up), and also the bottom few pixels of
433 * a linklabel aren't active when it comes to
438 protected override void OnMouseMove (MouseEventArgs e)
440 UpdateHover (PointInLink (e.X, e.Y));
441 base.OnMouseMove (e);
444 protected override void OnMouseUp (MouseEventArgs e)
446 if (!Enabled) return;
450 if (active_link == null)
453 Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
455 active_link.Active = false;
458 if (clicked_link != null)
459 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link, e.Button));
462 protected override void OnClick (EventArgs e)
464 if (active_link != null && this.Capture) {
465 this.Capture = false;
470 protected override void OnPaint (PaintEventArgs e)
472 // We need to invoke paintbackground because control is opaque
473 // and can have transparent colors.
474 base.InvokePaintBackground (this, e);
476 ThemeElements.LinkLabelPainter.Draw (e.Graphics, e.ClipRectangle, this);
477 // Do not call base.OnPaint since it's the Label class
480 protected override void OnPaintBackground (PaintEventArgs e)
482 base.OnPaintBackground (e);
485 protected override void OnTextAlignChanged (EventArgs e)
488 base.OnTextAlignChanged (e);
491 protected override void OnTextChanged (EventArgs e)
494 base.OnTextChanged (e);
497 protected Link PointInLink (int x, int y)
499 for (int i = 0; i < sorted_links.Length; i ++)
500 if (sorted_links[i].Contains (x, y))
501 return sorted_links[i];
506 protected override bool ProcessDialogKey (Keys keyData)
508 if ((keyData & Keys.KeyCode) == Keys.Tab) {
509 Select (true, (keyData & Keys.Shift) == 0);
512 return base.ProcessDialogKey (keyData);
515 protected override void Select (bool directed, bool forward)
518 if (focused_index != -1) {
519 sorted_links[focused_index].Focused = false;
524 for (int n = focused_index + 1; n < sorted_links.Length; n++) {
525 if (sorted_links[n].Enabled) {
526 sorted_links[n].Focused = true;
528 base.Select (directed, forward);
533 if (focused_index == -1)
534 focused_index = sorted_links.Length;
536 for (int n = focused_index - 1; n >= 0; n--) {
537 if (sorted_links[n].Enabled) {
538 sorted_links[n].Focused = true;
540 base.Select (directed, forward);
549 Parent.SelectNextControl (this, forward, false, true, true);
553 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
555 base.SetBoundsCore (x, y, width, height, specified);
559 protected override void WndProc (ref Message msg)
561 base.WndProc (ref msg);
564 #endregion //Public Methods
566 #region Private Methods
568 private ArrayList CreatePiecesFromText (int start, int len, Link link)
570 ArrayList rv = new ArrayList ();
572 if (start + len > Text.Length)
573 len = Text.Length - start;
577 string t = Text.Substring (start, len);
580 for (int i = 0; i < t.Length; i ++) {
583 Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link);
590 Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link);
597 private void CreateLinkPieces ()
599 if (Text.Length == 0) {
600 SetStyle (ControlStyles.Selectable, false);
603 link_area.Length = 0;
607 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1)
608 Links[0].Length = Text.Length;
612 // Set the LinkArea values based on first link.
613 if (Links.Count > 0) {
614 link_area.Start = Links[0].Start;
615 link_area.Length = Links[0].Length;
618 link_area.Length = 0;
621 TabStop = (LinkArea.Length > 0);
622 SetStyle (ControlStyles.Selectable, TabStop);
624 /* don't bother doing the rest if our handle hasn't been created */
625 if (!IsHandleCreated)
628 ArrayList pieces_list = new ArrayList ();
632 for (int l = 0; l < sorted_links.Length; l ++) {
633 int new_link_start = sorted_links[l].Start;
635 if (new_link_start > current_end) {
636 /* create/push a piece
637 * containing the text between
638 * the previous/new link */
639 ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null);
640 pieces_list.AddRange (text_pieces);
643 /* now create a group of pieces for
644 * the new link (split up by \n's) */
645 ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]);
646 pieces_list.AddRange (link_pieces);
647 sorted_links[l].pieces.AddRange (link_pieces);
649 current_end = sorted_links[l].Start + sorted_links[l].Length;
651 if (current_end < Text.Length) {
652 ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null);
653 pieces_list.AddRange (text_pieces);
656 pieces = new Piece[pieces_list.Count];
657 pieces_list.CopyTo (pieces, 0);
659 CharacterRange[] ranges = new CharacterRange[pieces.Length];
661 for(int i = 0; i < pieces.Length; i++)
662 ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length);
664 string_format.SetMeasurableCharacterRanges (ranges);
666 Region[] regions = TextRenderer.MeasureCharacterRanges (Text,
667 ThemeEngine.Current.GetLinkFont (this),
668 PaddingClientRectangle,
671 for (int i = 0; i < pieces.Length; i ++) {
672 pieces[i].region = regions[i];
673 pieces[i].region.Translate (Padding.Left, Padding.Top);
679 private void SortLinks ()
681 if (sorted_links != null)
684 sorted_links = new Link [Links.Count];
685 ((ICollection)Links).CopyTo (sorted_links, 0);
687 Array.Sort (sorted_links, new LinkComparer ());
690 /* Check if the links overlap */
691 private void CheckLinks ()
697 for (int i = 0; i < sorted_links.Length; i++) {
698 if (sorted_links[i].Start < current_end)
699 throw new InvalidOperationException ("Overlapping link regions.");
700 current_end = sorted_links[i].Start + sorted_links[i].Length;
704 #endregion // Private Methods
707 // System.Windows.Forms.LinkLabel.Link
709 [TypeConverter (typeof (LinkConverter))]
712 private bool enabled;
714 private object linkData;
716 private bool visited;
717 private LinkLabel owner;
718 private bool hovered;
719 internal ArrayList pieces;
720 private bool focused;
722 private string description;
726 internal Link (LinkLabel owner)
734 pieces = new ArrayList ();
741 this.name = string.Empty;
742 this.pieces = new ArrayList ();
745 public Link (int start, int length) : this ()
748 this.length = length;
751 public Link (int start, int length, Object linkData) : this (start, length)
753 this.linkData = linkData;
756 #region Public Properties
757 public string Description {
758 get { return this.description; }
759 set { this.description = value; }
764 get { return this.name; }
765 set { this.name = value; }
769 [Localizable (false)]
770 [DefaultValue (null)]
771 [TypeConverter (typeof (StringConverter))]
773 get { return this.tag; }
774 set { this.tag = value; }
777 [DefaultValue (true)]
778 public bool Enabled {
779 get { return enabled; }
781 if (enabled != value)
791 return owner.Text.Length;
802 owner.CreateLinkPieces ();
806 [DefaultValue (null)]
807 public object LinkData {
808 get { return linkData; }
809 set { linkData = value; }
813 get { return start; }
820 owner.sorted_links = null;
821 owner.CreateLinkPieces ();
825 [DefaultValue (false)]
826 public bool Visited {
827 get { return visited; }
829 if (visited != value)
836 internal bool Hovered {
837 get { return hovered; }
839 if (hovered != value)
845 internal bool Focused {
846 get { return focused; }
848 if (focused != value)
854 internal bool Active {
855 get { return active; }
863 internal LinkLabel Owner {
864 set { owner = value; }
868 private void Invalidate ()
870 for (int i = 0; i < pieces.Count; i ++)
871 owner.Invalidate (((Piece)pieces[i]).region);
874 internal bool Contains (int x, int y)
876 foreach (Piece p in pieces) {
877 if (p.region.IsVisible (new Point (x,y)))
884 class LinkComparer : IComparer
886 public int Compare (object x, object y)
891 return l1.Start - l2.Start;
896 // System.Windows.Forms.LinkLabel.LinkCollection
898 public class LinkCollection : IList, ICollection, IEnumerable
900 private LinkLabel owner;
901 private bool links_added;
903 public LinkCollection (LinkLabel owner)
906 throw new ArgumentNullException ("owner");
913 get { return owner.links.Count; }
916 public bool IsReadOnly {
917 get { return false; }
920 public virtual LinkLabel.Link this[int index] {
922 if (index < 0 || index >= Count)
923 throw new ArgumentOutOfRangeException();
925 return (LinkLabel.Link) owner.links[index];
928 if (index < 0 || index >= Count)
929 throw new ArgumentOutOfRangeException();
931 owner.links[index] = value;
935 public virtual Link this[string key] {
937 if (string.IsNullOrEmpty (key))
940 foreach (Link l in owner.links)
941 if (string.Compare (l.Name, key, true) == 0)
948 public int Add (Link value)
951 /* remove the default 0,-1 link */
953 /* don't call Clear() here to save the additional CreateLinkPieces */
954 owner.links.Clear ();
957 int idx = owner.links.Add (value);
960 owner.sorted_links = null;
962 owner.CreateLinkPieces ();
967 public Link Add (int start, int length)
969 return Add (start, length, null);
972 internal bool IsDefault {
975 && this[0].Start == 0
976 && this[0].length == -1);
980 public Link Add (int start, int length, object linkData)
982 Link link = new Link (owner);
983 link.Length = length;
985 link.LinkData = linkData;
987 int idx = Add (link);
989 return (Link) owner.links[idx];
992 public virtual void Clear ()
995 owner.sorted_links = null;
996 owner.CreateLinkPieces ();
999 public bool Contains (Link link)
1001 return owner.links.Contains (link);
1004 public virtual bool ContainsKey (string key)
1006 return !(this[key] == null);
1009 public IEnumerator GetEnumerator ()
1011 return owner.links.GetEnumerator ();
1014 public int IndexOf (Link link)
1016 return owner.links.IndexOf (link);
1019 public virtual int IndexOfKey (string key)
1021 if (string.IsNullOrEmpty (key))
1024 return IndexOf (this[key]);
1027 public bool LinksAdded {
1028 get { return this.links_added; }
1031 public void Remove (LinkLabel.Link value)
1033 owner.links.Remove (value);
1034 owner.sorted_links = null;
1035 owner.CreateLinkPieces ();
1038 public virtual void RemoveByKey (string key)
1043 public void RemoveAt (int index)
1046 throw new ArgumentOutOfRangeException ("Invalid value for array index");
1048 owner.links.Remove (owner.links[index]);
1049 owner.sorted_links = null;
1050 owner.CreateLinkPieces ();
1053 bool IList.IsFixedSize {
1057 object IList.this[int index] {
1058 get { return owner.links[index]; }
1059 set { owner.links[index] = value; }
1062 object ICollection.SyncRoot {
1066 bool ICollection.IsSynchronized {
1070 void ICollection.CopyTo (Array dest, int index)
1072 owner.links.CopyTo (dest, index);
1075 int IList.Add (object value)
1077 int idx = owner.links.Add (value);
1078 owner.sorted_links = null;
1079 owner.CheckLinks ();
1080 owner.CreateLinkPieces ();
1084 bool IList.Contains (object link)
1086 return Contains ((Link) link);
1089 int IList.IndexOf (object link)
1091 return owner.links.IndexOf (link);
1094 void IList.Insert (int index, object value)
1096 owner.links.Insert (index, value);
1097 owner.sorted_links = null;
1098 owner.CheckLinks ();
1099 owner.CreateLinkPieces ();
1102 void IList.Remove (object value)
1104 Remove ((Link) value);
1108 [RefreshProperties (RefreshProperties.Repaint)]
1109 public new bool UseCompatibleTextRendering {
1111 return use_compatible_text_rendering;
1114 use_compatible_text_rendering = value;