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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
27 using System.Collections;
28 using System.ComponentModel;
30 using System.Drawing.Text;
32 namespace System.Windows.Forms {
34 [DefaultEvent ("Popup")]
36 [ProvideProperty ("ToolTip", typeof(System.Windows.Forms.Control))]
37 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
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;
51 internal Color back_color;
52 internal Color fore_color;
54 internal ToolTipWindow tooltip_window; // The actual tooltip window
55 internal Hashtable tooltip_strings; // List of strings for each control, indexed by control
56 internal ArrayList controls;
57 internal Control active_control; // Control for which the tooltip is currently displayed
58 internal Control last_control; // last control the mouse was in
59 internal Timer timer; // Used for the various intervals
62 private bool isBalloon;
63 private bool owner_draw;
64 private bool stripAmpersands;
65 private ToolTipIcon tool_tip_icon;
66 private string tool_tip_title;
67 private bool useAnimation;
68 private bool useFading;
73 #endregion // Local variables
75 #region ToolTipWindow Class
76 internal class ToolTipWindow : Control {
77 #region ToolTipWindow Class Local Variables
78 internal StringFormat string_format;
80 internal bool owner_draw;
81 internal Control associated_control;
83 #endregion // ToolTipWindow Class Local Variables
85 #region ToolTipWindow Class Constructor
86 internal ToolTipWindow() {
88 string_format = new StringFormat();
89 string_format.LineAlignment = StringAlignment.Center;
90 string_format.FormatFlags = StringFormatFlags.NoWrap;
91 string_format.HotkeyPrefix = HotkeyPrefix.Hide;
94 Size = new Size(100, 20);
95 ForeColor = ThemeEngine.Current.ColorInfoText;
96 BackColor = ThemeEngine.Current.ColorInfo;
98 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
100 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
101 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
104 #endregion // ToolTipWindow Class Constructor
106 #region ToolTipWindow Class Protected Instance Methods
107 protected override void OnCreateControl() {
108 base.OnCreateControl ();
109 XplatUI.SetTopmost(this.window.Handle, true);
112 protected override CreateParams CreateParams {
116 cp = base.CreateParams;
118 cp.Style = (int)WindowStyles.WS_POPUP;
119 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
121 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
127 protected override void OnPaint(PaintEventArgs pevent) {
128 // We don't do double-buffering on purpose:
129 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
130 // 2) We don't draw much, no need to double buffer
133 OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
136 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
138 base.OnPaint(pevent);
141 protected override void OnTextChanged (EventArgs args)
144 base.OnTextChanged (args);
147 protected override void Dispose(bool disposing) {
149 this.string_format.Dispose();
151 base.Dispose (disposing);
154 protected override void WndProc(ref Message m) {
155 if (m.Msg == (int)Msg.WM_SETFOCUS) {
156 if (m.WParam != IntPtr.Zero) {
157 XplatUI.SetFocus(m.WParam);
160 base.WndProc (ref m);
164 #endregion // ToolTipWindow Class Protected Instance Methods
166 #region ToolTipWindow Class Private Methods
168 internal virtual void OnDraw (DrawToolTipEventArgs e)
170 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
175 internal virtual void OnPopup (PopupEventArgs e)
177 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
183 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
184 Control control = (Control)sender;
186 if (control.is_visible) {
187 XplatUI.SetTopmost(control.window.Handle, true);
189 XplatUI.SetTopmost(control.window.Handle, false);
192 #endregion // ToolTipWindow Class Protected Instance Methods
194 #region Internal Properties
195 internal override bool ActivateOnShow { get { return false; } }
198 public void Present (Control control, string text)
204 XplatUI.GetDisplaySize (out display_size);
207 associated_control = control;
210 Size size = ThemeEngine.Current.ToolTipSize (this, text);
214 PopupEventArgs pea = new PopupEventArgs (control, control, false, size);
220 size = pea.ToolTipSize;
224 Height = size.Height;
226 int cursor_w, cursor_h, hot_x, hot_y;
227 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
228 Point loc = Control.MousePosition;
229 loc.Y += (cursor_h - hot_y);
231 if ((loc.X + Width) > display_size.Width)
232 loc.X = display_size.Width - Width;
234 if ((loc.Y + Height) > display_size.Height)
235 loc.Y = Control.MousePosition.Y - Height - hot_y;
242 #region Internal Events
244 static object DrawEvent = new object ();
245 static object PopupEvent = new object ();
247 public event DrawToolTipEventHandler Draw {
248 add { Events.AddHandler (DrawEvent, value); }
249 remove { Events.RemoveHandler (DrawEvent, value); }
252 public event PopupEventHandler Popup {
253 add { Events.AddHandler (PopupEvent, value); }
254 remove { Events.RemoveHandler (PopupEvent, value); }
259 #endregion // ToolTipWindow Class
261 #region Public Constructors & Destructors
266 automatic_delay = 500;
267 autopop_delay = 5000;
271 back_color = SystemColors.Info;
272 fore_color = SystemColors.InfoText;
276 stripAmpersands = false;
280 tooltip_strings = new Hashtable(5);
281 controls = new ArrayList(5);
283 tooltip_window = new ToolTipWindow();
284 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
286 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
287 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
290 timer.Enabled = false;
291 timer.Tick +=new EventHandler(timer_Tick);
294 public ToolTip(System.ComponentModel.IContainer cont) : this() {
300 #endregion // Public Constructors & Destructors
302 #region Public Instance Properties
303 [DefaultValue (true)]
310 if (is_active != value) {
313 if (tooltip_window.Visible) {
314 tooltip_window.Visible = false;
315 active_control = null;
322 [RefreshProperties (RefreshProperties.All)]
323 public int AutomaticDelay {
325 return automatic_delay;
329 if (automatic_delay != value) {
330 automatic_delay = value;
331 autopop_delay = automatic_delay * 10;
332 initial_delay = automatic_delay;
333 re_show_delay = automatic_delay / 5;
338 [RefreshProperties (RefreshProperties.All)]
339 public int AutoPopDelay {
341 return autopop_delay;
345 if (autopop_delay != value) {
346 autopop_delay = value;
352 [DefaultValue ("Color [Info]")]
353 public Color BackColor {
354 get { return this.back_color; }
355 set { this.back_color = value; tooltip_window.BackColor = value; }
358 [DefaultValue ("Color [InfoText]")]
359 public Color ForeColor
361 get { return this.fore_color; }
362 set { this.fore_color = value; tooltip_window.ForeColor = value; }
366 [RefreshProperties (RefreshProperties.All)]
367 public int InitialDelay {
369 return initial_delay;
373 if (initial_delay != value) {
374 initial_delay = value;
380 [DefaultValue (false)]
381 public bool OwnerDraw {
382 get { return this.owner_draw; }
384 this.owner_draw = value;
385 tooltip_window.owner_draw = value;
390 [RefreshProperties (RefreshProperties.All)]
391 public int ReshowDelay {
393 return re_show_delay;
397 if (re_show_delay != value) {
398 re_show_delay = value;
403 [DefaultValue (false)]
404 public bool ShowAlways {
410 if (show_always != value) {
418 [DefaultValue (false)]
419 public bool IsBalloon {
420 get { return isBalloon; }
421 set { isBalloon = value; }
425 [DefaultValue (false)]
426 public bool StripAmpersands {
427 get { return stripAmpersands; }
428 set { stripAmpersands = value; }
431 [Localizable (false)]
433 [TypeConverter (typeof (StringConverter))]
434 [DefaultValue (null)]
440 [DefaultValue (ToolTipIcon.None)]
441 public ToolTipIcon ToolTipIcon {
442 get { return this.tool_tip_icon; }
443 set { this.tool_tip_icon = value; }
447 public string ToolTipTitle {
448 get { return this.tool_tip_title; }
449 set { this.tool_tip_title = value; }
453 [DefaultValue (true)]
454 public bool UseAnimation {
455 get { return useAnimation; }
456 set { useAnimation = value; }
460 [DefaultValue (true)]
461 public bool UseFading {
462 get { return useFading; }
463 set { useFading = value; }
467 #endregion // Public Instance Properties
469 #region Public Instance Methods
470 public bool CanExtend(object target) {
475 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
476 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
480 public string GetToolTip (Control control)
482 string tooltip = (string)tooltip_strings[control];
488 public void RemoveAll() {
489 tooltip_strings.Clear();
493 public void SetToolTip(Control control, string caption) {
494 tooltip_strings[control] = caption;
496 // no need for duplicates
497 if (!controls.Contains(control)) {
498 control.MouseEnter += new EventHandler(control_MouseEnter);
499 control.MouseMove += new MouseEventHandler(control_MouseMove);
500 control.MouseLeave += new EventHandler(control_MouseLeave);
501 control.MouseDown += new MouseEventHandler (control_MouseDown);
502 controls.Add(control);
505 // if SetToolTip is called from a control and the mouse is currently over that control,
506 // make sure that tooltip_window.Text gets updated if it's being shown,
507 // or show the tooltip for it if is not
508 if (active_control == control && caption != null && state == TipState.Show) {
509 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
510 tooltip_window.Width = size.Width;
511 tooltip_window.Height = size.Height;
512 tooltip_window.Text = caption;
515 } else if (control.IsHandleCreated && MouseInControl (control, false))
516 ShowTooltip (control);
519 public override string ToString() {
520 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
524 public void Show (string text, IWin32Window win)
526 SetToolTip (win as Control, text);
527 ShowTooltip (win as Control);
530 [MonoTODO ("Finish implementing tooltip location")]
531 public void Show (string text, IWin32Window win, Point p)
533 SetToolTip (win as Control, text);
534 ShowTooltip (win as Control);
537 public void Hide (IWin32Window win)
539 Hide (win as Control);
543 #endregion // Public Instance Methods
545 #region Protected Instance Methods
546 protected override void Dispose(bool disposing) {
548 // Mop up the mess; or should we wait for the GC to kick in?
552 // Not sure if we should clean up tooltip_window
553 tooltip_window.Dispose();
555 tooltip_strings.Clear();
560 #endregion // Protected Instance Methods
568 TipState state = TipState.Initial;
570 #region Private Methods
571 private void control_MouseEnter(object sender, EventArgs e)
573 ShowTooltip (sender as Control);
576 private void ShowTooltip (Control control)
578 last_control = control;
580 // Whatever we're displaying right now, we don't want it anymore
581 tooltip_window.Visible = false;
583 state = TipState.Initial;
589 IContainerControl cc = last_control.GetContainerControl ();
590 if ((cc == null) || (cc.ActiveControl == null)) {
595 string text = (string)tooltip_strings[control];
596 if (text != null && text.Length > 0) {
597 if (active_control == null) {
598 timer.Interval = initial_delay;
600 timer.Interval = Math.Max (re_show_delay, 1);
603 active_control = control;
608 private void timer_Tick(object sender, EventArgs e) {
612 case TipState.Initial:
613 if (active_control == null)
615 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
616 state = TipState.Show;
617 timer.Interval = autopop_delay;
622 tooltip_window.Visible = false;
623 state = TipState.Down;
627 throw new Exception ("Timer shouldn't be running in state: " + state);
632 private void tooltip_window_Popup (object sender, PopupEventArgs e)
637 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
643 private bool MouseInControl (Control control, bool fuzzy) {
648 if (control == null) {
652 m = Control.MousePosition;
653 c = new Point(control.Bounds.X, control.Bounds.Y);
654 if (control.Parent != null) {
655 c = control.Parent.PointToScreen(c);
657 cw = control.ClientSize;
660 Rectangle rect = new Rectangle (c, cw);
663 // We won't get mouse move events on all platforms with the exact same
664 // frequency, so cheat a bit.
668 return rect.Contains (m);
671 private void control_MouseLeave(object sender, EventArgs e)
673 Hide (sender as Control);
677 void control_MouseDown (object sender, MouseEventArgs e)
681 active_control = null;
682 tooltip_window.Visible = false;
684 if (last_control == sender)
688 private void Hide (Control sender)
692 if (!MouseInControl (tooltip_window, true) && !MouseInControl (active_control, true)) {
693 active_control = null;
694 tooltip_window.Visible = false;
697 if (last_control == sender)
701 private void control_MouseMove(object sender, MouseEventArgs e) {
702 if (state != TipState.Down) {
709 internal virtual void OnDraw (DrawToolTipEventArgs e)
711 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
716 internal virtual void OnPopup (PopupEventArgs e)
718 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
723 #endregion // Private Methods
727 static object PopupEvent = new object ();
728 static object DrawEvent = new object ();
730 public event PopupEventHandler Popup {
731 add { Events.AddHandler (PopupEvent, value); }
732 remove { Events.RemoveHandler (PopupEvent, value); }
735 public event DrawToolTipEventHandler Draw {
736 add { Events.AddHandler (DrawEvent, value); }
737 remove { Events.RemoveHandler (DrawEvent, value); }