* TreeView.cs: Don't draw the selected node when we lose
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Splitter.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 //      Peter Dennis Bartok     (pbartok@novell.com)
24 //
25 //
26
27 // COMPLETE
28
29 #undef Debug
30
31 using System;
32 using System.ComponentModel;
33 using System.Drawing;
34 using System.Reflection;
35 using System.Runtime.InteropServices;
36
37 namespace System.Windows.Forms {
38         [DefaultEvent("SplitterMoved")]
39         [Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
40         [DefaultProperty("Dock")]
41         public class Splitter : Control, IMessageFilter {
42                 #region Enums
43                 private enum DrawType {
44                         Initial,
45                         Redraw,
46                         Finish
47                 }
48                 #endregion      // Enums
49
50                 #region Local Variables
51                 static private Cursor           splitter_ns;
52                 static private Cursor           splitter_we;
53                 private BorderStyle             border_style;
54                 private int                     min_extra;
55                 private int                     min_size;
56                 private int                     split_position;         // Current splitter position
57                 private int                     prev_split_position;    // Previous splitter position, only valid during drag
58                 private int                     click_offset;           // Click offset from border of splitter control
59                 private int                     splitter_size;          // Size (width or height) of our splitter control
60                 private bool                    horizontal;             // true if we've got a horizontal splitter
61                 private Control                 affected;               // The control that the splitter resizes
62                 private Control                 filler;                 // The control that MinExtra prevents from being shrunk to 0 size
63                 private SplitterEventArgs       sevent;                 // We cache the object, prevents fragmentation
64                 private int                     limit_min;              // The max we're allowed to move the splitter left/up
65                 private int                     limit_max;              // The max we're allowed to move the splitter right/down
66                 #endregion      // Local Variables
67
68                 #region Constructors
69                 static Splitter() {
70                         try {
71                                 splitter_ns = new Cursor(typeof(Splitter), "SpliterNS.cur");
72                         }
73
74                         catch (System.IO.FileNotFoundException) {
75                                 splitter_ns = Cursors.SizeNS;
76                         }
77
78                         try {
79                                 splitter_we = new Cursor(typeof(Splitter), "SplitterWE.cur");
80                         }
81
82                         catch (System.IO.FileNotFoundException) {
83                                 splitter_we = Cursors.SizeWE;
84                         }
85                 }
86
87                 public Splitter() {
88
89                         min_extra = 25;
90                         min_size = 25;
91                         split_position = -1;
92                         splitter_size = 3;
93                         horizontal = false;
94                         sevent = new SplitterEventArgs(0, 0, 0, 0);
95
96                         SetStyle(ControlStyles.Selectable, false);
97                         Anchor = AnchorStyles.None;
98
99                         Paint += new PaintEventHandler(PaintSplitter);
100                         Layout += new LayoutEventHandler(LayoutSplitter);
101                         Cursor = splitter_we;
102                 }
103                 #endregion      // Constructors
104
105                 #region Public Instance Properties
106                 [Browsable(false)]
107                 [EditorBrowsable(EditorBrowsableState.Never)]
108                 public override bool AllowDrop {
109                         get {
110                                 return base.AllowDrop;
111                         }
112
113                         set {
114                                 base.AllowDrop = value;
115                         }
116                 }
117
118                 [Browsable(false)]
119                 [DefaultValue(AnchorStyles.None)]
120                 [EditorBrowsable(EditorBrowsableState.Never)]
121                 public override AnchorStyles Anchor {
122                         get {
123                                 return AnchorStyles.None;
124                         }
125
126                         set {
127                                 ;       // MS doesn't set it
128                         }
129                 }
130
131                 [Browsable(false)]
132                 [EditorBrowsable(EditorBrowsableState.Never)]
133                 public override Image BackgroundImage {
134                         get {
135                                 return base.BackgroundImage;
136                         }
137
138                         set {
139                                 base.BackgroundImage = value;
140                         }
141                 }
142
143                 [DispId(-504)]
144                 [DefaultValue (BorderStyle.None)]
145                 [MWFDescription("Sets the border style for the splitter")]
146                 [MWFCategory("Appearance")]
147                 public BorderStyle BorderStyle {
148                         get {
149                                 return border_style;
150                         }
151
152                         set {
153                                 border_style = value;
154
155                                 switch(value) {
156                                         case BorderStyle.FixedSingle: {
157                                                 splitter_size = 4;      // We don't get motion events for 1px wide windows on X11. sigh.
158                                                 break;
159                                         }
160
161                                         case BorderStyle.Fixed3D: {
162                                                 value = BorderStyle.None;
163                                                 splitter_size = 3;
164                                                 break;
165                                         }
166
167                                         case BorderStyle.None: {
168                                                 splitter_size = 3;
169                                                 break;
170                                         }
171
172                                         default: {
173                                                 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
174                                         }
175                                 }
176
177                                 base.InternalBorderStyle = value;
178                         }
179                 }
180
181                 [DefaultValue(DockStyle.Left)]
182                 [Localizable(true)]
183                 public override DockStyle Dock {
184                         get {
185                                 return base.Dock;
186                         }
187
188                         set {
189                                 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
190                                         throw new ArgumentException("Splitter must be docked left, top, bottom or right");
191                                 }
192
193                                 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
194                                         horizontal = true;
195                                         Cursor = splitter_ns;
196                                 } else {
197                                         horizontal = false;
198                                         Cursor = splitter_we;
199                                 }
200                                 base.Dock = value;
201                         }
202                 }
203
204                 [Browsable(false)]
205                 [EditorBrowsable(EditorBrowsableState.Never)]
206                 public override Font Font {
207                         get {
208                                 return base.Font;
209                         }
210
211                         set {
212                                 base.Font = value;
213                         }
214                 }
215
216                 [Browsable(false)]
217                 [EditorBrowsable(EditorBrowsableState.Never)]
218                 public override Color ForeColor {
219                         get {
220                                 return base.ForeColor;
221                         }
222
223                         set {
224                                 base.ForeColor = value;
225                         }
226                 }
227
228                 [Browsable(false)]
229                 [EditorBrowsable(EditorBrowsableState.Never)]
230                 public new ImeMode ImeMode {
231                         get {
232                                 return base.ImeMode;
233                         }
234
235                         set {
236                                 base.ImeMode = value;
237                         }
238                 }
239
240                 [DefaultValue(25)]
241                 [Localizable(true)]
242                 [MWFDescription("Sets minimum size of undocked window")]
243                 [MWFCategory("Behaviour")]
244                 public int MinExtra {
245                         get {
246                                 return min_extra;
247                         }
248
249                         set {
250                                 min_extra = value;
251                         }
252                 }
253
254                 [DefaultValue(25)]
255                 [Localizable(true)]
256                 [MWFDescription("Sets minimum size of the resized control")]
257                 [MWFCategory("Behaviour")]
258                 public int MinSize {
259                         get {
260                                 return min_size;
261                         }
262
263                         set {
264                                 min_size = value;
265                         }
266                 }
267
268                 
269                 [Browsable(false)]
270                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
271                 [MWFDescription("Current splitter position")]
272                 [MWFCategory("Layout")]
273                 public int SplitPosition {
274                         get {
275                                 if (affected == null) {
276                                         return -1;
277                                 }
278
279                                 if (Capture) {
280                                         return CalculateSplitPosition();
281                                 }
282
283                                 if (horizontal) {
284                                         return affected.Height;
285                                 } else {
286                                         return affected.Width;
287                                 }
288                         }
289
290                         set {
291                                 if (Capture || (affected == null)) {
292                                         return;
293                                 }
294
295                                 if (horizontal) {
296                                         affected.Height = value;
297                                 } else {
298                                         affected.Width = value;
299                                 }
300                         }
301                 }
302
303                 [Browsable(false)]
304                 [EditorBrowsable(EditorBrowsableState.Never)]
305                 public bool TabStop {
306                         get {
307                                 return base.TabStop;
308                         }
309
310                         set {
311                                 base.TabStop = value;
312                         }
313                 }
314
315                 [Bindable(false)]
316                 [Browsable(false)]
317                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
318                 [EditorBrowsable(EditorBrowsableState.Never)]
319                 public override string Text {
320                         get {
321                                 return base.Text;
322                         }
323
324                         set {
325                                 base.Text = value;
326                         }
327                 }
328
329                 #endregion      // Public Instance Properties
330
331                 #region Protected Instance Properties
332                 protected override CreateParams CreateParams {
333                         get {
334                                 return base.CreateParams;
335                         }
336                 }
337
338                 protected override ImeMode DefaultImeMode {
339                         get {
340                                 return ImeMode.Disable;
341                         }
342                 }
343
344                 protected override Size DefaultSize {
345                         get {
346                                 return new Size (3, 3);
347                         }
348                 }
349                 #endregion      // Protected Instance Properties
350
351                 #region Public Instance Methods
352                 public bool PreFilterMessage(ref Message m) {
353                         return false;
354                 }
355
356                 public override string ToString() {
357                         return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
358                 }
359                 #endregion      // Public Instance Methods
360
361                 #region Protected Instance Methods
362                 protected override void OnKeyDown(KeyEventArgs e) {
363                         base.OnKeyDown (e);
364                         if (Capture && (e.KeyCode == Keys.Escape)) {
365                                 Capture = false;
366                                 DrawDragHandle(DrawType.Finish);
367                         }
368                 }
369
370                 protected override void OnMouseDown(MouseEventArgs e) {
371                         Point   pt;
372
373                         base.OnMouseDown (e);
374
375                         // Only allow if we are set up properly
376                         if (affected == null || e.Button != MouseButtons.Left) {
377                                 return;
378                         }
379
380                         // Prepare the job
381                         Capture = true;
382
383                         // Calculate limits
384                         if (filler != null) {
385                                 if (horizontal) {
386                                         if (dock_style == DockStyle.Top) {
387                                                 limit_min = affected.Bounds.Top + min_size;
388                                                 limit_max = filler.Bounds.Bottom - min_extra + this.bounds.Top - filler.Bounds.Top;
389                                         } else {
390                                                 limit_min = filler.Bounds.Top + min_extra + this.bounds.Top - filler.Bounds.Bottom;
391                                                 limit_max = affected.Bounds.Bottom - min_size - this.Height;
392                                         }
393                                 } else {
394                                         if (dock_style == DockStyle.Left) {
395                                                 limit_min = affected.Bounds.Left + min_size;
396                                                 limit_max = filler.Bounds.Right - min_extra + this.bounds.Left - filler.Bounds.Left;
397                                         } else {
398                                                 limit_min = filler.Bounds.Left + min_extra + this.bounds.Left - filler.Bounds.Right;
399                                                 limit_max = affected.Bounds.Right - min_size - this.Width;
400                                         }
401                                 }
402                         } else {
403                                 limit_min = 0;
404                                 if (horizontal) {
405                                         limit_max = affected.Parent.Height;
406                                 } else {
407                                         limit_max = affected.Parent.Width;
408                                 }
409                         }
410
411                         #if Debug
412                                 Console.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min, limit_max);
413                         #endif
414
415                         pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
416
417                         if (horizontal) {
418                                 split_position = pt.Y;
419                                 if (dock_style == DockStyle.Top) {
420                                         click_offset = e.Y;
421                                 } else {
422                                         click_offset = -e.Y;
423                                 }
424                         } else {
425                                 split_position = pt.X;
426                                 if (dock_style == DockStyle.Left) {
427                                         click_offset = e.X;
428                                 } else {
429                                         click_offset = -e.X;
430                                 }
431                         }
432
433                         // We need to set this, in case we never get a mouse move
434                         prev_split_position = split_position;
435
436                         #if Debug
437                                 Console.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset, split_position);
438                         #endif
439
440                         // Draw our initial handle
441                         DrawDragHandle(DrawType.Initial);
442                 }
443
444                 protected override void OnMouseMove(MouseEventArgs e) {
445                         Point   pt;
446
447                         base.OnMouseMove (e);
448
449                         if (!Capture  || e.Button != MouseButtons.Left) {
450                                 return;
451                         }
452
453                         // We need our mouse coordinates relative to our parent
454                         pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
455
456                         // Grab our new coordinates
457                         prev_split_position = split_position;
458                         if (horizontal) {
459                                 split_position = pt.Y;
460                         } else {
461                                 split_position = pt.X;
462                         }
463                         // Enforce limits
464                         if (split_position < limit_min) {
465                                 #if Debug
466                                         Console.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position, limit_min);
467                                 #endif
468                                 split_position = limit_min;
469                         } else if (split_position > limit_max) {
470                                 #if Debug
471                                         Console.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position, limit_max);
472                                 #endif
473                                 split_position = limit_max;
474                         }
475
476                         // Don't waste cycles
477                         if (prev_split_position != split_position) {
478                                 // Update our handle location
479                                 DrawDragHandle(DrawType.Redraw);
480                         }
481
482                         // Prepare the event
483                         if (horizontal) {
484                                 sevent.split_x = 0;
485                                 sevent.split_y = split_position;
486                         } else {
487                                 sevent.split_x = split_position;
488                                 sevent.split_y = 0;
489                         }
490
491                         sevent.x = pt.X;
492                         sevent.y = pt.Y;
493
494                         // Fire the event
495                         OnSplitterMoving(sevent);
496                 }
497
498                 protected override void OnMouseUp(MouseEventArgs e) {
499                         if (!Capture || e.Button != MouseButtons.Left) {
500                                 base.OnMouseUp (e);
501                                 return;
502                         }
503
504                         Capture = false;
505                         DrawDragHandle(DrawType.Finish);
506
507                         // Resize the affected window
508                         if (horizontal) {
509                                 affected.Height = CalculateSplitPosition() - click_offset;
510                                 #if Debug
511                                         Console.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset);
512                                 #endif
513                         } else {
514                                 affected.Width = CalculateSplitPosition() - click_offset;
515                                 #if Debug
516                                         Console.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset);
517                                 #endif
518                         }
519
520                         base.OnMouseUp (e);
521
522                         // It seems that MS is sending some data that doesn't quite make sense
523                         // In this event. It tried to match their stuff.., not sure about split_x...
524
525                         // Prepare the event
526                         if (horizontal) {
527                                 sevent.x = 0;
528                                 sevent.y = split_position;
529                                 sevent.split_x = 200;
530                                 sevent.split_y = split_position;
531                         } else {
532                                 sevent.x = split_position;
533                                 sevent.y = 0;
534                                 sevent.split_x = split_position;
535                                 sevent.split_y = 200;
536                         }
537
538
539                         // Fire the event
540                         OnSplitterMoved(sevent);
541                 }
542
543                 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
544                         if (SplitterMoved != null) {
545                                 SplitterMoved(this, sevent);
546                         }
547                 }
548
549                 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
550                         if (SplitterMoving != null) {
551                                 SplitterMoving(this, sevent);
552                         }
553                 }
554
555                 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
556                         // enforce our width / height
557                         if (horizontal) {
558                                 base.SetBoundsCore (x, y, width, splitter_size, specified);
559                         } else {
560                                 base.SetBoundsCore (x, y, splitter_size, height, specified);
561                         }
562                 }
563                 #endregion      // Protected Instance Methods
564
565                 #region Private Properties and Methods
566                 private Control AffectedControl {
567                         get {
568                                 if (parent == null) {
569                                         return null;
570                                 }
571
572                                 // Doc says the first control preceeding us in the zorder 
573                                 for (int i = parent.Controls.GetChildIndex(this) + 1; i < parent.Controls.Count; i++) {
574                                         switch(this.Dock) {
575                                                 case DockStyle.Top: {
576                                                         if (Top == parent.Controls[i].Bottom) {
577                                                                 return parent.Controls[i];
578                                                         }
579                                                         break;
580                                                 }
581
582                                                 case DockStyle.Bottom: {
583                                                         if (Bottom == parent.Controls[i].Top) {
584                                                                 return parent.Controls[i];
585                                                         }
586                                                         break;
587                                                 }
588
589                                                 case DockStyle.Left: {
590                                                         if (Left == parent.Controls[i].Right) {
591                                                                 return parent.Controls[i];
592                                                         }
593                                                         break;
594                                                 }
595
596                                                 case DockStyle.Right: {
597                                                         if (Right == parent.Controls[i].Left) {
598                                                                 return parent.Controls[i];
599                                                         }
600                                                         break;
601                                                 }
602                                         }
603                                 }
604                                 return null;
605                         }
606                 }
607
608                 private Control FillerControl {
609                         get {
610                                 if (parent == null) {
611                                         return null;
612                                 }
613
614                                 // Doc says the first control preceeding us in the zorder 
615                                 for (int i = parent.Controls.GetChildIndex(this) - 1; i >= 0; i--) {
616                                         if (parent.Controls[i].Dock == DockStyle.Fill) {
617                                                 return parent.Controls[i];
618                                         }
619                                 }
620                                 return null;
621                         }
622                 }
623
624                 private int CalculateSplitPosition() {
625                         if (horizontal) {
626                                 if (dock_style == DockStyle.Top) {
627                                         return split_position;
628                                 } else {
629                                         return affected.Bottom - split_position - splitter_size;
630                                 }
631                         } else {
632                                 if (dock_style == DockStyle.Left) {
633                                         return split_position;
634                                 } else {
635                                         return affected.Right - split_position - splitter_size;
636                                 }
637                         }
638                 }
639
640                 private void PaintSplitter(object sender, PaintEventArgs e) {
641                         e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
642                 }
643
644                 private void LayoutSplitter(object sender, LayoutEventArgs e) {
645                         affected = AffectedControl;
646                         filler = FillerControl;
647                 }
648
649                 private void DrawDragHandle(DrawType type) {
650                         Rectangle       prev;
651                         Rectangle       current;
652
653                         if (horizontal) {
654                                 prev = new Rectangle(0, prev_split_position - click_offset + 1, Width, 0);
655                                 current = new Rectangle(0, split_position - click_offset + 1, Width, 0);
656                         } else {
657                                 prev = new Rectangle(prev_split_position - click_offset + 1, 0, 0, Height);
658                                 current = new Rectangle(split_position - click_offset + 1, 0, 0, Height);
659                         }
660
661                         switch(type) {
662                                 case DrawType.Initial: {
663                                         XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
664                                         return;
665                                 }
666
667                                 case DrawType.Redraw: {
668                                         if (prev.X == current.X && prev.Y == current.Y) {
669                                                 return;
670                                         }
671
672                                         XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
673                                         XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
674                                         return;
675                                 }
676
677                                 case DrawType.Finish: {
678                                         XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
679                                         return;
680                                 }
681                         }
682                 }
683                 #endregion      // Private Properties and Methods
684
685                 #region Events
686                 [Browsable(false)]
687                 [EditorBrowsable(EditorBrowsableState.Never)]
688                 public new event EventHandler BackgroundImageChanged;
689
690                 [Browsable(false)]
691                 [EditorBrowsable(EditorBrowsableState.Never)]
692                 public new event EventHandler Enter;
693
694                 [Browsable(false)]
695                 [EditorBrowsable(EditorBrowsableState.Never)]
696                 public new event EventHandler FontChanged;
697
698                 [Browsable(false)]
699                 [EditorBrowsable(EditorBrowsableState.Never)]
700                 public new event EventHandler ForeColorChanged;
701
702                 [Browsable(false)]
703                 [EditorBrowsable(EditorBrowsableState.Never)]
704                 public new event EventHandler ImeModeChanged;
705
706                 [Browsable(false)]
707                 [EditorBrowsable(EditorBrowsableState.Never)]
708                 public new event KeyEventHandler KeyDown;
709
710                 [Browsable(false)]
711                 [EditorBrowsable(EditorBrowsableState.Never)]
712                 public new event KeyPressEventHandler KeyPress;
713
714                 [Browsable(false)]
715                 [EditorBrowsable(EditorBrowsableState.Never)]
716                 public new event KeyEventHandler KeyUp;
717
718                 [Browsable(false)]
719                 [EditorBrowsable(EditorBrowsableState.Never)]
720                 public new event EventHandler Leave;
721
722                 [Browsable(false)]
723                 [EditorBrowsable(EditorBrowsableState.Never)]
724                 public new event EventHandler TabStopChanged;
725
726                 [Browsable(false)]
727                 [EditorBrowsable(EditorBrowsableState.Never)]
728                 public new event EventHandler TextChanged;
729
730                 public event SplitterEventHandler SplitterMoved;
731                 public event SplitterEventHandler SplitterMoving;
732                 #endregion      // Events
733         }
734 }