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")]
43 [ClassInterface (ClassInterfaceType.AutoDispatch)]
45 [ToolboxItem ("System.Windows.Forms.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)]
47 public class LinkLabel : Label, IButtonControl
49 /* Encapsulates a piece of text (regular or link)*/
55 public LinkLabel.Link link; // Empty link indicates regular text
58 public Piece (int start, int length, string text, Link link)
67 private Color active_link_color;
68 private Color disabled_link_color;
69 private Color link_color;
70 private Color visited_color;
71 private LinkArea link_area;
72 private LinkBehavior link_behavior;
73 private LinkCollection link_collection;
74 private ArrayList links = new ArrayList();
75 internal Link[] sorted_links;
76 private bool link_visited;
77 internal Piece[] pieces;
78 private Cursor override_cursor;
79 private DialogResult dialog_result;
81 private Link active_link;
82 private Link hovered_link;
83 /* this is an index instead of a Link because we have
84 * to search through sorted links for the new one */
85 private int focused_index;
88 static object LinkClickedEvent = new object ();
90 public event LinkLabelLinkClickedEventHandler LinkClicked {
91 add { Events.AddHandler (LinkClickedEvent, value); }
92 remove { Events.RemoveHandler (LinkClickedEvent, value); }
97 [EditorBrowsable (EditorBrowsableState.Always)]
98 public new event EventHandler TabStopChanged {
99 add { base.TabStopChanged += value; }
100 remove { base.TabStopChanged -= value; }
107 LinkArea = new LinkArea (0, -1);
108 link_behavior = LinkBehavior.SystemDefault;
109 link_visited = false;
113 string_format.FormatFlags |= StringFormatFlags.NoClip;
115 ActiveLinkColor = Color.Red;
116 DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
117 LinkColor = Color.FromArgb (255, 0, 0, 255);
118 VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
119 SetStyle (ControlStyles.Selectable, false);
120 SetStyle (ControlStyles.ResizeRedraw |
121 ControlStyles.UserPaint |
122 ControlStyles.AllPaintingInWmPaint |
123 ControlStyles.SupportsTransparentBackColor |
126 | ControlStyles.OptimizedDoubleBuffer
128 | ControlStyles.DoubleBuffer
134 #region Public Properties
136 public Color ActiveLinkColor {
137 get { return active_link_color; }
139 if (active_link_color == value)
142 active_link_color = value;
147 public Color DisabledLinkColor {
149 get { return disabled_link_color; }
151 if (disabled_link_color == value)
154 disabled_link_color = value;
159 public Color LinkColor {
160 get { return link_color; }
162 if (link_color == value)
170 public Color VisitedLinkColor {
171 get { return visited_color;}
173 if (visited_color == value)
176 visited_color = value;
182 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
184 [RefreshProperties (RefreshProperties.Repaint)]
186 public LinkArea LinkArea {
187 get { return link_area;}
190 if (value.Start <0 || value.Length < -1)
191 throw new ArgumentException ();
195 if (!value.IsEmpty) {
196 Links.Add (value.Start, value.Length);
204 [DefaultValue (LinkBehavior.SystemDefault)]
205 public LinkBehavior LinkBehavior {
207 get { return link_behavior;}
209 if (link_behavior == value)
212 link_behavior = value;
219 [EditorBrowsable (EditorBrowsableState.Advanced)]
221 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
222 public LinkLabel.LinkCollection Links {
224 if (link_collection == null)
225 link_collection = new LinkCollection (this);
226 return link_collection;
230 [DefaultValue (false)]
231 public bool LinkVisited {
232 get { return link_visited;}
234 if (link_visited == value)
237 link_visited = value;
242 protected Cursor OverrideCursor {
244 if (override_cursor == null)
245 override_cursor = Cursors.Hand;
246 return override_cursor;
248 set { override_cursor = value; }
251 [RefreshProperties(RefreshProperties.Repaint)]
252 public override string Text {
253 get { return base.Text; }
255 if (base.Text == value)
265 [EditorBrowsable (EditorBrowsableState.Never)]
266 public new FlatStyle FlatStyle {
267 get { return base.FlatStyle; }
269 if (base.FlatStyle == value)
272 base.FlatStyle = value;
276 [RefreshProperties (RefreshProperties.Repaint)]
277 public new Padding Padding {
278 get { return base.Padding; }
280 if (base.Padding == value)
283 base.Padding = value;
288 #endregion // Public Properties
290 DialogResult IButtonControl.DialogResult {
291 get { return dialog_result; }
292 set { dialog_result = value; }
296 void IButtonControl.NotifyDefault (bool value)
300 void IButtonControl.PerformClick ()
304 #region Public Methods
305 protected override AccessibleObject CreateAccessibilityInstance ()
307 return base.CreateAccessibilityInstance();
310 protected override void CreateHandle ()
312 base.CreateHandle ();
317 protected override void OnAutoSizeChanged (EventArgs e)
319 base.OnAutoSizeChanged (e);
323 protected override void OnEnabledChanged (EventArgs e)
325 base.OnEnabledChanged (e);
329 protected override void OnFontChanged (EventArgs e)
331 base.OnFontChanged (e);
335 protected override void OnGotFocus (EventArgs e)
339 // Set focus to the first enabled link piece
340 if (focused_index == -1) {
341 if ((Control.ModifierKeys & Keys.Shift) == 0) {
342 for (int i = 0; i < sorted_links.Length; i ++) {
343 if (sorted_links[i].Enabled) {
349 if (focused_index == -1)
350 focused_index = sorted_links.Length;
352 for (int n = focused_index - 1; n >= 0; n--) {
353 if (sorted_links[n].Enabled) {
354 sorted_links[n].Focused = true;
362 if (focused_index != -1)
363 sorted_links[focused_index].Focused = true;
366 protected override void OnKeyDown (KeyEventArgs e)
368 if (e.KeyCode == Keys.Return) {
369 if (focused_index != -1)
370 OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
376 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
378 LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
383 protected override void OnLostFocus (EventArgs e)
385 base.OnLostFocus (e);
387 // Clean focus in link pieces
388 if (focused_index != -1)
389 sorted_links[focused_index].Focused = false;
392 protected override void OnMouseDown (MouseEventArgs e)
394 if (!Enabled) return;
396 base.OnMouseDown (e);
398 for (int i = 0; i < sorted_links.Length; i ++) {
399 if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
400 sorted_links[i].Active = true;
401 if (focused_index != -1)
402 sorted_links[focused_index].Focused = false;
403 active_link = sorted_links[i];
405 sorted_links[focused_index].Focused = true;
411 protected override void OnMouseLeave(EventArgs e)
413 if (!Enabled) return;
414 base.OnMouseLeave (e);
419 protected override void OnPaddingChanged (EventArgs e)
421 base.OnPaddingChanged (e);
425 private void UpdateHover (Link link)
427 if (link == hovered_link)
430 if (hovered_link != null)
431 hovered_link.Hovered = false;
435 if (hovered_link != null)
436 hovered_link.Hovered = true;
438 Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
440 /* XXX this shouldn't be here. the
441 * Link.Invalidate machinery should be enough,
442 * but it seems the piece regions don't
443 * contain the area with the underline. this
444 * can be seen easily when you click on a link
445 * and the focus rectangle shows up (it's too
446 * far up), and also the bottom few pixels of
447 * a linklabel aren't active when it comes to
452 protected override void OnMouseMove (MouseEventArgs e)
454 UpdateHover (PointInLink (e.X, e.Y));
455 base.OnMouseMove (e);
458 protected override void OnMouseUp (MouseEventArgs e)
460 if (!Enabled) return;
464 if (active_link == null)
467 Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
469 active_link.Active = false;
472 if (clicked_link != null)
474 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link, e.Button));
476 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link));
480 protected override void OnPaint (PaintEventArgs e)
482 // We need to invoke paintbackground because control is opaque
483 // and can have transparent colors.
484 base.InvokePaintBackground (this, e);
486 ThemeElements.LinkLabelPainter.Draw (e.Graphics, e.ClipRectangle, this);
487 // Do not call base.OnPaint since it's the Label class
490 protected override void OnPaintBackground (PaintEventArgs e)
492 base.OnPaintBackground (e);
495 protected override void OnTextAlignChanged (EventArgs e)
498 base.OnTextAlignChanged (e);
501 protected override void OnTextChanged (EventArgs e)
504 base.OnTextChanged (e);
507 protected Link PointInLink (int x, int y)
509 for (int i = 0; i < sorted_links.Length; i ++)
510 if (sorted_links[i].Contains (x, y))
511 return sorted_links[i];
516 protected override bool ProcessDialogKey (Keys keyData)
518 if ((keyData & Keys.KeyCode) == Keys.Tab) {
519 Select (true, (keyData & Keys.Shift) == 0);
522 return base.ProcessDialogKey (keyData);
525 protected override void Select (bool directed, bool forward)
528 if (focused_index != -1) {
529 sorted_links[focused_index].Focused = false;
534 for (int n = focused_index + 1; n < sorted_links.Length; n++) {
535 if (sorted_links[n].Enabled) {
536 sorted_links[n].Focused = true;
538 base.Select (directed, forward);
543 if (focused_index == -1)
544 focused_index = sorted_links.Length;
546 for (int n = focused_index - 1; n >= 0; n--) {
547 if (sorted_links[n].Enabled) {
548 sorted_links[n].Focused = true;
550 base.Select (directed, forward);
559 Parent.SelectNextControl (this, forward, false, true, true);
563 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
565 base.SetBoundsCore (x, y, width, height, specified);
569 protected override void WndProc (ref Message msg)
571 base.WndProc (ref msg);
574 #endregion //Public Methods
576 #region Private Methods
578 private ArrayList CreatePiecesFromText (int start, int len, Link link)
580 ArrayList rv = new ArrayList ();
582 if (start + len > Text.Length)
583 len = Text.Length - start;
587 string t = Text.Substring (start, len);
590 for (int i = 0; i < t.Length; i ++) {
593 Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link);
600 Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link);
607 private void CreateLinkPieces ()
609 if (Text.Length == 0) {
610 SetStyle (ControlStyles.Selectable, false);
613 link_area.Length = 0;
617 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1)
618 Links[0].Length = Text.Length;
622 // Set the LinkArea values based on first link.
623 if (Links.Count > 0) {
624 link_area.Start = Links[0].Start;
625 link_area.Length = Links[0].Length;
628 link_area.Length = 0;
631 TabStop = (LinkArea.Length > 0);
632 SetStyle (ControlStyles.Selectable, TabStop);
634 /* don't bother doing the rest if our handle hasn't been created */
635 if (!IsHandleCreated)
638 ArrayList pieces_list = new ArrayList ();
642 for (int l = 0; l < sorted_links.Length; l ++) {
643 int new_link_start = sorted_links[l].Start;
645 if (new_link_start > current_end) {
646 /* create/push a piece
647 * containing the text between
648 * the previous/new link */
649 ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null);
650 pieces_list.AddRange (text_pieces);
653 /* now create a group of pieces for
654 * the new link (split up by \n's) */
655 ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]);
656 pieces_list.AddRange (link_pieces);
657 sorted_links[l].pieces.AddRange (link_pieces);
659 current_end = sorted_links[l].Start + sorted_links[l].Length;
661 if (current_end < Text.Length) {
662 ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null);
663 pieces_list.AddRange (text_pieces);
666 pieces = new Piece[pieces_list.Count];
667 pieces_list.CopyTo (pieces, 0);
669 CharacterRange[] ranges = new CharacterRange[pieces.Length];
671 for(int i = 0; i < pieces.Length; i++)
672 ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length);
674 string_format.SetMeasurableCharacterRanges (ranges);
676 Region[] regions = TextRenderer.MeasureCharacterRanges (Text,
677 ThemeEngine.Current.GetLinkFont (this),
681 for (int i = 0; i < pieces.Length; i ++)
682 pieces[i].region = regions[i];
687 private void SortLinks ()
689 if (sorted_links != null)
692 sorted_links = new Link [Links.Count];
693 ((ICollection)Links).CopyTo (sorted_links, 0);
695 Array.Sort (sorted_links, new LinkComparer ());
698 /* Check if the links overlap */
699 private void CheckLinks ()
705 for (int i = 0; i < sorted_links.Length; i++) {
706 if (sorted_links[i].Start < current_end)
707 throw new InvalidOperationException ("Overlapping link regions.");
708 current_end = sorted_links[i].Start + sorted_links[i].Length;
712 #endregion // Private Methods
715 // System.Windows.Forms.LinkLabel.Link
718 [TypeConverter (typeof (LinkConverter))]
722 private bool enabled;
724 private object linkData;
726 private bool visited;
727 private LinkLabel owner;
728 private bool hovered;
729 internal ArrayList pieces;
730 private bool focused;
733 private string description;
738 internal Link (LinkLabel owner)
746 pieces = new ArrayList ();
756 this.name = string.Empty;
757 this.pieces = new ArrayList ();
760 public Link (int start, int length) : this ()
763 this.length = length;
766 public Link (int start, int length, Object linkData) : this (start, length)
768 this.linkData = linkData;
772 #region Public Properties
774 public string Description {
775 get { return this.description; }
776 set { this.description = value; }
781 get { return this.name; }
782 set { this.name = value; }
786 [Localizable (false)]
787 [DefaultValue (null)]
788 [TypeConverter (typeof (StringConverter))]
790 get { return this.tag; }
791 set { this.tag = value; }
794 [DefaultValue (true)]
796 public bool Enabled {
797 get { return enabled; }
799 if (enabled != value)
809 return owner.Text.Length;
820 owner.CreateLinkPieces ();
825 [DefaultValue (null)]
827 public object LinkData {
828 get { return linkData; }
829 set { linkData = value; }
833 get { return start; }
840 owner.sorted_links = null;
841 owner.CreateLinkPieces ();
846 [DefaultValue (false)]
848 public bool Visited {
849 get { return visited; }
851 if (visited != value)
858 internal bool Hovered {
859 get { return hovered; }
861 if (hovered != value)
867 internal bool Focused {
868 get { return focused; }
870 if (focused != value)
876 internal bool Active {
877 get { return active; }
885 internal LinkLabel Owner {
886 set { owner = value; }
890 private void Invalidate ()
892 for (int i = 0; i < pieces.Count; i ++)
893 owner.Invalidate (((Piece)pieces[i]).region);
896 internal bool Contains (int x, int y)
898 foreach (Piece p in pieces) {
899 if (p.region.IsVisible (new Point (x,y)))
906 class LinkComparer : IComparer
908 public int Compare (object x, object y)
913 return l1.Start - l2.Start;
918 // System.Windows.Forms.LinkLabel.LinkCollection
920 public class LinkCollection : IList, ICollection, IEnumerable
922 private LinkLabel owner;
923 private bool links_added = false;
925 public LinkCollection (LinkLabel owner)
928 throw new ArgumentNullException ("owner");
935 get { return owner.links.Count; }
938 public bool IsReadOnly {
939 get { return false; }
942 public virtual LinkLabel.Link this[int index] {
944 if (index < 0 || index >= Count)
945 throw new ArgumentOutOfRangeException();
947 return (LinkLabel.Link) owner.links[index];
950 if (index < 0 || index >= Count)
951 throw new ArgumentOutOfRangeException();
953 owner.links[index] = value;
958 public virtual Link this[string key] {
960 if (string.IsNullOrEmpty (key))
963 foreach (Link l in owner.links)
964 if (string.Compare (l.Name, key, true) == 0)
978 /* remove the default 0,-1 link */
980 /* don't call Clear() here to save the additional CreateLinkPieces */
981 owner.links.Clear ();
984 int idx = owner.links.Add (value);
987 owner.sorted_links = null;
989 owner.CreateLinkPieces ();
994 public Link Add (int start, int length)
996 return Add (start, length, null);
999 internal bool IsDefault {
1002 && this[0].Start == 0
1003 && this[0].length == -1);
1007 public Link Add (int start, int length, object linkData)
1009 Link link = new Link (owner);
1010 link.Length = length;
1012 link.LinkData = linkData;
1014 int idx = Add (link);
1016 return (Link) owner.links[idx];
1019 public virtual void Clear ()
1021 owner.links.Clear();
1022 owner.sorted_links = null;
1023 owner.CreateLinkPieces ();
1026 public bool Contains (Link link)
1028 return owner.links.Contains (link);
1032 public virtual bool ContainsKey (string key)
1034 return !(this[key] == null);
1038 public IEnumerator GetEnumerator ()
1040 return owner.links.GetEnumerator ();
1043 public int IndexOf (Link link)
1045 return owner.links.IndexOf (link);
1049 public virtual int IndexOfKey (string key)
1051 if (string.IsNullOrEmpty (key))
1054 return IndexOf (this[key]);
1057 public bool LinksAdded {
1058 get { return this.links_added; }
1062 public void Remove (LinkLabel.Link value)
1064 owner.links.Remove (value);
1065 owner.sorted_links = null;
1066 owner.CreateLinkPieces ();
1070 public virtual void RemoveByKey (string key)
1076 public void RemoveAt (int index)
1079 throw new ArgumentOutOfRangeException ("Invalid value for array index");
1081 owner.links.Remove (owner.links[index]);
1082 owner.sorted_links = null;
1083 owner.CreateLinkPieces ();
1086 bool IList.IsFixedSize {
1090 object IList.this[int index] {
1091 get { return owner.links[index]; }
1092 set { owner.links[index] = value; }
1095 object ICollection.SyncRoot {
1099 bool ICollection.IsSynchronized {
1103 void ICollection.CopyTo (Array dest, int index)
1105 owner.links.CopyTo (dest, index);
1108 int IList.Add (object value)
1110 int idx = owner.links.Add (value);
1111 owner.sorted_links = null;
1112 owner.CheckLinks ();
1113 owner.CreateLinkPieces ();
1117 bool IList.Contains (object link)
1119 return Contains ((Link) link);
1122 int IList.IndexOf (object link)
1124 return owner.links.IndexOf (link);
1127 void IList.Insert (int index, object value)
1129 owner.links.Insert (index, value);
1130 owner.sorted_links = null;
1131 owner.CheckLinks ();
1132 owner.CreateLinkPieces ();
1135 void IList.Remove (object value)
1137 Remove ((Link) value);
1142 [RefreshProperties (RefreshProperties.Repaint)]
1143 public new bool UseCompatibleTextRendering {
1145 return use_compatible_text_rendering;
1148 use_compatible_text_rendering = value;