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 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
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
61 #region ToolTipWindow Class Constructor
62 internal ToolTipWindow(ToolTip owner) : base() {
65 string_format = new StringFormat();
66 string_format.LineAlignment = StringAlignment.Center;
67 string_format.Alignment = StringAlignment.Center;
68 string_format.FormatFlags = StringFormatFlags.NoWrap;
71 Size = new Size(100, 20);
72 ForeColor = ThemeEngine.Current.ColorInfoText;
73 BackColor = ThemeEngine.Current.ColorInfoWindow;
75 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
77 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
78 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
81 #endregion // ToolTipWindow Class Constructor
83 #region ToolTipWindow Class Protected Instance Methods
\r
84 protected override void OnCreateControl() {
\r
85 base.OnCreateControl ();
\r
86 XplatUI.SetTopmost(this.window.Handle, IntPtr.Zero, true);
89 protected override CreateParams CreateParams {
\r
93 cp = base.CreateParams;
\r
95 cp.Style = (int)WindowStyles.WS_POPUP;
\r
96 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
\r
98 cp.ExStyle = (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
104 protected override void OnPaint(PaintEventArgs pevent) {
\r
105 // We don't do double-buffering on purpose:
\r
106 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
\r
107 // 2) We don't draw much, no need to double buffer
\r
108 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, owner);
111 protected override void Dispose(bool disposing) {
\r
113 this.string_format.Dispose();
\r
115 base.Dispose (disposing);
\r
118 protected override void WndProc(ref Message m) {
\r
119 if (m.Msg == (int)Msg.WM_SETFOCUS) {
\r
120 if (m.WParam != IntPtr.Zero) {
\r
121 XplatUI.SetFocus(m.WParam);
\r
124 base.WndProc (ref m);
\r
128 #endregion // ToolTipWindow Class Protected Instance Methods
\r
130 #region ToolTipWindow Class Private Methods
\r
131 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
\r
132 Control control = (Control)sender;
\r
134 if (control.is_visible) {
\r
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
\r
142 #endregion // ToolTipWindow Class
144 #region Public Constructors & Destructors
146 XplatUI.GetDisplaySize(out display_size);
150 automatic_delay = 500;
151 autopop_delay = 5000;
156 tooltip_strings = new Hashtable(5);
158 tooltip_window = new ToolTipWindow(this);
159 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
162 timer.Enabled = false;
163 timer.Tick +=new EventHandler(timer_Tick);
\r
166 public ToolTip(System.ComponentModel.IContainer cont) : this() {
\r
167 // Dunno why I'd need the container
\r
172 #endregion // Public Constructors & Destructors
174 #region Public Instance Properties
175 [DefaultValue (true)]
182 if (is_active != value) {
185 if (tooltip_window.Visible) {
186 tooltip_window.Visible = false;
187 active_control = null;
194 [RefreshProperties (RefreshProperties.All)]
195 public int AutomaticDelay {
197 return automatic_delay;
201 if (automatic_delay != value) {
202 automatic_delay = value;
203 autopop_delay = automatic_delay * 10;
204 initial_delay = automatic_delay;
205 re_show_delay = automatic_delay / 5;
210 [RefreshProperties (RefreshProperties.All)]
211 public int AutoPopDelay {
213 return autopop_delay;
217 if (autopop_delay != value) {
218 autopop_delay = value;
223 [RefreshProperties (RefreshProperties.All)]
224 public int InitialDelay {
226 return initial_delay;
230 if (initial_delay != value) {
231 initial_delay = value;
236 [RefreshProperties (RefreshProperties.All)]
237 public int ReshowDelay {
239 return re_show_delay;
243 if (re_show_delay != value) {
244 re_show_delay = value;
249 [DefaultValue (false)]
250 public bool ShowAlways {
256 if (show_always != value) {
261 #endregion // Public Instance Properties
263 #region Public Instance Methods
264 public bool CanExtend(object target) {
270 public string GetToolTip(Control control) {
271 return (string)tooltip_strings[control];
274 public void RemoveAll() {
275 tooltip_strings.Clear();
278 public void SetToolTip(Control control, string caption) {
279 tooltip_strings[control] = caption;
281 control.MouseEnter += new EventHandler(control_MouseEnter);
282 control.MouseMove += new MouseEventHandler(control_MouseMove);
283 control.MouseLeave += new EventHandler(control_MouseLeave);
286 public override string ToString() {
\r
287 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
\r
289 #endregion // Public Instance Methods
291 #region Protected Instance Methods
292 protected override void Dispose(bool disposing) {
294 // Mop up the mess; or should we wait for the GC to kick in?
298 // Not sure if we should clean up tooltip_window
299 tooltip_window.Dispose();
301 tooltip_strings.Clear();
304 #endregion // Protected Instance Methods
\r
306 #region Private Methods
\r
307 private void control_MouseEnter(object sender, EventArgs e) {
\r
310 // Whatever we're displaying right now, we don't want it anymore
\r
311 tooltip_window.Visible = false;
\r
314 // if we're in the same control as before (how'd that happen?) or if we're not active, leave
\r
315 if (!is_active || (active_control == (Control)sender)) {
\r
319 // As of this writing, our MWF implementation had no clue what an active control was :-(
\r
320 if (!show_always) {
\r
321 if (((Control)sender).GetContainerControl().ActiveControl == null) {
\r
326 text = (string)tooltip_strings[sender];
\r
327 if (text != null) {
\r
330 size = ThemeEngine.Current.ToolTipSize(this, text);
\r
331 tooltip_window.Width = size.Width;
\r
332 tooltip_window.Height = size.Height;
\r
333 tooltip_window.Text = text;
\r
335 // FIXME - this needs to be improved; the tooltip will show up under the mouse, which is annoying; use cursor size once implemented
\r
337 if ((Control.MousePosition.X+1+tooltip_window.Width) < display_size.Width) {
\r
338 tooltip_window.Left = Control.MousePosition.X+1;
\r
340 tooltip_window.Left = display_size.Width-tooltip_window.Width;
\r
343 if ((Control.MousePosition.Y+tooltip_window.Height)<display_size.Height) {
\r
344 tooltip_window.Top = Control.MousePosition.Y;
\r
346 tooltip_window.Top = Control.MousePosition.Y-tooltip_window.Height;
\r
349 // Since we get the mouse enter before the mouse leave, active_control will still be non-null if we were in a
\r
350 // tooltip'd control; should prolly check on X11 too, and make sure that driver behaves the same way
\r
351 if (active_control == null) {
\r
352 timer.Interval = initial_delay;
\r
354 timer.Interval = re_show_delay;
\r
357 active_control = (Control)sender;
\r
359 // We're all set, lets wake the timer (which will then make us visible)
\r
360 timer.Enabled = true;
\r
364 private void timer_Tick(object sender, EventArgs e) {
\r
365 // Show our pretty selves
\r
368 // FIXME - Should not need this check../
\r
369 if (tooltip_window.IsDisposed) {
\r
373 if (!tooltip_window.Visible) {
\r
374 // The initial_delay timer kicked in
\r
375 tooltip_window.Visible = true;
\r
376 timer.Interval = autopop_delay;
\r
379 // The autopop_delay timer happened
\r
380 tooltip_window.Visible = false;
\r
385 private bool MouseInControl(Control control) {
\r
390 if (control == null) {
\r
394 m = Control.MousePosition;
\r
395 c = new Point(control.Bounds.X, control.Bounds.Y);
\r
396 if (control.parent != null) {
397 c = control.parent.PointToScreen(c);
399 cw = control.ClientSize;
\r
401 if (c.X<=m.X && m.X<(c.X+cw.Width) &&
\r
402 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
\r
408 private void control_MouseLeave(object sender, EventArgs e) {
\r
409 // In case the timer is still running, stop it
\r
412 if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
\r
413 active_control = null;
\r
414 tooltip_window.Visible = false;
\r
418 private void control_MouseMove(object sender, MouseEventArgs e) {
\r
419 // Restart the interval, the mouse moved
\r
424 #endregion // Private Methods
\r