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 private Cursor override_cursor;
75 private DialogResult dialog_result;
77 private Link active_link;
78 private Link hovered_link;
79 /* this is an index instead of a Link because we have
80 * to search through sorted links for the new one */
81 private int focused_index;
84 static object LinkClickedEvent = new object ();
86 public event LinkLabelLinkClickedEventHandler LinkClicked {
87 add { Events.AddHandler (LinkClickedEvent, value); }
88 remove { Events.RemoveHandler (LinkClickedEvent, value); }
94 LinkArea = new LinkArea (0, -1);
95 link_behavior = LinkBehavior.SystemDefault;
100 string_format.FormatFlags = StringFormatFlags.NoClip;
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.Selectable, false);
107 SetStyle (ControlStyles.ResizeRedraw |
108 ControlStyles.UserPaint |
109 ControlStyles.AllPaintingInWmPaint |
110 ControlStyles.SupportsTransparentBackColor |
113 | ControlStyles.OptimizedDoubleBuffer
115 | ControlStyles.DoubleBuffer
121 #region Public Properties
123 public Color ActiveLinkColor {
124 get { return active_link_color; }
126 if (active_link_color == value)
129 active_link_color = value;
134 public Color DisabledLinkColor {
136 get { return disabled_link_color; }
138 if (disabled_link_color == value)
141 disabled_link_color = value;
146 public Color LinkColor {
147 get { return link_color; }
149 if (link_color == value)
157 public Color VisitedLinkColor {
158 get { return visited_color;}
160 if (visited_color == value)
163 visited_color = value;
169 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
171 [RefreshProperties (RefreshProperties.Repaint)]
173 public LinkArea LinkArea {
174 get { return link_area;}
177 if (value.Start <0 || value.Length < -1)
178 throw new ArgumentException ();
183 if (!value.IsEmpty) {
184 Links.Add (value.Start, value.Length);
192 [DefaultValue (LinkBehavior.SystemDefault)]
193 public LinkBehavior LinkBehavior {
195 get { return link_behavior;}
197 if (link_behavior == value)
200 link_behavior = value;
207 [EditorBrowsable (EditorBrowsableState.Advanced)]
209 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
210 public LinkLabel.LinkCollection Links {
212 if (link_collection == null)
213 link_collection = new LinkCollection (this);
215 return link_collection;
219 [DefaultValue (false)]
220 public bool LinkVisited {
221 get { return link_visited;}
223 if (link_visited == value)
226 link_visited = value;
231 protected Cursor OverrideCursor {
233 if (override_cursor == null)
234 override_cursor = Cursors.Hand;
235 return override_cursor;
237 set { override_cursor = value; }
240 [RefreshProperties(RefreshProperties.Repaint)]
241 public override string Text {
242 get { return base.Text; }
244 if (base.Text == value)
252 #endregion // Public Properties
254 DialogResult IButtonControl.DialogResult {
255 get { return dialog_result; }
256 set { dialog_result = value; }
260 void IButtonControl.NotifyDefault (bool value)
264 void IButtonControl.PerformClick ()
268 #region Public Methods
269 protected override AccessibleObject CreateAccessibilityInstance ()
271 return base.CreateAccessibilityInstance();
274 protected override void CreateHandle ()
276 base.CreateHandle ();
280 protected override void OnEnabledChanged (EventArgs e)
282 base.OnEnabledChanged (e);
286 protected override void OnFontChanged (EventArgs e)
288 base.OnFontChanged (e);
292 protected override void OnGotFocus (EventArgs e)
296 // Set focus to the first enabled link piece
297 if (focused_index == -1) {
298 if ((Control.ModifierKeys & Keys.Shift) == 0) {
299 for (int i = 0; i < sorted_links.Length; i ++) {
300 if (sorted_links[i].Enabled) {
306 if (focused_index == -1)
307 focused_index = sorted_links.Length;
309 for (int n = focused_index - 1; n >= 0; n--) {
310 if (sorted_links[n].Enabled) {
311 sorted_links[n].Focused = true;
319 if (focused_index != -1)
320 sorted_links[focused_index].Focused = true;
323 protected override void OnKeyDown (KeyEventArgs e)
325 if (e.KeyCode == Keys.Return) {
326 if (focused_index != -1)
327 OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index]));
333 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
335 LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]);
340 protected override void OnLostFocus (EventArgs e)
342 base.OnLostFocus (e);
344 // Clean focus in link pieces
345 if (focused_index != -1)
346 sorted_links[focused_index].Focused = false;
349 protected override void OnMouseDown (MouseEventArgs e)
351 if (!Enabled) return;
353 base.OnMouseDown (e);
355 for (int i = 0; i < sorted_links.Length; i ++) {
356 if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) {
357 sorted_links[i].Active = true;
358 if (focused_index != -1)
359 sorted_links[focused_index].Focused = false;
360 active_link = sorted_links[i];
362 sorted_links[focused_index].Focused = true;
368 protected override void OnMouseLeave(EventArgs e)
370 if (!Enabled) return;
371 base.OnMouseLeave (e);
375 private void UpdateHover (Link link)
377 if (link == hovered_link)
380 if (hovered_link != null)
381 hovered_link.Hovered = false;
385 if (hovered_link != null)
386 hovered_link.Hovered = true;
388 Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default;
390 /* XXX this shouldn't be here. the
391 * Link.Invalidate machinery should be enough,
392 * but it seems the piece regions don't
393 * contain the area with the underline. this
394 * can be seen easily when you click on a link
395 * and the focus rectangle shows up (it's too
396 * far up), and also the bottom few pixels of
397 * a linklabel aren't active when it comes to
402 protected override void OnMouseMove (MouseEventArgs e)
404 UpdateHover (PointInLink (e.X, e.Y));
405 base.OnMouseMove (e);
408 protected override void OnMouseUp (MouseEventArgs e)
410 if (!Enabled) return;
414 if (active_link == null)
417 Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null;
419 active_link.Active = false;
422 if (clicked_link != null)
423 OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link));
426 protected override void OnPaint (PaintEventArgs pevent)
428 ThemeEngine.Current.DrawLinkLabel (pevent.Graphics, pevent.ClipRectangle, this);
429 DrawImage (pevent.Graphics, Image, ClientRectangle, image_align);
430 // Do not call base.OnPaint since it's the Label class
433 protected override void OnPaintBackground (PaintEventArgs e)
435 base.OnPaintBackground (e);
438 protected override void OnTextAlignChanged (EventArgs e)
441 base.OnTextAlignChanged (e);
444 protected override void OnTextChanged (EventArgs e)
447 base.OnTextChanged (e);
450 protected Link PointInLink (int x, int y)
452 for (int i = 0; i < sorted_links.Length; i ++)
453 if (sorted_links[i].Contains (x, y))
454 return sorted_links[i];
459 protected override bool ProcessDialogKey (Keys keyData)
461 if ((keyData & Keys.KeyCode) == Keys.Tab) {
462 Select (true, (keyData & Keys.Shift) == 0);
465 return base.ProcessDialogKey (keyData);
468 protected override void Select (bool directed, bool forward)
471 if (focused_index != -1)
472 sorted_links[focused_index].Focused = false;
475 for (int n = focused_index + 1; n < sorted_links.Length; n++) {
476 if (sorted_links[n].Enabled) {
477 sorted_links[n].Focused = true;
479 base.Select (directed, forward);
485 if (focused_index == -1)
486 focused_index = sorted_links.Length;
488 for (int n = focused_index - 1; n >= 0; n--) {
489 if (sorted_links[n].Enabled) {
490 sorted_links[n].Focused = true;
492 base.Select (directed, forward);
501 Parent.SelectNextControl (this, forward, false, true, true);
505 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
507 base.SetBoundsCore (x, y, width, height, specified);
511 protected override void WndProc (ref Message m)
513 base.WndProc (ref m);
516 #endregion //Public Methods
518 #region Private Methods
520 private ArrayList CreatePiecesFromText (int start, int len, Link link)
522 ArrayList rv = new ArrayList ();
524 if (start + len > Text.Length)
525 len = Text.Length - start;
529 string t = Text.Substring (start, len);
532 for (int i = 0; i < t.Length; i ++) {
535 Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link);
542 Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link);
549 private void CreateLinkPieces ()
551 if (Text.Length == 0) {
552 SetStyle (ControlStyles.Selectable, false);
557 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1)
558 Links[0].Length = Text.Length;
562 // Set the LinkArea values based on first link.
563 if (Links.Count > 0) {
564 link_area.Start = Links[0].Start;
565 link_area.Length = Links[0].Length;
568 link_area.Length = 0;
571 TabStop = (LinkArea.Length > 0);
572 SetStyle (ControlStyles.Selectable, TabStop);
574 /* don't bother doing the rest if our handle hasn't been created */
575 if (!IsHandleCreated)
578 ArrayList pieces_list = new ArrayList ();
582 for (int l = 0; l < sorted_links.Length; l ++) {
583 int new_link_start = sorted_links[l].Start;
585 if (new_link_start > current_end) {
586 /* create/push a piece
587 * containing the text between
588 * the previous/new link */
589 ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null);
590 pieces_list.AddRange (text_pieces);
593 /* now create a group of pieces for
594 * the new link (split up by \n's) */
595 ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]);
596 pieces_list.AddRange (link_pieces);
597 sorted_links[l].pieces.AddRange (link_pieces);
599 current_end = sorted_links[l].Start + sorted_links[l].Length;
601 if (current_end < Text.Length) {
602 ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null);
603 pieces_list.AddRange (text_pieces);
606 pieces = new Piece[pieces_list.Count];
607 pieces_list.CopyTo (pieces, 0);
609 CharacterRange[] ranges = new CharacterRange[pieces.Length];
611 for(int i = 0; i < pieces.Length; i++)
612 ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length);
614 string_format.SetMeasurableCharacterRanges (ranges);
616 Region[] regions = DeviceContext.MeasureCharacterRanges (Text,
617 ThemeEngine.Current.GetLinkFont (this),
621 for (int i = 0; i < pieces.Length; i ++)
622 pieces[i].region = regions[i];
627 private void SortLinks ()
629 if (sorted_links != null)
632 sorted_links = new Link [Links.Count];
633 ((ICollection)Links).CopyTo (sorted_links, 0);
635 Array.Sort (sorted_links, new LinkComparer ());
638 /* Check if the links overlap */
639 private void CheckLinks ()
645 for (int i = 0; i < sorted_links.Length; i++) {
646 if (sorted_links[i].Start < current_end)
647 throw new InvalidOperationException ("Overlapping link regions.");
648 current_end = sorted_links[i].Start + sorted_links[i].Length;
652 #endregion // Private Methods
655 // System.Windows.Forms.LinkLabel.Link
658 // XXX [TypeConverter (typeof (LinkConverter))]
662 private bool enabled;
664 private object linkData;
666 private bool visited;
667 private LinkLabel owner;
668 private bool hovered;
669 internal ArrayList pieces;
670 private bool focused;
673 internal Link (LinkLabel owner)
681 pieces = new ArrayList ();
685 [DefaultValue (true)]
687 public bool Enabled {
688 get { return enabled; }
690 if (enabled != value)
700 return owner.Text.Length;
711 owner.CreateLinkPieces ();
716 [DefaultValue (null)]
718 public object LinkData {
719 get { return linkData; }
720 set { linkData = value; }
724 get { return start; }
731 owner.sorted_links = null;
732 owner.CreateLinkPieces ();
737 [DefaultValue (false)]
739 public bool Visited {
740 get { return visited; }
742 if (visited != value)
749 internal bool Hovered {
750 get { return hovered; }
752 if (hovered != value)
758 internal bool Focused {
759 get { return focused; }
761 if (focused != value)
767 internal bool Active {
768 get { return active; }
776 private void Invalidate ()
778 for (int i = 0; i < pieces.Count; i ++)
779 owner.Invalidate (((Piece)pieces[i]).region);
782 internal bool Contains (int x, int y)
784 foreach (Piece p in pieces) {
785 if (p.region.IsVisible (new Point (x,y)))
792 class LinkComparer : IComparer
794 public int Compare (object x, object y)
799 return l1.Start - l2.Start;
804 // System.Windows.Forms.LinkLabel.Link
806 public class LinkCollection : IList, ICollection, IEnumerable
808 private LinkLabel owner;
809 private ArrayList collection = new ArrayList();
811 public LinkCollection (LinkLabel owner)
814 throw new ArgumentNullException ();
821 get { return collection.Count; }
824 public bool IsReadOnly {
825 get { return false; }
828 public virtual LinkLabel.Link this[int index] {
830 if (index < 0 || index >= Count)
831 throw new ArgumentOutOfRangeException();
833 return (LinkLabel.Link) collection[index];
836 if (index < 0 || index >= Count)
837 throw new ArgumentOutOfRangeException();
839 collection[index] = value;
843 public Link Add (int start, int length)
845 return Add (start, length, null);
848 internal bool IsDefault {
851 && this[0].Start == 0
852 && this[0].length == -1);
856 public Link Add (int start, int length, object o)
858 Link link = new Link (owner);
861 /* remove the default 0,-1 link */
863 /* don't call Clear() here to save the additional CreateLinkPieces */
867 link.Length = length;
870 idx = collection.Add (link);
872 owner.sorted_links = null;
874 owner.CreateLinkPieces ();
875 return (Link) collection[idx];
878 public virtual void Clear ()
881 owner.sorted_links = null;
882 owner.CreateLinkPieces ();
885 public bool Contains (Link link)
887 return collection.Contains (link);
890 public IEnumerator GetEnumerator ()
892 return collection.GetEnumerator ();
895 public int IndexOf (Link link)
897 return collection.IndexOf (link);
900 public void Remove (LinkLabel.Link value)
902 collection.Remove (value);
903 owner.sorted_links = null;
904 owner.CreateLinkPieces ();
907 public void RemoveAt (int index)
910 throw new ArgumentOutOfRangeException ("Invalid value for array index");
912 collection.Remove (collection[index]);
913 owner.sorted_links = null;
914 owner.CreateLinkPieces ();
917 bool IList.IsFixedSize {
921 object IList.this[int index] {
922 get { return collection[index]; }
923 set { collection[index] = value; }
926 object ICollection.SyncRoot {
930 bool ICollection.IsSynchronized {
934 void ICollection.CopyTo (Array dest, int index)
936 collection.CopyTo (dest, index);
939 int IList.Add (object control)
941 int idx = collection.Add (control);
942 owner.sorted_links = null;
944 owner.CreateLinkPieces ();
948 bool IList.Contains (object control)
950 return Contains ((Link)control);
953 int IList.IndexOf (object control)
955 return collection.IndexOf (control);
958 void IList.Insert (int index, object value)
960 collection.Insert (index, value);
961 owner.sorted_links = null;
963 owner.CreateLinkPieces ();
966 void IList.Remove (object control)
968 Remove ((Link)control);
973 [RefreshProperties (RefreshProperties.Repaint)]
974 public new bool UseCompatibleTextRendering {
976 return use_compatible_text_rendering;
980 use_compatible_text_rendering = value;