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
30 using System.Collections;
31 using System.ComponentModel;
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;
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
54 #region ToolTipWindow Class
55 internal class ToolTipWindow : Control {
56 #region ToolTipWindow Class Local Variables
57 internal StringFormat string_format;
58 #endregion // ToolTipWindow Class Local Variables
60 #region ToolTipWindow Class Constructor
61 internal ToolTipWindow() {
63 string_format = new StringFormat();
64 string_format.LineAlignment = StringAlignment.Center;
65 string_format.Alignment = StringAlignment.Center;
66 string_format.FormatFlags = StringFormatFlags.NoWrap;
69 Size = new Size(100, 20);
70 ForeColor = ThemeEngine.Current.ColorInfoText;
71 BackColor = ThemeEngine.Current.ColorInfo;
73 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
75 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
76 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
79 #endregion // ToolTipWindow Class Constructor
81 #region ToolTipWindow Class Protected Instance Methods
82 protected override void OnCreateControl() {
83 base.OnCreateControl ();
84 XplatUI.SetTopmost(this.window.Handle, IntPtr.Zero, true);
87 protected override CreateParams CreateParams {
91 cp = base.CreateParams;
93 cp.Style = (int)WindowStyles.WS_POPUP;
94 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
96 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
102 protected override void OnPaint(PaintEventArgs pevent) {
103 // We don't do double-buffering on purpose:
104 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
105 // 2) We don't draw much, no need to double buffer
106 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
108 base.OnPaint(pevent);
111 protected override void Dispose(bool disposing) {
113 this.string_format.Dispose();
115 base.Dispose (disposing);
118 protected override void WndProc(ref Message m) {
119 if (m.Msg == (int)Msg.WM_SETFOCUS) {
120 if (m.WParam != IntPtr.Zero) {
121 XplatUI.SetFocus(m.WParam);
124 base.WndProc (ref m);
128 #endregion // ToolTipWindow Class Protected Instance Methods
130 #region ToolTipWindow Class Private Methods
131 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
132 Control control = (Control)sender;
134 if (control.is_visible) {
135 XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, true);
137 XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, false);
140 #endregion // ToolTipWindow Class Protected Instance Methods
142 public void Present (Control control, string text)
148 XplatUI.GetDisplaySize (out display_size);
150 Size size = ThemeEngine.Current.ToolTipSize (this, text);
152 Height = size.Height;
155 int cursor_w, cursor_h, hot_x, hot_y;
156 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
157 Point loc = Control.MousePosition;
158 loc.Y += (cursor_h - hot_y);
160 if ((loc.X + Width) > display_size.Width)
161 loc.X = display_size.Width - Width;
163 if ((loc.Y + Height) > display_size.Height)
164 loc.Y = Control.MousePosition.Y - Height - hot_y;
170 #endregion // ToolTipWindow Class
172 #region Public Constructors & Destructors
177 automatic_delay = 500;
178 autopop_delay = 5000;
183 tooltip_strings = new Hashtable(5);
184 controls = new ArrayList(5);
186 tooltip_window = new ToolTipWindow();
187 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
190 timer.Enabled = false;
191 timer.Tick +=new EventHandler(timer_Tick);
194 public ToolTip(System.ComponentModel.IContainer cont) : this() {
200 #endregion // Public Constructors & Destructors
202 #region Public Instance Properties
203 [DefaultValue (true)]
210 if (is_active != value) {
213 if (tooltip_window.Visible) {
214 tooltip_window.Visible = false;
215 active_control = null;
222 [RefreshProperties (RefreshProperties.All)]
223 public int AutomaticDelay {
225 return automatic_delay;
229 if (automatic_delay != value) {
230 automatic_delay = value;
231 autopop_delay = automatic_delay * 10;
232 initial_delay = automatic_delay;
233 re_show_delay = automatic_delay / 5;
238 [RefreshProperties (RefreshProperties.All)]
239 public int AutoPopDelay {
241 return autopop_delay;
245 if (autopop_delay != value) {
246 autopop_delay = value;
251 [RefreshProperties (RefreshProperties.All)]
252 public int InitialDelay {
254 return initial_delay;
258 if (initial_delay != value) {
259 initial_delay = value;
264 [RefreshProperties (RefreshProperties.All)]
265 public int ReshowDelay {
267 return re_show_delay;
271 if (re_show_delay != value) {
272 re_show_delay = value;
277 [DefaultValue (false)]
278 public bool ShowAlways {
284 if (show_always != value) {
289 #endregion // Public Instance Properties
291 #region Public Instance Methods
292 public bool CanExtend(object target) {
298 public string GetToolTip(Control control) {
299 string tooltip = (string)tooltip_strings[control];
305 public void RemoveAll() {
306 tooltip_strings.Clear();
310 public void SetToolTip(Control control, string caption) {
311 tooltip_strings[control] = caption;
313 // no need for duplicates
314 if (!controls.Contains(control)) {
315 control.MouseEnter += new EventHandler(control_MouseEnter);
316 control.MouseMove += new MouseEventHandler(control_MouseMove);
317 control.MouseLeave += new EventHandler(control_MouseLeave);
318 controls.Add(control);
321 // if SetToolTip is called from a control and the mouse is currently over that control,
322 // make sure that tooltip_window.Text gets updated
323 if (caption != null && last_control == control) {
324 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
325 tooltip_window.Width = size.Width;
326 tooltip_window.Height = size.Height;
327 tooltip_window.Text = caption;
331 public override string ToString() {
332 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
334 #endregion // Public Instance Methods
336 #region Protected Instance Methods
337 protected override void Dispose(bool disposing) {
339 // Mop up the mess; or should we wait for the GC to kick in?
343 // Not sure if we should clean up tooltip_window
344 tooltip_window.Dispose();
346 tooltip_strings.Clear();
351 #endregion // Protected Instance Methods
359 TipState state = TipState.Initial;
361 #region Private Methods
362 private void control_MouseEnter(object sender, EventArgs e)
364 last_control = (Control)sender;
366 // Whatever we're displaying right now, we don't want it anymore
367 tooltip_window.Visible = false;
369 state = TipState.Initial;
371 // if we're in the same control as before (how'd that happen?) or if we're not active, leave
372 if (!is_active || (active_control == (Control)sender)) {
377 IContainerControl cc = last_control.GetContainerControl ();
378 if ((cc == null) || (cc.ActiveControl == null)) {
383 string text = (string)tooltip_strings[sender];
384 if (text != null && text.Length > 0) {
386 if (active_control == null) {
387 timer.Interval = initial_delay;
389 timer.Interval = re_show_delay;
392 active_control = (Control)sender;
397 private void timer_Tick(object sender, EventArgs e) {
401 case TipState.Initial:
402 if (active_control == null)
404 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
405 state = TipState.Show;
406 timer.Interval = autopop_delay;
411 tooltip_window.Visible = false;
412 state = TipState.Down;
416 throw new Exception ("Timer shouldn't be running in state: " + state);
421 private bool MouseInControl(Control control) {
426 if (control == null) {
430 m = Control.MousePosition;
431 c = new Point(control.Bounds.X, control.Bounds.Y);
432 if (control.parent != null) {
433 c = control.parent.PointToScreen(c);
435 cw = control.ClientSize;
437 if (c.X<=m.X && m.X<(c.X+cw.Width) &&
438 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
444 private void control_MouseLeave(object sender, EventArgs e) {
447 if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
448 active_control = null;
449 tooltip_window.Visible = false;
452 if (last_control == (Control)sender)
456 private void control_MouseMove(object sender, MouseEventArgs e) {
457 if (state != TipState.Down) {
462 #endregion // Private Methods