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