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
26 // Daniel Carrera, dcarrera@math.toronto.edu (stubbed out)
27 // Jaak Simm (jaaksimm@firm.ee) (stubbed out)
33 using System.ComponentModel;
34 using System.Collections;
36 using System.Drawing.Drawing2D;
38 namespace System.Windows.Forms
40 [DefaultEvent("LinkClicked")]
41 public class LinkLabel : Label, IButtonControl
43 /* Encapsulates a piece of text (regular or link)*/
49 public LinkLabel.Link link; // Empty link indicates regular text
50 public Rectangle rect;
63 private Color active_link;
64 private Color disabled_link;
65 private Color link_color;
66 private Color visited_color;
67 private LinkArea link_area;
68 private LinkBehavior link_behavior;
69 private LinkCollection link_collection;
70 private bool link_visited;
71 private bool link_click;
72 internal Piece[] pieces;
73 internal int num_pieces;
74 internal Font link_font;
75 private Cursor override_cursor;
76 private DialogResult dialog_result;
79 public event LinkLabelLinkClickedEventHandler LinkClicked;
84 LinkArea = new LinkArea (0, -1);
85 link_behavior = LinkBehavior.SystemDefault;
92 ActiveLinkColor = Color.Red;
93 DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
94 LinkColor = Color.FromArgb (255, 0, 0, 255);
95 VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
96 SetStyle (ControlStyles.Selectable, false);
97 SetStyle (ControlStyles.Opaque, true);
100 #region Public Properties
102 public Color ActiveLinkColor {
103 get { return active_link;}
105 if (active_link == value)
113 public Color DisabledLinkColor {
115 get { return disabled_link;}
117 if (disabled_link == value)
120 disabled_link = value;
125 public Color LinkColor {
126 get { return link_color;}
128 if (link_color == value)
136 public Color VisitedLinkColor {
137 get { return visited_color;}
139 if (visited_color == value)
142 visited_color = value;
148 [Editor ("System.Windows.Forms.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
149 public LinkArea LinkArea {
150 get { return link_area;}
153 if (value.Start <0 || value.Length < -1)
154 throw new ArgumentException ();
157 Links.Add (value.Start, value.Length);
164 [DefaultValue (LinkBehavior.SystemDefault)]
165 public LinkBehavior LinkBehavior {
167 get { return link_behavior;}
169 if (link_behavior == value)
172 link_behavior = value;
178 [EditorBrowsable (EditorBrowsableState.Advanced)]
179 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
180 public LinkLabel.LinkCollection Links {
182 if (link_collection == null)
183 link_collection = new LinkCollection (this);
185 return link_collection;
189 [DefaultValue (false)]
190 public bool LinkVisited {
191 get { return link_visited;}
193 if (link_visited == value)
196 link_visited = value;
201 protected Cursor OverrideCursor {
202 get { return override_cursor;}
203 set { override_cursor = value;}
206 [RefreshProperties(RefreshProperties.Repaint)]
207 public override string Text {
208 get { return base.Text; }
210 if (base.Text == value)
218 #endregion // Public Properties
220 DialogResult IButtonControl.DialogResult {
221 get { return dialog_result; }
222 set { dialog_result = value; }
226 void IButtonControl.NotifyDefault (bool value)
231 void IButtonControl.PerformClick ()
236 #region Public Methods
237 protected override AccessibleObject CreateAccessibilityInstance ()
239 return base.CreateAccessibilityInstance();
242 protected override void CreateHandle ()
244 base.CreateHandle ();
249 protected override void OnEnabledChanged (EventArgs e)
251 base.OnEnabledChanged (e);
255 protected override void OnFontChanged (EventArgs e)
257 base.OnFontChanged (e);
262 protected override void OnGotFocus (EventArgs e)
266 // Set focus to the first enabled link piece
267 for (int i = 0; i < num_pieces; i++) {
268 if (pieces[i].link != null && pieces[i].link.Enabled) {
269 pieces[i].focused = true;
270 Invalidate (pieces[i].rect);
276 protected override void OnKeyDown (KeyEventArgs e)
280 // Set focus to the next link piece
281 if (e.KeyCode == Keys.Tab || e.KeyCode == Keys.Right) {
282 for (int i = 0; i < num_pieces; i++) {
283 if (pieces[i].focused) {
284 pieces[i].focused = false;
285 Invalidate (pieces[i].rect);
287 for (int n = i + 1; n < num_pieces; n++) {
288 if (pieces[n].link != null && pieces[n].link.Enabled) {
289 pieces[n].focused = true;
291 Invalidate (pieces[n].rect);
297 } else if (e.KeyCode == Keys.Return) {
298 for (int i = 0; i < num_pieces; i++) {
299 if (pieces[i].focused && pieces[i].link != null) {
300 OnLinkClicked (new LinkLabelLinkClickedEventArgs (pieces[i].link));
307 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
309 if (LinkClicked != null)
310 LinkClicked (this, e);
313 protected override void OnLostFocus (EventArgs e)
315 base.OnLostFocus (e);
317 // Clean focus in link pieces
318 for (int i = 0; i < num_pieces; i++) {
319 if (pieces[i].focused) {
320 pieces[i].focused = false;
327 protected override void OnMouseDown (MouseEventArgs e)
329 if (!Enabled) return;
331 base.OnMouseDown (e);
334 for (int i = 0; i < num_pieces; i++) {
335 if (pieces[i].rect.Contains (e.X, e.Y)) {
336 if (pieces[i].link!= null) {
337 pieces[i].clicked = true;
338 Invalidate (pieces[i].rect);
345 protected override void OnMouseLeave(EventArgs e)
347 if (!Enabled) return;
349 base.OnMouseLeave (e);
352 protected override void OnMouseMove (MouseEventArgs e)
354 base.OnMouseMove (e);
356 Link link = PointInLink (e.X, e.Y);
359 Cursor = Cursors.Default;
360 bool changed = false;
361 if (link_behavior == LinkBehavior.HoverUnderline) {
362 for (int i = 0; i < Links.Count; i++) {
363 if (Links[i].Hoovered == true) {
365 Links[i].Hoovered = false;
375 if (link_behavior == LinkBehavior.HoverUnderline) {
376 if (link.Hoovered != true) {
377 link.Hoovered = true;
382 Cursor = Cursors.Hand;
385 protected override void OnMouseUp (MouseEventArgs e)
387 if (!Enabled) return;
390 this.Capture = false;
392 for (int i = 0; i < num_pieces; i++) {
393 if (pieces[i].link!= null && pieces[i].clicked == true) {
394 OnLinkClicked (new LinkLabelLinkClickedEventArgs (pieces[i].link));
395 pieces[i].clicked = false;
396 Invalidate (pieces[i].rect);
402 protected override void OnPaint (PaintEventArgs pevent)
404 ThemeEngine.Current.DrawLinkLabel (pevent.Graphics, pevent.ClipRectangle, this);
405 DrawImage (pevent.Graphics, Image, ClientRectangle, image_align);
406 // Do not call base.OnPaint since it's the Label class
409 protected override void OnPaintBackground (PaintEventArgs e)
411 base.OnPaintBackground (e);
414 protected override void OnTextAlignChanged (EventArgs e)
416 base.OnTextAlignChanged (e);
420 protected override void OnTextChanged (EventArgs e)
422 base.OnTextChanged (e);
425 protected Link PointInLink (int x, int y)
427 for (int i = 0; i < num_pieces; i++) {
428 if (pieces[i].rect.Contains (x,y) && pieces[i].link != null)
429 return pieces[i].link;
435 protected override bool ProcessDialogKey (Keys keyData)
437 return base.ProcessDialogKey (keyData);
440 protected override void Select (bool directed, bool forward)
442 base.Select (directed, forward);
445 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
447 base.SetBoundsCore (x, y, width, height, specified);
450 protected override void WndProc (ref Message m)
452 base.WndProc (ref m);
455 #endregion //Public Methods
457 #region Private Methods
459 internal void CreateLinkPieces ()
461 if (Links.Count == 0 || IsHandleCreated == false || Text.Length == 0)
467 if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1) {
468 Links[0].Length = Text.Length;
471 pieces = new Piece [(Links.Count * 2) + 1];
472 pieces[cur_piece] = new Piece();
473 pieces[cur_piece].start = 0;
475 for (int i = 0; i < Text.Length; i++) { /* Every char on the text*/
476 for (int l = 0; l < Links.Count; l++) { /* Every link that we know of*/
477 if (Links[l].Start == i) {
479 /*Push prev. regular text*/
480 pieces[cur_piece].end = i;
481 pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start,
482 pieces[cur_piece].end - pieces[cur_piece].start);
487 pieces[cur_piece] = new Piece ();
492 if (Links[l].Start + Links[l].Length > Text.Length) {
493 end = Text.Length - Links[l].Start;
496 end = Links[l].Length;
499 pieces[cur_piece].start = Links[l].Start;
500 pieces[cur_piece].end = Links[l].Start + end;
501 pieces[cur_piece].link = Links[l];
503 pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start, end);
505 cur_piece++; /* Push link*/
506 pieces[cur_piece] = new Piece();
508 pieces[cur_piece].start = i;
513 if (pieces[cur_piece].end == 0 && pieces[cur_piece].start < Text.Length) {
514 pieces[cur_piece].end = Text.Length;
515 pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start, pieces[cur_piece].end - pieces[cur_piece].start);
519 num_pieces = cur_piece;
521 CharacterRange[] charRanges = new CharacterRange [num_pieces];
523 for (int i = 0; i < num_pieces; i++)
524 charRanges[i] = new CharacterRange (pieces[i].start, pieces[i].end - pieces[i].start);
526 Region[] charRegions = new Region [num_pieces];
527 string_format.SetMeasurableCharacterRanges (charRanges);
529 // BUG: This sizes do not match the ones used later when drawing
530 charRegions = DeviceContext.MeasureCharacterRanges (Text, link_font, ClientRectangle, string_format);
533 for (int i = 0; i < num_pieces; i++) {
534 rect = charRegions[i].GetBounds (DeviceContext);
535 pieces[i].rect = Rectangle.Ceiling (rect);
536 charRegions[i].Dispose ();
539 if (Visible && IsHandleCreated)
544 /* Check if the links overlap */
545 internal void CheckLinks ()
547 for (int i = 0; i < Links.Count; i++) {
548 for (int l = 0; l < Links.Count; l++) {
551 if (((Links[i].Start + Links[i].Length) >= Links[l].Start &&
552 Links[i].Start + Links[i].Length <= Links[l].Start + Links[l].Length) ||
553 (Links[i].Start >= Links[l].Start &&
554 Links[i].Start <= Links[l].Start + Links[l].Length))
555 throw new InvalidOperationException ("Overlapping link regions.");
560 internal Font GetPieceFont (Piece piece)
562 switch (link_behavior) {
563 case LinkBehavior.AlwaysUnderline:
564 case LinkBehavior.SystemDefault: // Depends on IE configuration
566 if (piece.link == null) {
572 case LinkBehavior.HoverUnderline:
574 if (piece.link != null && piece.link.Hoovered) {
581 case LinkBehavior.NeverUnderline:
589 internal Color GetLinkColor (Piece piece, int i)
593 if (Enabled == false ||
594 (piece.link != null && piece.link.Enabled == false))
595 color = DisabledLinkColor;
597 if (piece.clicked == true)
598 color = ActiveLinkColor;
600 if ((LinkVisited == true && i == 0) ||
601 (piece.link != null && piece.link.Visited == true))
602 color = VisitedLinkColor;
609 private void CreateLinkFont ()
611 if (link_font != null)
612 link_font.Dispose ();
614 link_font = new Font (Font.FontFamily, Font.Size, Font.Style | FontStyle.Underline,
618 #endregion // Private Methods
621 // System.Windows.Forms.LinkLabel.Link
625 private bool enabled;
627 private object linkData;
629 private bool visited;
630 private LinkLabel owner;
631 private bool hoovered;
642 internal Link (LinkLabel owner)
651 public bool Enabled {
652 get { return enabled; }
654 if (enabled == value)
667 return owner.Text.Length;
679 owner.CreateLinkPieces ();
683 public object LinkData {
684 get { return linkData; }
685 set { linkData = value; }
689 get { return start; }
697 owner.CreateLinkPieces ();
701 public bool Visited {
702 get { return visited; }
704 if (visited == value)
714 internal bool Hoovered {
715 get { return hoovered; }
716 set { hoovered = value; }
721 // System.Windows.Forms.LinkLabel.Link
723 public class LinkCollection : IList, ICollection, IEnumerable
725 private LinkLabel owner;
726 private ArrayList collection = new ArrayList();
728 public LinkCollection (LinkLabel owner)
731 throw new ArgumentNullException ();
738 get { return collection.Count; }
741 public bool IsReadOnly {
742 get { return false; }
745 public virtual LinkLabel.Link this[int index] {
747 if (index < 0 || index >= Count)
748 throw new ArgumentOutOfRangeException();
750 return (LinkLabel.Link) collection[index];
753 if (index < 0 || index >= Count)
754 throw new ArgumentOutOfRangeException();
756 collection[index] = value;
760 public Link Add (int start, int length)
762 return Add (start, length, null);
766 public Link Add (int start, int length, object o)
768 Link link = new Link (owner);
771 if (Count == 1 && this[0].Start == 0
772 && this[0].length == -1) {
776 link.Length = length;
779 idx = collection.Add (link);
782 owner.CreateLinkPieces ();
783 return (Link) collection[idx];
786 public virtual void Clear ()
789 owner.CreateLinkPieces ();
792 public bool Contains (LinkLabel.Link link)
794 return collection.Contains (link);
797 public IEnumerator GetEnumerator ()
799 return collection.GetEnumerator ();
802 public int IndexOf (LinkLabel.Link link)
804 return collection.IndexOf (link);
807 public void Remove (LinkLabel.Link value)
809 collection.Remove (value);
810 owner.CreateLinkPieces ();
813 public void RemoveAt (int index)
816 throw new ArgumentOutOfRangeException ("Invalid value for array index");
818 collection.Remove (collection[index]);
819 owner.CreateLinkPieces ();
822 bool IList.IsFixedSize {
826 object IList.this[int index] {
827 get { return collection[index]; }
828 set { collection[index] = value; }
831 object ICollection.SyncRoot {
835 bool ICollection.IsSynchronized {
839 void ICollection.CopyTo (Array dest, int index)
841 collection.CopyTo (dest, index);
844 int IList.Add (object control)
846 int idx = collection.Add (control);
848 owner.CreateLinkPieces ();
852 bool IList.Contains (object control)
854 return collection.Contains (control);
857 int IList.IndexOf (object control)
859 return collection.IndexOf (control);
862 void IList.Insert (int index, object value)
864 collection.Insert (index, value);
866 owner.CreateLinkPieces ();
869 void IList.Remove (object control)
871 collection.Remove (control);
872 owner.CreateLinkPieces ();