In System.Windows.Forms:
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Splitter.cs
blobf0b6238ccc76bf9e9a44c3e4d7757c9f07b37f34
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-2008 Novell, Inc. (http://www.novell.com)
22 // Authors:
23 // Peter Dennis Bartok (pbartok@novell.com)
24 // Ivan N. Zlatev (contact i-nz.net)
28 // COMPLETE
30 #undef Debug
32 using System;
33 using System.ComponentModel;
34 using System.Drawing;
35 using System.Reflection;
36 using System.Runtime.InteropServices;
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
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 #endregion // Local Variables
67 #region Constructors
68 static Splitter() {
69 splitter_ns = Cursors.HSplit;
70 splitter_we = Cursors.VSplit;
73 public Splitter() {
75 min_extra = 25;
76 min_size = 25;
77 split_requested = -1;
78 splitter_size = 3;
79 horizontal = false;
81 SetStyle(ControlStyles.Selectable, false);
82 Anchor = AnchorStyles.None;
83 Dock = DockStyle.Left;
85 Layout += new LayoutEventHandler(LayoutSplitter);
86 this.ParentChanged += new EventHandler(ReparentSplitter);
87 Cursor = splitter_we;
89 #endregion // Constructors
91 #region Public Instance Properties
92 [Browsable(false)]
93 [EditorBrowsable(EditorBrowsableState.Never)]
94 public override bool AllowDrop {
95 get {
96 return base.AllowDrop;
99 set {
100 base.AllowDrop = value;
104 [Browsable(false)]
105 [DefaultValue(AnchorStyles.None)]
106 [EditorBrowsable(EditorBrowsableState.Never)]
107 public override AnchorStyles Anchor {
108 get {
109 return AnchorStyles.None;
112 set {
113 ; // MS doesn't set it
117 [Browsable(false)]
118 [EditorBrowsable(EditorBrowsableState.Never)]
119 public override Image BackgroundImage {
120 get {
121 return base.BackgroundImage;
124 set {
125 base.BackgroundImage = value;
129 #if NET_2_0
130 [Browsable (false)]
131 [EditorBrowsable (EditorBrowsableState.Never)]
132 public override ImageLayout BackgroundImageLayout {
133 get { return base.BackgroundImageLayout; }
134 set { base.BackgroundImageLayout = value; }
136 #endif
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;
259 private int MaxSize {
260 get {
261 if (this.Parent == null)
262 return 0;
264 if (affected == null)
265 affected = AffectedControl;
267 int widths = 0;
268 int heights = 0;
269 foreach (Control c in this.Parent.Controls) {
270 if (c != affected) {
271 switch (c.Dock) {
272 case DockStyle.Left:
273 case DockStyle.Right:
274 widths += c.Width;
275 break;
276 case DockStyle.Top:
277 case DockStyle.Bottom:
278 heights += c.Height;
279 break;
284 if (horizontal)
285 return Parent.ClientSize.Height - heights - MinExtra;
286 else
287 return Parent.ClientSize.Width - widths - MinExtra;
291 [Browsable(false)]
292 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
293 [MWFDescription("Current splitter position")]
294 [MWFCategory("Layout")]
295 public int SplitPosition {
296 get {
297 affected = AffectedControl;
298 if (affected == null) {
299 return -1;
302 if (Capture) {
303 return CalculateSplitPosition();
306 if (horizontal) {
307 return affected.Height;
308 } else {
309 return affected.Width;
313 set {
314 if (value > MaxSize)
315 value = MaxSize;
316 if (value < MinSize)
317 value = MinSize;
319 affected = AffectedControl;
320 if (affected == null)
321 split_requested = value;
322 else {
323 if (horizontal)
324 affected.Height = value;
325 else
326 affected.Width = value;
327 OnSplitterMoved (new SplitterEventArgs (Left, Top, value, value));
332 [Browsable(false)]
333 [EditorBrowsable(EditorBrowsableState.Never)]
334 public new bool TabStop {
335 get { return base.TabStop; }
336 set { base.TabStop = value; }
339 [Bindable(false)]
340 [Browsable(false)]
341 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
342 [EditorBrowsable(EditorBrowsableState.Never)]
343 public override string Text {
344 get {
345 return base.Text;
348 set {
349 base.Text = value;
353 #endregion // Public Instance Properties
355 #region Protected Instance Properties
356 protected override CreateParams CreateParams {
357 get {
358 return base.CreateParams;
362 #if NET_2_0
363 protected override Cursor DefaultCursor {
364 get { return base.DefaultCursor; }
366 #endif
368 protected override ImeMode DefaultImeMode {
369 get {
370 return ImeMode.Disable;
374 protected override Size DefaultSize {
375 get {
376 return new Size (3, 3);
379 #endregion // Protected Instance Properties
381 #region Public Instance Methods
382 #if !NET_2_0
383 public bool PreFilterMessage(ref Message m) {
384 return false;
386 #endif
388 public override string ToString() {
389 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
391 #endregion // Public Instance Methods
393 #region Protected Instance Methods
394 protected override void OnKeyDown(KeyEventArgs e) {
395 base.OnKeyDown (e);
396 if (Capture && (e.KeyCode == Keys.Escape)) {
397 Capture = false;
398 SplitterEndMove (Point.Empty, true);
402 protected override void OnMouseDown(MouseEventArgs e) {
403 base.OnMouseDown (e);
405 // Only allow if we are set up properly
406 if (affected == null)
407 affected = AffectedControl;
408 max_size = MaxSize;
410 if (affected == null || e.Button != MouseButtons.Left)
411 return;
413 Capture = true;
414 SplitterBeginMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))));
417 protected override void OnMouseMove(MouseEventArgs e) {
418 base.OnMouseMove (e);
420 if (!Capture || e.Button != MouseButtons.Left || affected == null)
421 return;
423 // We need our mouse coordinates relative to our parent
424 SplitterMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))));
427 protected override void OnMouseUp(MouseEventArgs e) {
428 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
429 base.OnMouseUp (e);
430 return;
433 base.OnMouseUp (e);
434 Capture = false;
435 SplitterEndMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))), false);
438 private void SplitterBeginMove (Point location)
440 splitter_rectangle_moving = new Rectangle (Bounds.X, Bounds.Y,
441 Width, Height);
442 splitter_prev_move = horizontal ? location.Y : location.X;
445 private void SplitterMove (Point location)
447 int currentMove = horizontal ? location.Y : location.X;
448 int delta = currentMove - splitter_prev_move;
449 Rectangle prev_location = splitter_rectangle_moving;
450 bool moved = false;
451 int min = this.MinSize;
452 int max = max_size;
454 if (horizontal) {
455 if (splitter_rectangle_moving.Y + delta > min && splitter_rectangle_moving.Y + delta < max) {
456 splitter_rectangle_moving.Y += delta;
457 moved = true;
458 } else {
459 // Ensure that the splitter is set to minimum or maximum position,
460 // even if the mouse "skips".
462 if (splitter_rectangle_moving.Y + delta <= min && splitter_rectangle_moving.Y != min) {
463 splitter_rectangle_moving.Y = min;
464 moved = true;
465 } else if (splitter_rectangle_moving.Y + delta >= max && splitter_rectangle_moving.Y != max) {
466 splitter_rectangle_moving.Y = max;
467 moved = true;
470 } else {
471 if (splitter_rectangle_moving.X + delta > min && splitter_rectangle_moving.X + delta < max) {
472 splitter_rectangle_moving.X += delta;
473 moved = true;
474 } else {
475 // Ensure that the splitter is set to minimum or maximum position,
476 // even if the mouse "skips".
478 if (splitter_rectangle_moving.X + delta <= min && splitter_rectangle_moving.X != min) {
479 splitter_rectangle_moving.X = min;
480 moved = true;
481 } else if (splitter_rectangle_moving.X + delta >= max && splitter_rectangle_moving.X != max) {
482 splitter_rectangle_moving.X = max;
483 moved = true;
488 if (moved) {
489 splitter_prev_move = currentMove;
490 OnSplitterMoving (new SplitterEventArgs (location.X, location.Y,
491 splitter_rectangle_moving.X,
492 splitter_rectangle_moving.Y));
493 XplatUI.DrawReversibleRectangle (this.Parent.Handle, prev_location, 1);
494 XplatUI.DrawReversibleRectangle (this.Parent.Handle, splitter_rectangle_moving, 1);
498 private void SplitterEndMove (Point location, bool cancel)
500 if (!cancel) {
501 // Resize the affected window
502 if (horizontal)
503 affected.Height = CalculateSplitPosition();
504 else
505 affected.Width = CalculateSplitPosition();
508 this.Parent.Refresh (); // to clean up the drag handle artifacts from all controls
509 SplitterEventArgs args = new SplitterEventArgs (location.X, location.Y,
510 splitter_rectangle_moving.X,
511 splitter_rectangle_moving.Y);
512 OnSplitterMoved (args);
515 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
516 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
517 if (eh != null)
518 eh (this, sevent);
521 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
522 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovingEvent]);
523 if (eh != null)
524 eh (this, sevent);
527 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
528 // enforce our width / height
529 if (horizontal) {
530 splitter_size = height;
531 if (splitter_size < 1) {
532 splitter_size = 3;
534 base.SetBoundsCore (x, y, width, splitter_size, specified);
535 } else {
536 splitter_size = width;
537 if (splitter_size < 1) {
538 splitter_size = 3;
540 base.SetBoundsCore (x, y, splitter_size, height, specified);
543 #endregion // Protected Instance Methods
545 #region Private Properties and Methods
546 private Control AffectedControl {
547 get {
548 if (Parent == null)
549 return null;
551 // Doc says the first control preceeding us in the zorder
552 for (int i = Parent.Controls.GetChildIndex(this) + 1; i < Parent.Controls.Count; i++) {
553 switch (Dock) {
554 case DockStyle.Top:
555 if (Top == Parent.Controls[i].Bottom)
556 return Parent.Controls[i];
557 break;
558 case DockStyle.Bottom:
559 if (Bottom == Parent.Controls[i].Top)
560 return Parent.Controls[i];
561 break;
562 case DockStyle.Left:
563 if (Left == Parent.Controls[i].Right)
564 return Parent.Controls[i];
565 break;
566 case DockStyle.Right:
567 if (Right == Parent.Controls[i].Left)
568 return Parent.Controls[i];
569 break;
572 return null;
576 private int CalculateSplitPosition() {
577 if (horizontal) {
578 if (Dock == DockStyle.Top)
579 return splitter_rectangle_moving.Y - affected.Top;
580 else
581 return affected.Bottom - splitter_rectangle_moving.Y - splitter_size;
582 } else {
583 if (Dock == DockStyle.Left)
584 return splitter_rectangle_moving.X - affected.Left;
585 else
586 return affected.Right - splitter_rectangle_moving.X - splitter_size;
590 internal override void OnPaintInternal (PaintEventArgs e) {
591 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
594 private void LayoutSplitter(object sender, LayoutEventArgs e) {
595 affected = AffectedControl;
596 if (split_requested != -1) {
597 SplitPosition = split_requested;
598 split_requested = -1;
602 private void ReparentSplitter(object sender, EventArgs e) {
603 affected = null;
606 #endregion // Private Properties and Methods
608 #region Events
609 [Browsable(false)]
610 [EditorBrowsable(EditorBrowsableState.Never)]
611 public new event EventHandler BackgroundImageChanged {
612 add { base.BackgroundImageChanged += value; }
613 remove { base.BackgroundImageChanged -= value; }
616 #if NET_2_0
617 [Browsable (false)]
618 [EditorBrowsable (EditorBrowsableState.Never)]
619 public new event EventHandler BackgroundImageLayoutChanged
621 add { base.BackgroundImageLayoutChanged += value; }
622 remove { base.BackgroundImageLayoutChanged -= value; }
625 #endif
627 [Browsable(false)]
628 [EditorBrowsable(EditorBrowsableState.Never)]
629 public new event EventHandler Enter {
630 add { base.Enter += value; }
631 remove { base.Enter -= value; }
634 [Browsable(false)]
635 [EditorBrowsable(EditorBrowsableState.Never)]
636 public new event EventHandler FontChanged {
637 add { base.FontChanged += value; }
638 remove { base.FontChanged -= value; }
641 [Browsable(false)]
642 [EditorBrowsable(EditorBrowsableState.Never)]
643 public new event EventHandler ForeColorChanged {
644 add { base.ForeColorChanged += value; }
645 remove { base.ForeColorChanged -= value; }
648 [Browsable(false)]
649 [EditorBrowsable(EditorBrowsableState.Never)]
650 public new event EventHandler ImeModeChanged {
651 add { base.ImeModeChanged += value; }
652 remove { base.ImeModeChanged -= value; }
655 [Browsable(false)]
656 [EditorBrowsable(EditorBrowsableState.Never)]
657 public new event KeyEventHandler KeyDown {
658 add { base.KeyDown += value; }
659 remove { base.KeyDown -= value; }
662 [Browsable(false)]
663 [EditorBrowsable(EditorBrowsableState.Never)]
664 public new event KeyPressEventHandler KeyPress {
665 add { base.KeyPress += value; }
666 remove { base.KeyPress -= value; }
669 [Browsable(false)]
670 [EditorBrowsable(EditorBrowsableState.Never)]
671 public new event KeyEventHandler KeyUp {
672 add { base.KeyUp += value; }
673 remove { base.KeyUp -= value; }
676 [Browsable(false)]
677 [EditorBrowsable(EditorBrowsableState.Never)]
678 public new event EventHandler Leave {
679 add { base.Leave += value; }
680 remove { base.Leave -= value; }
683 [Browsable(false)]
684 [EditorBrowsable(EditorBrowsableState.Never)]
685 public new event EventHandler TabStopChanged {
686 add { base.TabStopChanged += value; }
687 remove { base.TabStopChanged -= value; }
690 [Browsable(false)]
691 [EditorBrowsable(EditorBrowsableState.Never)]
692 public new event EventHandler TextChanged {
693 add { base.TextChanged += value; }
694 remove { base.TextChanged -= value; }
697 static object SplitterMovedEvent = new object ();
698 static object SplitterMovingEvent = new object ();
700 public event SplitterEventHandler SplitterMoved {
701 add { Events.AddHandler (SplitterMovedEvent, value); }
702 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
705 public event SplitterEventHandler SplitterMoving {
706 add { Events.AddHandler (SplitterMovingEvent, value); }
707 remove { Events.RemoveHandler (SplitterMovingEvent, value); }
709 #endregion // Events