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