* ToolTip.cs: use Visible instead of is_visible.
[mcs.git] / class / Managed.Windows.Forms / System.Windows.Forms / ToolTip.cs
blob39bf9b63941c805bf5332418b5bdd612ef55e5d4
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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)
22 // Authors:
23 // Peter Bartok pbartok@novell.com
28 // COMPLETE
30 using System.Collections;
31 using System.ComponentModel;
32 using System.Drawing;
34 namespace System.Windows.Forms {
35 [ProvideProperty ("ToolTip", typeof(System.Windows.Forms.Control))]
36 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
37 public
38 #if !NET_2_0
39 sealed
40 #endif
41 class ToolTip : System.ComponentModel.Component, System.ComponentModel.IExtenderProvider {
42 #region Local variables
43 internal bool is_active;
44 internal int automatic_delay;
45 internal int autopop_delay;
46 internal int initial_delay;
47 internal int re_show_delay;
48 internal bool show_always;
50 internal ToolTipWindow tooltip_window; // The actual tooltip window
51 internal Hashtable tooltip_strings; // List of strings for each control, indexed by control
52 internal ArrayList controls;
53 internal Control active_control; // Control for which the tooltip is currently displayed
54 internal Control last_control; // last control the mouse was in
55 internal Timer timer; // Used for the various intervals
56 #endregion // Local variables
58 #region ToolTipWindow Class
59 internal class ToolTipWindow : Control {
60 #region ToolTipWindow Class Local Variables
61 internal StringFormat string_format;
62 #endregion // ToolTipWindow Class Local Variables
64 #region ToolTipWindow Class Constructor
65 internal ToolTipWindow() {
67 string_format = new StringFormat();
68 string_format.LineAlignment = StringAlignment.Center;
69 string_format.Alignment = StringAlignment.Center;
70 string_format.FormatFlags = StringFormatFlags.NoWrap;
72 Visible = false;
73 Size = new Size(100, 20);
74 ForeColor = ThemeEngine.Current.ColorInfoText;
75 BackColor = ThemeEngine.Current.ColorInfo;
77 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
79 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
80 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
83 #endregion // ToolTipWindow Class Constructor
85 #region ToolTipWindow Class Protected Instance Methods
86 protected override void OnCreateControl() {
87 base.OnCreateControl ();
88 XplatUI.SetTopmost(this.window.Handle, IntPtr.Zero, true);
91 protected override CreateParams CreateParams {
92 get {
93 CreateParams cp;
95 cp = base.CreateParams;
97 cp.Style = (int)WindowStyles.WS_POPUP;
98 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
100 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
102 return cp;
106 protected override void OnPaint(PaintEventArgs pevent) {
107 // We don't do double-buffering on purpose:
108 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
109 // 2) We don't draw much, no need to double buffer
110 ThemeEngine.Current.DrawToolTip(pevent.Graphics, ClientRectangle, this);
112 base.OnPaint(pevent);
115 protected override void Dispose(bool disposing) {
116 if (disposing) {
117 this.string_format.Dispose();
119 base.Dispose (disposing);
122 protected override void WndProc(ref Message m) {
123 if (m.Msg == (int)Msg.WM_SETFOCUS) {
124 if (m.WParam != IntPtr.Zero) {
125 XplatUI.SetFocus(m.WParam);
128 base.WndProc (ref m);
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.Visible) {
139 XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, true);
140 } else {
141 XplatUI.SetTopmost(control.window.Handle, IntPtr.Zero, false);
144 #endregion // ToolTipWindow Class Protected Instance Methods
146 public void Present (Control control, string text)
148 if (IsDisposed)
149 return;
151 Size display_size;
152 XplatUI.GetDisplaySize (out display_size);
154 Size size = ThemeEngine.Current.ToolTipSize (this, text);
155 Width = size.Width;
156 Height = size.Height;
157 Text = text;
159 int cursor_w, cursor_h, hot_x, hot_y;
160 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
161 Point loc = Control.MousePosition;
162 loc.Y += (cursor_h - hot_y);
164 if ((loc.X + Width) > display_size.Width)
165 loc.X = display_size.Width - Width;
167 if ((loc.Y + Height) > display_size.Height)
168 loc.Y = Control.MousePosition.Y - Height - hot_y;
170 Location = loc;
171 Visible = true;
174 #endregion // ToolTipWindow Class
176 #region Public Constructors & Destructors
177 public ToolTip() {
179 // Defaults from MS
180 is_active = true;
181 automatic_delay = 500;
182 autopop_delay = 5000;
183 initial_delay = 500;
184 re_show_delay = 100;
185 show_always = false;
187 tooltip_strings = new Hashtable(5);
188 controls = new ArrayList(5);
190 tooltip_window = new ToolTipWindow();
191 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
193 timer = new Timer();
194 timer.Enabled = false;
195 timer.Tick +=new EventHandler(timer_Tick);
198 public ToolTip(System.ComponentModel.IContainer cont) : this() {
199 cont.Add (this);
202 ~ToolTip() {
204 #endregion // Public Constructors & Destructors
206 #region Public Instance Properties
207 [DefaultValue (true)]
208 public bool Active {
209 get {
210 return is_active;
213 set {
214 if (is_active != value) {
215 is_active = value;
217 if (tooltip_window.Visible) {
218 tooltip_window.Visible = false;
219 active_control = null;
225 [DefaultValue (500)]
226 [RefreshProperties (RefreshProperties.All)]
227 public int AutomaticDelay {
228 get {
229 return automatic_delay;
232 set {
233 if (automatic_delay != value) {
234 automatic_delay = value;
235 autopop_delay = automatic_delay * 10;
236 initial_delay = automatic_delay;
237 re_show_delay = automatic_delay / 5;
242 [RefreshProperties (RefreshProperties.All)]
243 public int AutoPopDelay {
244 get {
245 return autopop_delay;
248 set {
249 if (autopop_delay != value) {
250 autopop_delay = value;
255 [RefreshProperties (RefreshProperties.All)]
256 public int InitialDelay {
257 get {
258 return initial_delay;
261 set {
262 if (initial_delay != value) {
263 initial_delay = value;
268 [RefreshProperties (RefreshProperties.All)]
269 public int ReshowDelay {
270 get {
271 return re_show_delay;
274 set {
275 if (re_show_delay != value) {
276 re_show_delay = value;
281 [DefaultValue (false)]
282 public bool ShowAlways {
283 get {
284 return show_always;
287 set {
288 if (show_always != value) {
289 show_always = value;
293 #endregion // Public Instance Properties
295 #region Public Instance Methods
296 public bool CanExtend(object target) {
297 return false;
300 [Localizable (true)]
301 [DefaultValue ("")]
302 public string GetToolTip(Control control) {
303 string tooltip = (string)tooltip_strings[control];
304 if (tooltip == null)
305 return "";
306 return tooltip;
309 public void RemoveAll() {
310 tooltip_strings.Clear();
311 controls.Clear();
314 public void SetToolTip(Control control, string caption) {
315 tooltip_strings[control] = caption;
317 // no need for duplicates
318 if (!controls.Contains(control)) {
319 control.MouseEnter += new EventHandler(control_MouseEnter);
320 control.MouseMove += new MouseEventHandler(control_MouseMove);
321 control.MouseLeave += new EventHandler(control_MouseLeave);
322 controls.Add(control);
325 // if SetToolTip is called from a control and the mouse is currently over that control,
326 // make sure that tooltip_window.Text gets updated
327 if (caption != null && last_control == control) {
328 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
329 tooltip_window.Width = size.Width;
330 tooltip_window.Height = size.Height;
331 tooltip_window.Text = caption;
335 public override string ToString() {
336 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
338 #endregion // Public Instance Methods
340 #region Protected Instance Methods
341 protected override void Dispose(bool disposing) {
342 if (disposing) {
343 // Mop up the mess; or should we wait for the GC to kick in?
344 timer.Stop();
345 timer.Dispose();
347 // Not sure if we should clean up tooltip_window
348 tooltip_window.Dispose();
350 tooltip_strings.Clear();
352 controls.Clear();
355 #endregion // Protected Instance Methods
357 enum TipState {
358 Initial,
359 Show,
360 Down
363 TipState state = TipState.Initial;
365 #region Private Methods
366 private void control_MouseEnter(object sender, EventArgs e)
368 last_control = (Control)sender;
370 // Whatever we're displaying right now, we don't want it anymore
371 tooltip_window.Visible = false;
372 timer.Stop();
373 state = TipState.Initial;
375 // if we're in the same control as before (how'd that happen?) or if we're not active, leave
376 if (!is_active || (active_control == (Control)sender)) {
377 return;
380 if (!show_always) {
381 IContainerControl cc = last_control.GetContainerControl ();
382 if ((cc == null) || (cc.ActiveControl == null)) {
383 return;
387 string text = (string)tooltip_strings[sender];
388 if (text != null && text.Length > 0) {
390 if (active_control == null) {
391 timer.Interval = initial_delay;
392 } else {
393 timer.Interval = re_show_delay;
396 active_control = (Control)sender;
397 timer.Start ();
401 private void timer_Tick(object sender, EventArgs e) {
402 timer.Stop();
404 switch (state) {
405 case TipState.Initial:
406 if (active_control == null)
407 return;
408 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
409 state = TipState.Show;
410 timer.Interval = autopop_delay;
411 timer.Start();
412 break;
414 case TipState.Show:
415 tooltip_window.Visible = false;
416 state = TipState.Down;
417 break;
419 default:
420 throw new Exception ("Timer shouldn't be running in state: " + state);
425 private bool MouseInControl(Control control) {
426 Point m;
427 Point c;
428 Size cw;
430 if (control == null) {
431 return false;
434 m = Control.MousePosition;
435 c = new Point(control.Bounds.X, control.Bounds.Y);
436 if (control.Parent != null) {
437 c = control.Parent.PointToScreen(c);
439 cw = control.ClientSize;
441 if (c.X<=m.X && m.X<(c.X+cw.Width) &&
442 c.Y<=m.Y && m.Y<(c.Y+cw.Height)) {
443 return true;
445 return false;
448 private void control_MouseLeave(object sender, EventArgs e) {
449 timer.Stop();
451 if (!MouseInControl(tooltip_window) && !MouseInControl(active_control)) {
452 active_control = null;
453 tooltip_window.Visible = false;
456 if (last_control == (Control)sender)
457 last_control = null;
460 private void control_MouseMove(object sender, MouseEventArgs e) {
461 if (state != TipState.Down) {
462 timer.Stop();
463 timer.Start();
466 #endregion // Private Methods