* TreeView.cs: Don't draw the selected node when we lose
[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 Control        active_control;                 // Control for which the tooltip is currently displayed
49                 internal Control        last_control;                   // last control the mouse was in; null if the last control did not have a tooltip
50                 internal Size           display_size;                   // Size of the screen
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                         internal 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)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.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                 #endregion      // ToolTipWindow Class
145
146                 #region Public Constructors & Destructors
147                 public ToolTip() {
148                         XplatUI.GetDisplaySize(out display_size);
149
150                         // Defaults from MS
151                         is_active = true;
152                         automatic_delay = 500;
153                         autopop_delay = 5000;
154                         initial_delay = 500;
155                         re_show_delay = 100;
156                         show_always = false;
157
158                         tooltip_strings = new Hashtable(5);
159
160                         tooltip_window = new ToolTipWindow(this);
161                         tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
162
163                         timer = new Timer();
164                         timer.Enabled = false;
165                         timer.Tick +=new EventHandler(timer_Tick);
166                 }
167
168                 public ToolTip(System.ComponentModel.IContainer cont) : this() {
169                         // Dunno why I'd need the container
170                 }
171
172                 ~ToolTip() {
173                 }
174                 #endregion      // Public Constructors & Destructors
175
176                 #region Public Instance Properties
177                 [DefaultValue (true)]
178                 public bool Active {
179                         get {
180                                 return is_active;
181                         }
182
183                         set {
184                                 if (is_active != value) {
185                                         is_active = value;
186
187                                         if (tooltip_window.Visible) {
188                                                 tooltip_window.Visible = false;
189                                                 active_control = null;
190                                         }
191                                 }
192                         }
193                 }
194
195                 [DefaultValue (500)]
196                 [RefreshProperties (RefreshProperties.All)]
197                 public int AutomaticDelay {
198                         get {
199                                 return automatic_delay;
200                         }
201
202                         set {
203                                 if (automatic_delay != value) {
204                                         automatic_delay = value;
205                                         autopop_delay = automatic_delay * 10;
206                                         initial_delay = automatic_delay;
207                                         re_show_delay = automatic_delay / 5;
208                                 }
209                         }
210                 }
211
212                 [RefreshProperties (RefreshProperties.All)]
213                 public int AutoPopDelay {
214                         get {
215                                 return autopop_delay;
216                         }
217
218                         set {
219                                 if (autopop_delay != value) {
220                                         autopop_delay = value;
221                                 }
222                         }
223                 }
224
225                 [RefreshProperties (RefreshProperties.All)]
226                 public int InitialDelay {
227                         get {
228                                 return initial_delay;
229                         }
230
231                         set {
232                                 if (initial_delay != value) {
233                                         initial_delay = value;
234                                 }
235                         }
236                 }
237
238                 [RefreshProperties (RefreshProperties.All)]
239                 public int ReshowDelay {
240                         get {
241                                 return re_show_delay;
242                         }
243
244                         set {
245                                 if (re_show_delay != value) {
246                                         re_show_delay = value;
247                                 }
248                         }
249                 }
250
251                 [DefaultValue (false)]
252                 public bool ShowAlways {
253                         get {
254                                 return show_always;
255                         }
256
257                         set {
258                                 if (show_always != value) {
259                                         show_always = value;
260                                 }
261                         }
262                 }
263                 #endregion      // Public Instance Properties
264
265                 #region Public Instance Methods
266                 public bool CanExtend(object target) {
267                         return false;
268                 }
269
270                 [Localizable (true)]
271                 [DefaultValue ("")]
272                 public string GetToolTip(Control control) {
273                         string tooltip = (string)tooltip_strings[control];
274                         if (tooltip == null)
275                                 return "";
276                         return tooltip;
277                 }
278
279                 public void RemoveAll() {
280                         tooltip_strings.Clear();
281                 }
282
283                 public void SetToolTip(Control control, string caption) {
284                         tooltip_strings[control] = caption;
285
286                         control.MouseEnter += new EventHandler(control_MouseEnter);
287                         control.MouseMove += new MouseEventHandler(control_MouseMove);
288                         control.MouseLeave += new EventHandler(control_MouseLeave);
289                 }
290
291                 public override string ToString() {
292                         return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
293                 }
294                 #endregion      // Public Instance Methods
295
296                 #region Protected Instance Methods
297                 protected override void Dispose(bool disposing) {
298                         if (disposing) {
299                                 // Mop up the mess; or should we wait for the GC to kick in?
300                                 timer.Stop();
301                                 timer.Dispose();
302
303                                 // Not sure if we should clean up tooltip_window
304                                 tooltip_window.Dispose();
305
306                                 tooltip_strings.Clear();
307                         }
308                 }
309                 #endregion      // Protected Instance Methods
310
311                 #region Private Methods
312                 private void control_MouseEnter(object sender, EventArgs e) {
313                         string  text;
314
315                         // Whatever we're displaying right now, we don't want it anymore
316                         tooltip_window.Visible = false;
317                         timer.Stop();
318
319                         // if we're in the same control as before (how'd that happen?) or if we're not active, leave
320                         if (!is_active || (active_control == (Control)sender)) {
321                                 return;
322                         }
323
324                         if (!show_always) {
325                                 if (((Control)sender).GetContainerControl().ActiveControl == null) {
326                                         return;
327                                 }
328                         }
329
330                         text = (string)tooltip_strings[sender];
331                         if (text != null) {
332                                 Size size;
333
334                                 size = ThemeEngine.Current.ToolTipSize(tooltip_window, text);
335                                 tooltip_window.Width = size.Width;
336                                 tooltip_window.Height = size.Height;
337                                 tooltip_window.Text = text;
338
339                                 // FIXME - this needs to be improved; the tooltip will show up under the mouse, which is annoying; use cursor size once implemented
340
341                                 if ((Control.MousePosition.X+1+tooltip_window.Width) < display_size.Width) {
342                                         tooltip_window.Left = Control.MousePosition.X+1;
343                                 } else {
344                                         tooltip_window.Left = display_size.Width-tooltip_window.Width;
345                                 }
346
347                                 if ((Control.MousePosition.Y+tooltip_window.Height)<display_size.Height) {
348                                         tooltip_window.Top = Control.MousePosition.Y;
349                                 } else {
350                                         tooltip_window.Top = Control.MousePosition.Y-tooltip_window.Height;
351                                 }
352
353                                 // Since we get the mouse enter before the mouse leave, active_control will still be non-null if we were in a 
354                                 // tooltip'd control; should prolly check on X11 too, and make sure that driver behaves the same way
355                                 if (active_control == null) {
356                                         timer.Interval = initial_delay;
357                                 } else {
358                                         timer.Interval = re_show_delay;
359                                 }
360
361                                 active_control = (Control)sender;
362
363                                 // We're all set, lets wake the timer (which will then make us visible)
364                                 timer.Enabled = true;
365                         }
366                 }
367
368                 private void timer_Tick(object sender, EventArgs e) {
369                         // Show our pretty selves
370                         timer.Stop();
371
372                         // FIXME - Should not need this check../
373                         if (tooltip_window.IsDisposed) {
374                                 return;
375                         }
376
377                         if (!tooltip_window.Visible) {
378                                 // The initial_delay timer kicked in
379                                 tooltip_window.Visible = true;
380                                 timer.Interval = autopop_delay;
381                                 timer.Start();
382                         } else {
383                                 // The autopop_delay timer happened
384                                 tooltip_window.Visible = false;
385                         }
386                 }
387
388
389                 private bool MouseInControl(Control control) {
390                         Point   m;
391                         Point   c;
392                         Size    cw;
393
394                         if (control == null) {
395                                 return false;
396                         }
397
398                         m = Control.MousePosition;
399                         c = new Point(control.Bounds.X, control.Bounds.Y);
400                         if (control.parent != null) {
401                                 c = control.parent.PointToScreen(c);
402                         }
403                         cw = control.ClientSize;
404
405                         if (c.X<=m.X && m.X<(c.X+cw.Width) &&
406                                 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
407                                 return true;
408                         }
409                         return false;
410                 }
411
412                 private void control_MouseLeave(object sender, EventArgs e) {
413                         // In case the timer is still running, stop it
414                         timer.Stop();
415
416                         if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
417                                 active_control = null;
418                                 tooltip_window.Visible = false;
419                         }
420                 }
421
422                 private void control_MouseMove(object sender, MouseEventArgs e) {
423                         // Restart the interval, the mouse moved
424                         timer.Stop();
425                         timer.Start();
426
427                 }
428                 #endregion      // Private Methods
429         }
430 }