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