In corlib/System.Runtime.InteropServices:
[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
28 // COMPLETE
29
30 using System.Collections;
31 using System.ComponentModel;
32 using System.Drawing;
33 using System.Drawing.Text;
34
35 namespace System.Windows.Forms {
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                 #endregion      // Local variables
58
59                 #region ToolTipWindow Class
60                 internal class ToolTipWindow : Control {
61                         #region ToolTipWindow Class Local Variables
62                         internal StringFormat string_format;
63                         #endregion      // ToolTipWindow Class Local Variables
64
65                         #region ToolTipWindow Class Constructor
66                         internal ToolTipWindow() {
67
68                                 string_format = new StringFormat();
69                                 string_format.LineAlignment = StringAlignment.Center;
70                                 string_format.Alignment = StringAlignment.Center;
71                                 string_format.FormatFlags = StringFormatFlags.NoWrap;
72                                 string_format.HotkeyPrefix = HotkeyPrefix.Hide;
73
74                                 Visible = false;
75                                 Size = new Size(100, 20);
76                                 ForeColor = ThemeEngine.Current.ColorInfoText;
77                                 BackColor = ThemeEngine.Current.ColorInfo;
78
79                                 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
80
81                                 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
82                                 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
83                         }
84
85                         #endregion      // ToolTipWindow Class Constructor
86
87                         #region ToolTipWindow Class Protected Instance Methods
88                         protected override void OnCreateControl() {
89                                 base.OnCreateControl ();
90                                 XplatUI.SetTopmost(this.window.Handle, IntPtr.Zero, true);
91                         }
92
93                         protected override CreateParams CreateParams {
94                                 get {
95                                         CreateParams cp;
96
97                                         cp = base.CreateParams;
98
99                                         cp.Style = (int)WindowStyles.WS_POPUP;
100                                         cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
101
102                                         cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
103
104                                         return cp;
105                                 }
106                         }
107
108                         protected override void OnPaint(PaintEventArgs pevent) {
109                                 // We don't do double-buffering on purpose:
110                                 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
111                                 // 2) We don't draw much, no need to double buffer
112                                 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
113
114                                 base.OnPaint(pevent);
115                         }
116
117                         protected override void Dispose(bool disposing) {
118                                 if (disposing) {
119                                         this.string_format.Dispose();
120                                 }
121                                 base.Dispose (disposing);
122                         }
123
124                         protected override void WndProc(ref Message m) {
125                                 if (m.Msg == (int)Msg.WM_SETFOCUS) {
126                                         if (m.WParam != IntPtr.Zero) {
127                                                 XplatUI.SetFocus(m.WParam);
128                                         }
129                                 }
130                                 base.WndProc (ref m);
131                         }
132
133
134                         #endregion      // ToolTipWindow Class Protected Instance Methods
135
136                         #region ToolTipWindow Class Private Methods
137                         private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
138                                 Control control = (Control)sender;
139
140                                 if (control.is_visible) {
141                                         XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, true);
142                                 } else {
143                                         XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, false);
144                                 }
145                         }
146                         #endregion      // ToolTipWindow Class Protected Instance Methods
147
148                         public void Present (Control control, string text)
149                         {
150                                 if (IsDisposed)
151                                         return;
152
153                                 Size display_size;
154                                 XplatUI.GetDisplaySize (out display_size);
155
156                                 Size size = ThemeEngine.Current.ToolTipSize (this, text);
157                                 Width = size.Width;
158                                 Height = size.Height;
159                                 Text = text;
160
161                                 int cursor_w, cursor_h, hot_x, hot_y;
162                                 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
163                                 Point loc = Control.MousePosition;
164                                 loc.Y += (cursor_h - hot_y);
165
166                                 if ((loc.X + Width) > display_size.Width)
167                                         loc.X = display_size.Width - Width;
168
169                                 if ((loc.Y + Height) > display_size.Height)
170                                         loc.Y = Control.MousePosition.Y - Height - hot_y;
171                                 
172                                 Location = loc;
173                                 Visible = true;
174                         }
175                 }
176                 #endregion      // ToolTipWindow Class
177
178                 #region Public Constructors & Destructors
179                 public ToolTip() {
180
181                         // Defaults from MS
182                         is_active = true;
183                         automatic_delay = 500;
184                         autopop_delay = 5000;
185                         initial_delay = 500;
186                         re_show_delay = 100;
187                         show_always = false;
188
189                         tooltip_strings = new Hashtable(5);
190                         controls = new ArrayList(5);
191
192                         tooltip_window = new ToolTipWindow();
193                         tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
194
195                         timer = new Timer();
196                         timer.Enabled = false;
197                         timer.Tick +=new EventHandler(timer_Tick);
198                 }
199
200                 public ToolTip(System.ComponentModel.IContainer cont) : this() {
201                         cont.Add (this);
202                 }
203
204                 ~ToolTip() {
205                 }
206                 #endregion      // Public Constructors & Destructors
207
208                 #region Public Instance Properties
209                 [DefaultValue (true)]
210                 public bool Active {
211                         get {
212                                 return is_active;
213                         }
214
215                         set {
216                                 if (is_active != value) {
217                                         is_active = value;
218
219                                         if (tooltip_window.Visible) {
220                                                 tooltip_window.Visible = false;
221                                                 active_control = null;
222                                         }
223                                 }
224                         }
225                 }
226
227                 [DefaultValue (500)]
228                 [RefreshProperties (RefreshProperties.All)]
229                 public int AutomaticDelay {
230                         get {
231                                 return automatic_delay;
232                         }
233
234                         set {
235                                 if (automatic_delay != value) {
236                                         automatic_delay = value;
237                                         autopop_delay = automatic_delay * 10;
238                                         initial_delay = automatic_delay;
239                                         re_show_delay = automatic_delay / 5;
240                                 }
241                         }
242                 }
243
244                 [RefreshProperties (RefreshProperties.All)]
245                 public int AutoPopDelay {
246                         get {
247                                 return autopop_delay;
248                         }
249
250                         set {
251                                 if (autopop_delay != value) {
252                                         autopop_delay = value;
253                                 }
254                         }
255                 }
256
257                 [RefreshProperties (RefreshProperties.All)]
258                 public int InitialDelay {
259                         get {
260                                 return initial_delay;
261                         }
262
263                         set {
264                                 if (initial_delay != value) {
265                                         initial_delay = value;
266                                 }
267                         }
268                 }
269
270                 [RefreshProperties (RefreshProperties.All)]
271                 public int ReshowDelay {
272                         get {
273                                 return re_show_delay;
274                         }
275
276                         set {
277                                 if (re_show_delay != value) {
278                                         re_show_delay = value;
279                                 }
280                         }
281                 }
282
283                 [DefaultValue (false)]
284                 public bool ShowAlways {
285                         get {
286                                 return show_always;
287                         }
288
289                         set {
290                                 if (show_always != value) {
291                                         show_always = value;
292                                 }
293                         }
294                 }
295                 #endregion      // Public Instance Properties
296
297                 #region Public Instance Methods
298                 public bool CanExtend(object target) {
299                         return false;
300                 }
301
302                 [Localizable (true)]
303                 [DefaultValue ("")]
304                 public string GetToolTip(Control control) {
305                         string tooltip = (string)tooltip_strings[control];
306                         if (tooltip == null)
307                                 return "";
308                         return tooltip;
309                 }
310
311                 public void RemoveAll() {
312                         tooltip_strings.Clear();
313                         controls.Clear();
314                 }
315
316                 public void SetToolTip(Control control, string caption) {
317                         tooltip_strings[control] = caption;
318
319                         // no need for duplicates
320                         if (!controls.Contains(control)) {
321                                 control.MouseEnter += new EventHandler(control_MouseEnter);
322                                 control.MouseMove += new MouseEventHandler(control_MouseMove);
323                                 control.MouseLeave += new EventHandler(control_MouseLeave);
324                                 controls.Add(control);
325                         }
326                         
327                         // if SetToolTip is called from a control and the mouse is currently over that control,
328                         // make sure that tooltip_window.Text gets updated
329                         if (caption != null && last_control == control) {
330                                 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
331                                 tooltip_window.Width = size.Width;
332                                 tooltip_window.Height = size.Height;
333                                 tooltip_window.Text = caption;
334                         }
335                 }
336
337                 public override string ToString() {
338                         return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
339                 }
340                 #endregion      // Public Instance Methods
341
342                 #region Protected Instance Methods
343                 protected override void Dispose(bool disposing) {
344                         if (disposing) {
345                                 // Mop up the mess; or should we wait for the GC to kick in?
346                                 timer.Stop();
347                                 timer.Dispose();
348
349                                 // Not sure if we should clean up tooltip_window
350                                 tooltip_window.Dispose();
351
352                                 tooltip_strings.Clear();
353                                 
354                                 controls.Clear();
355                         }
356                 }
357                 #endregion      // Protected Instance Methods
358
359                 enum TipState {
360                         Initial,
361                         Show,
362                         Down
363                 }
364
365                 TipState state = TipState.Initial;
366
367                 #region Private Methods
368                 private void control_MouseEnter(object sender, EventArgs e) 
369                 {
370                         last_control = (Control)sender;
371
372                         // Whatever we're displaying right now, we don't want it anymore
373                         tooltip_window.Visible = false;
374                         timer.Stop();
375                         state = TipState.Initial;
376
377                         // if we're in the same control as before (how'd that happen?) or if we're not active, leave
378                         if (!is_active || (active_control == (Control)sender)) {
379                                 return;
380                         }
381
382                         if (!show_always) {
383                                 IContainerControl cc = last_control.GetContainerControl ();
384                                 if ((cc == null) || (cc.ActiveControl == null)) {
385                                         return;
386                                 }
387                         }
388
389                         string text = (string)tooltip_strings[sender];
390                         if (text != null && text.Length > 0) {
391
392                                 if (active_control == null) {
393                                         timer.Interval = initial_delay;
394                                 } else {
395                                         timer.Interval = re_show_delay;
396                                 }
397
398                                 active_control = (Control)sender;
399                                 timer.Start ();
400                         }
401                 }
402
403                 private void timer_Tick(object sender, EventArgs e) {
404                         timer.Stop();
405
406                         switch (state) {
407                         case TipState.Initial:
408                                 if (active_control == null)
409                                         return;
410                                 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
411                                 state = TipState.Show;
412                                 timer.Interval = autopop_delay;
413                                 timer.Start();
414                                 break;
415
416                         case TipState.Show:
417                                 tooltip_window.Visible = false;
418                                 state = TipState.Down;
419                                 break;
420
421                         default:
422                                 throw new Exception ("Timer shouldn't be running in state: " + state);
423                         }
424                 }
425
426
427                 private bool MouseInControl(Control control) {
428                         Point   m;
429                         Point   c;
430                         Size    cw;
431
432                         if (control == null) {
433                                 return false;
434                         }
435
436                         m = Control.MousePosition;
437                         c = new Point(control.Bounds.X, control.Bounds.Y);
438                         if (control.Parent != null) {
439                                 c = control.Parent.PointToScreen(c);
440                         }
441                         cw = control.ClientSize;
442
443                         if (c.X<=m.X && m.X<(c.X+cw.Width) &&
444                                 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
445                                 return true;
446                         }
447                         return false;
448                 }
449
450                 private void control_MouseLeave(object sender, EventArgs e) {
451                         timer.Stop();
452
453                         if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
454                                 active_control = null;
455                                 tooltip_window.Visible = false;
456                         }
457                         
458                         if (last_control == (Control)sender)
459                                 last_control = null;
460                 }
461
462                 private void control_MouseMove(object sender, MouseEventArgs e) {
463                         if (state != TipState.Down) {
464                                 timer.Stop();
465                                 timer.Start();
466                         }
467                 }
468                 #endregion      // Private Methods
469         }
470 }