Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

708 lines
18 KiB
C#

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2005-2008 Novell, Inc. (http://www.novell.com)
//
// Authors:
// Peter Dennis Bartok (pbartok@novell.com)
// Ivan N. Zlatev (contact i-nz.net)
//
//
// COMPLETE
#undef Debug
using System;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
namespace System.Windows.Forms {
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDispatch)]
[DefaultEvent("SplitterMoved")]
[Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
[DefaultProperty("Dock")]
public class Splitter : Control
{
#region Local Variables
static private Cursor splitter_ns;
static private Cursor splitter_we;
// XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
new private BorderStyle border_style;
private int min_extra;
private int min_size;
private int max_size;
private int splitter_size; // Size (width or height) of our splitter control
private bool horizontal; // true if we've got a horizontal splitter
private Control affected; // The control that the splitter resizes
private int split_requested; // If the user requests a position before we have ever laid out the doc
private int splitter_prev_move;
private Rectangle splitter_rectangle_moving;
private int moving_offset;
#endregion // Local Variables
#region Constructors
static Splitter() {
splitter_ns = Cursors.HSplit;
splitter_we = Cursors.VSplit;
}
public Splitter() {
min_extra = 25;
min_size = 25;
split_requested = -1;
splitter_size = 3;
horizontal = false;
SetStyle(ControlStyles.Selectable, false);
Anchor = AnchorStyles.None;
Dock = DockStyle.Left;
Layout += new LayoutEventHandler(LayoutSplitter);
this.ParentChanged += new EventHandler(ReparentSplitter);
Cursor = splitter_we;
}
#endregion // Constructors
#region Public Instance Properties
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool AllowDrop {
get {
return base.AllowDrop;
}
set {
base.AllowDrop = value;
}
}
[Browsable(false)]
[DefaultValue(AnchorStyles.None)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override AnchorStyles Anchor {
get {
return AnchorStyles.None;
}
set {
; // MS doesn't set it
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Image BackgroundImage {
get {
return base.BackgroundImage;
}
set {
base.BackgroundImage = value;
}
}
[Browsable (false)]
[EditorBrowsable (EditorBrowsableState.Never)]
public override ImageLayout BackgroundImageLayout {
get { return base.BackgroundImageLayout; }
set { base.BackgroundImageLayout = value; }
}
[DispId(-504)]
[DefaultValue (BorderStyle.None)]
[MWFDescription("Sets the border style for the splitter")]
[MWFCategory("Appearance")]
public BorderStyle BorderStyle {
get {
return border_style;
}
set {
border_style = value;
switch(value) {
case BorderStyle.FixedSingle:
splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
break;
case BorderStyle.Fixed3D:
value = BorderStyle.None;
splitter_size = 3;
break;
case BorderStyle.None:
splitter_size = 3;
break;
default:
throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
}
base.InternalBorderStyle = value;
}
}
[DefaultValue(DockStyle.Left)]
[Localizable(true)]
public override DockStyle Dock {
get {
return base.Dock;
}
set {
if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
throw new ArgumentException("Splitter must be docked left, top, bottom or right");
}
if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
horizontal = true;
Cursor = splitter_ns;
} else {
horizontal = false;
Cursor = splitter_we;
}
base.Dock = value;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Font Font {
get {
return base.Font;
}
set {
base.Font = value;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color ForeColor {
get {
return base.ForeColor;
}
set {
base.ForeColor = value;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ImeMode ImeMode {
get {
return base.ImeMode;
}
set {
base.ImeMode = value;
}
}
[DefaultValue(25)]
[Localizable(true)]
[MWFDescription("Sets minimum size of undocked window")]
[MWFCategory("Behaviour")]
public int MinExtra {
get {
return min_extra;
}
set {
min_extra = value;
}
}
[DefaultValue(25)]
[Localizable(true)]
[MWFDescription("Sets minimum size of the resized control")]
[MWFCategory("Behaviour")]
public int MinSize {
get {
return min_size;
}
set {
min_size = value;
}
}
internal int MaxSize {
get {
if (this.Parent == null)
return 0;
if (affected == null)
affected = AffectedControl;
int widths = 0;
int heights = 0;
int vert_offset = 0;
int horiz_offset = 0;
foreach (Control c in this.Parent.Controls) {
if (c != affected) {
switch (c.Dock) {
case DockStyle.Left:
case DockStyle.Right:
widths += c.Width;
if (c.Location.X < this.Location.X)
vert_offset += c.Width;
break;
case DockStyle.Top:
case DockStyle.Bottom:
heights += c.Height;
if (c.Location.Y < this.Location.Y)
horiz_offset += c.Height;
break;
}
}
}
if (horizontal) {
moving_offset = horiz_offset;
return Parent.ClientSize.Height - heights - MinExtra;
} else {
moving_offset = vert_offset;
return Parent.ClientSize.Width - widths - MinExtra;
}
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[MWFDescription("Current splitter position")]
[MWFCategory("Layout")]
public int SplitPosition {
get {
affected = AffectedControl;
if (affected == null) {
return -1;
}
if (Capture) {
return CalculateSplitPosition();
}
if (horizontal) {
return affected.Height;
} else {
return affected.Width;
}
}
set {
if (value > MaxSize)
value = MaxSize;
if (value < MinSize)
value = MinSize;
affected = AffectedControl;
if (affected == null)
split_requested = value;
else {
if (horizontal)
affected.Height = value;
else
affected.Width = value;
OnSplitterMoved (new SplitterEventArgs (Left, Top, value, value));
}
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new bool TabStop {
get { return base.TabStop; }
set { base.TabStop = value; }
}
[Bindable(false)]
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override string Text {
get {
return base.Text;
}
set {
base.Text = value;
}
}
#endregion // Public Instance Properties
#region Protected Instance Properties
protected override CreateParams CreateParams {
get {
return base.CreateParams;
}
}
protected override Cursor DefaultCursor {
get { return base.DefaultCursor; }
}
protected override ImeMode DefaultImeMode {
get {
return ImeMode.Disable;
}
}
protected override Size DefaultSize {
get {
return new Size (3, 3);
}
}
#endregion // Protected Instance Properties
#region Public Instance Methods
public override string ToString() {
return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
}
#endregion // Public Instance Methods
#region Protected Instance Methods
protected override void OnKeyDown(KeyEventArgs e) {
base.OnKeyDown (e);
if (Capture && (e.KeyCode == Keys.Escape)) {
Capture = false;
SplitterEndMove (Point.Empty, true);
}
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown (e);
// Only allow if we are set up properly
if (affected == null)
affected = AffectedControl;
max_size = MaxSize;
if (affected == null || e.Button != MouseButtons.Left)
return;
Capture = true;
SplitterBeginMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))));
}
protected override void OnMouseMove (MouseEventArgs e)
{
base.OnMouseMove (e);
if (!Capture || e.Button != MouseButtons.Left || affected == null)
return;
// We need our mouse coordinates relative to our parent
SplitterMove (Parent.PointToClient (PointToScreen (e.Location)));
}
protected override void OnMouseUp (MouseEventArgs e)
{
base.OnMouseUp (e);
if (!Capture || e.Button != MouseButtons.Left || affected == null)
return;
SplitterEndMove (Parent.PointToClient (PointToScreen (e.Location)), false);
Capture = false;
}
private void SplitterBeginMove (Point location)
{
splitter_rectangle_moving = new Rectangle (Bounds.X, Bounds.Y,
Width, Height);
splitter_prev_move = horizontal ? location.Y : location.X;
}
private void SplitterMove (Point location)
{
int currentMove = horizontal ? location.Y : location.X;
int delta = currentMove - splitter_prev_move;
Rectangle prev_location = splitter_rectangle_moving;
bool moved = false;
int min = this.MinSize + moving_offset;
int max = max_size + moving_offset;
if (horizontal) {
if (splitter_rectangle_moving.Y + delta > min && splitter_rectangle_moving.Y + delta < max) {
splitter_rectangle_moving.Y += delta;
moved = true;
} else {
// Ensure that the splitter is set to minimum or maximum position,
// even if the mouse "skips".
//
if (splitter_rectangle_moving.Y + delta <= min && splitter_rectangle_moving.Y != min) {
splitter_rectangle_moving.Y = min;
moved = true;
} else if (splitter_rectangle_moving.Y + delta >= max && splitter_rectangle_moving.Y != max) {
splitter_rectangle_moving.Y = max;
moved = true;
}
}
} else {
if (splitter_rectangle_moving.X + delta > min && splitter_rectangle_moving.X + delta < max) {
splitter_rectangle_moving.X += delta;
moved = true;
} else {
// Ensure that the splitter is set to minimum or maximum position,
// even if the mouse "skips".
//
if (splitter_rectangle_moving.X + delta <= min && splitter_rectangle_moving.X != min) {
splitter_rectangle_moving.X = min;
moved = true;
} else if (splitter_rectangle_moving.X + delta >= max && splitter_rectangle_moving.X != max) {
splitter_rectangle_moving.X = max;
moved = true;
}
}
}
if (moved) {
splitter_prev_move = currentMove;
OnSplitterMoving (new SplitterEventArgs (location.X, location.Y,
splitter_rectangle_moving.X,
splitter_rectangle_moving.Y));
XplatUI.DrawReversibleRectangle (this.Parent.Handle, prev_location, 1);
XplatUI.DrawReversibleRectangle (this.Parent.Handle, splitter_rectangle_moving, 1);
}
}
private void SplitterEndMove (Point location, bool cancel)
{
if (!cancel) {
// Resize the affected window
if (horizontal)
affected.Height = CalculateSplitPosition();
else
affected.Width = CalculateSplitPosition();
}
this.Parent.Refresh (); // to clean up the drag handle artifacts from all controls
SplitterEventArgs args = new SplitterEventArgs (location.X, location.Y,
splitter_rectangle_moving.X,
splitter_rectangle_moving.Y);
OnSplitterMoved (args);
}
protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
if (eh != null)
eh (this, sevent);
}
protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovingEvent]);
if (eh != null)
eh (this, sevent);
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
// enforce our width / height
if (horizontal) {
splitter_size = height;
if (splitter_size < 1) {
splitter_size = 3;
}
base.SetBoundsCore (x, y, width, splitter_size, specified);
} else {
splitter_size = width;
if (splitter_size < 1) {
splitter_size = 3;
}
base.SetBoundsCore (x, y, splitter_size, height, specified);
}
}
#endregion // Protected Instance Methods
#region Private Properties and Methods
private Control AffectedControl {
get {
if (Parent == null)
return null;
// Doc says the first control preceeding us in the zorder
for (int i = Parent.Controls.GetChildIndex(this) + 1; i < Parent.Controls.Count; i++) {
switch (Dock) {
case DockStyle.Top:
if (Top == Parent.Controls[i].Bottom)
return Parent.Controls[i];
break;
case DockStyle.Bottom:
if (Bottom == Parent.Controls[i].Top)
return Parent.Controls[i];
break;
case DockStyle.Left:
if (Left == Parent.Controls[i].Right)
return Parent.Controls[i];
break;
case DockStyle.Right:
if (Right == Parent.Controls[i].Left)
return Parent.Controls[i];
break;
}
}
return null;
}
}
private int CalculateSplitPosition() {
if (horizontal) {
if (Dock == DockStyle.Top)
return splitter_rectangle_moving.Y - affected.Top;
else
return affected.Bottom - splitter_rectangle_moving.Y - splitter_size;
} else {
if (Dock == DockStyle.Left)
return splitter_rectangle_moving.X - affected.Left;
else
return affected.Right - splitter_rectangle_moving.X - splitter_size;
}
}
internal override void OnPaintInternal (PaintEventArgs e) {
e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
}
private void LayoutSplitter(object sender, LayoutEventArgs e) {
affected = AffectedControl;
if (split_requested != -1) {
SplitPosition = split_requested;
split_requested = -1;
}
}
private void ReparentSplitter(object sender, EventArgs e) {
affected = null;
}
#endregion // Private Properties and Methods
#region Events
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler BackgroundImageChanged {
add { base.BackgroundImageChanged += value; }
remove { base.BackgroundImageChanged -= value; }
}
[Browsable (false)]
[EditorBrowsable (EditorBrowsableState.Never)]
public new event EventHandler BackgroundImageLayoutChanged
{
add { base.BackgroundImageLayoutChanged += value; }
remove { base.BackgroundImageLayoutChanged -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler Enter {
add { base.Enter += value; }
remove { base.Enter -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler FontChanged {
add { base.FontChanged += value; }
remove { base.FontChanged -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler ForeColorChanged {
add { base.ForeColorChanged += value; }
remove { base.ForeColorChanged -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler ImeModeChanged {
add { base.ImeModeChanged += value; }
remove { base.ImeModeChanged -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyEventHandler KeyDown {
add { base.KeyDown += value; }
remove { base.KeyDown -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyPressEventHandler KeyPress {
add { base.KeyPress += value; }
remove { base.KeyPress -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyEventHandler KeyUp {
add { base.KeyUp += value; }
remove { base.KeyUp -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler Leave {
add { base.Leave += value; }
remove { base.Leave -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler TabStopChanged {
add { base.TabStopChanged += value; }
remove { base.TabStopChanged -= value; }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler TextChanged {
add { base.TextChanged += value; }
remove { base.TextChanged -= value; }
}
static object SplitterMovedEvent = new object ();
static object SplitterMovingEvent = new object ();
public event SplitterEventHandler SplitterMoved {
add { Events.AddHandler (SplitterMovedEvent, value); }
remove { Events.RemoveHandler (SplitterMovedEvent, value); }
}
public event SplitterEventHandler SplitterMoving {
add { Events.AddHandler (SplitterMovingEvent, value); }
remove { Events.RemoveHandler (SplitterMovingEvent, value); }
}
#endregion // Events
}
}