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