* InternalWindowManager.cs: ToolTipShow: Don't show tooltip if the form
[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 ToolTipWindow  tooltip_window;                 // The actual tooltip window
52                 internal Hashtable      tooltip_strings;                // List of strings for each control, indexed by control
53                 internal ArrayList      controls;
54                 internal Control        active_control;                 // Control for which the tooltip is currently displayed
55                 internal Control        last_control;                   // last control the mouse was in
56                 internal Timer          timer;                          // Used for the various intervals
57
58 #if NET_2_0
59                 private bool isBalloon;
60                 private bool stripAmpersands;
61                 private bool useAnimation;
62                 private bool useFading;
63 #endif
64
65                 #endregion      // Local variables
66
67                 #region ToolTipWindow Class
68                 internal class ToolTipWindow : Control {
69                         #region ToolTipWindow Class Local Variables
70                         internal StringFormat string_format;
71                         #endregion      // ToolTipWindow Class Local Variables
72
73                         #region ToolTipWindow Class Constructor
74                         internal ToolTipWindow() {
75
76                                 string_format = new StringFormat();
77                                 string_format.LineAlignment = StringAlignment.Center;
78                                 string_format.FormatFlags = StringFormatFlags.NoWrap;
79                                 string_format.HotkeyPrefix = HotkeyPrefix.Hide;
80
81                                 Visible = false;
82                                 Size = new Size(100, 20);
83                                 ForeColor = ThemeEngine.Current.ColorInfoText;
84                                 BackColor = ThemeEngine.Current.ColorInfo;
85
86                                 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
87
88                                 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
89                                 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
90                         }
91
92                         #endregion      // ToolTipWindow Class Constructor
93
94                         #region ToolTipWindow Class Protected Instance Methods
95                         protected override void OnCreateControl() {
96                                 base.OnCreateControl ();
97                                 XplatUI.SetTopmost(this.window.Handle, true);
98                         }
99
100                         protected override CreateParams CreateParams {
101                                 get {
102                                         CreateParams cp;
103
104                                         cp = base.CreateParams;
105
106                                         cp.Style = (int)WindowStyles.WS_POPUP;
107                                         cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
108
109                                         cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
110
111                                         return cp;
112                                 }
113                         }
114
115                         protected override void OnPaint(PaintEventArgs pevent) {
116                                 // We don't do double-buffering on purpose:
117                                 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
118                                 // 2) We don't draw much, no need to double buffer
119                                 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
120
121                                 base.OnPaint(pevent);
122                         }
123
124                         protected override void OnTextChanged (EventArgs args)
125                         {
126                                 Invalidate ();
127                                 base.OnTextChanged (args); 
128                         }
129
130                         protected override void Dispose(bool disposing) {
131                                 if (disposing) {
132                                         this.string_format.Dispose();
133                                 }
134                                 base.Dispose (disposing);
135                         }
136
137                         protected override void WndProc(ref Message m) {
138                                 if (m.Msg == (int)Msg.WM_SETFOCUS) {
139                                         if (m.WParam != IntPtr.Zero) {
140                                                 XplatUI.SetFocus(m.WParam);
141                                         }
142                                 }
143                                 base.WndProc (ref m);
144                         }
145
146
147                         #endregion      // ToolTipWindow Class Protected Instance Methods
148
149                         #region ToolTipWindow Class Private Methods
150                         private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
151                                 Control control = (Control)sender;
152
153                                 if (control.is_visible) {
154                                         XplatUI.SetTopmost(control.window.Handle, true);
155                                 } else {
156                                         XplatUI.SetTopmost(control.window.Handle, false);
157                                 }
158                         }
159                         #endregion      // ToolTipWindow Class Protected Instance Methods
160
161                         #region Internal Properties
162                         internal override bool ActivateOnShow { get { return false; } }
163                         #endregion
164                         
165                         public void Present (Control control, string text)
166                         {
167                                 if (IsDisposed)
168                                         return;
169
170                                 Size display_size;
171                                 XplatUI.GetDisplaySize (out display_size);
172
173                                 Size size = ThemeEngine.Current.ToolTipSize (this, text);
174                                 Width = size.Width;
175                                 Height = size.Height;
176                                 Text = text;
177
178                                 int cursor_w, cursor_h, hot_x, hot_y;
179                                 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
180                                 Point loc = Control.MousePosition;
181                                 loc.Y += (cursor_h - hot_y);
182
183                                 if ((loc.X + Width) > display_size.Width)
184                                         loc.X = display_size.Width - Width;
185
186                                 if ((loc.Y + Height) > display_size.Height)
187                                         loc.Y = Control.MousePosition.Y - Height - hot_y;
188                                 
189                                 Location = loc;
190                                 Visible = true;
191                         }
192                 }
193                 #endregion      // ToolTipWindow Class
194
195                 #region Public Constructors & Destructors
196                 public ToolTip() {
197
198                         // Defaults from MS
199                         is_active = true;
200                         automatic_delay = 500;
201                         autopop_delay = 5000;
202                         initial_delay = 500;
203                         re_show_delay = 100;
204                         show_always = false;
205 #if NET_2_0
206                         isBalloon = false;
207                         stripAmpersands = false;
208                         useAnimation = true;
209                         useFading = true;
210 #endif
211                         tooltip_strings = new Hashtable(5);
212                         controls = new ArrayList(5);
213
214                         tooltip_window = new ToolTipWindow();
215                         tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
216
217                         timer = new Timer();
218                         timer.Enabled = false;
219                         timer.Tick +=new EventHandler(timer_Tick);
220                 }
221
222                 public ToolTip(System.ComponentModel.IContainer cont) : this() {
223                         cont.Add (this);
224                 }
225
226                 ~ToolTip() {
227                 }
228                 #endregion      // Public Constructors & Destructors
229
230                 #region Public Instance Properties
231                 [DefaultValue (true)]
232                 public bool Active {
233                         get {
234                                 return is_active;
235                         }
236
237                         set {
238                                 if (is_active != value) {
239                                         is_active = value;
240
241                                         if (tooltip_window.Visible) {
242                                                 tooltip_window.Visible = false;
243                                                 active_control = null;
244                                         }
245                                 }
246                         }
247                 }
248
249                 [DefaultValue (500)]
250                 [RefreshProperties (RefreshProperties.All)]
251                 public int AutomaticDelay {
252                         get {
253                                 return automatic_delay;
254                         }
255
256                         set {
257                                 if (automatic_delay != value) {
258                                         automatic_delay = value;
259                                         autopop_delay = automatic_delay * 10;
260                                         initial_delay = automatic_delay;
261                                         re_show_delay = automatic_delay / 5;
262                                 }
263                         }
264                 }
265
266                 [RefreshProperties (RefreshProperties.All)]
267                 public int AutoPopDelay {
268                         get {
269                                 return autopop_delay;
270                         }
271
272                         set {
273                                 if (autopop_delay != value) {
274                                         autopop_delay = value;
275                                 }
276                         }
277                 }
278
279                 [RefreshProperties (RefreshProperties.All)]
280                 public int InitialDelay {
281                         get {
282                                 return initial_delay;
283                         }
284
285                         set {
286                                 if (initial_delay != value) {
287                                         initial_delay = value;
288                                 }
289                         }
290                 }
291
292                 [RefreshProperties (RefreshProperties.All)]
293                 public int ReshowDelay {
294                         get {
295                                 return re_show_delay;
296                         }
297
298                         set {
299                                 if (re_show_delay != value) {
300                                         re_show_delay = value;
301                                 }
302                         }
303                 }
304
305                 [DefaultValue (false)]
306                 public bool ShowAlways {
307                         get {
308                                 return show_always;
309                         }
310
311                         set {
312                                 if (show_always != value) {
313                                         show_always = value;
314                                 }
315                         }
316                 }
317
318
319 #if NET_2_0
320                 [DefaultValue (false)]
321                 public bool IsBalloon {
322                         get { return isBalloon; }
323                         set { isBalloon = value; }
324                 }
325
326                 [Browsable (true)]
327                 [DefaultValue (false)]
328                 public bool StripAmpersands {
329                         get { return stripAmpersands; }
330                         set { stripAmpersands = value; }
331                 }
332
333                 [Browsable (true)]
334                 [DefaultValue (true)]
335                 public bool UseAnimation {
336                         get { return useAnimation; }
337                         set { useAnimation = value; }
338                 }
339
340                 [Browsable (true)]
341                 [DefaultValue (true)]
342                 public bool UseFading {
343                         get { return useFading; }
344                         set { useFading = value; }
345                 }
346 #endif
347
348                 #endregion      // Public Instance Properties
349
350                 #region Public Instance Methods
351                 public bool CanExtend(object target) {
352                         return false;
353                 }
354
355 #if NET_2_0
356                 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
357                          "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
358 #endif
359                 [Localizable (true)]
360                 [DefaultValue ("")]
361                 public string GetToolTip (Control control)
362                 {
363                         string tooltip = (string)tooltip_strings[control];
364                         if (tooltip == null)
365                                 return "";
366                         return tooltip;
367                 }
368
369                 public void RemoveAll() {
370                         tooltip_strings.Clear();
371                         controls.Clear();
372                 }
373
374                 public void SetToolTip(Control control, string caption) {
375                         tooltip_strings[control] = caption;
376
377                         // no need for duplicates
378                         if (!controls.Contains(control)) {
379                                 control.MouseEnter += new EventHandler(control_MouseEnter);
380                                 control.MouseMove += new MouseEventHandler(control_MouseMove);
381                                 control.MouseLeave += new EventHandler(control_MouseLeave);
382                                 controls.Add(control);
383                         }
384                         
385                         // if SetToolTip is called from a control and the mouse is currently over that control,
386                         // make sure that tooltip_window.Text gets updated if it's being shown,
387                         // or show the tooltip for it if is not
388                         if (active_control == control && caption != null && state == TipState.Show) {
389                                 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
390                                 tooltip_window.Width = size.Width;
391                                 tooltip_window.Height = size.Height;
392                                 tooltip_window.Text = caption;
393                                 timer.Stop ();
394                                 timer.Start ();
395                         } else if (MouseInControl (control))
396                                 ShowTooltip (control);
397                 }
398
399                 public override string ToString() {
400                         return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
401                 }
402
403 #if NET_2_0
404                 public void Show (string text, IWin32Window win)
405                 {
406                         SetToolTip (win as Control, text);
407                         ShowTooltip (win as Control);
408                 }
409
410                 [MonoTODO ("Finish implementing tooltip location")]
411                 public void Show (string text, IWin32Window win, Point p)
412                 {
413                         SetToolTip (win as Control, text);
414                         ShowTooltip (win as Control);
415                 }
416
417                 public void Hide (IWin32Window win) 
418                 {
419                         Hide (win as Control);
420                 }
421 #endif
422
423                 #endregion      // Public Instance Methods
424
425                 #region Protected Instance Methods
426                 protected override void Dispose(bool disposing) {
427                         if (disposing) {
428                                 // Mop up the mess; or should we wait for the GC to kick in?
429                                 timer.Stop();
430                                 timer.Dispose();
431
432                                 // Not sure if we should clean up tooltip_window
433                                 tooltip_window.Dispose();
434
435                                 tooltip_strings.Clear();
436                                 
437                                 controls.Clear();
438                         }
439                 }
440                 #endregion      // Protected Instance Methods
441
442                 enum TipState {
443                         Initial,
444                         Show,
445                         Down
446                 }
447
448                 TipState state = TipState.Initial;
449
450                 #region Private Methods
451                 private void control_MouseEnter(object sender, EventArgs e) 
452                 {
453                         ShowTooltip (sender as Control);
454                 }
455
456                 private void ShowTooltip (Control control) 
457                 {
458                         last_control = control;
459
460                         // Whatever we're displaying right now, we don't want it anymore
461                         tooltip_window.Visible = false;
462                         timer.Stop();
463                         state = TipState.Initial;
464
465                         if (!is_active)
466                                 return;
467
468                         if (!show_always) {
469                                 IContainerControl cc = last_control.GetContainerControl ();
470                                 if ((cc == null) || (cc.ActiveControl == null)) {
471                                         return;
472                                 }
473                         }
474
475                         string text = (string)tooltip_strings[control];
476                         if (text != null && text.Length > 0) {
477
478                                 if (active_control == null) {
479                                         timer.Interval = initial_delay;
480                                 } else {
481                                         timer.Interval = re_show_delay;
482                                 }
483
484                                 active_control = control;
485                                 timer.Start ();
486                         }
487                 }
488
489                 private void timer_Tick(object sender, EventArgs e) {
490                         timer.Stop();
491
492                         switch (state) {
493                         case TipState.Initial:
494                                 if (active_control == null)
495                                         return;
496                                 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
497                                 state = TipState.Show;
498                                 timer.Interval = autopop_delay;
499                                 timer.Start();
500                                 break;
501
502                         case TipState.Show:
503                                 tooltip_window.Visible = false;
504                                 state = TipState.Down;
505                                 break;
506
507                         default:
508                                 throw new Exception ("Timer shouldn't be running in state: " + state);
509                         }
510                 }
511
512
513                 private bool MouseInControl(Control control) {
514                         Point   m;
515                         Point   c;
516                         Size    cw;
517
518                         if (control == null) {
519                                 return false;
520                         }
521
522                         m = Control.MousePosition;
523                         c = new Point(control.Bounds.X, control.Bounds.Y);
524                         if (control.Parent != null) {
525                                 c = control.Parent.PointToScreen(c);
526                         }
527                         cw = control.ClientSize;
528
529                         if (c.X<=m.X && m.X<(c.X+cw.Width) &&
530                                 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
531                                 return true;
532                         }
533                         return false;
534                 }
535
536                 private void control_MouseLeave(object sender, EventArgs e) 
537                 {
538                         Hide (sender as Control);
539                 }
540
541                 private void Hide (Control sender)
542                 {
543                         timer.Stop();
544
545                         if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
546                                 active_control = null;
547                                 tooltip_window.Visible = false;
548                         }
549                         
550                         if (last_control == sender)
551                                 last_control = null;
552                 }
553
554                 private void control_MouseMove(object sender, MouseEventArgs e) {
555                         if (state != TipState.Down) {
556                                 timer.Stop();
557                                 timer.Start();
558                         }
559                 }
560
561 #if NET_2_0
562                 internal virtual void OnPopup (PopupEventArgs e)
563                 {
564                         PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
565                         if (eh != null)
566                                 eh (this, e);
567                 }
568 #endif
569                 #endregion      // Private Methods
570
571                 #region Events
572 #if NET_2_0
573                 static object PopupEvent = new object ();
574                 
575                 public event PopupEventHandler Popup {
576                         add { Events.AddHandler (PopupEvent, value); }
577                         remove { Events.RemoveHandler (PopupEvent, value); }
578                 }
579 #endif
580                 #endregion
581         }
582 }