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 // $Log: ToolTip.cs,v $
28 // Revision 1.4 2004/11/08 20:49:35 pbartok
29 // - Fixed arguments for updated SetTopmost function
30 // - Fixed usage of PointToClient
32 // Revision 1.3 2004/10/19 06:04:59 ravindra
35 // Revision 1.2 2004/10/18 06:28:30 ravindra
36 // Suppressed a warning message.
38 // Revision 1.1 2004/10/18 05:19:57 pbartok
39 // - Complete implementation
46 using System
.Collections
;
47 using System
.ComponentModel
;
50 namespace System
.Windows
.Forms
{
51 public sealed class ToolTip
: System
.ComponentModel
.Component
, System
.ComponentModel
.IExtenderProvider
{
52 #region Local variables
53 internal bool is_active
;
54 internal int automatic_delay
;
55 internal int autopop_delay
;
56 internal int initial_delay
;
57 internal int re_show_delay
;
58 internal bool show_always
;
60 internal ToolTipWindow tooltip_window
; // The actual tooltip window
61 internal Hashtable tooltip_strings
; // List of strings for each control, indexed by control
62 internal Control active_control
; // Control for which the tooltip is currently displayed
63 internal Control last_control
; // last control the mouse was in; null if the last control did not have a tooltip
64 internal Size display_size
; // Size of the screen
65 internal Timer timer
; // Used for the various intervals
66 #endregion // Local variables
68 #region ToolTipWindow Class
69 internal class ToolTipWindow
: Control
{
70 #region ToolTipWindow Class Local Variables
71 internal StringFormat string_format
;
72 internal ToolTip owner
;
73 #endregion // ToolTipWindow Class Local Variables
75 #region ToolTipWindow Class Constructor
76 internal ToolTipWindow(ToolTip owner
) : base() {
79 string_format
= new StringFormat();
80 string_format
.LineAlignment
= StringAlignment
.Center
;
81 string_format
.Alignment
= StringAlignment
.Center
;
82 string_format
.FormatFlags
= StringFormatFlags
.NoWrap
;
85 Size
= new Size(100, 20);
86 ForeColor
= ThemeEngine
.Current
.ColorInfoText
;
87 BackColor
= ThemeEngine
.Current
.ColorInfoWindow
;
89 VisibleChanged
+= new EventHandler(ToolTipWindow_VisibleChanged
);
91 SetStyle (ControlStyles
.UserPaint
| ControlStyles
.AllPaintingInWmPaint
, true);
92 SetStyle (ControlStyles
.ResizeRedraw
| ControlStyles
.Opaque
, true);
95 #endregion // ToolTipWindow Class Constructor
97 #region ToolTipWindow Class Protected Instance Methods
98 protected override void OnCreateControl() {
99 base.OnCreateControl ();
100 XplatUI
.SetTopmost(this.window
.Handle
, IntPtr
.Zero
, true);
103 protected override CreateParams CreateParams
{
107 cp
= base.CreateParams
;
109 cp
.Style
= (int)WindowStyles
.WS_POPUP
;
110 cp
.Style
|= (int)WindowStyles
.WS_CLIPSIBLINGS
;
112 cp
.ExStyle
|= (int)WindowStyles
.WS_EX_TOOLWINDOW
;
118 protected override void OnPaint(PaintEventArgs pevent
) {
119 // We don't do double-buffering on purpose:
120 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
121 // 2) We don't draw much, no need to double buffer
122 ThemeEngine
.Current
.DrawToolTip(pevent
.Graphics
, ClientRectangle
, owner
);
125 protected override void Dispose(bool disposing
) {
127 this.string_format
.Dispose();
129 base.Dispose (disposing
);
132 #endregion // ToolTipWindow Class Protected Instance Methods
134 #region ToolTipWindow Class Private Methods
135 private void ToolTipWindow_VisibleChanged(object sender
, EventArgs e
) {
136 Control control
= (Control
)sender
;
138 if (control
.is_visible
) {
139 XplatUI
.SetTopmost(control
.window
.Handle
, IntPtr
.Zero
, true);
141 XplatUI
.SetTopmost(control
.window
.Handle
, IntPtr
.Zero
, false);
144 #endregion // ToolTipWindow Class Protected Instance Methods
146 #endregion // ToolTipWindow Class
148 #region Public Constructors & Destructors
150 XplatUI
.GetDisplaySize(out display_size
);
154 automatic_delay
= 500;
155 autopop_delay
= 5000;
160 tooltip_strings
= new Hashtable(5);
162 tooltip_window
= new ToolTipWindow(this);
163 tooltip_window
.MouseLeave
+= new EventHandler(control_MouseLeave
);
166 timer
.Enabled
= false;
167 timer
.Tick
+=new EventHandler(timer_Tick
);
170 public ToolTip(System
.ComponentModel
.IContainer cont
) : this() {
171 // Dunno why I'd need the container
176 #endregion // Public Constructors & Destructors
178 #region Public Instance Properties
185 if (is_active
!= value) {
188 if (tooltip_window
.Visible
) {
189 tooltip_window
.Visible
= false;
190 active_control
= null;
196 public int AutomaticDelay
{
198 return automatic_delay
;
202 if (automatic_delay
!= value) {
203 automatic_delay
= value;
204 autopop_delay
= automatic_delay
* 10;
205 initial_delay
= automatic_delay
;
206 re_show_delay
= automatic_delay
/ 5;
211 public int AutoPopDelay
{
213 return autopop_delay
;
217 if (autopop_delay
!= value) {
218 autopop_delay
= value;
223 public int InitialDelay
{
225 return initial_delay
;
229 if (initial_delay
!= value) {
230 initial_delay
= value;
235 public int ReshowDelay
{
237 return re_show_delay
;
241 if (re_show_delay
!= value) {
242 re_show_delay
= value;
247 public bool ShowAlways
{
253 if (show_always
!= value) {
258 #endregion // Public Instance Properties
260 #region Public Instance Methods
261 public bool CanExtend(object target
) {
265 public string GetToolTip(Control control
) {
266 return (string)tooltip_strings
[control
];
269 public void RemoveAll() {
270 tooltip_strings
.Clear();
273 public void SetToolTip(Control control
, string caption
) {
274 tooltip_strings
[control
] = caption
;
276 control
.MouseEnter
+= new EventHandler(control_MouseEnter
);
277 control
.MouseMove
+= new MouseEventHandler(control_MouseMove
);
278 control
.MouseLeave
+= new EventHandler(control_MouseLeave
);
281 public override string ToString() {
282 return base.ToString() + " InitialDelay: " + initial_delay
+ ", ShowAlways: " + show_always
;
284 #endregion // Public Instance Methods
286 #region Protected Instance Methods
287 protected override void Dispose(bool disposing
) {
289 // Mop up the mess; or should we wait for the GC to kick in?
293 // Not sure if we should clean up tooltip_window
294 tooltip_window
.Dispose();
296 tooltip_strings
.Clear();
299 #endregion // Protected Instance Methods
301 #region Private Methods
302 private void control_MouseEnter(object sender
, EventArgs e
) {
305 // Whatever we're displaying right now, we don't want it anymore
306 tooltip_window
.Visible
= false;
309 // if we're in the same control as before (how'd that happen?) or if we're not active, leave
310 if (!is_active
|| (active_control
== (Control
)sender
)) {
314 // As of this writing, our MWF implementation had no clue what an active control was :-(
316 if (((Control
)sender
).GetContainerControl().ActiveControl
== null) {
321 text
= (string)tooltip_strings
[sender
];
325 size
= ThemeEngine
.Current
.ToolTipSize(this, text
);
326 tooltip_window
.Width
= size
.Width
;
327 tooltip_window
.Height
= size
.Height
;
328 tooltip_window
.Text
= text
;
330 // FIXME - this needs to be improved; the tooltip will show up under the mouse, which is annoying; use cursor size once implemented
332 if ((Control
.MousePosition
.X
+1+tooltip_window
.Width
) < display_size
.Width
) {
333 tooltip_window
.Left
= Control
.MousePosition
.X
+1;
335 tooltip_window
.Left
= display_size
.Width
-tooltip_window
.Width
;
338 if ((Control
.MousePosition
.Y
+tooltip_window
.Height
)<display_size
.Height
) {
339 tooltip_window
.Top
= Control
.MousePosition
.Y
;
341 tooltip_window
.Top
= Control
.MousePosition
.Y
-tooltip_window
.Height
;
344 // Since we get the mouse enter before the mouse leave, active_control will still be non-null if we were in a
345 // tooltip'd control; should prolly check on X11 too, and make sure that driver behaves the same way
346 if (active_control
== null) {
347 timer
.Interval
= initial_delay
;
349 timer
.Interval
= re_show_delay
;
352 active_control
= (Control
)sender
;
354 // We're all set, lets wake the timer (which will then make us visible)
355 timer
.Enabled
= true;
359 private void timer_Tick(object sender
, EventArgs e
) {
360 // Show our pretty selves
362 if (!tooltip_window
.Visible
) {
363 // The initial_delay timer kicked in
364 tooltip_window
.Visible
= true;
365 timer
.Interval
= autopop_delay
;
368 // The autopop_delay timer happened
369 tooltip_window
.Visible
= false;
374 private bool MouseInControl(Control control
) {
379 if (control
== null) {
383 m
= Control
.MousePosition
;
384 c
= new Point(control
.Bounds
.X
, control
.Bounds
.Y
);
385 if (control
.parent
!= null) {
386 c
= control
.parent
.PointToScreen(c
);
388 cw
= control
.ClientSize
;
390 if (c
.X
<=m
.X
&& m
.X
<(c
.X
+cw
.Width
) &&
391 c
.Y
<=m
.Y
&& m
.Y
<(c
.Y
+cw
.Height
)) {
397 private void control_MouseLeave(object sender
, EventArgs e
) {
398 // In case the timer is still running, stop it
401 if (!MouseInControl(tooltip_window
) && !MouseInControl(active_control
)) {
402 active_control
= null;
403 tooltip_window
.Visible
= false;
407 private void control_MouseMove(object sender
, MouseEventArgs e
) {
408 // Restart the interval, the mouse moved
413 #endregion // Private Methods