2007-03-19 Chris Toshok <toshok@ximian.com>
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Splitter.cs
blob677279fd28ff7c048487027899aca19088e67e27
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.
20 // Copyright (c) 2005-2006 Novell, Inc. (http://www.novell.com)
22 // Authors:
23 // Peter Dennis Bartok (pbartok@novell.com)
27 // COMPLETE
29 #undef Debug
31 using System;
32 using System.ComponentModel;
33 using System.Drawing;
34 using System.Reflection;
35 using System.Runtime.InteropServices;
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
42 #if !NET_2_0
43 , IMessageFilter
44 #endif
46 #region Enums
47 private enum DrawType {
48 Initial,
49 Redraw,
50 Finish
52 #endregion // Enums
54 #region Local Variables
55 static private Cursor splitter_ns;
56 static private Cursor splitter_we;
57 // XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
58 new private BorderStyle border_style;
59 private int min_extra;
60 private int min_size;
61 private int split_position; // Current splitter position
62 private int prev_split_position; // Previous splitter position, only valid during drag
63 private int click_offset; // Click offset from border of splitter control
64 private int splitter_size; // Size (width or height) of our splitter control
65 private bool horizontal; // true if we've got a horizontal splitter
66 private Control affected; // The control that the splitter resizes
67 private Control filler; // The control that MinExtra prevents from being shrunk to 0 size
68 private SplitterEventArgs sevent; // We cache the object, prevents fragmentation
69 private int limit_min; // The max we're allowed to move the splitter left/up
70 private int limit_max; // The max we're allowed to move the splitter right/down
71 private int split_requested; // If the user requests a position before we have ever laid out the doc
72 #endregion // Local Variables
74 #region Constructors
75 static Splitter() {
76 splitter_ns = Cursors.HSplit;
77 splitter_we = Cursors.VSplit;
80 public Splitter() {
82 min_extra = 25;
83 min_size = 25;
84 split_position = -1;
85 split_requested = -1;
86 splitter_size = 3;
87 horizontal = false;
88 sevent = new SplitterEventArgs(0, 0, 0, 0);
90 SetStyle(ControlStyles.Selectable, false);
91 Anchor = AnchorStyles.None;
92 Dock = DockStyle.Left;
94 Layout += new LayoutEventHandler(LayoutSplitter);
95 this.ParentChanged += new EventHandler(ReparentSplitter);
96 Cursor = splitter_we;
98 #endregion // Constructors
100 #region Public Instance Properties
101 [Browsable(false)]
102 [EditorBrowsable(EditorBrowsableState.Never)]
103 public override bool AllowDrop {
104 get {
105 return base.AllowDrop;
108 set {
109 base.AllowDrop = value;
113 [Browsable(false)]
114 [DefaultValue(AnchorStyles.None)]
115 [EditorBrowsable(EditorBrowsableState.Never)]
116 public override AnchorStyles Anchor {
117 get {
118 return AnchorStyles.None;
121 set {
122 ; // MS doesn't set it
126 [Browsable(false)]
127 [EditorBrowsable(EditorBrowsableState.Never)]
128 public override Image BackgroundImage {
129 get {
130 return base.BackgroundImage;
133 set {
134 base.BackgroundImage = value;
138 [DispId(-504)]
139 [DefaultValue (BorderStyle.None)]
140 [MWFDescription("Sets the border style for the splitter")]
141 [MWFCategory("Appearance")]
142 public BorderStyle BorderStyle {
143 get {
144 return border_style;
147 set {
148 border_style = value;
150 switch(value) {
151 case BorderStyle.FixedSingle:
152 splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
153 break;
155 case BorderStyle.Fixed3D:
156 value = BorderStyle.None;
157 splitter_size = 3;
158 break;
160 case BorderStyle.None:
161 splitter_size = 3;
162 break;
164 default:
165 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
168 base.InternalBorderStyle = value;
172 [DefaultValue(DockStyle.Left)]
173 [Localizable(true)]
174 public override DockStyle Dock {
175 get {
176 return base.Dock;
179 set {
180 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
181 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
184 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
185 horizontal = true;
186 Cursor = splitter_ns;
187 } else {
188 horizontal = false;
189 Cursor = splitter_we;
191 base.Dock = value;
195 [Browsable(false)]
196 [EditorBrowsable(EditorBrowsableState.Never)]
197 public override Font Font {
198 get {
199 return base.Font;
202 set {
203 base.Font = value;
207 [Browsable(false)]
208 [EditorBrowsable(EditorBrowsableState.Never)]
209 public override Color ForeColor {
210 get {
211 return base.ForeColor;
214 set {
215 base.ForeColor = value;
219 [Browsable(false)]
220 [EditorBrowsable(EditorBrowsableState.Never)]
221 public new ImeMode ImeMode {
222 get {
223 return base.ImeMode;
226 set {
227 base.ImeMode = value;
231 [DefaultValue(25)]
232 [Localizable(true)]
233 [MWFDescription("Sets minimum size of undocked window")]
234 [MWFCategory("Behaviour")]
235 public int MinExtra {
236 get {
237 return min_extra;
240 set {
241 min_extra = value;
245 [DefaultValue(25)]
246 [Localizable(true)]
247 [MWFDescription("Sets minimum size of the resized control")]
248 [MWFCategory("Behaviour")]
249 public int MinSize {
250 get {
251 return min_size;
254 set {
255 min_size = value;
260 [Browsable(false)]
261 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
262 [MWFDescription("Current splitter position")]
263 [MWFCategory("Layout")]
264 public int SplitPosition {
265 get {
266 affected = AffectedControl;
267 if (affected == null) {
268 return -1;
271 if (Capture) {
272 return CalculateSplitPosition();
275 if (horizontal) {
276 return affected.Height;
277 } else {
278 return affected.Width;
282 set {
283 affected = AffectedControl;
285 if (affected == null) {
286 split_requested = value;
288 else {
289 if (horizontal) {
290 affected.Height = value;
291 } else {
292 affected.Width = value;
298 [Browsable(false)]
299 [EditorBrowsable(EditorBrowsableState.Never)]
300 public new bool TabStop {
301 get { return base.TabStop; }
302 set { base.TabStop = value; }
305 [Bindable(false)]
306 [Browsable(false)]
307 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
308 [EditorBrowsable(EditorBrowsableState.Never)]
309 public override string Text {
310 get {
311 return base.Text;
314 set {
315 base.Text = value;
319 #endregion // Public Instance Properties
321 #region Protected Instance Properties
322 protected override CreateParams CreateParams {
323 get {
324 return base.CreateParams;
328 protected override ImeMode DefaultImeMode {
329 get {
330 return ImeMode.Disable;
334 protected override Size DefaultSize {
335 get {
336 return new Size (3, 3);
339 #endregion // Protected Instance Properties
341 #region Public Instance Methods
342 #if !NET_2_0
343 public bool PreFilterMessage(ref Message m) {
344 return false;
346 #endif
348 public override string ToString() {
349 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
351 #endregion // Public Instance Methods
353 #region Protected Instance Methods
354 protected override void OnKeyDown(KeyEventArgs e) {
355 base.OnKeyDown (e);
356 if (Capture && (e.KeyCode == Keys.Escape)) {
357 Capture = false;
358 DrawDragHandle(DrawType.Finish);
362 protected override void OnMouseDown(MouseEventArgs e) {
363 Point pt;
365 base.OnMouseDown (e);
367 // Only allow if we are set up properly
368 if ((affected == null) || (filler == null)) {
369 affected = AffectedControl;
370 filler = FillerControl;
373 if (affected == null || e.Button != MouseButtons.Left) {
374 return;
377 // Prepare the job
378 Capture = true;
380 // Calculate limits
381 if (filler != null) {
382 if (horizontal) {
383 if (Dock == DockStyle.Top) {
384 limit_min = affected.Bounds.Top + min_size;
385 limit_max = filler.Bounds.Bottom - min_extra + this.bounds.Top - filler.Bounds.Top;
386 } else {
387 limit_min = filler.Bounds.Top + min_extra + this.bounds.Top - filler.Bounds.Bottom;
388 limit_max = affected.Bounds.Bottom - min_size - this.Height;
390 } else {
391 if (Dock == DockStyle.Left) {
392 limit_min = affected.Bounds.Left + min_size;
393 limit_max = filler.Bounds.Right - min_extra + this.bounds.Left - filler.Bounds.Left;
394 } else {
395 limit_min = filler.Bounds.Left + min_extra + this.bounds.Left - filler.Bounds.Right;
396 limit_max = affected.Bounds.Right - min_size - this.Width;
399 } else {
400 limit_min = 0;
401 if (horizontal) {
402 limit_max = affected.Parent.Height;
403 } else {
404 limit_max = affected.Parent.Width;
408 #if Debug
409 Console.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min, limit_max);
410 #endif
412 pt = PointToScreen(Parent.PointToClient(new Point(e.X, e.Y)));
414 if (horizontal) {
415 split_position = pt.Y;
416 if (Dock == DockStyle.Top) {
417 click_offset = e.Y;
418 } else {
419 click_offset = -e.Y;
421 } else {
422 split_position = pt.X;
423 if (Dock == DockStyle.Left) {
424 click_offset = e.X;
425 } else {
426 click_offset = -e.X;
430 // We need to set this, in case we never get a mouse move
431 prev_split_position = split_position;
433 #if Debug
434 Console.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset, split_position);
435 #endif
437 // Draw our initial handle
438 DrawDragHandle(DrawType.Initial);
441 protected override void OnMouseMove(MouseEventArgs e) {
442 Point pt;
444 base.OnMouseMove (e);
446 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
447 return;
450 // We need our mouse coordinates relative to our parent
451 pt = PointToScreen(Parent.PointToClient(new Point(e.X, e.Y)));
453 // Grab our new coordinates
454 prev_split_position = split_position;
456 int candidate = horizontal ? pt.Y : pt.X;
458 // Enforce limit on what we send to the event
459 if (candidate < limit_min)
460 candidate = limit_min;
461 else if (candidate > limit_max)
462 candidate = limit_max;
464 sevent.x = pt.X;
465 sevent.y = pt.Y;
466 sevent.split_x = horizontal ? 0 : candidate;
467 sevent.split_y = horizontal ? candidate : 0;
469 // Fire the event
470 OnSplitterMoving(sevent);
472 split_position = horizontal ? sevent.split_y : sevent.split_x;
474 // Enforce limits
475 if (split_position < limit_min) {
476 #if Debug
477 Console.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position, limit_min);
478 #endif
479 split_position = limit_min;
480 } else if (split_position > limit_max) {
481 #if Debug
482 Console.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position, limit_max);
483 #endif
484 split_position = limit_max;
487 // Update our handle location
488 DrawDragHandle(DrawType.Redraw);
491 protected override void OnMouseUp(MouseEventArgs e) {
492 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
493 base.OnMouseUp (e);
494 return;
497 Capture = false;
498 DrawDragHandle(DrawType.Finish);
500 // Resize the affected window
501 if (horizontal) {
502 affected.Height = CalculateSplitPosition() - click_offset;
503 #if Debug
504 Console.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset);
505 #endif
506 } else {
507 affected.Width = CalculateSplitPosition() - click_offset;
508 #if Debug
509 Console.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset);
510 #endif
513 base.OnMouseUp (e);
515 // It seems that MS is sending some data that doesn't quite make sense
516 // In this event. It tried to match their stuff.., not sure about split_x...
518 // Prepare the event
519 if (horizontal) {
520 sevent.x = 0;
521 sevent.y = split_position;
522 sevent.split_x = 200;
523 sevent.split_y = split_position;
524 } else {
525 sevent.x = split_position;
526 sevent.y = 0;
527 sevent.split_x = split_position;
528 sevent.split_y = 200;
532 // Fire the event
533 OnSplitterMoved(sevent);
536 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
537 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
538 if (eh != null)
539 eh (this, sevent);
542 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
543 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovingEvent]);
544 if (eh != null)
545 eh (this, sevent);
548 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
549 // enforce our width / height
550 if (horizontal) {
551 splitter_size = height;
552 if (splitter_size < 1) {
553 splitter_size = 3;
555 base.SetBoundsCore (x, y, width, splitter_size, specified);
556 } else {
557 splitter_size = width;
558 if (splitter_size < 1) {
559 splitter_size = 3;
561 base.SetBoundsCore (x, y, splitter_size, height, specified);
564 #endregion // Protected Instance Methods
566 #region Private Properties and Methods
567 private Control AffectedControl {
568 get {
569 if (Parent == null)
570 return null;
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 (Dock) {
575 case DockStyle.Top:
576 if (Top == Parent.Controls[i].Bottom)
577 return Parent.Controls[i];
578 break;
579 case DockStyle.Bottom:
580 if (Bottom == Parent.Controls[i].Top)
581 return Parent.Controls[i];
582 break;
583 case DockStyle.Left:
584 if (Left == Parent.Controls[i].Right)
585 return Parent.Controls[i];
586 break;
587 case DockStyle.Right:
588 if (Right == Parent.Controls[i].Left)
589 return Parent.Controls[i];
590 break;
593 return null;
597 private Control FillerControl {
598 get {
599 if (Parent == null)
600 return null;
602 // Doc says the first control preceeding us in the zorder
603 for (int i = Parent.Controls.GetChildIndex(this) - 1; i >= 0; i--) {
604 if (Parent.Controls[i].Dock == DockStyle.Fill) {
605 return Parent.Controls[i];
608 return null;
612 private int CalculateSplitPosition() {
613 if (horizontal) {
614 if (Dock == DockStyle.Top)
615 return split_position - affected.Top;
616 else
617 return affected.Bottom - split_position - splitter_size;
618 } else {
619 if (Dock == DockStyle.Left)
620 return split_position - affected.Left;
621 else
622 return affected.Right - split_position - splitter_size;
626 internal override void OnPaintInternal (PaintEventArgs e) {
627 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
630 private void LayoutSplitter(object sender, LayoutEventArgs e) {
631 affected = AffectedControl;
632 filler = FillerControl;
633 if (split_requested != -1) {
634 SplitPosition = split_requested;
635 split_requested = -1;
639 private void ReparentSplitter(object sender, EventArgs e) {
640 affected = null;
641 filler = null;
644 private void DrawDragHandle(DrawType type) {
645 Rectangle prev;
646 Rectangle current;
648 if (horizontal) {
649 prev = new Rectangle(Location.X, prev_split_position - click_offset + 1, Width, 0);
650 current = new Rectangle(Location.X, split_position - click_offset + 1, Width, 0);
651 } else {
652 prev = new Rectangle(prev_split_position - click_offset + 1, Location.Y, 0, Height);
653 current = new Rectangle(split_position - click_offset + 1, Location.Y, 0, Height);
656 switch(type) {
657 case DrawType.Initial:
658 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
659 return;
661 case DrawType.Redraw:
662 if (prev.X == current.X && prev.Y == current.Y)
663 return;
665 XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
666 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
667 return;
669 case DrawType.Finish:
670 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
671 return;
674 #endregion // Private Properties and Methods
676 #region Events
677 [Browsable(false)]
678 [EditorBrowsable(EditorBrowsableState.Never)]
679 public new event EventHandler BackgroundImageChanged {
680 add { base.BackgroundImageChanged += value; }
681 remove { base.BackgroundImageChanged -= value; }
684 [Browsable(false)]
685 [EditorBrowsable(EditorBrowsableState.Never)]
686 public new event EventHandler Enter {
687 add { base.Enter += value; }
688 remove { base.Enter -= value; }
691 [Browsable(false)]
692 [EditorBrowsable(EditorBrowsableState.Never)]
693 public new event EventHandler FontChanged {
694 add { base.FontChanged += value; }
695 remove { base.FontChanged -= value; }
698 [Browsable(false)]
699 [EditorBrowsable(EditorBrowsableState.Never)]
700 public new event EventHandler ForeColorChanged {
701 add { base.ForeColorChanged += value; }
702 remove { base.ForeColorChanged -= value; }
705 [Browsable(false)]
706 [EditorBrowsable(EditorBrowsableState.Never)]
707 public new event EventHandler ImeModeChanged {
708 add { base.ImeModeChanged += value; }
709 remove { base.ImeModeChanged -= value; }
712 [Browsable(false)]
713 [EditorBrowsable(EditorBrowsableState.Never)]
714 public new event KeyEventHandler KeyDown {
715 add { base.KeyDown += value; }
716 remove { base.KeyDown -= value; }
719 [Browsable(false)]
720 [EditorBrowsable(EditorBrowsableState.Never)]
721 public new event KeyPressEventHandler KeyPress {
722 add { base.KeyPress += value; }
723 remove { base.KeyPress -= value; }
726 [Browsable(false)]
727 [EditorBrowsable(EditorBrowsableState.Never)]
728 public new event KeyEventHandler KeyUp {
729 add { base.KeyUp += value; }
730 remove { base.KeyUp -= value; }
733 [Browsable(false)]
734 [EditorBrowsable(EditorBrowsableState.Never)]
735 public new event EventHandler Leave {
736 add { base.Leave += value; }
737 remove { base.Leave -= value; }
740 [Browsable(false)]
741 [EditorBrowsable(EditorBrowsableState.Never)]
742 public new event EventHandler TabStopChanged {
743 add { base.TabStopChanged += value; }
744 remove { base.TabStopChanged -= value; }
747 [Browsable(false)]
748 [EditorBrowsable(EditorBrowsableState.Never)]
749 public new event EventHandler TextChanged {
750 add { base.TextChanged += value; }
751 remove { base.TextChanged -= value; }
754 static object SplitterMovedEvent = new object ();
755 static object SplitterMovingEvent = new object ();
757 public event SplitterEventHandler SplitterMoved {
758 add { Events.AddHandler (SplitterMovedEvent, value); }
759 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
762 public event SplitterEventHandler SplitterMoving {
763 add { Events.AddHandler (SplitterMovingEvent, value); }
764 remove { Events.RemoveHandler (SplitterMovingEvent, value); }
766 #endregion // Events