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>
27 // Daniel Carrera, dcarrera@math.toronto.edu (stubbed out)
28 // Jaak Simm (jaaksimm@firm.ee) (stubbed out)
31 using System.ComponentModel;
32 using System.Collections;
34 using System.Drawing.Drawing2D;
35 using System.Runtime.InteropServices;
37 namespace System.Windows.Forms
39 [DefaultEvent("LinkClicked")]
41 [ClassInterface (ClassInterfaceType.AutoDispatch)]
44 public class LinkLabel : Label, IButtonControl
46 /* Encapsulates a piece of text (regular or link)*/
52 public LinkLabel.Link link; // Empty link indicates regular text
55 public Piece (int start, int length, string text, Link link)
64 private Color active_link_color;
65 private Color disabled_link_color;
66 private Color link_color;
67 private Color visited_color;
68 private LinkArea link_area;
69 private LinkBehavior link_behavior;
70 private LinkCollection link_collection;
71 internal Link[] sorted_links;
72 private bool link_visited;
73 internal Piece[] pieces;
74 internal Font link_font;
75 private Cursor override_cursor;
76 private DialogResult dialog_result;
78 private Link active_link;
79 private Link hovered_link;
80 /* this is an index instead of a Link because we have
81 * to search through sorted links for the new one */
82 private int focused_index;
85 static object LinkClickedEvent = new object ();
87 public event LinkLabelLinkClickedEventHandler LinkClicked {
88 add { Events.AddHandler (LinkClickedEvent, value); }
89 remove { Events.RemoveHandler (LinkClickedEvent, value); }
95 LinkArea = new LinkArea (0, -1);
96 link_behavior = LinkBehavior.SystemDefault;
102 ActiveLinkColor = Color.Red;
103 DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
104 LinkColor = Color.FromArgb (255, 0, 0, 255);
105 VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
106 SetStyle (ControlStyles.Opaque, true);
107 SetStyle (ControlStyles.Selectable, false);
110 #region Public Properties
112 public Color ActiveLinkColor {
113 get { return active_link_color; }
115 if (active_link_color == value)
118 active_link_color = value;
123 public Color DisabledLinkColor {
125 get { return disabled_link_color; }
127 if (disabled_link_color == value)
130 disabled_link_color = value;
135 public Color LinkColor {
136 get { return link_color; }
138 if (link_color == value)
146 public Color VisitedLinkColor {
147 get { return visited_color;}
149 if (visited_color == value)
152 visited_color = value;
158 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
160 [RefreshProperties (RefreshProperties.Repaint)]
162 public LinkArea LinkArea {
163 get { return link_area;}
166 if (value.Start <0 || value.Length < -1)
167 throw new ArgumentException ();
172 if (!value.IsEmpty) {
173 Links.Add (value.Start, value.Length);
181 [DefaultValue (LinkBehavior.SystemDefault)]
182 public LinkBehavior LinkBehavior {
184 get { return link_behavior;}
186 if (link_behavior == value)
189 link_behavior = value;
196 [EditorBrowsable (EditorBrowsableState.Advanced)]
198 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
199 public LinkLabel.LinkCollection Links {
201 if (link_collection == null)
202 link_collection = new LinkCollection (this);
204 return link_collection;
208 [DefaultValue (false)]
209 public bool LinkVisited {
210 get { return link_visited;}
212 if (link_visited == value)
215 link_visited = value;
220 protected Cursor OverrideCursor {
222 if (override_cursor == null)
223 override_cursor = Cursors.Hand;
224 return override_cursor;
226 set { override_cursor = value; }
229 [RefreshProperties(RefreshProperties.Repaint)]
230 public override string Text {
231 get { return base.Text; }
233 if (base.Text == value)
241 #endregion // Public Properties
243 DialogResult IButtonControl.DialogResult {
244 get { return dialog_result; }
245 set { dialog_result = value; }
249 void IButtonControl.NotifyDefault (bool value)
253 void IButtonControl.PerformClick ()
257 #region Public Methods
258 protected override AccessibleObject CreateAccessibilityInstance ()
260 return base.CreateAccessibilityInstance();
263 protected override void CreateHandle ()
265 base.CreateHandle ();
270 protected override void OnEnabledChanged (EventArgs e)
272 base.OnEnabledChanged (e);
276 protected override void OnFontChanged (EventArgs e)
278 base.OnFontChanged (e);
283 protected override void OnGotFocus (EventArgs e)
287 // Set focus to the first enabled link piece
288 if (focused_index == -1) {
289 if ((Control.ModifierKeys & Keys.Shift) == 0) {
290 for (int i = 0; i < sorted_links.Length; i ++) {
291 if (sorted_links[i].Enabled) {
297 if (focused_index == -1)
298 focused_index = sorted_links.Length;
300 for (int n = focused_index - 1; n >= 0; n--) {
301 if (sorted_links[n].Enabled) {
302 sorted_links[n].Focused = true;
310 if (focused_index != -1)
311 sorted_links[focused_index].Focused = true;
314 protected override void OnKeyDown (KeyEventArgs e)
316 if (e.KeyCode == Keys.Return) {
317 if (focused_index != -1)
318 OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
324 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
326 LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
331 protected override void OnLostFocus (EventArgs e)
333 base.OnLostFocus (e);
335 // Clean focus in link pieces
336 if (focused_index != -1)
337 sorted_links[focused_index].Focused = false;
340 protected override void OnMouseDown (MouseEventArgs e)
342 if (!Enabled) return;
344 base.OnMouseDown (e);
346 for (int i = 0; i < sorted_links.Length; i ++) {
347 if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
348 sorted_links[i].Active = true;
349 if (focused_index != -1)
350 sorted_links[focused_index].Focused = false;
351 active_link = sorted_links[i];
353 sorted_links[focused_index].Focused = true;
359 protected override void OnMouseLeave(EventArgs e)
361 if (!Enabled) return;
362 base.OnMouseLeave (e);
366 private void UpdateHover (Link link)
368 if (link == hovered_link)
371 if (hovered_link != null)
372 hovered_link.Hovered = false;
376 if (hovered_link != null)
377 hovered_link.Hovered = true;
379 Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
381 /* XXX this shouldn't be here. the
382 * Link.Invalidate machinery should be enough,
383 * but it seems the piece regions don't
384 * contain the area with the underline. this
385 * can be seen easily when you click on a link
386 * and the focus rectangle shows up (it's too
387 * far up), and also the bottom few pixels of
388 * a linklabel aren't active when it comes to
393 protected override void OnMouseMove (MouseEventArgs e)
395 UpdateHover (PointInLink (e.X, e.Y));
396 base.OnMouseMove (e);
399 protected override void OnMouseUp (MouseEventArgs e)
401 if (!Enabled) return;
405 if (active_link == null)
408 Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
410 active_link.Active = false;
413 if (clicked_link != null)
414 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link));
417 protected override void OnPaint (PaintEventArgs pevent)
419 ThemeEngine.Current.DrawLinkLabel (pevent.Graphics, pevent.ClipRectangle, this);
420 DrawImage (pevent.Graphics, Image, ClientRectangle, image_align);
421 // Do not call base.OnPaint since it's the Label class
424 protected override void OnPaintBackground (PaintEventArgs e)
426 base.OnPaintBackground (e);
429 protected override void OnTextAlignChanged (EventArgs e)
432 base.OnTextAlignChanged (e);
435 protected override void OnTextChanged (EventArgs e)
438 base.OnTextChanged (e);
441 protected Link PointInLink (int x, int y)
443 for (int i = 0; i < sorted_links.Length; i ++)
444 if (sorted_links[i].Contains (x, y))
445 return sorted_links[i];
450 protected override bool ProcessDialogKey (Keys keyData)
452 if ((keyData & Keys.KeyCode) == Keys.Tab) {
453 Select (true, (keyData & Keys.Shift) == 0);
456 return base.ProcessDialogKey (keyData);
459 protected override void Select (bool directed, bool forward)
462 if (focused_index != -1)
463 sorted_links[focused_index].Focused = false;
466 for (int n = focused_index + 1; n < sorted_links.Length; n++) {
467 if (sorted_links[n].Enabled) {
468 sorted_links[n].Focused = true;
475 if (focused_index == -1)
476 focused_index = sorted_links.Length;
478 for (int n = focused_index - 1; n >= 0; n--) {
479 if (sorted_links[n].Enabled) {
480 sorted_links[n].Focused = true;
490 Parent.SelectNextControl (this, forward, false, true, true);
494 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
496 base.SetBoundsCore (x, y, width, height, specified);
501 protected override void WndProc (ref Message m)
503 base.WndProc (ref m);
506 #endregion //Public Methods
508 #region Private Methods
510 private ArrayList CreatePiecesFromText (int start, int len, Link link)
512 ArrayList rv = new ArrayList ();
514 if (start + len > Text.Length)
515 len = Text.Length - start;
519 string t = Text.Substring (start, len);
522 for (int i = 0; i < t.Length; i ++) {
525 Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link);
532 Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link);
539 private void CreateLinkPieces ()
541 if (Text.Length == 0) {
542 SetStyle (ControlStyles.Selectable, false);
546 SetStyle (ControlStyles.Selectable, true);
548 /* don't bother doing the rest if our handle hasn't been created */
549 if (!IsHandleCreated)
552 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1)
553 Links[0].Length = Text.Length;
557 ArrayList pieces_list = new ArrayList ();
561 for (int l = 0; l < sorted_links.Length; l ++) {
562 int new_link_start = sorted_links[l].Start;
564 if (new_link_start > current_end) {
565 /* create/push a piece
566 * containing the text between
567 * the previous/new link */
568 ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null);
569 pieces_list.AddRange (text_pieces);
572 /* now create a group of pieces for
573 * the new link (split up by \n's) */
574 ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]);
575 pieces_list.AddRange (link_pieces);
576 sorted_links[l].pieces.AddRange (link_pieces);
578 current_end = sorted_links[l].Start + sorted_links[l].Length;
580 if (current_end < Text.Length) {
581 ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null);
582 pieces_list.AddRange (text_pieces);
585 pieces = new Piece[pieces_list.Count];
586 pieces_list.CopyTo (pieces, 0);
588 CharacterRange[] ranges = new CharacterRange[pieces.Length];
590 for(int i = 0; i < pieces.Length; i++)
591 ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length);
593 string_format.FormatFlags = StringFormatFlags.NoClip;
594 string_format.SetMeasurableCharacterRanges (ranges);
596 Region[] regions = DeviceContext.MeasureCharacterRanges (Text,
601 for (int i = 0; i < pieces.Length; i ++)
602 pieces[i].region = regions[i];
607 private void SortLinks ()
609 if (sorted_links != null)
612 sorted_links = new Link [Links.Count];
613 ((ICollection)Links).CopyTo (sorted_links, 0);
615 Array.Sort (sorted_links, new LinkComparer ());
618 /* Check if the links overlap */
619 private void CheckLinks ()
625 for (int i = 0; i < sorted_links.Length; i++) {
626 if (sorted_links[i].Start < current_end)
627 throw new InvalidOperationException ("Overlapping link regions.");
628 current_end = sorted_links[i].Start + sorted_links[i].Length;
632 internal Font GetPieceFont (Piece piece)
634 if (piece.link == null)
637 switch (link_behavior) {
638 case LinkBehavior.AlwaysUnderline:
639 case LinkBehavior.SystemDefault: // Depends on IE configuration
643 case LinkBehavior.HoverUnderline:
645 if (piece.link.Hovered) {
652 case LinkBehavior.NeverUnderline:
660 internal Color GetPieceColor (Piece piece, int i)
664 if (Enabled == false)
665 return DisabledLinkColor;
667 if (piece.link == null)
670 if (!piece.link.Enabled)
671 color = DisabledLinkColor;
672 else if (piece.link.Active)
673 color = ActiveLinkColor;
674 else if ((LinkVisited && i == 0) || piece.link.Visited == true)
675 color = VisitedLinkColor;
682 private void CreateLinkFont ()
684 if (link_font != null)
685 link_font.Dispose ();
687 link_font = new Font (Font.FontFamily, Font.Size, Font.Style | FontStyle.Underline,
691 #endregion // Private Methods
694 // System.Windows.Forms.LinkLabel.Link
697 // XXX [TypeConverter (typeof (LinkConverter))]
701 private bool enabled;
703 private object linkData;
705 private bool visited;
706 private LinkLabel owner;
707 private bool hovered;
708 internal ArrayList pieces;
709 private bool focused;
712 internal Link (LinkLabel owner)
720 pieces = new ArrayList ();
724 [DefaultValue (true)]
726 public bool Enabled {
727 get { return enabled; }
729 if (enabled != value)
739 return owner.Text.Length;
750 owner.CreateLinkPieces ();
755 [DefaultValue (null)]
757 public object LinkData {
758 get { return linkData; }
759 set { linkData = value; }
763 get { return start; }
770 owner.sorted_links = null;
771 owner.CreateLinkPieces ();
776 [DefaultValue (false)]
778 public bool Visited {
779 get { return visited; }
781 if (visited != value)
788 internal bool Hovered {
789 get { return hovered; }
791 if (hovered != value)
797 internal bool Focused {
798 get { return focused; }
800 if (focused != value)
806 internal bool Active {
807 get { return active; }
815 private void Invalidate ()
817 for (int i = 0; i < pieces.Count; i ++)
818 owner.Invalidate (((Piece)pieces[i]).region);
821 internal bool Contains (int x, int y)
823 foreach (Piece p in pieces) {
824 if (p.region.IsVisible (new Point (x,y)))
831 class LinkComparer : IComparer
833 public int Compare (object x, object y)
838 return l1.Start - l2.Start;
843 // System.Windows.Forms.LinkLabel.Link
845 public class LinkCollection : IList, ICollection, IEnumerable
847 private LinkLabel owner;
848 private ArrayList collection = new ArrayList();
850 public LinkCollection (LinkLabel owner)
853 throw new ArgumentNullException ();
860 get { return collection.Count; }
863 public bool IsReadOnly {
864 get { return false; }
867 public virtual LinkLabel.Link this[int index] {
869 if (index < 0 || index >= Count)
870 throw new ArgumentOutOfRangeException();
872 return (LinkLabel.Link) collection[index];
875 if (index < 0 || index >= Count)
876 throw new ArgumentOutOfRangeException();
878 collection[index] = value;
882 public Link Add (int start, int length)
884 return Add (start, length, null);
887 internal bool IsDefault {
890 && this[0].Start == 0
891 && this[0].length == -1);
895 public Link Add (int start, int length, object o)
897 Link link = new Link (owner);
900 /* remove the default 0,-1 link */
902 /* don't call Clear() here to save the additional CreateLinkPieces */
906 link.Length = length;
909 idx = collection.Add (link);
911 owner.sorted_links = null;
913 owner.CreateLinkPieces ();
914 return (Link) collection[idx];
917 public virtual void Clear ()
920 owner.sorted_links = null;
921 owner.CreateLinkPieces ();
924 public bool Contains (Link link)
926 return collection.Contains (link);
929 public IEnumerator GetEnumerator ()
931 return collection.GetEnumerator ();
934 public int IndexOf (Link link)
936 return collection.IndexOf (link);
939 public void Remove (LinkLabel.Link value)
941 collection.Remove (value);
942 owner.sorted_links = null;
943 owner.CreateLinkPieces ();
946 public void RemoveAt (int index)
949 throw new ArgumentOutOfRangeException ("Invalid value for array index");
951 collection.Remove (collection[index]);
952 owner.sorted_links = null;
953 owner.CreateLinkPieces ();
956 bool IList.IsFixedSize {
960 object IList.this[int index] {
961 get { return collection[index]; }
962 set { collection[index] = value; }
965 object ICollection.SyncRoot {
969 bool ICollection.IsSynchronized {
973 void ICollection.CopyTo (Array dest, int index)
975 collection.CopyTo (dest, index);
978 int IList.Add (object control)
980 int idx = collection.Add (control);
981 owner.sorted_links = null;
983 owner.CreateLinkPieces ();
987 bool IList.Contains (object control)
989 return Contains ((Link)control);
992 int IList.IndexOf (object control)
994 return collection.IndexOf (control);
997 void IList.Insert (int index, object value)
999 collection.Insert (index, value);
1000 owner.sorted_links = null;
1001 owner.CheckLinks ();
1002 owner.CreateLinkPieces ();
1005 void IList.Remove (object control)
1007 Remove ((Link)control);
1012 [RefreshProperties (RefreshProperties.Repaint)]
1013 public new bool UseCompatibleTextRendering {
1015 return use_compatible_text_rendering;
1019 use_compatible_text_rendering = value;