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)]
43 [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 internal Link[] sorted_links;
73 private bool link_visited;
74 internal Piece[] pieces;
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;
101 string_format.FormatFlags = StringFormatFlags.NoClip;
103 ActiveLinkColor = Color.Red;
104 DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
105 LinkColor = Color.FromArgb (255, 0, 0, 255);
106 VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
107 SetStyle (ControlStyles.Selectable, false);
108 SetStyle (ControlStyles.ResizeRedraw |
109 ControlStyles.UserPaint |
110 ControlStyles.AllPaintingInWmPaint |
111 ControlStyles.SupportsTransparentBackColor |
114 | ControlStyles.OptimizedDoubleBuffer
116 | ControlStyles.DoubleBuffer
122 #region Public Properties
124 public Color ActiveLinkColor {
125 get { return active_link_color; }
127 if (active_link_color == value)
130 active_link_color = value;
135 public Color DisabledLinkColor {
137 get { return disabled_link_color; }
139 if (disabled_link_color == value)
142 disabled_link_color = value;
147 public Color LinkColor {
148 get { return link_color; }
150 if (link_color == value)
158 public Color VisitedLinkColor {
159 get { return visited_color;}
161 if (visited_color == value)
164 visited_color = value;
170 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
172 [RefreshProperties (RefreshProperties.Repaint)]
174 public LinkArea LinkArea {
175 get { return link_area;}
178 if (value.Start <0 || value.Length < -1)
179 throw new ArgumentException ();
184 if (!value.IsEmpty) {
185 Links.Add (value.Start, value.Length);
193 [DefaultValue (LinkBehavior.SystemDefault)]
194 public LinkBehavior LinkBehavior {
196 get { return link_behavior;}
198 if (link_behavior == value)
201 link_behavior = value;
208 [EditorBrowsable (EditorBrowsableState.Advanced)]
210 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
211 public LinkLabel.LinkCollection Links {
213 if (link_collection == null)
214 link_collection = new LinkCollection (this);
216 return link_collection;
220 [DefaultValue (false)]
221 public bool LinkVisited {
222 get { return link_visited;}
224 if (link_visited == value)
227 link_visited = value;
232 protected Cursor OverrideCursor {
234 if (override_cursor == null)
235 override_cursor = Cursors.Hand;
236 return override_cursor;
238 set { override_cursor = value; }
241 [RefreshProperties(RefreshProperties.Repaint)]
242 public override string Text {
243 get { return base.Text; }
245 if (base.Text == value)
255 [EditorBrowsable (EditorBrowsableState.Never)]
256 public FlatStyle FlatStyle {
257 get { return base.FlatStyle; }
259 if (base.FlatStyle == value)
262 base.FlatStyle = value;
266 [RefreshProperties (RefreshProperties.Repaint)]
267 public Padding Padding {
268 get { return base.Padding; }
270 if (base.Padding == value)
273 base.Padding = value;
278 #endregion // Public Properties
280 DialogResult IButtonControl.DialogResult {
281 get { return dialog_result; }
282 set { dialog_result = value; }
286 void IButtonControl.NotifyDefault (bool value)
290 void IButtonControl.PerformClick ()
294 #region Public Methods
295 protected override AccessibleObject CreateAccessibilityInstance ()
297 return base.CreateAccessibilityInstance();
300 protected override void CreateHandle ()
302 base.CreateHandle ();
306 protected override void OnEnabledChanged (EventArgs e)
308 base.OnEnabledChanged (e);
312 protected override void OnFontChanged (EventArgs e)
314 base.OnFontChanged (e);
318 protected override void OnGotFocus (EventArgs e)
322 // Set focus to the first enabled link piece
323 if (focused_index == -1) {
324 if ((Control.ModifierKeys & Keys.Shift) == 0) {
325 for (int i = 0; i < sorted_links.Length; i ++) {
326 if (sorted_links[i].Enabled) {
332 if (focused_index == -1)
333 focused_index = sorted_links.Length;
335 for (int n = focused_index - 1; n >= 0; n--) {
336 if (sorted_links[n].Enabled) {
337 sorted_links[n].Focused = true;
345 if (focused_index != -1)
346 sorted_links[focused_index].Focused = true;
349 protected override void OnKeyDown (KeyEventArgs e)
351 if (e.KeyCode == Keys.Return) {
352 if (focused_index != -1)
353 OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
359 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
361 LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
366 protected override void OnLostFocus (EventArgs e)
368 base.OnLostFocus (e);
370 // Clean focus in link pieces
371 if (focused_index != -1)
372 sorted_links[focused_index].Focused = false;
375 protected override void OnMouseDown (MouseEventArgs e)
377 if (!Enabled) return;
379 base.OnMouseDown (e);
381 for (int i = 0; i < sorted_links.Length; i ++) {
382 if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
383 sorted_links[i].Active = true;
384 if (focused_index != -1)
385 sorted_links[focused_index].Focused = false;
386 active_link = sorted_links[i];
388 sorted_links[focused_index].Focused = true;
394 protected override void OnMouseLeave(EventArgs e)
396 if (!Enabled) return;
397 base.OnMouseLeave (e);
401 private void UpdateHover (Link link)
403 if (link == hovered_link)
406 if (hovered_link != null)
407 hovered_link.Hovered = false;
411 if (hovered_link != null)
412 hovered_link.Hovered = true;
414 Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
416 /* XXX this shouldn't be here. the
417 * Link.Invalidate machinery should be enough,
418 * but it seems the piece regions don't
419 * contain the area with the underline. this
420 * can be seen easily when you click on a link
421 * and the focus rectangle shows up (it's too
422 * far up), and also the bottom few pixels of
423 * a linklabel aren't active when it comes to
428 protected override void OnMouseMove (MouseEventArgs e)
430 UpdateHover (PointInLink (e.X, e.Y));
431 base.OnMouseMove (e);
434 protected override void OnMouseUp (MouseEventArgs e)
436 if (!Enabled) return;
440 if (active_link == null)
443 Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
445 active_link.Active = false;
448 if (clicked_link != null)
450 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link, e.Button));
452 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link));
456 protected override void OnPaint (PaintEventArgs pevent)
458 // We need to invoke paintbackground because control is opaque
459 // and can have transparent colors.
460 base.InvokePaintBackground (this, pevent);
462 ThemeEngine.Current.DrawLinkLabel (pevent.Graphics, pevent.ClipRectangle, this);
463 DrawImage (pevent.Graphics, Image, ClientRectangle, image_align);
464 // Do not call base.OnPaint since it's the Label class
467 protected override void OnPaintBackground (PaintEventArgs e)
469 base.OnPaintBackground (e);
472 protected override void OnTextAlignChanged (EventArgs e)
475 base.OnTextAlignChanged (e);
478 protected override void OnTextChanged (EventArgs e)
481 base.OnTextChanged (e);
484 protected Link PointInLink (int x, int y)
486 for (int i = 0; i < sorted_links.Length; i ++)
487 if (sorted_links[i].Contains (x, y))
488 return sorted_links[i];
493 protected override bool ProcessDialogKey (Keys keyData)
495 if ((keyData & Keys.KeyCode) == Keys.Tab) {
496 Select (true, (keyData & Keys.Shift) == 0);
499 return base.ProcessDialogKey (keyData);
502 protected override void Select (bool directed, bool forward)
505 if (focused_index != -1)
506 sorted_links[focused_index].Focused = false;
509 for (int n = focused_index + 1; n < sorted_links.Length; n++) {
510 if (sorted_links[n].Enabled) {
511 sorted_links[n].Focused = true;
513 base.Select (directed, forward);
519 if (focused_index == -1)
520 focused_index = sorted_links.Length;
522 for (int n = focused_index - 1; n >= 0; n--) {
523 if (sorted_links[n].Enabled) {
524 sorted_links[n].Focused = true;
526 base.Select (directed, forward);
535 Parent.SelectNextControl (this, forward, false, true, true);
539 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
541 base.SetBoundsCore (x, y, width, height, specified);
545 protected override void WndProc (ref Message m)
547 base.WndProc (ref m);
550 #endregion //Public Methods
552 #region Private Methods
554 private ArrayList CreatePiecesFromText (int start, int len, Link link)
556 ArrayList rv = new ArrayList ();
558 if (start + len > Text.Length)
559 len = Text.Length - start;
563 string t = Text.Substring (start, len);
566 for (int i = 0; i < t.Length; i ++) {
569 Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link);
576 Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link);
583 private void CreateLinkPieces ()
585 if (Text.Length == 0) {
586 SetStyle (ControlStyles.Selectable, false);
591 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1)
592 Links[0].Length = Text.Length;
596 // Set the LinkArea values based on first link.
597 if (Links.Count > 0) {
598 link_area.Start = Links[0].Start;
599 link_area.Length = Links[0].Length;
602 link_area.Length = 0;
605 TabStop = (LinkArea.Length > 0);
606 SetStyle (ControlStyles.Selectable, TabStop);
608 /* don't bother doing the rest if our handle hasn't been created */
609 if (!IsHandleCreated)
612 ArrayList pieces_list = new ArrayList ();
616 for (int l = 0; l < sorted_links.Length; l ++) {
617 int new_link_start = sorted_links[l].Start;
619 if (new_link_start > current_end) {
620 /* create/push a piece
621 * containing the text between
622 * the previous/new link */
623 ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null);
624 pieces_list.AddRange (text_pieces);
627 /* now create a group of pieces for
628 * the new link (split up by \n's) */
629 ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]);
630 pieces_list.AddRange (link_pieces);
631 sorted_links[l].pieces.AddRange (link_pieces);
633 current_end = sorted_links[l].Start + sorted_links[l].Length;
635 if (current_end < Text.Length) {
636 ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null);
637 pieces_list.AddRange (text_pieces);
640 pieces = new Piece[pieces_list.Count];
641 pieces_list.CopyTo (pieces, 0);
643 CharacterRange[] ranges = new CharacterRange[pieces.Length];
645 for(int i = 0; i < pieces.Length; i++)
646 ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length);
648 string_format.SetMeasurableCharacterRanges (ranges);
650 Region[] regions = DeviceContext.MeasureCharacterRanges (Text,
651 ThemeEngine.Current.GetLinkFont (this),
655 for (int i = 0; i < pieces.Length; i ++)
656 pieces[i].region = regions[i];
661 private void SortLinks ()
663 if (sorted_links != null)
666 sorted_links = new Link [Links.Count];
667 ((ICollection)Links).CopyTo (sorted_links, 0);
669 Array.Sort (sorted_links, new LinkComparer ());
672 /* Check if the links overlap */
673 private void CheckLinks ()
679 for (int i = 0; i < sorted_links.Length; i++) {
680 if (sorted_links[i].Start < current_end)
681 throw new InvalidOperationException ("Overlapping link regions.");
682 current_end = sorted_links[i].Start + sorted_links[i].Length;
686 #endregion // Private Methods
689 // System.Windows.Forms.LinkLabel.Link
692 [TypeConverter (typeof (LinkConverter))]
696 private bool enabled;
698 private object linkData;
700 private bool visited;
701 private LinkLabel owner;
702 private bool hovered;
703 internal ArrayList pieces;
704 private bool focused;
707 private string description;
712 internal Link (LinkLabel owner)
720 pieces = new ArrayList ();
727 this.name = string.Empty;
728 this.pieces = new ArrayList ();
731 public Link (int start, int length) : this ()
734 this.length = length;
737 public Link (int start, int length, Object linkData) : this (start, length)
739 this.linkData = linkData;
743 #region Public Properties
745 public string Description {
746 get { return this.description; }
747 set { this.description = value; }
751 get { return this.name; }
752 set { this.name = value; }
756 [Localizable (false)]
758 get { return this.tag; }
759 set { this.tag = value; }
762 [DefaultValue (true)]
764 public bool Enabled {
765 get { return enabled; }
767 if (enabled != value)
777 return owner.Text.Length;
788 owner.CreateLinkPieces ();
793 [DefaultValue (null)]
795 public object LinkData {
796 get { return linkData; }
797 set { linkData = value; }
801 get { return start; }
808 owner.sorted_links = null;
809 owner.CreateLinkPieces ();
814 [DefaultValue (false)]
816 public bool Visited {
817 get { return visited; }
819 if (visited != value)
826 internal bool Hovered {
827 get { return hovered; }
829 if (hovered != value)
835 internal bool Focused {
836 get { return focused; }
838 if (focused != value)
844 internal bool Active {
845 get { return active; }
854 private void Invalidate ()
856 for (int i = 0; i < pieces.Count; i ++)
857 owner.Invalidate (((Piece)pieces[i]).region);
860 internal bool Contains (int x, int y)
862 foreach (Piece p in pieces) {
863 if (p.region.IsVisible (new Point (x,y)))
870 class LinkComparer : IComparer
872 public int Compare (object x, object y)
877 return l1.Start - l2.Start;
882 // System.Windows.Forms.LinkLabel.LinkCollection
884 public class LinkCollection : IList, ICollection, IEnumerable
886 private LinkLabel owner;
887 private ArrayList collection = new ArrayList();
889 public LinkCollection (LinkLabel owner)
892 throw new ArgumentNullException ();
899 get { return collection.Count; }
902 public bool IsReadOnly {
903 get { return false; }
906 public virtual LinkLabel.Link this[int index] {
908 if (index < 0 || index >= Count)
909 throw new ArgumentOutOfRangeException();
911 return (LinkLabel.Link) collection[index];
914 if (index < 0 || index >= Count)
915 throw new ArgumentOutOfRangeException();
917 collection[index] = value;
928 /* remove the default 0,-1 link */
930 /* don't call Clear() here to save the additional CreateLinkPieces */
934 int idx = collection.Add (link);
936 owner.sorted_links = null;
938 owner.CreateLinkPieces ();
943 public Link Add (int start, int length)
945 return Add (start, length, null);
948 internal bool IsDefault {
951 && this[0].Start == 0
952 && this[0].length == -1);
956 public Link Add (int start, int length, object o)
958 Link link = new Link (owner);
959 link.Length = length;
963 int idx = Add (link);
965 return (Link) collection[idx];
968 public virtual void Clear ()
971 owner.sorted_links = null;
972 owner.CreateLinkPieces ();
975 public bool Contains (Link link)
977 return collection.Contains (link);
980 public IEnumerator GetEnumerator ()
982 return collection.GetEnumerator ();
985 public int IndexOf (Link link)
987 return collection.IndexOf (link);
990 public void Remove (LinkLabel.Link value)
992 collection.Remove (value);
993 owner.sorted_links = null;
994 owner.CreateLinkPieces ();
997 public void RemoveAt (int index)
1000 throw new ArgumentOutOfRangeException ("Invalid value for array index");
1002 collection.Remove (collection[index]);
1003 owner.sorted_links = null;
1004 owner.CreateLinkPieces ();
1007 bool IList.IsFixedSize {
1011 object IList.this[int index] {
1012 get { return collection[index]; }
1013 set { collection[index] = value; }
1016 object ICollection.SyncRoot {
1020 bool ICollection.IsSynchronized {
1024 void ICollection.CopyTo (Array dest, int index)
1026 collection.CopyTo (dest, index);
1029 int IList.Add (object control)
1031 int idx = collection.Add (control);
1032 owner.sorted_links = null;
1033 owner.CheckLinks ();
1034 owner.CreateLinkPieces ();
1038 bool IList.Contains (object control)
1040 return Contains ((Link)control);
1043 int IList.IndexOf (object control)
1045 return collection.IndexOf (control);
1048 void IList.Insert (int index, object value)
1050 collection.Insert (index, value);
1051 owner.sorted_links = null;
1052 owner.CheckLinks ();
1053 owner.CreateLinkPieces ();
1056 void IList.Remove (object control)
1058 Remove ((Link)control);
1063 [RefreshProperties (RefreshProperties.Repaint)]
1064 public new bool UseCompatibleTextRendering {
1066 return use_compatible_text_rendering;
1070 use_compatible_text_rendering = value;