New test.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / InternalWindowManager.cs
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.
19 //
20 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
21 //
22 // Authors:
23 //      Jackson Harper (jackson@ximian.com)
24 //
25 //
26
27
28 using System;
29 using System.Drawing;
30 using System.Runtime.InteropServices;
31
32
33 namespace System.Windows.Forms {
34
35         internal class InternalWindowManager {
36
37                 private static Color titlebar_color;
38
39                 private Size MinTitleBarSize = new Size (115, 25);
40
41                 internal Form form;
42
43                 internal TitleButton close_button;
44                 internal TitleButton maximize_button;
45                 internal TitleButton minimize_button;
46                 protected Rectangle icon_rect;
47                 
48                 private TitleButton [] title_buttons = new TitleButton [3];
49                 
50                 // moving windows
51                 internal Point start;
52                 internal State state;
53                 private FormPos sizing_edge;
54                 internal Rectangle virtual_position;
55
56                 public class TitleButton {
57                         public Rectangle Rectangle;
58                         public ButtonState State;
59                         public CaptionButton Caption;
60                         public EventHandler Clicked;
61
62                         public TitleButton (CaptionButton caption, EventHandler clicked)
63                         {
64                                 Caption = caption;
65                                 Clicked = clicked;
66                         }
67                 }
68
69                 public enum State {
70                         Idle,
71                         Moving,
72                         Sizing,
73                 }
74
75                 [Flags]
76                 public enum FormPos {
77                         None,
78
79                         TitleBar = 1,
80
81                         Top = 2,
82                         Left = 4,
83                         Right = 8,
84                         Bottom = 16,
85
86                         TopLeft = Top | Left,
87                         TopRight = Top | Right,
88
89                         BottomLeft = Bottom | Left,
90                         BottomRight = Bottom | Right,
91
92                         AnyEdge = Top | Left | Right | Bottom,
93                 }
94
95                 public InternalWindowManager (Form form)
96                 {
97                         titlebar_color = Color.FromArgb (255, 0, 0, 255);
98                         this.form = form;
99
100                         form.SizeChanged += new EventHandler (FormSizeChangedHandler);
101
102                         CreateButtons ();
103                 }
104
105                 public Form Form {
106                         get { return form; }
107                 }
108
109                 public Rectangle CloseButtonRect {
110                         get { return close_button.Rectangle; }
111                         set { close_button.Rectangle = value; }
112                 }
113
114                 public Rectangle MinimizeButtonRect {
115                         get { return minimize_button.Rectangle; }
116                         set { minimize_button.Rectangle = value; }
117                 }
118
119                 public Rectangle MaximizeButtonRect {
120                         get { return maximize_button.Rectangle; }
121                         set { maximize_button.Rectangle = value; }
122                 }
123
124                 public Rectangle IconRect {
125                         get { return icon_rect; }
126                         set { value = icon_rect; }
127                 }
128
129                 public int IconWidth {
130                         get { return TitleBarHeight - 5; }
131                 }
132
133                 public bool HandleMessage (ref Message m)
134                 {
135                         switch ((Msg)m.Msg) {
136
137
138                                 // The mouse handling messages are actually
139                                 // not WM_NC* messages except for the first button and NCMOVEs
140                                 // down because we capture on the form
141
142                         case Msg.WM_MOUSEMOVE:
143                                 return HandleMouseMove (form, ref m);
144
145                         case Msg.WM_LBUTTONUP:
146                                 HandleLButtonUp (ref m);
147                                 break;
148
149                         case Msg.WM_RBUTTONDOWN:
150                         case Msg.WM_LBUTTONDOWN:
151                                 return HandleButtonDown (ref m);
152
153                         case Msg.WM_NCHITTEST:
154                                 int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
155                                 int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
156
157                                 NCPointToClient (ref x, ref y);
158
159                                 FormPos pos = FormPosForCoords (x, y);
160                                 
161                                 if (pos == FormPos.TitleBar) {
162                                         m.Result = new IntPtr ((int) HitTest.HTCAPTION);
163                                         return true;
164                                 }
165
166                                 if (!IsSizable)
167                                         return false;
168
169                                 switch (pos) {
170                                 case FormPos.Top:
171                                         m.Result = new IntPtr ((int) HitTest.HTTOP);
172                                         break;
173                                 case FormPos.Left:
174                                         m.Result = new IntPtr ((int) HitTest.HTLEFT);
175                                         break;
176                                 case FormPos.Right:
177                                         m.Result = new IntPtr ((int) HitTest.HTRIGHT);
178                                         break;
179                                 case FormPos.Bottom:
180                                         m.Result = new IntPtr ((int) HitTest.HTBOTTOM);
181                                         break;
182                                 case FormPos.TopLeft:
183                                         m.Result = new IntPtr ((int) HitTest.HTTOPLEFT);
184                                         break;
185                                 case FormPos.TopRight:
186                                         m.Result = new IntPtr ((int) HitTest.HTTOPRIGHT);
187                                         break;
188                                 case FormPos.BottomLeft:
189                                         m.Result = new IntPtr ((int) HitTest.HTBOTTOMLEFT);
190                                         break;
191                                 case FormPos.BottomRight:
192                                         m.Result = new IntPtr ((int) HitTest.HTBOTTOMRIGHT);
193                                         break;
194                                 default:
195                                         // We return false so that DefWndProc handles things
196                                         return false;
197                                 }
198                                 return true;
199
200                                 // Return true from these guys, otherwise win32 will mess up z-order
201                         case Msg.WM_NCLBUTTONUP:
202                                 HandleNCLButtonUp (ref m);
203                                 return true;
204
205                         case Msg.WM_NCLBUTTONDOWN:
206                                 HandleNCLButtonDown (ref m);
207                                 return true;
208
209                         case Msg.WM_NCLBUTTONDBLCLK:
210                                 HandleNCLButtonDblClick (ref m);
211                                 break;
212
213                         case Msg.WM_MOUSE_LEAVE:
214                                 FormMouseLeave (ref m);
215                                 break;
216
217                         case Msg.WM_NCCALCSIZE:
218                                 XplatUIWin32.NCCALCSIZE_PARAMS  ncp;
219
220                                 if (m.WParam == (IntPtr) 1) {
221                                         ncp = (XplatUIWin32.NCCALCSIZE_PARAMS) Marshal.PtrToStructure (m.LParam,
222                                                         typeof (XplatUIWin32.NCCALCSIZE_PARAMS));
223
224                                         int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
225
226                                         if (HasBorders) {
227                                                 ncp.rgrc1.top += TitleBarHeight + bw;
228                                                 ncp.rgrc1.bottom -= bw;
229                                                 ncp.rgrc1.left += bw;
230                                                 ncp.rgrc1.right -= bw;
231                                         }
232
233                                         Marshal.StructureToPtr(ncp, m.LParam, true);
234                                 }
235
236                                 break;
237
238                         case Msg.WM_NCPAINT:
239                                 PaintEventArgs pe = XplatUI.PaintEventStart (form.Handle, false);
240
241                                 Rectangle clip;
242                                 if (m.WParam.ToInt32 () > 1) {
243                                         Region r = Region.FromHrgn (m.WParam);
244                                         RectangleF rf = r.GetBounds (pe.Graphics);
245                                         clip = new Rectangle ((int) rf.X, (int) rf.Y, (int) rf.Width, (int) rf.Height);
246                                 } else {        
247                                         clip = new Rectangle (0, 0, form.Width, form.Height);
248                                 }
249
250                                 ThemeEngine.Current.DrawManagedWindowDecorations (pe.Graphics, clip, this);
251                                 XplatUI.PaintEventEnd (form.Handle, false);
252                                 return true;
253                         }
254
255                         return false;
256                 }
257
258                 public virtual void UpdateBorderStyle (FormBorderStyle border_style)
259                 {
260                         XplatUI.SetBorderStyle (form.Handle, border_style);
261
262                         if (ShouldRemoveWindowManager (border_style)) {
263                                 form.RemoveWindowManager ();
264                                 return;
265                         }
266                                 
267                         CreateButtons ();
268                 }
269
270                 public void HandleMenuMouseDown (MainMenu menu, int x, int y)
271                 {
272                         Point pt = MenuTracker.ScreenToMenu (menu, new Point (x, y));
273
274                         foreach (TitleButton button in title_buttons) {
275                                 if (button != null && button.Rectangle.Contains (pt)) {
276                                         button.Clicked (this, EventArgs.Empty);
277                                         button.State = ButtonState.Pushed;
278                                         return;
279                                 }
280                         }
281                 }
282
283                 public virtual void SetWindowState (FormWindowState old_state, FormWindowState window_state)
284                 {
285                 }
286
287                 public virtual FormWindowState GetWindowState ()
288                 {
289                         return form.window_state;
290                 }
291
292                 public virtual void PointToClient (ref int x, ref int y)
293                 {
294                         // toolwindows stay in screencoords we just have to make sure
295                         // they obey the working area
296                         Rectangle working = SystemInformation.WorkingArea;
297
298                         if (x > working.Right)
299                                 x = working.Right;
300                         if (x < working.Left)
301                                 x = working.Left;
302
303                         if (y < working.Top)
304                                 y = working.Top;
305                         if (y > working.Bottom)
306                                 y = working.Bottom;
307                 }
308
309                 public virtual void PointToScreen (ref int x, ref int y)
310                 {
311                         XplatUI.ClientToScreen (form.Handle, ref x, ref y);
312                 }
313
314                 protected virtual bool ShouldRemoveWindowManager (FormBorderStyle style)
315                 {
316                         return style != FormBorderStyle.FixedToolWindow && style != FormBorderStyle.SizableToolWindow;
317                 }
318
319                 protected virtual void Activate ()
320                 {
321                         // Hack to get a paint
322                         NativeWindow.WndProc (form.Handle, Msg.WM_NCPAINT, IntPtr.Zero, IntPtr.Zero);
323                         form.Refresh ();
324                 }
325
326                 public virtual bool IsActive ()
327                 {
328                         return true;
329                 }
330
331                 private void FormSizeChangedHandler (object sender, EventArgs e)
332                 {
333                         ThemeEngine.Current.ManagedWindowSetButtonLocations (this);
334                         Message m = new Message ();
335                         m.Msg = (int) Msg.WM_NCPAINT;
336                         m.HWnd = form.Handle;
337                         m.LParam = IntPtr.Zero;
338                         m.WParam = new IntPtr (1);
339                         XplatUI.SendMessage (ref m);
340                 }
341
342                 protected void CreateButtons ()
343                 {
344                         switch (form.FormBorderStyle) {
345                         case FormBorderStyle.None:
346                                 close_button = null;
347                                 minimize_button = null;
348                                 maximize_button = null;
349                                 if (IsMaximized || IsMinimized)
350                                         goto case FormBorderStyle.Sizable;
351                                 break;
352                         case FormBorderStyle.FixedToolWindow:
353                         case FormBorderStyle.SizableToolWindow:
354                                 close_button = new TitleButton (CaptionButton.Close, new EventHandler (CloseClicked));
355                                 if (IsMaximized || IsMinimized)
356                                         goto case FormBorderStyle.Sizable;
357                                 break;
358                         case FormBorderStyle.FixedSingle:
359                         case FormBorderStyle.Fixed3D:
360                         case FormBorderStyle.FixedDialog:
361                         case FormBorderStyle.Sizable:
362                                 close_button = new TitleButton (CaptionButton.Close, new EventHandler (CloseClicked));
363                                 minimize_button = new TitleButton (CaptionButton.Minimize, new EventHandler (MinimizeClicked));
364                                 maximize_button = new TitleButton (CaptionButton.Maximize, new EventHandler (MaximizeClicked));
365                                 break;
366                         }
367
368                         title_buttons [0] = close_button;
369                         title_buttons [1] = minimize_button;
370                         title_buttons [2] = maximize_button;
371
372                         ThemeEngine.Current.ManagedWindowSetButtonLocations (this);
373                 }
374
375                 protected virtual bool HandleButtonDown (ref Message m)
376                 {
377                         Activate ();
378                         return false;
379                 }
380
381                 protected virtual bool HandleNCLButtonDown (ref Message m)
382                 {
383                         Activate ();
384
385                         start = Cursor.Position;
386                         virtual_position = form.Bounds;
387
388                         int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
389                         int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
390                         
391                         // Need to adjust because we are in NC land
392                         NCPointToClient (ref x, ref y);\r
393                         FormPos pos = FormPosForCoords (x, y);
394                         Console.WriteLine ("NC POS:   {0} for coords:  {1}, {2}", pos, x, y);
395                         
396                         if (pos == FormPos.TitleBar) {
397                                 HandleTitleBarDown (x, y);
398                                 return true;
399                         }
400
401                         if (IsSizable) {
402                                 if ((pos & FormPos.AnyEdge) == 0)
403                                         return false;
404
405                                 virtual_position = form.Bounds;
406                                 state = State.Sizing;
407                                 sizing_edge = pos;
408                                 form.Capture = true;
409                                 return true;
410                         }
411
412                         return false;
413                 }
414
415                 protected virtual void HandleNCLButtonDblClick (ref Message m)
416                 {
417                 }
418
419                 protected virtual void HandleTitleBarDown (int x, int y)
420                 {
421                         foreach (TitleButton button in title_buttons) {
422                                 if (button != null && button.Rectangle.Contains (x, y)) {
423                                         button.State = ButtonState.Pushed;
424                                         return;
425                                 }
426                         }
427
428                         if (IsMaximized)
429                                 return;
430
431                         state = State.Moving;
432                         form.Capture = true;
433                 }
434
435                 private bool HandleMouseMove (Form form, ref Message m)
436                 {
437                         switch (state) {
438                         case State.Moving:
439                                 HandleWindowMove (m);
440                                 return true;
441                         case State.Sizing:
442                                 HandleSizing (m);
443                                 return true;
444                         }
445
446                         /*
447                         if (IsSizable) {
448                                 int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
449                                 int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
450                                 FormPos pos = FormPosForCoords (x, y);
451                                 Console.WriteLine ("position:   " + pos);
452                                 SetCursorForPos (pos);
453
454                                 ClearVirtualPosition ();
455                                 state = State.Idle;
456                         }
457                         */
458                         
459                         return false;
460                 }
461
462                 private void FormMouseLeave (ref Message m)
463                 {
464                         form.ResetCursor ();
465                 }
466         
467                 protected virtual void HandleWindowMove (Message m)
468                 {
469                         Point move = MouseMove (m);
470
471                         UpdateVP (virtual_position.X + move.X, virtual_position.Y + move.Y,
472                                         virtual_position.Width, virtual_position.Height);
473                 }
474
475                 private void HandleSizing (Message m)
476                 {
477                         Rectangle pos = virtual_position;
478                         int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
479                         int mw = MinTitleBarSize.Width + (bw * 2);
480                         int mh = MinTitleBarSize.Height + (bw * 2);
481                         int x = Cursor.Position.X;
482                         int y = Cursor.Position.Y;
483
484                         PointToClient (ref x, ref y);
485
486                         if ((sizing_edge & FormPos.Top) != 0) {
487                                 if (pos.Bottom - y < mh)
488                                         y = pos.Bottom - mh;
489                                 pos.Height = pos.Bottom - y;
490                                 pos.Y = y;
491                         } else if ((sizing_edge & FormPos.Bottom) != 0) {
492                                 int height = y - pos.Top;
493                                 if (height <= mh)
494                                         height = mh;
495                                 pos.Height = height;
496                         }
497
498                         if ((sizing_edge & FormPos.Left) != 0) {
499                                 if (pos.Right - x < mw)
500                                         x = pos.Right - mw;
501                                 pos.Width = pos.Right - x;
502                                 pos.X = x;
503                         } else if ((sizing_edge & FormPos.Right) != 0) {
504                                 int width = x - form.Left;
505                                 if (width <= mw)
506                                         width = mw;
507                                 pos.Width = width;
508                         }
509
510                         UpdateVP (pos);
511                 }
512
513                 public bool IsMaximized {
514                         get { return GetWindowState () == FormWindowState.Maximized; }
515                 }
516
517                 public bool IsMinimized {
518                         get { return GetWindowState () == FormWindowState.Minimized; }
519                 }
520
521                 public bool IsSizable {
522                         get {
523                                 switch (form.FormBorderStyle) {
524                                 case FormBorderStyle.Sizable:
525                                 case FormBorderStyle.SizableToolWindow:
526                                         return true;
527                                 default:
528                                         return false;
529                                 }
530                         }
531                 }
532
533                 public bool HasBorders {
534                         get {
535                                 return form.FormBorderStyle != FormBorderStyle.None;
536                         }
537                 }
538
539                 public bool IsToolWindow {
540                         get {
541                                 if (form.FormBorderStyle == FormBorderStyle.SizableToolWindow ||
542                                                 form.FormBorderStyle == FormBorderStyle.FixedToolWindow)
543                                         return true;
544                                 return false;
545                         }
546                 }
547
548                 public int TitleBarHeight {
549                         get {
550                                 return ThemeEngine.Current.ManagedWindowTitleBarHeight (this);
551                         }
552                 }
553
554                 protected void UpdateVP (Rectangle r)
555                 {
556                         UpdateVP (r.X, r.Y, r.Width, r.Height);
557                 }
558
559                 protected void UpdateVP (Point loc, int w, int h)
560                 {
561                         UpdateVP (loc.X, loc.Y, w, h);
562                 }
563
564                 protected void UpdateVP (int x, int y, int w, int h)
565                 {
566                         virtual_position.X = x;
567                         virtual_position.Y = y;
568                         virtual_position.Width = w;
569                         virtual_position.Height = h;
570
571                         DrawVirtualPosition (virtual_position);
572                 }
573
574                 private void HandleLButtonUp (ref Message m)
575                 {
576                         if (state == State.Idle)
577                                 return;
578
579                         ClearVirtualPosition ();
580
581                         form.Capture = false;
582                         form.Bounds = virtual_position;
583                         state = State.Idle;
584
585                         OnWindowFinishedMoving ();
586                 }
587
588                 private bool HandleNCLButtonUp (ref Message m)
589                 {
590                         if (form.Capture) {
591                                 ClearVirtualPosition ();
592
593                                 form.Capture = false;
594                                 state = State.Idle;
595                         }
596                                 
597                         int x = Control.LowOrder ((int) m.LParam.ToInt32 ());
598                         int y = Control.HighOrder ((int) m.LParam.ToInt32 ());
599
600                         NCPointToClient (ref x, ref y);
601
602                         foreach (TitleButton button in title_buttons) {
603                                 if (button != null && button.Rectangle.Contains (x, y)) {
604                                         button.Clicked (this, EventArgs.Empty);
605                                         return true;
606                                 }
607                         }
608
609                         return true;
610                 }
611                 
612                 protected void DrawTitleButton (Graphics dc, TitleButton button, Rectangle clip)
613                 {
614                         if (!button.Rectangle.IntersectsWith (clip))
615                                 return;
616
617                         dc.FillRectangle (SystemBrushes.Control, button.Rectangle);
618
619                         ControlPaint.DrawCaptionButton (dc, button.Rectangle,
620                                         button.Caption, ButtonState.Normal);
621                 }
622
623                 public virtual void DrawMaximizedButtons (object sender, PaintEventArgs pe)
624                 {
625                 }
626
627                 protected virtual void CloseClicked (object sender, EventArgs e)
628                 {
629                         form.Close ();
630                 }
631
632                 private void MinimizeClicked (object sender, EventArgs e)
633                 {
634                         if (GetWindowState () != FormWindowState.Minimized) {
635                                 form.WindowState = FormWindowState.Minimized;
636                         } else {
637                                 form.WindowState = FormWindowState.Normal;
638                         }
639                 }
640
641                 private void MaximizeClicked (object sender, EventArgs e)
642                 {
643                         if (GetWindowState () != FormWindowState.Maximized) {
644                                 form.WindowState = FormWindowState.Maximized;
645                         } else {
646                                 form.WindowState = FormWindowState.Normal;
647                         }
648                 }
649
650                 protected Point MouseMove (Message m)
651                 {
652                         Point cp = Cursor.Position;
653                         return new Point (cp.X - start.X, cp.Y - start.Y);
654                 }
655
656                 protected virtual void DrawVirtualPosition (Rectangle virtual_position)
657                 {
658                         form.Bounds = virtual_position;
659                         start = Cursor.Position;
660                 }
661
662                 protected virtual void ClearVirtualPosition ()
663                 {
664                         
665                 }
666
667                 protected virtual void OnWindowFinishedMoving ()
668                 {
669                 }
670
671                 protected virtual void NCPointToClient(ref int x, ref int y) {
672                         form.PointToClient(ref x, ref y);
673                         y += TitleBarHeight;
674                         y += ThemeEngine.Current.ManagedWindowBorderWidth (this);
675                 }
676
677                 protected FormPos FormPosForCoords (int x, int y)
678                 {
679                         int bw = ThemeEngine.Current.ManagedWindowBorderWidth (this);
680                         if (y < TitleBarHeight + bw) {
681                                 //      Console.WriteLine ("A");
682                                 if (y > bw && x > bw &&
683                                                 x < form.Width - bw)
684                                         return FormPos.TitleBar;
685
686                                 if (x < bw || (x < 20 && y < bw))
687                                         return FormPos.TopLeft;
688
689                                 if (x > form.Width - bw ||
690                                         (x > form.Width - 20 && y < bw))
691                                         return FormPos.TopRight;
692
693                                 if (y < bw)
694                                         return FormPos.Top;
695
696                         } else if (y > form.Height - 20) {
697                                 //      Console.WriteLine ("B");
698                                 if (x < bw ||
699                                                 (x < 20 && y > form.Height - bw))
700                                         return FormPos.BottomLeft;
701
702                                 if (x > form.Width - (bw * 2) ||
703                                                 (x > form.Width - 20 &&
704                                                  y > form.Height - bw))
705                                         return FormPos.BottomRight;
706
707                                 if (y > form.Height - (bw * 2))
708                                         return FormPos.Bottom;
709
710
711                         } else if (x < bw) {
712                                 //      Console.WriteLine ("C");
713                                 return FormPos.Left;
714                         } else if (x > form.Width - (bw * 2)) {
715 //                              Console.WriteLine ("D");
716                                 return FormPos.Right;
717                         } else {
718                                 //                      Console.WriteLine ("E   {0}", form.Width - bw);
719                         }
720                         
721                         return FormPos.None;
722                 }
723         }
724 }
725
726