2007-08-10 Jonathan Pobst <monkey@jpobst.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / ToolTip.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. (http://www.novell.com)
21 //
22 // Authors:
23 //      Peter Bartok    pbartok@novell.com
24 //
25 //
26
27 using System.Collections;
28 using System.ComponentModel;
29 using System.Drawing;
30 using System.Drawing.Text;
31
32 namespace System.Windows.Forms {
33 #if NET_2_0
34         [DefaultEvent ("Popup")]
35 #endif
36         [ProvideProperty ("ToolTip", typeof(System.Windows.Forms.Control))]
37         [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
38         public
39 #if !NET_2_0
40         sealed
41 #endif
42         class ToolTip : System.ComponentModel.Component, System.ComponentModel.IExtenderProvider {
43                 #region Local variables
44                 internal bool           is_active;
45                 internal int            automatic_delay;
46                 internal int            autopop_delay;
47                 internal int            initial_delay;
48                 internal int            re_show_delay;
49                 internal bool           show_always;
50
51                 internal Color          back_color;
52                 internal Color          fore_color;
53                 
54                 internal ToolTipWindow  tooltip_window;                 // The actual tooltip window
55                 internal Hashtable      tooltip_strings;                // List of strings for each control, indexed by control
56                 internal ArrayList      controls;
57                 internal Control        active_control;                 // Control for which the tooltip is currently displayed
58                 internal Control        last_control;                   // last control the mouse was in
59                 internal Timer          timer;                          // Used for the various intervals
60
61 #if NET_2_0
62                 private bool isBalloon;
63                 private bool owner_draw;
64                 private bool stripAmpersands;
65                 private ToolTipIcon tool_tip_icon;
66                 private string tool_tip_title;
67                 private bool useAnimation;
68                 private bool useFading;
69                 private object tag;
70
71 #endif
72
73                 #endregion      // Local variables
74
75                 #region ToolTipWindow Class
76                 internal class ToolTipWindow : Control {
77                         #region ToolTipWindow Class Local Variables
78                         internal StringFormat string_format;
79 #if NET_2_0
80                         internal bool owner_draw;
81                         internal Control associated_control;
82 #endif
83                         #endregion      // ToolTipWindow Class Local Variables
84
85                         #region ToolTipWindow Class Constructor
86                         internal ToolTipWindow() {
87
88                                 string_format = new StringFormat();
89                                 string_format.LineAlignment = StringAlignment.Center;
90                                 string_format.FormatFlags = StringFormatFlags.NoWrap;
91                                 string_format.HotkeyPrefix = HotkeyPrefix.Hide;
92
93                                 Visible = false;
94                                 Size = new Size(100, 20);
95                                 ForeColor = ThemeEngine.Current.ColorInfoText;
96                                 BackColor = ThemeEngine.Current.ColorInfo;
97
98                                 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
99
100                                 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
101                                 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
102                         }
103
104                         #endregion      // ToolTipWindow Class Constructor
105
106                         #region ToolTipWindow Class Protected Instance Methods
107                         protected override void OnCreateControl() {
108                                 base.OnCreateControl ();
109                                 XplatUI.SetTopmost(this.window.Handle, true);
110                         }
111
112                         protected override CreateParams CreateParams {
113                                 get {
114                                         CreateParams cp;
115
116                                         cp = base.CreateParams;
117
118                                         cp.Style = (int)WindowStyles.WS_POPUP;
119                                         cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
120
121                                         cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
122
123                                         return cp;
124                                 }
125                         }
126
127                         protected override void OnPaint(PaintEventArgs pevent) {
128                                 // We don't do double-buffering on purpose:
129                                 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
130                                 // 2) We don't draw much, no need to double buffer
131 #if NET_2_0
132                                 if (owner_draw)
133                                         OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
134                                 else
135 #endif
136                                         ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
137
138                                 base.OnPaint(pevent);
139                         }
140
141                         protected override void OnTextChanged (EventArgs args)
142                         {
143                                 Invalidate ();
144                                 base.OnTextChanged (args); 
145                         }
146
147                         protected override void Dispose(bool disposing) {
148                                 if (disposing) {
149                                         this.string_format.Dispose();
150                                 }
151                                 base.Dispose (disposing);
152                         }
153
154                         protected override void WndProc(ref Message m) {
155                                 if (m.Msg == (int)Msg.WM_SETFOCUS) {
156                                         if (m.WParam != IntPtr.Zero) {
157                                                 XplatUI.SetFocus(m.WParam);
158                                         }
159                                 }
160                                 base.WndProc (ref m);
161                         }
162
163
164                         #endregion      // ToolTipWindow Class Protected Instance Methods
165
166                         #region ToolTipWindow Class Private Methods
167 #if NET_2_0
168                         internal virtual void OnDraw (DrawToolTipEventArgs e)
169                         {
170                                 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
171                                 if (eh != null)
172                                         eh (this, e);
173                         }
174                         
175                         internal virtual void OnPopup (PopupEventArgs e)
176                         {
177                                 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
178                                 if (eh != null)
179                                         eh (this, e);
180                         }
181 #endif
182
183                         private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
184                                 Control control = (Control)sender;
185
186                                 if (control.is_visible) {
187                                         XplatUI.SetTopmost(control.window.Handle, true);
188                                 } else {
189                                         XplatUI.SetTopmost(control.window.Handle, false);
190                                 }
191                         }
192                         #endregion      // ToolTipWindow Class Protected Instance Methods
193
194                         #region Internal Properties
195                         internal override bool ActivateOnShow { get { return false; } }
196                         #endregion
197                         
198                         public void Present (Control control, string text)
199                         {
200                                 if (IsDisposed)
201                                         return;
202
203                                 Size display_size;
204                                 XplatUI.GetDisplaySize (out display_size);
205
206 #if NET_2_0
207                                 associated_control = control;
208 #endif
209
210                                 Size size = ThemeEngine.Current.ToolTipSize (this, text);
211                                 Text = text;
212
213 #if NET_2_0
214                                 PopupEventArgs pea = new PopupEventArgs (control, control, false, size);
215                                 OnPopup (pea);
216                                 
217                                 if (pea.Cancel)
218                                         return;
219                                         
220                                 size = pea.ToolTipSize;
221 #endif
222
223                                 Width = size.Width;
224                                 Height = size.Height;
225
226                                 int cursor_w, cursor_h, hot_x, hot_y;
227                                 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
228                                 Point loc = Control.MousePosition;
229                                 loc.Y += (cursor_h - hot_y);
230
231                                 if ((loc.X + Width) > display_size.Width)
232                                         loc.X = display_size.Width - Width;
233
234                                 if ((loc.Y + Height) > display_size.Height)
235                                         loc.Y = Control.MousePosition.Y - Height - hot_y;
236                                 
237                                 Location = loc;
238                                 Visible = true;
239                         }
240
241
242                         #region Internal Events
243 #if NET_2_0
244                         static object DrawEvent = new object ();
245                         static object PopupEvent = new object ();
246
247                         public event DrawToolTipEventHandler Draw {
248                                 add { Events.AddHandler (DrawEvent, value); }
249                                 remove { Events.RemoveHandler (DrawEvent, value); }
250                         }
251
252                         public event PopupEventHandler Popup {
253                                 add { Events.AddHandler (PopupEvent, value); }
254                                 remove { Events.RemoveHandler (PopupEvent, value); }
255                         }
256 #endif
257                         #endregion
258                 }
259                 #endregion      // ToolTipWindow Class
260
261                 #region Public Constructors & Destructors
262                 public ToolTip() {
263
264                         // Defaults from MS
265                         is_active = true;
266                         automatic_delay = 500;
267                         autopop_delay = 5000;
268                         initial_delay = 500;
269                         re_show_delay = 100;
270                         show_always = false;
271                         back_color = SystemColors.Info;
272                         fore_color = SystemColors.InfoText;
273                         
274 #if NET_2_0
275                         isBalloon = false;
276                         stripAmpersands = false;
277                         useAnimation = true;
278                         useFading = true;
279 #endif
280                         tooltip_strings = new Hashtable(5);
281                         controls = new ArrayList(5);
282
283                         tooltip_window = new ToolTipWindow();
284                         tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
285 #if NET_2_0
286                         tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
287                         tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
288 #endif
289                         timer = new Timer();
290                         timer.Enabled = false;
291                         timer.Tick +=new EventHandler(timer_Tick);
292                 }
293
294                 public ToolTip(System.ComponentModel.IContainer cont) : this() {
295                         cont.Add (this);
296                 }
297
298                 ~ToolTip() {
299                 }
300                 #endregion      // Public Constructors & Destructors
301
302                 #region Public Instance Properties
303                 [DefaultValue (true)]
304                 public bool Active {
305                         get {
306                                 return is_active;
307                         }
308
309                         set {
310                                 if (is_active != value) {
311                                         is_active = value;
312
313                                         if (tooltip_window.Visible) {
314                                                 tooltip_window.Visible = false;
315                                                 active_control = null;
316                                         }
317                                 }
318                         }
319                 }
320
321                 [DefaultValue (500)]
322                 [RefreshProperties (RefreshProperties.All)]
323                 public int AutomaticDelay {
324                         get {
325                                 return automatic_delay;
326                         }
327
328                         set {
329                                 if (automatic_delay != value) {
330                                         automatic_delay = value;
331                                         autopop_delay = automatic_delay * 10;
332                                         initial_delay = automatic_delay;
333                                         re_show_delay = automatic_delay / 5;
334                                 }
335                         }
336                 }
337
338                 [RefreshProperties (RefreshProperties.All)]
339                 public int AutoPopDelay {
340                         get {
341                                 return autopop_delay;
342                         }
343
344                         set {
345                                 if (autopop_delay != value) {
346                                         autopop_delay = value;
347                                 }
348                         }
349                 }
350
351 #if NET_2_0
352                 [DefaultValue ("Color [Info]")]
353                 public Color BackColor {
354                         get { return this.back_color; }
355                         set { this.back_color = value; tooltip_window.BackColor = value; }
356                 }
357
358                 [DefaultValue ("Color [InfoText]")]
359                 public Color ForeColor
360                 {
361                         get { return this.fore_color; }
362                         set { this.fore_color = value; tooltip_window.ForeColor = value; }
363                 }
364 #endif
365
366                 [RefreshProperties (RefreshProperties.All)]
367                 public int InitialDelay {
368                         get {
369                                 return initial_delay;
370                         }
371
372                         set {
373                                 if (initial_delay != value) {
374                                         initial_delay = value;
375                                 }
376                         }
377                 }
378
379 #if NET_2_0
380                 [DefaultValue (false)]
381                 public bool OwnerDraw {
382                         get { return this.owner_draw; }
383                         set { 
384                                 this.owner_draw = value;
385                                 tooltip_window.owner_draw = value;
386                         }
387                 }
388 #endif
389
390                 [RefreshProperties (RefreshProperties.All)]
391                 public int ReshowDelay {
392                         get {
393                                 return re_show_delay;
394                         }
395
396                         set {
397                                 if (re_show_delay != value) {
398                                         re_show_delay = value;
399                                 }
400                         }
401                 }
402
403                 [DefaultValue (false)]
404                 public bool ShowAlways {
405                         get {
406                                 return show_always;
407                         }
408
409                         set {
410                                 if (show_always != value) {
411                                         show_always = value;
412                                 }
413                         }
414                 }
415
416
417 #if NET_2_0
418                 [DefaultValue (false)]
419                 public bool IsBalloon {
420                         get { return isBalloon; }
421                         set { isBalloon = value; }
422                 }
423
424                 [Browsable (true)]
425                 [DefaultValue (false)]
426                 public bool StripAmpersands {
427                         get { return stripAmpersands; }
428                         set { stripAmpersands = value; }
429                 }
430
431                 [Localizable (false)]
432                 [Bindable (true)]
433                 [TypeConverter (typeof (StringConverter))]
434                 [DefaultValue (null)]
435                 public object Tag {
436                         get { return tag; }
437                         set { tag = value; }
438                 }
439
440                 [DefaultValue (ToolTipIcon.None)]
441                 public ToolTipIcon ToolTipIcon {
442                         get { return this.tool_tip_icon; }
443                         set { this.tool_tip_icon = value; }
444                 }
445                 
446                 [DefaultValue ("")]
447                 public string ToolTipTitle {
448                         get { return this.tool_tip_title; }
449                         set { this.tool_tip_title = value; }
450                 }
451                 
452                 [Browsable (true)]
453                 [DefaultValue (true)]
454                 public bool UseAnimation {
455                         get { return useAnimation; }
456                         set { useAnimation = value; }
457                 }
458
459                 [Browsable (true)]
460                 [DefaultValue (true)]
461                 public bool UseFading {
462                         get { return useFading; }
463                         set { useFading = value; }
464                 }
465 #endif
466
467                 #endregion      // Public Instance Properties
468
469                 #region Public Instance Methods
470                 public bool CanExtend(object target) {
471                         return false;
472                 }
473
474 #if NET_2_0
475                 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
476                          "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
477 #endif
478                 [Localizable (true)]
479                 [DefaultValue ("")]
480                 public string GetToolTip (Control control)
481                 {
482                         string tooltip = (string)tooltip_strings[control];
483                         if (tooltip == null)
484                                 return "";
485                         return tooltip;
486                 }
487
488                 public void RemoveAll() {
489                         tooltip_strings.Clear();
490                         controls.Clear();
491                 }
492
493                 public void SetToolTip(Control control, string caption) {
494                         tooltip_strings[control] = caption;
495
496                         // no need for duplicates
497                         if (!controls.Contains(control)) {
498                                 control.MouseEnter += new EventHandler(control_MouseEnter);
499                                 control.MouseMove += new MouseEventHandler(control_MouseMove);
500                                 control.MouseLeave += new EventHandler(control_MouseLeave);
501                                 control.MouseDown += new MouseEventHandler (control_MouseDown);
502                                 controls.Add(control);
503                         }
504                         
505                         // if SetToolTip is called from a control and the mouse is currently over that control,
506                         // make sure that tooltip_window.Text gets updated if it's being shown,
507                         // or show the tooltip for it if is not
508                         if (active_control == control && caption != null && state == TipState.Show) {
509                                 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
510                                 tooltip_window.Width = size.Width;
511                                 tooltip_window.Height = size.Height;
512                                 tooltip_window.Text = caption;
513                                 timer.Stop ();
514                                 timer.Start ();
515                         } else if (control.IsHandleCreated && MouseInControl (control, false))
516                                 ShowTooltip (control);
517                 }
518
519                 public override string ToString() {
520                         return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
521                 }
522
523 #if NET_2_0
524                 public void Show (string text, IWin32Window win)
525                 {
526                         SetToolTip (win as Control, text);
527                         ShowTooltip (win as Control);
528                 }
529
530                 [MonoTODO ("Finish implementing tooltip location")]
531                 public void Show (string text, IWin32Window win, Point p)
532                 {
533                         SetToolTip (win as Control, text);
534                         ShowTooltip (win as Control);
535                 }
536
537                 public void Hide (IWin32Window win) 
538                 {
539                         Hide (win as Control);
540                 }
541 #endif
542
543                 #endregion      // Public Instance Methods
544
545                 #region Protected Instance Methods
546                 protected override void Dispose(bool disposing) {
547                         if (disposing) {
548                                 // Mop up the mess; or should we wait for the GC to kick in?
549                                 timer.Stop();
550                                 timer.Dispose();
551
552                                 // Not sure if we should clean up tooltip_window
553                                 tooltip_window.Dispose();
554
555                                 tooltip_strings.Clear();
556                                 
557                                 controls.Clear();
558                         }
559                 }
560                 #endregion      // Protected Instance Methods
561
562                 enum TipState {
563                         Initial,
564                         Show,
565                         Down
566                 }
567
568                 TipState state = TipState.Initial;
569
570                 #region Private Methods
571                 private void control_MouseEnter(object sender, EventArgs e) 
572                 {
573                         ShowTooltip (sender as Control);
574                 }
575
576                 private void ShowTooltip (Control control) 
577                 {
578                         last_control = control;
579
580                         // Whatever we're displaying right now, we don't want it anymore
581                         tooltip_window.Visible = false;
582                         timer.Stop();
583                         state = TipState.Initial;
584
585                         if (!is_active)
586                                 return;
587
588                         if (!show_always) {
589                                 IContainerControl cc = last_control.GetContainerControl ();
590                                 if ((cc == null) || (cc.ActiveControl == null)) {
591                                         return;
592                                 }
593                         }
594
595                         string text = (string)tooltip_strings[control];
596                         if (text != null && text.Length > 0) {
597                                 if (active_control == null) {
598                                         timer.Interval = initial_delay;
599                                 } else {
600                                         timer.Interval = Math.Max (re_show_delay, 1);
601                                 }
602
603                                 active_control = control;
604                                 timer.Start ();
605                         }
606                 }
607
608                 private void timer_Tick(object sender, EventArgs e) {
609                         timer.Stop();
610
611                         switch (state) {
612                         case TipState.Initial:
613                                 if (active_control == null)
614                                         return;
615                                 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
616                                 state = TipState.Show;
617                                 timer.Interval = autopop_delay;
618                                 timer.Start();
619                                 break;
620
621                         case TipState.Show:
622                                 tooltip_window.Visible = false;
623                                 state = TipState.Down;
624                                 break;
625
626                         default:
627                                 throw new Exception ("Timer shouldn't be running in state: " + state);
628                         }
629                 }
630
631 #if NET_2_0
632                 private void tooltip_window_Popup (object sender, PopupEventArgs e)
633                 {
634                         OnPopup (e);
635                 }
636
637                 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
638                 {
639                         OnDraw (e);
640                 }
641 #endif
642
643                 private bool MouseInControl (Control control, bool fuzzy) {
644                         Point   m;
645                         Point   c;
646                         Size    cw;
647
648                         if (control == null) {
649                                 return false;
650                         }
651
652                         m = Control.MousePosition;
653                         c = new Point(control.Bounds.X, control.Bounds.Y);
654                         if (control.Parent != null) {
655                                 c = control.Parent.PointToScreen(c);
656                         }
657                         cw = control.ClientSize;
658
659
660                         Rectangle rect = new Rectangle (c, cw);
661                         
662                         //
663                         // We won't get mouse move events on all platforms with the exact same
664                         // frequency, so cheat a bit.
665                         if (fuzzy)
666                                 rect.Inflate (2, 2);
667
668                         return rect.Contains (m);
669                 }
670
671                 private void control_MouseLeave(object sender, EventArgs e) 
672                 {
673                         Hide (sender as Control);
674                 }
675
676
677                 void control_MouseDown (object sender, MouseEventArgs e)
678                 {
679                         timer.Stop();
680
681                         active_control = null;
682                         tooltip_window.Visible = false;
683                         
684                         if (last_control == sender)
685                                 last_control = null;
686                 }
687                 
688                 private void Hide (Control sender)
689                 {
690                         timer.Stop();
691
692                         if (!MouseInControl (tooltip_window, true) && !MouseInControl (active_control, true)) {
693                                 active_control = null;
694                                 tooltip_window.Visible = false;
695                         }
696                         
697                         if (last_control == sender)
698                                 last_control = null;
699                 }
700
701                 private void control_MouseMove(object sender, MouseEventArgs e) {
702                         if (state != TipState.Down) {
703                                 timer.Stop();
704                                 timer.Start();
705                         }
706                 }
707
708 #if NET_2_0
709                 internal virtual void OnDraw (DrawToolTipEventArgs e)
710                 {
711                         DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
712                         if (eh != null)
713                                 eh (this, e);
714                 }
715
716                 internal virtual void OnPopup (PopupEventArgs e)
717                 {
718                         PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
719                         if (eh != null)
720                                 eh (this, e);
721                 }
722 #endif
723                 #endregion      // Private Methods
724
725                 #region Events
726 #if NET_2_0
727                 static object PopupEvent = new object ();
728                 static object DrawEvent = new object ();
729                 
730                 public event PopupEventHandler Popup {
731                         add { Events.AddHandler (PopupEvent, value); }
732                         remove { Events.RemoveHandler (PopupEvent, value); }
733                 }
734
735                 public event DrawToolTipEventHandler Draw {
736                         add { Events.AddHandler (DrawEvent, value); }
737                         remove { Events.RemoveHandler (DrawEvent, value); }
738                 }
739 #endif
740                 #endregion
741         }
742 }