* X11Keyboard.cs: Detect and use the num lock mask.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / LinkLabel.cs
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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.
19 //
20 // Copyright (c) 2004 Novell, Inc.
21 //
22 // Authors:
23 //      Jordi Mas i Hernandez, jordi@ximian.com
24 //
25 // Based on work by:
26 //      Daniel Carrera, dcarrera@math.toronto.edu (stubbed out)
27 //      Jaak Simm (jaaksimm@firm.ee) (stubbed out)
28 //
29 // TODO:
30 //      - Change the cursor to a hand cursor when you are over a link (when cursors are available)
31 //      - Focus handeling
32 //
33 // $Revision: 1.9 $
34 // $Modtime: $
35 // $Log: LinkLabel.cs,v $
36 // Revision 1.9  2004/09/28 18:44:25  pbartok
37 // - Streamlined Theme interfaces:
38 //   * Each DrawXXX method for a control now is passed the object for the
39 //     control to be drawn in order to allow accessing any state the theme
40 //     might require
41 //
42 //   * ControlPaint methods for the theme now have a CP prefix to avoid
43 //     name clashes with the Draw methods for controls
44 //
45 //   * Every control now retrieves it's DefaultSize from the current theme
46 //
47 // Revision 1.8  2004/09/07 09:40:15  jordi
48 // LinkLabel fixes, methods, multiple links
49 //
50 // Revision 1.7  2004/08/21 22:32:14  pbartok
51 // - Signature Fixes
52 //
53 // Revision 1.6  2004/08/10 15:24:35  jackson
54 // Let Control handle buffering.
55 //
56 // Revision 1.5  2004/08/08 17:52:12  jordi
57 // *** empty log message ***
58 //
59 // Revision 1.4  2004/08/07 23:31:15  jordi
60 // fixes label bug and draw method name
61 //
62 // Revision 1.3  2004/08/07 19:16:31  jordi
63 // throw exceptions, fixes events, missing methods
64 //
65 // Revision 1.2  2004/07/22 15:22:19  jordi
66 // link label: check link overlapping, implement events, and fixes
67 //
68 // Revision 1.1  2004/07/21 16:19:17  jordi
69 // LinkLabel control implementation
70 //
71 //
72 // INCOMPLETE
73
74
75 using System.ComponentModel;
76 using System.Collections;
77 using System.Drawing;
78 using System.Drawing.Drawing2D;
79
80 namespace System.Windows.Forms
81 {
82         public class LinkLabel : Label, IButtonControl
83         {
84                 /* Encapsulates a piece of text (regular or link)*/
85                 internal class Piece
86                 {
87                         public string           text;
88                         public int              start;
89                         public int              end;
90                         public LinkLabel.Link   link;   // Empty link indicates regular text
91                         public RectangleF       rect;
92                         public bool             clicked;
93
94                         public Piece ()
95                         {
96                                 start = end = 0;
97                                 link = null;
98                                 clicked = false;
99                         }
100                 }
101
102                 private Color active_link;
103                 private Color disabled_link;
104                 private Color link_color;
105                 private Color visited_color;
106                 private LinkArea link_area;
107                 private LinkBehavior link_behavior;
108                 private LinkCollection link_collection;
109                 private bool link_visited;
110                 private Font link_font;
111                 private bool link_click;
112                 private Piece[] pieces;
113                 private Cursor override_cursor;
114                 private DialogResult dialog_result;
115
116                 #region Events
117                 public event LinkLabelLinkClickedEventHandler LinkClicked;
118                 #endregion // Events
119
120                 public LinkLabel ()
121                 {
122                         link_collection = new LinkCollection (this);
123                         LinkArea = new LinkArea (0, -1);
124                         link_behavior = LinkBehavior.SystemDefault;
125                         link_visited = false;
126                         link_click = false;
127                         pieces = null;
128
129                         ActiveLinkColor = Color.Red;
130                         DisabledLinkColor = ThemeEngine.Current.ColorGrayText;
131                         LinkColor = Color.FromArgb (255, 0, 0, 255);
132                         VisitedLinkColor = Color.FromArgb (255, 128, 0, 128);
133                 }
134
135                 #region Public Properties
136
137                 public Color ActiveLinkColor {
138                         get { return active_link;}
139                         set {
140                                 if (active_link == value)
141                                         return;
142
143                                 active_link = value;
144                                 Refresh ();
145                         }
146                 }
147
148                 public Color DisabledLinkColor {
149
150                         get { return disabled_link;}
151                         set {
152                                 if (disabled_link == value)
153                                         return;
154
155                                 disabled_link = value;
156                                 Refresh ();
157                         }
158                 }
159
160                 public Color LinkColor {
161                         get { return link_color;}
162                         set {
163                                 if (link_color == value)
164                                         return;
165
166                                 link_color = value;
167                                 Refresh ();
168                         }
169                 }
170
171                 public Color VisitedLinkColor {
172                         get { return visited_color;}
173                         set {
174                                 if (visited_color == value)
175                                         return;
176
177                                 visited_color = value;
178                                 Refresh ();
179                         }
180                 }
181
182                 public LinkArea LinkArea {
183                         get { return link_area;}
184                         set {
185
186                                 if (value.Start <0 || value.Length > 0)
187                                         throw new ArgumentException ();
188
189                                 if (!value.IsEmpty)
190                                         Links.Add (value.Start, value.Length);
191
192                                 link_area = value;
193                                 Refresh ();
194                         }
195                 }
196
197                 public LinkBehavior LinkBehavior {
198
199                         get { return link_behavior;}
200                         set {
201                                 if (link_behavior == value)
202                                         return;
203
204                                 link_behavior = value;
205                                 Refresh ();
206                         }
207                 }
208
209                 public LinkLabel.LinkCollection Links {
210                         get { return link_collection;}
211                 }
212
213                 public bool LinkVisited {
214                         get { return link_visited;}
215                         set {
216                                 if (link_visited == value)
217                                         return;
218
219                                 link_visited = value;
220                                 Refresh ();
221                         }
222                 }
223                 \r
224                 protected Cursor OverrideCursor {
225                         get { return override_cursor;}
226                         set { override_cursor = value;}
227                 }
228
229                 public override string Text {
230                         get { return base.Text; }
231                         set {
232                                 if (base.Text == value)
233                                         return;
234
235                                 base.Text = value;
236                                 Refresh ();
237                         }
238                 }
239
240                 #endregion // Public Properties
241
242                 DialogResult IButtonControl.DialogResult {
243                         get { return dialog_result; }
244                         set { dialog_result = value; }
245                 }
246
247
248                 void IButtonControl.NotifyDefault (bool value)
249                 {
250
251                 }
252
253                 void IButtonControl.PerformClick ()
254                 {
255                         throw new NotImplementedException ();
256                 }
257
258                 #region Public Methods
259                 protected override AccessibleObject CreateAccessibilityInstance()
260                 {
261                         return base.CreateAccessibilityInstance();
262                 }
263
264                 protected override void CreateHandle ()
265                 {
266                         CreateLinkFont ();
267                         CreateLinkPieces ();
268                         base.CreateHandle();
269                 }
270
271
272                 protected override void OnEnabledChanged (EventArgs e)
273                 {
274                         base.OnEnabledChanged (e);
275                         Refresh ();
276                 }
277
278                 protected override void OnFontChanged (EventArgs e)
279                 {
280                         base.OnFontChanged (e);
281                         CreateLinkFont ();
282                         Refresh ();
283                 }
284
285                 protected override void OnGotFocus (EventArgs e)
286                 {
287                         base.OnGotFocus(e);
288                 }
289
290                 protected override void OnKeyDown (KeyEventArgs e)
291                 {
292                         base.OnKeyDown(e);
293                 }
294 \r
295                 protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e)
296                 {
297                         if (LinkClicked != null)
298                                 LinkClicked (this, e);
299                 }
300
301                 protected override void OnLostFocus (EventArgs e)
302                 {
303                         base.OnLostFocus (e);
304                 }
305
306                 protected override void OnMouseDown (MouseEventArgs e)
307                 {
308                         if (!Enabled) return;
309
310                         base.OnMouseDown(e);
311                         this.Capture = true;
312
313                         for (int i = 0; i < pieces.Length; i++) {
314                                 if (pieces[i].rect.Contains (e.X, e.Y)) {
315                                         if (pieces[i].link!= null) {
316                                                 pieces[i].clicked = true;
317                                                 Refresh ();
318                                         }
319                                         break;
320                                 }
321                         }
322                 }
323
324                 protected override void OnMouseLeave(EventArgs e)
325                 {
326                         if (!Enabled) return;
327
328                         base.OnMouseLeave(e);
329                 }
330
331                 protected override void OnMouseMove (MouseEventArgs e)
332                 {
333                         base.OnMouseMove (e);
334                 }
335
336                 protected override void OnMouseUp (MouseEventArgs e)
337                 {
338                         if (!Enabled) return;
339
340                         base.OnMouseUp (e);
341                         this.Capture = false;
342
343                         for (int i = 0; i < pieces.Length; i++) {
344                                 if (pieces[i].link!= null && pieces[i].clicked == true) {
345
346                                         if (LinkClicked != null)
347                                                 LinkClicked (this, new LinkLabelLinkClickedEventArgs (pieces[i].link));
348
349                                         pieces[i].clicked = false;
350                                         Refresh ();
351                                 }
352                         }
353                 }
354
355                 protected override void OnPaint (PaintEventArgs pevent)
356                 {
357                         if (Width <= 0 || Height <=  0 || Visible == false)
358                                 return;
359
360                         Draw ();
361                         pevent.Graphics.DrawImage (ImageBuffer, 0, 0);
362                 }
363
364                 protected override void OnPaintBackground(PaintEventArgs e)
365                 {
366
367                 }
368
369                 protected override void OnTextAlignChanged (EventArgs e)
370                 {
371                         base.OnTextAlignChanged (e);
372                         Refresh ();
373                 }
374
375                 protected override void OnTextChanged (EventArgs e)
376                 {
377                         base.OnTextChanged (e);
378                         Refresh ();
379                 }
380                 \r
381                 protected Link PointInLink (int x, int y)
382                 {
383                         for (int i = 0; i < pieces.Length; i++) {
384                                 if (pieces[i].rect.Contains (x,y) && pieces[i].link != null)
385                                         return pieces[i].link;
386                         }
387
388                         return null;
389                 }
390
391                 protected override bool ProcessDialogKey (Keys keyData)
392                 {
393                         return base.ProcessDialogKey (keyData);
394                 }
395
396                 protected override void Select (bool directed, bool forward)
397                 {
398                         base.Select (directed, forward);
399                 }
400
401                 public void Select ()
402                 {
403                         base.Select ();
404                 }
405                 \r
406                 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
407                 {
408                         base.SetBoundsCore (x, y, width, height, specified);
409                         Refresh ();
410                 }
411
412                 protected override void WndProc (ref Message m)
413                 {
414                         base.WndProc (ref m);
415                 }
416
417                 #endregion //Public Methods
418
419                 #region Private Methods
420
421                 internal void CreateLinkPieces ()
422                 {
423                         if (Links.Count == 0)
424                                 return;
425
426                         int cur_piece = 0;
427
428                         if (Links.Count == 1 && Links[0].Start == 0 &&  Links[0].Length == -1) {
429                                 pieces = new Piece [1];
430                                 pieces[cur_piece] = new Piece();
431                                 pieces[cur_piece].start = 0;
432                                 pieces[cur_piece].end = Text.Length;
433                                 pieces[cur_piece].link = Links[0];
434                                 pieces[cur_piece].text = Text;
435                                 pieces[cur_piece].rect = ClientRectangle;
436                                 return;
437                         }
438
439                         pieces = new Piece [(Links.Count * 2) + 1];
440                         pieces[cur_piece] = new Piece();
441                         pieces[cur_piece].start = 0;
442
443                         for (int i = 0; i < Text.Length; i++) { /* Every char on the text*/
444                                 for (int l = 0; l < Links.Count; l++)   { /* Every link that we know of*/
445                                         if (Links[l].Start == i) {
446                                                 if (i > 0) {
447                                                         /*Push prev. regular text*/
448                                                         pieces[cur_piece].end = i;
449                                                         pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start,
450                                                                 pieces[cur_piece].end - pieces[cur_piece].start);
451
452                                                         cur_piece++;
453
454                                                         /* New link*/
455                                                         pieces[cur_piece] = new Piece ();
456                                                 }
457
458                                                 pieces[cur_piece].start = Links[l].Start;
459                                                 pieces[cur_piece].end = Links[l].Start + Links[l].Length;
460                                                 pieces[cur_piece].link = Links[l];
461                                                 pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start,
462                                                 pieces[cur_piece].end - pieces[cur_piece].start);
463
464                                                 cur_piece++; /* Push link*/
465                                                 pieces[cur_piece] = new Piece();
466                                                 i+= Links[l].Length;
467                                                 pieces[cur_piece].start = i;
468                                         }
469                                 }
470                         }
471
472                         if (pieces[cur_piece].end == 0) {
473                                 pieces[cur_piece].end = Text.Length;
474                                 pieces[cur_piece].text = Text.Substring (pieces[cur_piece].start, pieces[cur_piece].end - pieces[cur_piece].start);
475                         }
476
477                         CharacterRange[] charRanges = new CharacterRange [pieces.Length];
478
479                         for (int i = 0; i < pieces.Length; i++)
480                                 charRanges[i] = new CharacterRange (pieces[i].start, pieces[i].end - pieces[i].start);
481
482                         Region[] charRegions = new Region [pieces.Length];
483                         string_format.SetMeasurableCharacterRanges (charRanges);
484
485                         charRegions = DeviceContext.MeasureCharacterRanges (Text, Font, ClientRectangle, string_format);
486
487                         for (int i = 0; i < pieces.Length; i++)  {
488                                 //RectangleF[] f = charRegions[i].GetRegionScans (new Matrix());
489                                 pieces[i].rect = charRegions[i].GetBounds (DeviceContext);
490                                 Console.WriteLine (pieces[i].rect);
491                         }
492
493                         if (Visible && IsHandleCreated)
494                                 Refresh ();
495
496                 }
497
498                 /* Check if the links overlap */
499                 internal void CheckLinks ()
500                 {
501                         for (int i = 0; i < Links.Count; i++) {
502                                 for (int l = 0; l < Links.Count; l++) {
503                                         if (i==l) continue;
504
505                                         if (((Links[i].Start + Links[i].Length) >= Links[l].Start &&
506                                                 Links[i].Start + Links[i].Length <= Links[l].Start + Links[l].Length) ||
507                                                 (Links[i].Start  >= Links[l].Start &&
508                                                 Links[i].Start  <= Links[l].Start + Links[l].Length))
509                                                 throw new InvalidOperationException ("Overlapping link regions.");
510                                 }
511                         }
512                 }
513
514                 private Color GetLinkColor (Piece piece, int i)
515                 {
516                         Color color;
517
518                         if (Enabled == false ||
519                                 (piece.link != null && piece.link.Enabled == false))
520                                 color = DisabledLinkColor;
521                         else
522                                 if (piece.clicked == true)
523                                         color = ActiveLinkColor;
524                                 else
525                                         if ((LinkVisited == true && i == 0) ||
526                                                 (piece.link != null && piece.link.Visited == true))
527                                                 color = VisitedLinkColor;
528                                         else
529                                                 color = LinkColor;
530
531                         return color;
532                 }
533
534                 internal void Draw ()
535                 {
536                         Color color;
537
538                         //dc.FillRectangle (label_br_back_color, area);
539                         ThemeEngine.Current.CPDrawBorderStyle (DeviceContext, ClientRectangle, BorderStyle);
540
541                         if (Links.Count == 1 && Links[0].Start == 0 &&  Links[0].Length == -1) {
542
543                                 color = GetLinkColor (pieces[0], 0);
544                                 DeviceContext.DrawString (Text, Font, new SolidBrush (color),
545                                         ClientRectangle, string_format);
546                                 return;
547                         }
548
549                         for (int i = 0; i < pieces.Length; i++) {
550
551                                 color = GetLinkColor (pieces[i], i);
552
553                                 if (pieces[i].link == null)
554                                         DeviceContext.DrawString (pieces[i].text, Font, new SolidBrush (Color.Black),
555                                                 pieces[i].rect.X, pieces[i].rect.Y, string_format);
556                                 else
557                                         DeviceContext.DrawString (pieces[i].text, link_font, new SolidBrush (color),
558                                                 pieces[i].rect.X, pieces[i].rect.Y, string_format);
559                         }
560
561                         DrawImage (DeviceContext, Image, ClientRectangle, image_align);
562                 }
563
564                 private void CreateLinkFont ()
565                 {
566                         link_font  = new Font (Font.FontFamily, Font.Size, Font.Style | FontStyle.Underline,
567                                  Font.Unit);
568                 }
569
570                 #endregion // Private Methods
571
572                 //
573                 // System.Windows.Forms.LinkLabel.Link
574                 //
575                 public class Link
576                 {
577                         private bool enabled;
578                         private int length;
579                         private object linkData;
580                         private int start;
581                         private bool visited;
582                         private LinkLabel owner;
583
584                         internal Link ()
585                         {
586                                 enabled = true;
587                                 visited = false;
588                                 length = start = 0;
589                                 linkData = null;
590                                 owner = null;
591                         }
592
593                         internal Link (LinkLabel owner)
594                         {
595                                 enabled = true;
596                                 visited = false;
597                                 length = start = 0;
598                                 linkData = null;
599                                 this.owner = owner;
600                         }
601
602                         public bool Enabled {
603                                 get { return enabled; }
604                                 set {
605                                         if (enabled == value)
606                                                 return;
607
608                                         enabled = value;
609
610                                         if (owner != null)
611                                                 owner.CreateLinkPieces ();
612                                 }
613                         }
614
615                         public int Length {
616                                 get { return length; }
617                                 set {
618                                         if (length == value)
619                                                 return;
620
621                                         length = value;
622
623                                         if (owner != null)
624                                                 owner.CreateLinkPieces ();
625                                 }
626                         }
627
628                         public object LinkData {
629                                 get { return linkData; }
630                                 set { linkData = value; }
631                         }
632
633                         public int Start {
634                                 get { return start; }
635                                 set {
636                                         if (start == value)
637                                                 return;
638
639                                         start = value;
640
641                                         if (owner != null)
642                                                 owner.CreateLinkPieces ();
643                                 }
644                         }
645
646                         public bool Visited {
647                                 get { return visited; }
648                                 set {
649                                         if (visited == value)
650                                                 return;
651
652                                         visited = value;
653
654                                         if (owner != null)
655                                                 owner.CreateLinkPieces ();
656                                 }
657                         }
658                 }
659
660                 //
661                 // System.Windows.Forms.LinkLabel.Link
662                 //
663                 public class LinkCollection :  IList, ICollection, IEnumerable
664                 {
665                         private LinkLabel owner;
666                         private ArrayList collection = new ArrayList();
667
668                         public LinkCollection (LinkLabel owner)
669                         {
670                                 if (owner==null)
671                                         throw new ArgumentNullException ();
672
673                                 this.owner = owner;
674                         }
675
676                         public int Count {
677                                 get { return collection.Count; }
678                         }
679
680                         public bool IsReadOnly {
681                                 get { return false; }
682                         }
683
684                         public virtual LinkLabel.Link this[int index]  {
685                                 get {
686                                         if (index < 0 || index >= Count)
687                                                 throw  new  ArgumentOutOfRangeException();
688
689                                         return (LinkLabel.Link) collection[index];
690                                 }
691                                 set {
692                                         if (index < 0 || index >= Count)
693                                                 throw new  ArgumentOutOfRangeException();
694
695                                         collection[index] = value;
696                                 }
697                         }
698
699                         public Link Add (int start, int length)
700                         {
701                                 return Add (start, length, null);
702                         }
703
704
705                         public Link Add (int start, int length, object o)
706                         {
707                                 Link link = new Link ();
708                                 int idx;
709
710                                 if (Count == 1 && this[0].Start == 0
711                                         && this[0].Length == -1) {
712                                         Console.WriteLine ("Clear list");
713                                         Clear ();
714                                 }
715
716                                 link.Length = length;
717                                 link.Start = start;
718                                 link.LinkData = o;
719                                 idx = collection.Add (link);
720
721                                 owner.CheckLinks ();
722                                 owner.CreateLinkPieces ();
723                                 return (Link) collection[idx];
724                         }
725
726                         public virtual void Clear ()
727                         {
728                                 collection.Clear();
729                                 owner.CreateLinkPieces ();
730                         }
731
732                         public bool Contains (LinkLabel.Link link)
733                         {
734                                 return collection.Contains (link);
735                         }
736
737                         public IEnumerator GetEnumerator ()
738                         {
739                                 return collection.GetEnumerator ();
740                         }
741
742                         public int IndexOf (LinkLabel.Link link)
743                         {
744                                 return collection.IndexOf (link);
745                         }
746
747                         public void Remove (LinkLabel.Link value)
748                         {
749                                 collection.Remove (value);
750                                 owner.CreateLinkPieces ();
751                         }
752
753                         public void RemoveAt (int index)
754                         {
755                                 if (index >= Count)
756                                         throw new ArgumentOutOfRangeException ("Invalid value for array index");
757
758                                 collection.Remove (collection[index]);
759                                 owner.CreateLinkPieces ();
760                         }
761
762                         bool IList.IsFixedSize {
763                                 get {return false;}
764                         }
765
766                         object IList.this[int index] {
767                                 get { return collection[index]; }
768                                 set { collection[index] = value; }
769                         }
770
771                         object ICollection.SyncRoot {
772                                 get {return this;}
773                         }
774
775                         bool ICollection.IsSynchronized {
776                                 get {return false;}
777                         }
778
779                         void ICollection.CopyTo (Array dest, int index)
780                         {
781                                 collection.CopyTo (dest, index);
782                         }
783
784                         int IList.Add (object control)
785                         {
786                                 return collection.Add (control);
787                         }
788
789                         bool IList.Contains (object control)
790                         {
791                                 return collection.Contains (control);
792                         }
793
794                         int IList.IndexOf (object control)
795                         {
796                                 return collection.IndexOf (control);
797                         }
798
799                         void IList.Insert (int index, object value)
800                         {
801                                 collection.Insert (index, value);
802                         }
803
804                         void IList.Remove (object control)
805                         {
806                                 collection.Remove (control);
807                         }
808                 }
809         }
810 }