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 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
59 private bool isBalloon;
60 private bool stripAmpersands;
61 private bool useAnimation;
62 private bool useFading;
65 #endregion // Local variables
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
73 #region ToolTipWindow Class Constructor
74 internal ToolTipWindow() {
76 string_format = new StringFormat();
77 string_format.LineAlignment = StringAlignment.Center;
78 string_format.FormatFlags = StringFormatFlags.NoWrap;
79 string_format.HotkeyPrefix = HotkeyPrefix.Hide;
82 Size = new Size(100, 20);
83 ForeColor = ThemeEngine.Current.ColorInfoText;
84 BackColor = ThemeEngine.Current.ColorInfo;
86 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
88 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
89 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
92 #endregion // ToolTipWindow Class Constructor
94 #region ToolTipWindow Class Protected Instance Methods
95 protected override void OnCreateControl() {
96 base.OnCreateControl ();
97 XplatUI.SetTopmost(this.window.Handle, true);
100 protected override CreateParams CreateParams {
104 cp = base.CreateParams;
106 cp.Style = (int)WindowStyles.WS_POPUP;
107 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
109 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
115 protected override void OnPaint(PaintEventArgs pevent) {
116 // We don't do double-buffering on purpose:
117 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
118 // 2) We don't draw much, no need to double buffer
119 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
121 base.OnPaint(pevent);
124 protected override void OnTextChanged (EventArgs args)
127 base.OnTextChanged (args);
130 protected override void Dispose(bool disposing) {
132 this.string_format.Dispose();
134 base.Dispose (disposing);
137 protected override void WndProc(ref Message m) {
138 if (m.Msg == (int)Msg.WM_SETFOCUS) {
139 if (m.WParam != IntPtr.Zero) {
140 XplatUI.SetFocus(m.WParam);
143 base.WndProc (ref m);
147 #endregion // ToolTipWindow Class Protected Instance Methods
149 #region ToolTipWindow Class Private Methods
150 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
151 Control control = (Control)sender;
153 if (control.is_visible) {
154 XplatUI.SetTopmost(control.window.Handle, true);
156 XplatUI.SetTopmost(control.window.Handle, false);
159 #endregion // ToolTipWindow Class Protected Instance Methods
161 #region Internal Properties
162 internal override bool ActivateOnShow { get { return false; } }
165 public void Present (Control control, string text)
171 XplatUI.GetDisplaySize (out display_size);
173 Size size = ThemeEngine.Current.ToolTipSize (this, text);
175 Height = size.Height;
178 int cursor_w, cursor_h, hot_x, hot_y;
179 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
180 Point loc = Control.MousePosition;
181 loc.Y += (cursor_h - hot_y);
183 if ((loc.X + Width) > display_size.Width)
184 loc.X = display_size.Width - Width;
186 if ((loc.Y + Height) > display_size.Height)
187 loc.Y = Control.MousePosition.Y - Height - hot_y;
193 #endregion // ToolTipWindow Class
195 #region Public Constructors & Destructors
200 automatic_delay = 500;
201 autopop_delay = 5000;
207 stripAmpersands = false;
211 tooltip_strings = new Hashtable(5);
212 controls = new ArrayList(5);
214 tooltip_window = new ToolTipWindow();
215 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
218 timer.Enabled = false;
219 timer.Tick +=new EventHandler(timer_Tick);
222 public ToolTip(System.ComponentModel.IContainer cont) : this() {
228 #endregion // Public Constructors & Destructors
230 #region Public Instance Properties
231 [DefaultValue (true)]
238 if (is_active != value) {
241 if (tooltip_window.Visible) {
242 tooltip_window.Visible = false;
243 active_control = null;
250 [RefreshProperties (RefreshProperties.All)]
251 public int AutomaticDelay {
253 return automatic_delay;
257 if (automatic_delay != value) {
258 automatic_delay = value;
259 autopop_delay = automatic_delay * 10;
260 initial_delay = automatic_delay;
261 re_show_delay = automatic_delay / 5;
266 [RefreshProperties (RefreshProperties.All)]
267 public int AutoPopDelay {
269 return autopop_delay;
273 if (autopop_delay != value) {
274 autopop_delay = value;
279 [RefreshProperties (RefreshProperties.All)]
280 public int InitialDelay {
282 return initial_delay;
286 if (initial_delay != value) {
287 initial_delay = value;
292 [RefreshProperties (RefreshProperties.All)]
293 public int ReshowDelay {
295 return re_show_delay;
299 if (re_show_delay != value) {
300 re_show_delay = value;
305 [DefaultValue (false)]
306 public bool ShowAlways {
312 if (show_always != value) {
320 [DefaultValue (false)]
321 public bool IsBalloon {
322 get { return isBalloon; }
323 set { isBalloon = value; }
327 [DefaultValue (false)]
328 public bool StripAmpersands {
329 get { return stripAmpersands; }
330 set { stripAmpersands = value; }
334 [DefaultValue (true)]
335 public bool UseAnimation {
336 get { return useAnimation; }
337 set { useAnimation = value; }
341 [DefaultValue (true)]
342 public bool UseFading {
343 get { return useFading; }
344 set { useFading = value; }
348 #endregion // Public Instance Properties
350 #region Public Instance Methods
351 public bool CanExtend(object target) {
356 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
357 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
361 public string GetToolTip (Control control)
363 string tooltip = (string)tooltip_strings[control];
369 public void RemoveAll() {
370 tooltip_strings.Clear();
374 public void SetToolTip(Control control, string caption) {
375 tooltip_strings[control] = caption;
377 // no need for duplicates
378 if (!controls.Contains(control)) {
379 control.MouseEnter += new EventHandler(control_MouseEnter);
380 control.MouseMove += new MouseEventHandler(control_MouseMove);
381 control.MouseLeave += new EventHandler(control_MouseLeave);
382 controls.Add(control);
385 // if SetToolTip is called from a control and the mouse is currently over that control,
386 // make sure that tooltip_window.Text gets updated if it's being shown,
387 // or show the tooltip for it if is not
388 if (active_control == control && caption != null && state == TipState.Show) {
389 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
390 tooltip_window.Width = size.Width;
391 tooltip_window.Height = size.Height;
392 tooltip_window.Text = caption;
395 } else if (MouseInControl (control))
396 ShowTooltip (control);
399 public override string ToString() {
400 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
404 public void Show (string text, IWin32Window win)
406 SetToolTip (win as Control, text);
407 ShowTooltip (win as Control);
410 [MonoTODO ("Finish implementing tooltip location")]
411 public void Show (string text, IWin32Window win, Point p)
413 SetToolTip (win as Control, text);
414 ShowTooltip (win as Control);
417 public void Hide (IWin32Window win)
419 Hide (win as Control);
423 #endregion // Public Instance Methods
425 #region Protected Instance Methods
426 protected override void Dispose(bool disposing) {
428 // Mop up the mess; or should we wait for the GC to kick in?
432 // Not sure if we should clean up tooltip_window
433 tooltip_window.Dispose();
435 tooltip_strings.Clear();
440 #endregion // Protected Instance Methods
448 TipState state = TipState.Initial;
450 #region Private Methods
451 private void control_MouseEnter(object sender, EventArgs e)
453 ShowTooltip (sender as Control);
456 private void ShowTooltip (Control control)
458 last_control = control;
460 // Whatever we're displaying right now, we don't want it anymore
461 tooltip_window.Visible = false;
463 state = TipState.Initial;
469 IContainerControl cc = last_control.GetContainerControl ();
470 if ((cc == null) || (cc.ActiveControl == null)) {
475 string text = (string)tooltip_strings[control];
476 if (text != null && text.Length > 0) {
478 if (active_control == null) {
479 timer.Interval = initial_delay;
481 timer.Interval = re_show_delay;
484 active_control = control;
489 private void timer_Tick(object sender, EventArgs e) {
493 case TipState.Initial:
494 if (active_control == null)
496 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
497 state = TipState.Show;
498 timer.Interval = autopop_delay;
503 tooltip_window.Visible = false;
504 state = TipState.Down;
508 throw new Exception ("Timer shouldn't be running in state: " + state);
513 private bool MouseInControl(Control control) {
518 if (control == null) {
522 m = Control.MousePosition;
523 c = new Point(control.Bounds.X, control.Bounds.Y);
524 if (control.Parent != null) {
525 c = control.Parent.PointToScreen(c);
527 cw = control.ClientSize;
529 if (c.X<=m.X && m.X<(c.X+cw.Width) &&
530 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
536 private void control_MouseLeave(object sender, EventArgs e)
538 Hide (sender as Control);
541 private void Hide (Control sender)
545 if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
546 active_control = null;
547 tooltip_window.Visible = false;
550 if (last_control == sender)
554 private void control_MouseMove(object sender, MouseEventArgs e) {
555 if (state != TipState.Down) {
562 internal virtual void OnPopup (PopupEventArgs e)
564 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
569 #endregion // Private Methods
573 static object PopupEvent = new object ();
575 public event PopupEventHandler Popup {
576 add { Events.AddHandler (PopupEvent, value); }
577 remove { Events.RemoveHandler (PopupEvent, value); }