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