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) 2005 Novell, Inc. (http://www.novell.com)
23 // Jackson Harper (jackson@ximian.com)
30 using System.Runtime.InteropServices;
33 namespace System.Windows.Forms {
35 internal class InternalWindowManager {
36 private Size MinTitleBarSize = new Size (115, 25);
40 internal TitleButton close_button;
41 internal TitleButton maximize_button;
42 internal TitleButton minimize_button;
44 private ToolTip.ToolTipWindow tooltip;
45 private Timer tooltip_timer;
46 private TitleButton tooltip_hovered_button;
47 private TitleButton tooltip_hidden_button;
48 private const int tooltip_hide_interval = 3000;
49 private const int tooltip_show_interval = 1000;
50 private TitleButton [] title_buttons = new TitleButton [3];
55 protected Point clicked_point;
56 private FormPos sizing_edge;
57 internal Rectangle virtual_position;
59 public class TitleButton {
60 public Rectangle Rectangle;
61 public ButtonState State;
62 public CaptionButton Caption;
63 public EventHandler Clicked;
65 public TitleButton (CaptionButton caption, EventHandler clicked)
90 TopRight = Top | Right,
92 BottomLeft = Bottom | Left,
93 BottomRight = Bottom | Right,
95 AnyEdge = Top | Left | Right | Bottom,
98 public InternalWindowManager (Form form)
102 form.SizeChanged += new EventHandler (FormSizeChangedHandler);
111 public bool AnyPushedTitleButtons {
113 return (minimize_button != null && minimize_button.State == ButtonState.Pushed) ||
114 (maximize_button != null && maximize_button.State == ButtonState.Pushed) ||
115 (close_button != null && close_button.State == ButtonState.Pushed);
119 public int IconWidth {
120 get { return TitleBarHeight - 5; }
123 public virtual bool HandleMessage (ref Message m)
125 switch ((Msg)m.Msg) {
128 // The mouse handling messages are actually
129 // not WM_NC* messages except for the first button and NCMOVEs
130 // down because we capture on the form
132 case Msg.WM_MOUSEMOVE:
133 return HandleMouseMove (form, ref m);
135 case Msg.WM_LBUTTONUP:
136 HandleLButtonUp (ref m);
139 case Msg.WM_RBUTTONDOWN:
140 return HandleRButtonDown (ref m);
142 case Msg.WM_LBUTTONDOWN:
143 return HandleLButtonDown (ref m);
145 case Msg.WM_LBUTTONDBLCLK:
146 return HandleLButtonDblClick (ref m);
148 case Msg.WM_PARENTNOTIFY:
149 if (Control.LowOrder(m.WParam.ToInt32()) == (int) Msg.WM_LBUTTONDOWN)
153 case Msg.WM_NCHITTEST:
154 return HandleNCHitTest (ref m);
156 // Return true from these guys, otherwise win32 will mess up z-order
157 case Msg.WM_NCLBUTTONUP:
158 HandleNCLButtonUp (ref m);
161 case Msg.WM_NCLBUTTONDOWN:
162 HandleNCLButtonDown (ref m);
165 case Msg.WM_NCMOUSEMOVE:
166 HandleNCMouseMove (ref m);
169 case Msg.WM_NCLBUTTONDBLCLK:
170 HandleNCLButtonDblClick (ref m);
173 case Msg.WM_NCMOUSELEAVE:
174 HandleNCMouseLeave (ref m);
177 case Msg.WM_MOUSE_LEAVE:
178 HandleMouseLeave (ref m);
181 case Msg.WM_NCCALCSIZE:
182 HandleNCCalcSize (ref m);
186 return HandleNCPaint ();
192 protected virtual bool HandleNCPaint ()
194 PaintEventArgs pe = XplatUI.PaintEventStart (form.Handle, false);
197 // clip region is not correct on win32.
198 // if (m.WParam.ToInt32 () > 1) {
199 // Region r = Region.FromHrgn (m.WParam);
200 // RectangleF rf = r.GetBounds (pe.Graphics);
201 // clip = new Rectangle ((int) rf.X, (int) rf.Y, (int) rf.Width, (int) rf.Height);
203 clip = new Rectangle (0, 0, form.Width, form.Height);
206 ThemeEngine.Current.DrawManagedWindowDecorations (pe.Graphics, clip, this);
207 XplatUI.PaintEventEnd (form.Handle, false);
211 protected virtual void HandleNCCalcSize (ref Message m)
213 XplatUIWin32.NCCALCSIZE_PARAMS ncp;
215 if (m.WParam == (IntPtr)1) {
216 ncp = (XplatUIWin32.NCCALCSIZE_PARAMS)Marshal.PtrToStructure (m.LParam,
217 typeof (XplatUIWin32.NCCALCSIZE_PARAMS));
219 int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
222 ncp.rgrc1.top += TitleBarHeight + bw;
223 ncp.rgrc1.bottom -= bw;
224 ncp.rgrc1.left += bw;
225 ncp.rgrc1.right -= bw;
228 Marshal.StructureToPtr (ncp, m.LParam, true);
232 protected virtual bool HandleNCHitTest (ref Message m)
235 int x = Control.LowOrder ((int)m.LParam.ToInt32 ());
236 int y = Control.HighOrder ((int)m.LParam.ToInt32 ());
238 NCPointToClient (ref x, ref y);
240 FormPos pos = FormPosForCoords (x, y);
242 if (pos == FormPos.TitleBar) {
243 m.Result = new IntPtr ((int)HitTest.HTCAPTION);
252 m.Result = new IntPtr ((int)HitTest.HTTOP);
255 m.Result = new IntPtr ((int)HitTest.HTLEFT);
258 m.Result = new IntPtr ((int)HitTest.HTRIGHT);
261 m.Result = new IntPtr ((int)HitTest.HTBOTTOM);
263 case FormPos.TopLeft:
264 m.Result = new IntPtr ((int)HitTest.HTTOPLEFT);
266 case FormPos.TopRight:
267 m.Result = new IntPtr ((int)HitTest.HTTOPRIGHT);
269 case FormPos.BottomLeft:
270 m.Result = new IntPtr ((int)HitTest.HTBOTTOMLEFT);
272 case FormPos.BottomRight:
273 m.Result = new IntPtr ((int)HitTest.HTBOTTOMRIGHT);
276 // We return false so that DefWndProc handles things
282 public virtual void UpdateBorderStyle (FormBorderStyle border_style)
284 XplatUI.SetBorderStyle (form.Handle, border_style);
286 if (ShouldRemoveWindowManager (border_style)) {
287 form.RemoveWindowManager ();
294 #region ToolTip helpers
295 // Called from MouseMove if mouse is over a button
296 private void ToolTipStart (TitleButton button)
298 tooltip_hovered_button = button;
300 if (tooltip_hovered_button == tooltip_hidden_button)
302 tooltip_hidden_button = null;
304 if (tooltip != null && tooltip.Visible)
307 if (tooltip_timer == null) {
309 tooltip_timer = new Timer ();
310 tooltip_timer.Tick += new EventHandler (ToolTipTimerTick);
313 tooltip_timer.Interval = tooltip_show_interval;
314 tooltip_timer.Start ();
315 tooltip_hovered_button = button;
318 private void ToolTipTimerTick (object sender, EventArgs e)
320 if (tooltip_timer.Interval == tooltip_hide_interval) {
321 tooltip_hidden_button = tooltip_hovered_button;
327 // Called from timer (with only_refresh = false)
328 // Called from ToolTipStart if tooltip is already shown (with only_refresh = true)
329 private void ToolTipShow (bool only_refresh)
331 string text = Locale.GetText (tooltip_hovered_button.Caption.ToString ());
333 tooltip_timer.Interval = tooltip_hide_interval;
334 tooltip_timer.Enabled = true;
336 if (only_refresh && (tooltip == null || !tooltip.Visible )) {
341 tooltip = new ToolTip.ToolTipWindow ();
342 else if (tooltip.Text == text && tooltip.Visible)
344 else if (tooltip.Visible)
345 tooltip.Visible = false;
347 if (form.WindowState == FormWindowState.Maximized)
348 tooltip.Present (form.MdiParent, text);
350 tooltip.Present (form, text);
354 // Called from MouseLeave (with reset_hidden_button = true)
355 // Called from MouseDown (with reset_hidden_button = false)
356 // Called from MouseMove if mouse isn't over any button (with reset_hidden_button = false)
357 // Called from Timer if hiding (with reset_hidden_button = false)
358 private void ToolTipHide (bool reset_hidden_button)
360 if (tooltip_timer != null)
361 tooltip_timer.Enabled = false;
362 if (tooltip != null && tooltip.Visible)
363 tooltip.Visible = false;
364 if (reset_hidden_button)
365 tooltip_hidden_button = null;
369 public virtual void SetWindowState (FormWindowState old_state, FormWindowState window_state)
373 public virtual FormWindowState GetWindowState ()
375 return form.window_state;
378 public virtual void PointToClient (ref int x, ref int y)
380 // toolwindows stay in screencoords we just have to make sure
381 // they obey the working area
382 Rectangle working = SystemInformation.WorkingArea;
384 if (x > working.Right)
386 if (x < working.Left)
391 if (y > working.Bottom)
395 public virtual void PointToScreen (ref int x, ref int y)
397 XplatUI.ClientToScreen (form.Handle, ref x, ref y);
400 protected virtual bool ShouldRemoveWindowManager (FormBorderStyle style)
402 return style != FormBorderStyle.FixedToolWindow && style != FormBorderStyle.SizableToolWindow;
405 protected virtual void Activate ()
410 public virtual bool IsActive ()
416 private void FormSizeChangedHandler (object sender, EventArgs e)
418 ThemeEngine.Current.ManagedWindowSetButtonLocations (this);
419 XplatUI.InvalidateNC (form.Handle);
422 protected void CreateButtons ()
424 switch (form.FormBorderStyle) {
425 case FormBorderStyle.None:
427 minimize_button = null;
428 maximize_button = null;
429 if (IsMaximized || IsMinimized)
430 goto case FormBorderStyle.Sizable;
432 case FormBorderStyle.FixedToolWindow:
433 case FormBorderStyle.SizableToolWindow:
434 close_button = new TitleButton (CaptionButton.Close, new EventHandler (CloseClicked));
435 if (IsMaximized || IsMinimized)
436 goto case FormBorderStyle.Sizable;
438 case FormBorderStyle.FixedSingle:
439 case FormBorderStyle.Fixed3D:
440 case FormBorderStyle.FixedDialog:
441 case FormBorderStyle.Sizable:
442 close_button = new TitleButton (CaptionButton.Close, new EventHandler (CloseClicked));
443 minimize_button = new TitleButton (CaptionButton.Minimize, new EventHandler (MinimizeClicked));
444 maximize_button = new TitleButton (CaptionButton.Maximize, new EventHandler (MaximizeClicked));
448 title_buttons [0] = close_button;
449 title_buttons [1] = minimize_button;
450 title_buttons [2] = maximize_button;
452 ThemeEngine.Current.ManagedWindowSetButtonLocations (this);
455 protected virtual bool HandleRButtonDown (ref Message m)
461 protected virtual bool HandleLButtonDown (ref Message m)
467 protected virtual bool HandleLButtonDblClick(ref Message m)
472 protected virtual bool HandleNCMouseLeave (ref Message m)
474 int x = Control.LowOrder ((int)m.LParam.ToInt32 ());
475 int y = Control.HighOrder ((int)m.LParam.ToInt32 ());
477 NCPointToClient (ref x, ref y);
478 FormPos pos = FormPosForCoords (x, y);
480 if (pos != FormPos.TitleBar) {
481 HandleTitleBarLeave (x, y);
488 protected virtual bool HandleNCMouseMove (ref Message m)
490 int x = Control.LowOrder((int)m.LParam.ToInt32( ));
491 int y = Control.HighOrder((int)m.LParam.ToInt32( ));
493 NCPointToClient (ref x, ref y);
494 FormPos pos = FormPosForCoords (x, y);
496 if (pos == FormPos.TitleBar) {
497 HandleTitleBarMouseMove (x, y);
505 protected virtual bool HandleNCLButtonDown (ref Message m)
509 start = Cursor.Position;
510 virtual_position = form.Bounds;
512 int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
513 int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
515 // Need to adjust because we are in NC land
516 NCPointToClient (ref x, ref y);
517 FormPos pos = FormPosForCoords (x, y);
519 if (pos == FormPos.TitleBar) {
520 HandleTitleBarDown (x, y);
525 if ((pos & FormPos.AnyEdge) == 0)
528 virtual_position = form.Bounds;
529 state = State.Sizing;
538 protected virtual void HandleNCLButtonDblClick (ref Message m)
540 int x = Control.LowOrder ((int)m.LParam.ToInt32 ());
541 int y = Control.HighOrder ((int)m.LParam.ToInt32 ());
543 // Need to adjust because we are in NC land
544 NCPointToClient (ref x, ref y);
546 FormPos pos = FormPosForCoords (x, y);
547 if (pos == FormPos.TitleBar || pos == FormPos.Top)
548 HandleTitleBarDoubleClick (x, y);
552 protected virtual void HandleTitleBarDoubleClick (int x, int y)
557 protected virtual void HandleTitleBarLeave (int x, int y)
559 foreach (TitleButton button in title_buttons) {
560 if (button != null) {
561 button.State = ButtonState.Normal;
568 protected virtual void HandleTitleBarMouseMove (int x, int y)
570 bool any_change = false;
571 bool any_tooltip = false;
572 bool any_pushed_buttons = AnyPushedTitleButtons;
574 foreach (TitleButton button in title_buttons) {
578 if (button.Rectangle.Contains (x, y)) {
579 if (any_pushed_buttons) {
580 any_change |= button.State != ButtonState.Pushed;
581 button.State = ButtonState.Pushed;
583 ToolTipStart (button);
586 if (any_pushed_buttons) {
587 any_change |= button.State != ButtonState.Normal;
588 button.State = ButtonState.Normal;
593 if (IsMaximized && form.IsMdiChild)
594 XplatUI.InvalidateNC (form.MdiParent.Handle);
596 XplatUI.InvalidateNC (form.Handle);
602 protected virtual void HandleTitleBarUp (int x, int y)
604 foreach (TitleButton button in title_buttons) {
608 button.State = ButtonState.Normal;
609 if (button.Rectangle.Contains (x, y)) {
610 button.Clicked (this, EventArgs.Empty);
617 protected virtual void HandleTitleBarDown (int x, int y)
621 foreach (TitleButton button in title_buttons) {
622 if (button != null) {
623 if (button.Rectangle.Contains (x, y)) {
624 button.State = ButtonState.Pushed;
626 button.State = ButtonState.Normal;
631 if (!AnyPushedTitleButtons && !IsMaximized){
632 state = State.Moving;
633 clicked_point = new Point (x, y);
634 if (form.IsMdiChild) {
635 form.CaptureWithConfine (form.Parent);
641 XplatUI.InvalidateNC (form.Handle);
644 private bool HandleMouseMove (Form form, ref Message m)
648 HandleWindowMove (m);
658 private void HandleMouseLeave (ref Message m)
663 protected virtual void HandleWindowMove (Message m)
665 Point move = MouseMove (m);
667 UpdateVP (virtual_position.X + move.X, virtual_position.Y + move.Y,
668 virtual_position.Width, virtual_position.Height);
671 private void HandleSizing (Message m)
673 Rectangle pos = virtual_position;
674 int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
675 int mw = MinTitleBarSize.Width + (bw * 2);
676 int mh = MinTitleBarSize.Height + (bw * 2);
677 int x = Cursor.Position.X;
678 int y = Cursor.Position.Y;
680 PointToClient (ref x, ref y);
682 if ((sizing_edge & FormPos.Top) != 0) {
683 if (pos.Bottom - y < mh)
685 pos.Height = pos.Bottom - y;
687 } else if ((sizing_edge & FormPos.Bottom) != 0) {
688 int height = y - pos.Top;
694 if ((sizing_edge & FormPos.Left) != 0) {
695 if (pos.Right - x < mw)
697 pos.Width = pos.Right - x;
699 } else if ((sizing_edge & FormPos.Right) != 0) {
700 int width = x - form.Left;
709 public bool IsMaximized {
710 get { return GetWindowState () == FormWindowState.Maximized; }
713 public bool IsMinimized {
714 get { return GetWindowState () == FormWindowState.Minimized; }
717 public bool IsSizable {
719 switch (form.FormBorderStyle) {
720 case FormBorderStyle.Sizable:
721 case FormBorderStyle.SizableToolWindow:
722 return (form.window_state != FormWindowState.Minimized);
729 public bool HasBorders {
731 return form.FormBorderStyle != FormBorderStyle.None;
735 public bool IsToolWindow {
737 if (form.FormBorderStyle == FormBorderStyle.SizableToolWindow ||
738 form.FormBorderStyle == FormBorderStyle.FixedToolWindow)
744 public int TitleBarHeight {
746 return ThemeEngine.Current.ManagedWindowTitleBarHeight (this);
750 protected void UpdateVP (Rectangle r)
752 UpdateVP (r.X, r.Y, r.Width, r.Height);
755 protected void UpdateVP (Point loc, int w, int h)
757 UpdateVP (loc.X, loc.Y, w, h);
760 protected void UpdateVP (int x, int y, int w, int h)
762 virtual_position.X = x;
763 virtual_position.Y = y;
764 virtual_position.Width = w;
765 virtual_position.Height = h;
767 DrawVirtualPosition (virtual_position);
770 protected virtual void HandleLButtonUp (ref Message m)
772 if (state == State.Idle)
775 ClearVirtualPosition ();
777 form.Capture = false;
778 if (state == State.Moving && form.Location != virtual_position.Location)
779 form.Location = virtual_position.Location;
780 else if (state == State.Sizing && form.Bounds != virtual_position)
781 form.Bounds = virtual_position;
784 OnWindowFinishedMoving ();
787 private bool HandleNCLButtonUp (ref Message m)
790 ClearVirtualPosition ();
792 form.Capture = false;
794 if (form.MdiContainer != null)
795 form.MdiContainer.SizeScrollBars();
798 int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
799 int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
801 NCPointToClient (ref x, ref y);
802 FormPos pos = FormPosForCoords (x, y);
804 if (pos == FormPos.TitleBar) {
805 HandleTitleBarUp (x, y);
812 protected void DrawTitleButton (Graphics dc, TitleButton button, Rectangle clip)
814 if (!button.Rectangle.IntersectsWith (clip))
817 dc.FillRectangle (SystemBrushes.Control, button.Rectangle);
818 ControlPaint.DrawCaptionButton (dc, button.Rectangle,
819 button.Caption, button.State);
822 public virtual void DrawMaximizedButtons (object sender, PaintEventArgs pe)
826 protected virtual void CloseClicked (object sender, EventArgs e)
831 private void MinimizeClicked (object sender, EventArgs e)
833 if (GetWindowState () != FormWindowState.Minimized) {
834 form.WindowState = FormWindowState.Minimized;
836 form.WindowState = FormWindowState.Normal;
840 private void MaximizeClicked (object sender, EventArgs e)
842 if (GetWindowState () != FormWindowState.Maximized) {
843 form.WindowState = FormWindowState.Maximized;
845 form.WindowState = FormWindowState.Normal;
849 protected Point MouseMove (Message m)
851 Point cp = Cursor.Position;
852 return new Point (cp.X - start.X, cp.Y - start.Y);
855 protected virtual void DrawVirtualPosition (Rectangle virtual_position)
857 form.Bounds = virtual_position;
858 start = Cursor.Position;
861 protected virtual void ClearVirtualPosition ()
866 protected virtual void OnWindowFinishedMoving ()
870 protected virtual void NCPointToClient(ref int x, ref int y) {
871 form.PointToClient(ref x, ref y);
872 NCClientToNC (ref x, ref y);
875 protected virtual void NCClientToNC (ref int x, ref int y) {
877 y += ThemeEngine.Current.ManagedWindowBorderWidth (this);
880 protected FormPos FormPosForCoords (int x, int y)
882 int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
883 if (y < TitleBarHeight + bw) {
884 // Console.WriteLine ("A");
885 if (y > bw && x > bw &&
887 return FormPos.TitleBar;
889 if (x < bw || (x < 20 && y < bw))
890 return FormPos.TopLeft;
892 if (x > form.Width - bw ||
893 (x > form.Width - 20 && y < bw))
894 return FormPos.TopRight;
899 } else if (y > form.Height - 20) {
900 // Console.WriteLine ("B");
902 (x < 20 && y > form.Height - bw))
903 return FormPos.BottomLeft;
905 if (x > form.Width - (bw * 2) ||
906 (x > form.Width - 20 &&
907 y > form.Height - bw))
908 return FormPos.BottomRight;
910 if (y > form.Height - (bw * 2))
911 return FormPos.Bottom;
915 // Console.WriteLine ("C");
917 } else if (x > form.Width - (bw * 2)) {
918 // Console.WriteLine ("D");
919 return FormPos.Right;
921 // Console.WriteLine ("E {0}", form.Width - bw);