#region Copyright Notice
/*
* gitter - VCS repository management tool
* Copyright (C) 2013 Popovskiy Maxim Vladimirovitch <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#endregion
namespace gitter.Framework.Controls
{
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
/// <summary>Control for hosting <see cref="T:ViewBase"/> controls.
[ToolboxItem(false)]
public sealed class ViewHost : Control, IDockHost
{
#region Static
private static readonly LinkedList<ViewHost> _viewHosts;
public static IEnumerable<ViewHost> ViewHosts
{
get { return _viewHosts; }
}
/// <summary>Initializes the <see cref="ViewHost"/> class.
static ViewHost()
{
_viewHosts = new LinkedList<ViewHost>();
}
#endregion
#region Data
private readonly ViewDockGrid _grid;
private readonly bool _isRoot;
private readonly List<ViewBase> _views;
private readonly Panel _viewContainer;
private readonly ViewHostDockMarkers _dockMarkers;
private readonly ViewHostDockingProcess _dockingProcess;
private readonly ViewHostResizingProcess _resizingProcess;
private bool _isDocumentWell;
private bool _isActive;
private ViewHostHeader _header;
private ViewHostTabs _tabs;
private ViewHostFooter _footer;
private ViewBase _activeView;
private ViewHostStatus _status;
private Form _ownerForm;
private ViewDockSide _dockSide;
private bool _readyToMove;
private int _mdX;
private int _mdY;
#endregion
#region Events
private static readonly object ActiveViewChangedEvent = new object();
/// <summary>Occurs when active view changes.</summary>
public event EventHandler ActiveViewChanged
{
add { Events.AddHandler(ActiveViewChangedEvent, value); }
remove { Events.RemoveHandler(ActiveViewChangedEvent, value); }
}
private static readonly object StatusChangedEvent = new object();
/// <summary>Occurs when status changes.</summary>
public event EventHandler StatusChanged
{
add { Events.AddHandler(StatusChangedEvent, value); }
remove { Events.RemoveHandler(StatusChangedEvent, value); }
}
private static readonly object ViewAddedEvent = new object();
/// <summary>Occurs when view is added to this host.</summary>
public event EventHandler<ViewEventArgs> ViewAdded
{
add { Events.AddHandler(ViewAddedEvent, value); }
remove { Events.RemoveHandler(ViewAddedEvent, value); }
}
private static readonly object ViewRemovedEvent = new object();
/// <summary>Occurs when view is removed from this host.</summary>
public event EventHandler<ViewEventArgs> ViewRemoved
{
add { Events.AddHandler(ViewRemovedEvent, value); }
remove { Events.RemoveHandler(ViewRemovedEvent, value); }
}
#endregion
#region .ctor
/// <summary>Initializes a new instance of the <see cref="ViewHost"/> class.
/// <param name="grid">Host grid.</param>
/// <param name="isRoot">if set to <c>true disables host auto-destruction on losing all hosted views.
/// <param name="isDocumentWell">if set to <c>true uses advanced layout features for hosting variable size documents.
/// <param name="views">Hosted views.</param>
internal ViewHost(ViewDockGrid grid, bool isRoot, bool isDocumentWell, IEnumerable<ViewBase> views)
{
Verify.Argument.IsNotNull(grid, "grid");
SetStyle(ControlStyles.ContainerControl, true);
SetStyle(
ControlStyles.SupportsTransparentBackColor |
ControlStyles.Selectable,
false);
_grid = grid;
_isRoot = isRoot;
_isDocumentWell = isDocumentWell;
_views = new List<ViewBase>();
var size = new Size(ViewConstants.MinimumHostWidth, ViewConstants.MinimumHostHeight);
if(views != null)
{
foreach(var view in views)
{
Verify.Argument.IsTrue(view != null, "views", "List of views contains invalid arguments.");
_views.Add(view);
view.TextChanged += OnViewTextChanged;
view.Host = this;
var ts = view.Size;
if(ts.Width > size.Width)
{
size.Width = ts.Width;
}
if(ts.Height > size.Height)
{
size.Height = ts.Height;
}
}
}
if(_views.Count > 0)
{
if(isDocumentWell)
{
size.Height += Renderer.TabHeight +
Renderer.TabFooterHeight +
Renderer.FooterHeight;
}
else
{
size.Height += Renderer.HeaderHeight;
if(_views.Count > 1)
{
size.Height += Renderer.TabHeight;
}
}
}
_dockMarkers = new ViewHostDockMarkers(this);
_dockingProcess = new ViewHostDockingProcess(this);
_resizingProcess = new ViewHostResizingProcess(this);
BackColor = Renderer.BackgroundColor;
SuspendLayout();
int bottomOffset;
int topOffset;
if(_views.Count != 0)
{
_activeView = _views[0];
if(isDocumentWell)
{
SpawnTabs(size);
topOffset = _tabs.Height;
SpawnFooter(size);
bottomOffset = Renderer.FooterHeight;
}
else
{
SpawnHeader(size);
topOffset = _header.Height;
if(_views.Count > 1)
{
SpawnTabs(size);
bottomOffset = _tabs.Height;
}
else
{
bottomOffset = 0;
}
}
}
else
{
topOffset = 0;
bottomOffset = 0;
}
_viewContainer = new Panel()
{
Bounds = new Rectangle(0, topOffset, size.Width, size.Height - topOffset - bottomOffset),
Anchor = ViewConstants.AnchorAll,
};
if(_activeView != null)
{
_activeView.Bounds = new Rectangle(Point.Empty, _viewContainer.Size);
_activeView.Anchor = ViewConstants.AnchorAll;
_activeView.Parent = _viewContainer;
}
Size = size;
_viewContainer.Parent = this;
ResumeLayout(true);
lock(_viewHosts)
{
_viewHosts.AddLast(this);
}
}
#endregion
private ViewRenderer Renderer
{
get { return ViewManager.Renderer; }
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.ParentChanged"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
UpdateOwnerForm();
}
private void UpdateOwnerForm()
{
var form = TopLevelControl as Form;
if(form == null)
{
if(_ownerForm != null)
{
_ownerForm.Activated -= OnOwnerFormActivated;
_ownerForm.Deactivate -= OnOwnerFormDeactivated;
_ownerForm = null;
_isActive = false;
}
}
else
{
if(_ownerForm != form)
{
if(_ownerForm != null)
{
_ownerForm.Activated -= OnOwnerFormActivated;
_ownerForm.Deactivate -= OnOwnerFormDeactivated;
}
_ownerForm = form;
form.Activated += OnOwnerFormActivated;
form.Deactivate += OnOwnerFormDeactivated;
_isActive = form.Focused;
}
}
}
private void OnOwnerFormActivated(object sender, EventArgs e)
{
if(ContainsFocus)
{
_isActive = true;
InvalidateHelpers();
}
}
private void OnOwnerFormDeactivated(object sender, EventArgs e)
{
if(_isActive)
{
_isActive = false;
InvalidateHelpers();
}
}
/// <summary>Gets a value indicating whether this <see cref="ViewHost"/> is resizing.
/// <value><c>true if this instance is resizing; otherwise, false.
public bool IsResizing
{
get { return _resizingProcess.IsActive; }
}
/// <summary>Gets a value indicating whether this <see cref="ViewHost"/> is moving.
/// <value><c>true if this instance is moving; otherwise, false.
public bool IsMoving
{
get { return _dockingProcess.IsActive; }
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.MouseLeave"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
if(Status == ViewHostStatus.AutoHide)
{
Cursor = Cursors.Default;
}
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.MouseEnter"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
if(Status == ViewHostStatus.AutoHide)
{
switch(DockSide.Orientation)
{
case Orientation.Vertical:
Cursor = Cursors.SizeWE;
break;
case Orientation.Horizontal:
Cursor = Cursors.SizeNS;
break;
default:
throw new ApplicationException("Unexpected ViewDockSide.Orientation: " + DockSide.Orientation);
}
}
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.MouseDown"/> event.
/// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if(Status == ViewHostStatus.AutoHide)
{
if(e.Button == MouseButtons.Left)
{
_resizingProcess.Start(e.Location);
}
}
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.MouseMove"/> event.
/// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if(_resizingProcess.IsActive)
{
_resizingProcess.Update(e.Location);
}
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"/> event.
/// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if((e.Button & MouseButtons.Left) == MouseButtons.Left && _resizingProcess.IsActive)
{
_resizingProcess.Commit(e.Location);
}
}
/// <summary>Creates <see cref="ViewHostHeader"/> control.
/// <param name="size">Control size.</param>
private void SpawnHeader(Size size)
{
Verify.State.IsTrue(_header == null, "Header is already spawned.");
var headerHeight = Renderer.HeaderHeight;
if(headerHeight > 0)
{
_header = new ViewHostHeader(this)
{
Bounds = new Rectangle(0, 0, size.Width, headerHeight),
Anchor = ViewConstants.AnchorDockTop,
Parent = this,
};
ResetHeaderButtons();
_header.MouseDown += OnHeaderMouseDown;
_header.MouseMove += OnHeaderMouseMove;
_header.MouseUp += OnHeaderMouseUp;
_header.MouseDoubleClick += OnHeaderMouseDoubleClick;
_header.HeaderButtonClick += OnHeaderButtonClick;
}
}
/// <summary>Destroy header control.</summary>
private void RemoveHeader()
{
if(_header != null)
{
_header.Parent = null;
_header.MouseDown -= OnHeaderMouseDown;
_header.MouseMove -= OnHeaderMouseMove;
_header.MouseUp -= OnHeaderMouseUp;
_header.MouseDoubleClick -= OnHeaderMouseDoubleClick;
_header.HeaderButtonClick -= OnHeaderButtonClick;
_header.Dispose();
_header = null;
}
}
/// <summary>Creates <see cref="ViewHostFooter"/> control.
/// <param name="size">Control size.</param>
private void SpawnFooter(Size size)
{
Verify.State.IsTrue(_footer == null, "Footer is already spawned.");
var footerHeight = Renderer.FooterHeight;
if(footerHeight > 0)
{
_footer = new ViewHostFooter(this)
{
Bounds = new Rectangle(
0, size.Height - footerHeight,
size.Width, footerHeight),
Anchor = ViewConstants.AnchorDockBottom,
Parent = this,
};
}
}
/// <summary>Destroys footer control.</summary>
private void RemoveFooter()
{
if(_footer != null)
{
_footer.Parent = null;
_footer.Dispose();
_footer = null;
}
}
private static AnchorStyles GetRelativeSide(ViewSplit viewSplit, int index, int side)
{
var item = viewSplit[index];
var th = item as ViewHost;
bool isDw = false;
if(th != null)
{
isDw = th.IsDocumentWell;
}
else
{
var ts = item as ViewSplit;
if(ts != null)
{
isDw = ts.ContainsDocumentWell;
}
}
if(isDw)
{
switch(viewSplit.Orientation)
{
case Orientation.Horizontal:
return side == -1 ? AnchorStyles.Right : AnchorStyles.Left;
case Orientation.Vertical:
return side == -1 ? AnchorStyles.Bottom : AnchorStyles.Top;
default:
throw new ApplicationException();
}
}
return AnchorStyles.None;
}
private static AnchorStyles GetRelativeSide(ViewSplit viewSplit, Control control)
{
var index = viewSplit.IndexOf(control);
for(int i = 0; i < index; ++i)
{
var anchor = GetRelativeSide(viewSplit, i, -1);
if(anchor != AnchorStyles.None) return anchor;
}
for(int i = index + 1; i < viewSplit.Count; ++i)
{
var anchor = GetRelativeSide(viewSplit, i, 1);
if(anchor != AnchorStyles.None) return anchor;
}
var parent = viewSplit.Parent as ViewSplit;
if(parent != null)
{
return GetRelativeSide(parent, viewSplit);
}
return AnchorStyles.None;
}
public AnchorStyles GetRelativeSide()
{
if(_isDocumentWell) return AnchorStyles.None;
if(_status == ViewHostStatus.Floating) return AnchorStyles.None;
var parent = Parent;
if(parent == null) return AnchorStyles.None;
var ts = parent as ViewSplit;
if(ts != null)
{
return GetRelativeSide(ts, this);
}
else
{
return AnchorStyles.None;
}
}
/// <summary>Pins this <see cref="ViewHost"/>.
/// <exception cref="InvalidOperationException">This <see cref="ViewHost"/> is not in auto-hide mode.
public void Pin()
{
Verify.State.IsTrue(_status == ViewHostStatus.AutoHide);
var side = _dockSide.Side;
var grid = _dockSide.Grid;
Undock();
DockResult dockResult;
switch(side)
{
case AnchorStyles.Left:
dockResult = DockResult.Left;
break;
case AnchorStyles.Top:
dockResult = DockResult.Top;
break;
case AnchorStyles.Right:
dockResult = DockResult.Right;
break;
case AnchorStyles.Bottom:
dockResult = DockResult.Bottom;
break;
default:
throw new ApplicationException("Unexpected ViewDockGridSide.Side: " + side);
}
grid.PerformDock(this, dockResult);
}
internal void UnpinFromLeft()
{
var w = Width;
_grid.PerformDock(this, DockResult.AutoHideLeft);
_dockSide = _grid.LeftSide;
Width = w + ViewConstants.SideDockPanelBorderSize;
if(_tabs != null)
{
RemoveTabs();
_viewContainer.Height += Renderer.TabHeight;
}
_header.Width = w;
_viewContainer.Width = w;
}
internal void UnpinFromTop()
{
var h = Height - Renderer.HeaderHeight;
_grid.PerformDock(this, DockResult.AutoHideTop);
_dockSide = _grid.TopSide;
RemoveTabs();
Height += ViewConstants.SideDockPanelBorderSize;
_viewContainer.Height = h;
}
internal void UnpinFromRight()
{
var w = Width;
_grid.PerformDock(this, DockResult.AutoHideRight);
_dockSide = _grid.RightSide;
_viewContainer.SuspendLayout();
if(_tabs == null)
{
_viewContainer.SetBounds(
ViewConstants.SideDockPanelBorderSize, 0, w, 0,
BoundsSpecified.X | BoundsSpecified.Width);
}
else
{
_viewContainer.SetBounds(
ViewConstants.SideDockPanelBorderSize, 0, w, Height - Renderer.HeaderHeight,
BoundsSpecified.X | BoundsSpecified.Width | BoundsSpecified.Height);
RemoveTabs();
}
Width += ViewConstants.SideDockPanelBorderSize;
_header.SetBounds(
ViewConstants.SideDockPanelBorderSize, 0, w, 0,
BoundsSpecified.X | BoundsSpecified.Width);
_viewContainer.ResumeLayout(true);
}
internal void UnpinFromBottom()
{
var h = Height;
_grid.PerformDock(this, DockResult.AutoHideBottom);
_dockSide = _grid.BottomSide;
_viewContainer.SuspendLayout();
var headerHeight = Renderer.HeaderHeight;
if(_tabs == null)
{
_viewContainer.Top = headerHeight +
ViewConstants.SideDockPanelBorderSize;
}
else
{
_viewContainer.SetBounds(
0, ViewConstants.SideDockPanelBorderSize + headerHeight,
0, h - headerHeight,
BoundsSpecified.Y | BoundsSpecified.Height);
RemoveTabs();
}
_header.Top = ViewConstants.SideDockPanelBorderSize;
Height += ViewConstants.SideDockPanelBorderSize;
_viewContainer.ResumeLayout(true);
}
/// <summary>Unpins this <see cref="ViewHost"/>.
/// <exception cref="InvalidOperationException">This <see cref="ViewHost"/> is not docked.
public void Unpin()
{
Verify.State.IsFalse(_isDocumentWell);
Verify.State.IsTrue(_status == ViewHostStatus.Docked);
var side = GetRelativeSide();
if(side == AnchorStyles.None) return;
Undock();
switch(side)
{
case AnchorStyles.Left:
UnpinFromLeft();
break;
case AnchorStyles.Top:
UnpinFromTop();
break;
case AnchorStyles.Right:
UnpinFromRight();
break;
case AnchorStyles.Bottom:
UnpinFromBottom();
break;
default:
throw new ApplicationException();
}
Status = ViewHostStatus.AutoHide;
}
private void MaximizeFloatingForm()
{
if(_status == ViewHostStatus.Floating)
{
_header.SetAvailableButtons(new[]
{
ViewButtonType.Normalize,
ViewButtonType.Close,
});
((Form)TopLevelControl).WindowState = FormWindowState.Maximized;
}
}
private void NormalizeFloatingForm()
{
if(_status == ViewHostStatus.Floating)
{
_header.SetAvailableButtons(new[]
{
ViewButtonType.Maximize,
ViewButtonType.Close,
});
((Form)TopLevelControl).WindowState = FormWindowState.Normal;
}
}
private void OnHeaderMouseDoubleClick(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
if(_status == ViewHostStatus.Floating)
{
var f = (Form)TopLevelControl;
if(f.WindowState == FormWindowState.Maximized)
{
NormalizeFloatingForm();
}
else
{
MaximizeFloatingForm();
}
}
else
{
var owner = GetRootOwnerForm();
var loc = PointToScreen(Point.Empty);
loc.X -= Renderer.FloatBorderSize;
loc.Y -= Renderer.FloatBorderSize;
var form = PrepareFloatingMode();
form.Location = loc;
form.Show(owner);
}
}
}
private void OnHeaderButtonClick(object sender, ViewButtonClickEventArgs e)
{
switch(e.Button)
{
case ViewButtonType.Close:
if(_status == ViewHostStatus.Floating)
{
((Form)TopLevelControl).Close();
}
else
{
if(_activeView != null)
{
var view = _activeView;
var index = _views.IndexOf(view);
if(index == _views.Count - 1)
{
--index;
}
else
{
++index;
}
if(index >= 0)
{
SetActiveView(_views[index]);
}
view.Close();
}
}
break;
case ViewButtonType.Pin:
Pin();
Activate();
break;
case ViewButtonType.Unpin:
Unpin();
break;
case ViewButtonType.Maximize:
MaximizeFloatingForm();
break;
case ViewButtonType.Normalize:
NormalizeFloatingForm();
break;
}
}
/// <summary>This host should not be destroyed if all hosted views are removed.</summary>
public bool IsRoot
{
get { return _isRoot; }
}
/// <summary>This is a document well - advanced view host which has tabs instead of title bar.</summary>
public bool IsDocumentWell
{
get { return _isDocumentWell; }
}
/// <summary>Host specified view.</summary>
/// <param name="view">View to host.</param>
public void AddView(ViewBase view)
{
Verify.Argument.IsNotNull(view, "view");
Verify.Argument.IsTrue(view.Host == null, "view", "View is already hosted.");
_views.Add(view);
view.Host = this;
if(_isDocumentWell)
{
if(_views.Count == 1)
{
var size = Size;
SuspendLayout();
SpawnTabs(size);
SpawnFooter(size);
UpdateContentPanelBounds();
_activeView = view;
_activeView.Bounds = new Rectangle(Point.Empty, _viewContainer.Size);
_activeView.Anchor = ViewConstants.AnchorAll;
_activeView.Parent = _viewContainer;
ResumeLayout(true);
Events.Raise(ActiveViewChangedEvent, this);
}
else
{
_tabs.AddView(view);
}
}
else
{
if(_views.Count == 1)
{
var size = Size;
SuspendLayout();
SpawnHeader(size);
UpdateContentPanelBounds();
_activeView = view;
_activeView.Bounds = new Rectangle(Point.Empty, _viewContainer.Size);
_activeView.Anchor = ViewConstants.AnchorAll;
_activeView.Parent = _viewContainer;
ResumeLayout(true);
Events.Raise(ActiveViewChangedEvent, this);
}
else if(_views.Count == 2)
{
var size = Size;
SuspendLayout();
SpawnTabs(size);
UpdateContentPanelBounds();
ResumeLayout(true);
}
else
{
_tabs.AddView(view);
}
}
view.TextChanged += OnViewTextChanged;
Events.Raise(ViewAddedEvent, this, new ViewEventArgs(view));
}
/// <summary>Number of hosted views.</summary>
public int ViewsCount
{
get { return _views.Count; }
}
/// <summary>Returns hosted view.</summary>
/// <param name="index">View index.</param>
/// <returns>Hosted view with specified index.</returns>
public ViewBase GetView(int index)
{
return _views[index];
}
public bool Contains(ViewBase view)
{
return _views.Contains(view);
}
public int IndexOf(ViewBase view)
{
return _views.IndexOf(view);
}
/// <summary>Set active view.</summary>
/// <param name="view">View to activate.</param>
public void SetActiveView(ViewBase view)
{
Verify.Argument.IsNotNull(view, "view");
Verify.Argument.IsTrue(view.Host == this, "view", "View is hosted in another host.");
if(_activeView != view)
{
var old = _activeView;
_activeView = view;
_activeView.Bounds = new Rectangle(Point.Empty, _viewContainer.Size);
_activeView.Anchor = ViewConstants.AnchorAll;
_activeView.Parent = _viewContainer;
if(old != null)
{
old.Parent = null;
}
if(_header != null)
{
_header.Invalidate();
}
if(_tabs != null)
{
_tabs.EnsureVisible(view);
_tabs.Invalidate();
}
Events.Raise(ActiveViewChangedEvent, this);
}
}
private void PreventActiveViewDispose()
{
if(_activeView != null)
{
_activeView.Parent = null;
}
}
/// <summary>Recalculate bounds of view hosting panel.</summary>
private void UpdateContentPanelBounds()
{
var size = Size;
if(_isDocumentWell)
{
int topOffset = _tabs != null ? _tabs.Height : 0;
int bottomOffset = _footer != null ? _footer.Height : 0;
_viewContainer.Bounds = new Rectangle(0, topOffset, size.Width, size.Height - topOffset - bottomOffset);
}
else
{
if(_status == ViewHostStatus.AutoHide)
{
}
int topOffset = _header != null ? _header.Height : 0;
int bottomOffset = _tabs != null ? _tabs.Height : 0;
_viewContainer.Bounds = new Rectangle(0, topOffset, size.Width, size.Height - topOffset - bottomOffset);
}
}
/// <summary>Remove view from host.</summary>
/// <param name="view">View to remove.</param>
/// <remarks>
/// Removing last view from non-host control destroys host.
/// Host container gets updated and sibling hosts resize accordingly.
/// </remarks>
internal void RemoveView(ViewBase view)
{
Verify.Argument.IsNotNull(view, "view");
Verify.Argument.IsTrue(view.Host == this, "view", "View is not hosted in this ViewHost.");
int index = _views.IndexOf(view);
Assert.AreNotEqual(index, -1);
_views.RemoveAt(index);
view.TextChanged -= OnViewTextChanged;
view.Host = null;
if(_isDocumentWell)
{
if(_views.Count == 0)
{
if(view == _activeView)
{
view.Parent = null;
}
if(!_isRoot)
{
Undock();
Dispose();
Status = ViewHostStatus.Disposed;
}
else
{
SuspendLayout();
RemoveTabs();
RemoveFooter();
ResumeLayout(true);
UpdateContentPanelBounds();
}
}
else
{
_tabs.RemoveView(view);
if(_activeView == view)
{
bool focused = view.ContainsFocus;
if(index >= _views.Count)
{
index = _views.Count - 1;
}
if(focused)
{
Activate(_views[index]);
}
else
{
SetActiveView(_views[index]);
}
view.Parent = null;
}
}
}
else
{
if(_views.Count == 0)
{
if(view == _activeView)
{
view.Parent = null;
}
if(!_isRoot)
{
Undock();
Dispose();
Status = ViewHostStatus.Disposed;
}
else
{
RemoveHeader();
UpdateContentPanelBounds();
}
}
else
{
if(_status != ViewHostStatus.AutoHide)
{
if(_views.Count == 1)
{
RemoveTabs();
UpdateContentPanelBounds();
}
else
{
_tabs.RemoveView(view);
}
}
if(_activeView == view)
{
bool focused = view.ContainsFocus;
if(index >= _views.Count)
{
index = _views.Count - 1;
}
if(focused)
{
Activate(_views[index]);
}
else
{
SetActiveView(_views[index]);
}
view.Parent = null;
}
}
}
Events.Raise(ViewRemovedEvent, this, new ViewEventArgs(view));
}
/// <summary>Creates <see cref="ViewHostTabs"/> control.
/// <param name="size">Control size.</param>
private void SpawnTabs(Size size)
{
Verify.State.IsTrue(_tabs == null, "Tabs are already spawned.");
var tabHeight = Renderer.TabHeight;
if(_isDocumentWell)
{
_tabs = new ViewHostTabs(this, AnchorStyles.Top)
{
Bounds = new Rectangle(
0, 0,
size.Width, tabHeight + Renderer.TabFooterHeight),
Anchor = ViewConstants.AnchorDockTop,
};
}
else
{
_tabs = new ViewHostTabs(this, AnchorStyles.Bottom)
{
Bounds = new Rectangle(
0, size.Height - tabHeight,
size.Width, tabHeight),
Anchor = ViewConstants.AnchorDockBottom,
};
}
_tabs.Parent = this;
}
/// <summary>Destroy tabs control.</summary>
private void RemoveTabs()
{
if(_tabs != null)
{
_tabs.Parent = null;
_tabs.Dispose();
_tabs = null;
}
}
private void OnHeaderMouseDown(object sender, MouseEventArgs e)
{
switch(e.Button)
{
case MouseButtons.Left:
if(_status == ViewHostStatus.Floating)
{
var form = (Form)TopLevelControl;
if(form.WindowState != FormWindowState.Normal) return;
}
_mdX = e.X;
_mdY = e.Y;
_readyToMove = true;
break;
}
}
private void OnHeaderMouseMove(object sender, MouseEventArgs e)
{
bool moving = false;
if(_readyToMove)
{
if(_status != ViewHostStatus.Floating)
{
if(Math.Abs(e.X - _mdX) > 6 || Math.Abs(e.Y - _mdY) > 6)
{
_readyToMove = false;
moving = true;
GoFloatingMode();
}
}
else
{
_readyToMove = false;
moving = true;
}
}
if(moving || _dockingProcess.IsActive)
{
var p = TopLevelControl;
if(p != null)
{
int dx = e.X - _mdX;
int dy = e.Y - _mdY;
if(dx != 0 || dy != 0)
{
var loc = p.Location;
loc.Offset(dx, dy);
p.Location = loc;
}
}
var location = e.Location;
location.X += _header.Left;
location.Y += _header.Top;
if(_dockingProcess.IsActive)
{
_dockingProcess.Update(location);
}
else
{
_dockingProcess.Start(location);
}
}
}
private void OnHeaderMouseUp(object sender, MouseEventArgs e)
{
if(_dockingProcess.IsActive)
{
var location = e.Location;
location.X += _header.Left;
location.Y += _header.Top;
_dockingProcess.Commit(location);
}
_readyToMove = false;
}
private void OnViewTextChanged(object sender, EventArgs e)
{
var view = (ViewBase)sender;
if(_header != null)
{
_header.Invalidate();
}
if(_tabs != null)
{
_tabs.InvalidateTab(view);
}
}
private void InvalidateHelpers()
{
if(_tabs != null)
{
_tabs.Invalidate();
}
if(_footer != null)
{
_footer.Invalidate();
}
if(_header != null)
{
_header.Invalidate();
}
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.Enter"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
_isActive = true;
InvalidateHelpers();
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.Leave"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
_isActive = false;
InvalidateHelpers();
}
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.Resize"/> event.
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if(_tabs != null && _activeView != null)
{
_tabs.EnsureVisible(_activeView);
}
}
public void Activate()
{
if(_activeView != null)
{
if(_activeView.Focus())
{
_isActive = true;
}
}
}
public void Activate(ViewBase view)
{
Verify.Argument.IsNotNull(view, "view");
Verify.Argument.IsTrue(view.Host == this, "view", "View is not hosted in this ViewHost.");
SetActiveView(view);
_activeView.Focus();
}
/// <summary>This view host is active.</summary>
public bool IsActive
{
get { return ContainsFocus; }
}
/// <summary>Undocks this <see cref="ViewHost"/>.
private void Undock()
{
switch(Status)
{
case ViewHostStatus.AutoHide:
{
_dockSide.RemoveHost(this);
switch(_dockSide.Side)
{
case AnchorStyles.Left:
{
var w = Width - ViewConstants.SideDockPanelBorderSize;
var h = Height;
Width = w;
_header.Width = w;
if(_views.Count > 1)
{
SpawnTabs(new Size(w, h));
_viewContainer.SetBounds(
0, 0, w, h - Renderer.TabHeight - Renderer.HeaderHeight,
BoundsSpecified.Width | BoundsSpecified.Height);
}
else
{
_viewContainer.Width = w;
}
}
break;
case AnchorStyles.Top:
{
var w = Width;
var h = Height - ViewConstants.SideDockPanelBorderSize;
Height = h;
if(_views.Count > 1)
{
SpawnTabs(new Size(w, h));
UpdateContentPanelBounds();
}
else
{
_viewContainer.Height = h - Renderer.HeaderHeight;
}
}
break;
case AnchorStyles.Right:
{
var w = Width - ViewConstants.SideDockPanelBorderSize;
var h = Height;
Width = w;
_header.SetBounds(0, 0, w, Renderer.HeaderHeight, BoundsSpecified.X | BoundsSpecified.Width);
if(_views.Count > 1)
{
SpawnTabs(new Size(w, h));
UpdateContentPanelBounds();
}
else
{
_viewContainer.SetBounds(0, 0, w, 0, BoundsSpecified.X | BoundsSpecified.Width);
}
}
break;
case AnchorStyles.Bottom:
{
var w = Width;
var h = Height - ViewConstants.SideDockPanelBorderSize;
Height = h;
_header.Top = 0;
if(_views.Count > 1)
{
SpawnTabs(new Size(w, h));
UpdateContentPanelBounds();
}
else
{
_viewContainer.SetBounds(0, Renderer.HeaderHeight, 0, h, BoundsSpecified.Y | BoundsSpecified.Height);
}
}
break;
default:
throw new ApplicationException("Unexpected ViewDockSide.Side: " + _dockSide.Side);
}
_dockSide = null;
}
break;
case ViewHostStatus.DockedOnFloat:
case ViewHostStatus.Docked:
{
var parent = Parent;
if(parent != null)
{
parent.DisableRedraw();
try
{
var split = parent as ViewSplit;
if(split != null)
{
split.Remove(this);
return;
}
else
{
throw new ApplicationException("Unexpeceted ViewHost.Parent: " + parent);
}
}
finally
{
if(!parent.IsDisposed)
{
parent.EnableRedraw();
parent.RedrawWindow();
}
}
}
}
break;
case ViewHostStatus.Floating:
{
var parent = (Form)TopLevelControl;
Parent = null;
parent.Close();
parent.Dispose();
Anchor = ViewConstants.AnchorDefault;
}
break;
case ViewHostStatus.Offscreen:
case ViewHostStatus.Disposed:
throw new InvalidOperationException();
default:
throw new ApplicationException();
}
Status = ViewHostStatus.Offscreen;
}
public void StartMoving()
{
int d = Renderer.FloatBorderSize + Renderer.HeaderHeight / 2;
StartMoving(d, d);
}
public void StartMoving(int dx, int dy)
{
if(_header != null)
{
var pos = Control.MousePosition;
pos.Offset(-dx, -dy);
_mdX = dx - Renderer.FloatBorderSize;
_mdY = dy - Renderer.FloatBorderSize;
Parent.Location = pos;
_dockingProcess.Start(new Point(dx, dy));
_header.Capture = true;
}
}
/// <summary>Undock and embed into floating form.</summary>
/// <returns>Floating form.</returns>
internal FloatingViewForm PrepareFloatingMode()
{
if(_status != ViewHostStatus.Offscreen) Undock();
var floatingForm = new FloatingViewForm(_grid, this);
Location = new Point(
Renderer.FloatBorderSize,
Renderer.FloatBorderSize);
Anchor = ViewConstants.AnchorAll;
Parent = floatingForm;
Status = ViewHostStatus.Floating;
return floatingForm;
}
/// <summary>Gets the root owner form.</summary>
/// <returns>Root owner form.</returns>
public Form GetRootOwnerForm()
{
var form = TopLevelControl as Form;
if(form == null) return null;
while(form.Owner != null)
{
form = form.Owner;
}
return form;
}
/// <summary>Undock and go floating.</summary>
/// <param name="parent">Perent form.</param>
/// <returns>Floating form.</returns>
public Form GoFloatingMode(IWin32Window parent)
{
var floatingForm = PrepareFloatingMode();
floatingForm.Show(parent);
return floatingForm;
}
/// <summary>Undock and go floating.</summary>
/// <returns>Floating form.</returns>
public Form GoFloatingMode()
{
return GoFloatingMode(GetRootOwnerForm());
}
/// <summary>Destroy floating host form and get ready to dock.</summary>
private void ReturnFromFloatingMode()
{
if(Status == ViewHostStatus.Floating)
{
var p = (Form)TopLevelControl;
Parent = null;
p.Close();
p.Dispose();
Anchor = ViewConstants.AnchorDefault;
Status = ViewHostStatus.Offscreen;
}
}
/// <summary>Gets the hosting <see cref="ViewDockGrid"/>.
/// <value>Hosting <see cref="ViewDockGrid"/>.
internal ViewDockGrid Grid
{
get { return _grid; }
}
/// <summary>Host dock status.</summary>
public ViewHostStatus Status
{
get { return _status; }
internal set
{
if(_status != value)
{
_status = value;
if(_header != null) ResetHeaderButtons();
Events.Raise(StatusChangedEvent, this);
}
}
}
private void ResetHeaderButtons()
{
if(_header == null) return;
switch(Status)
{
case ViewHostStatus.AutoHide:
_header.SetAvailableButtons(ViewButtonType.Pin, ViewButtonType.Close);
break;
case ViewHostStatus.Floating:
switch(((Form)TopLevelControl).WindowState)
{
case FormWindowState.Maximized:
_header.SetAvailableButtons(ViewButtonType.Normalize, ViewButtonType.Close);
break;
case FormWindowState.Normal:
_header.SetAvailableButtons(ViewButtonType.Maximize, ViewButtonType.Close);
break;
}
break;
case ViewHostStatus.Docked:
_header.SetAvailableButtons(ViewButtonType.Unpin, ViewButtonType.Close);
break;
case ViewHostStatus.DockedOnFloat:
_header.SetAvailableButtons(ViewButtonType.Close);
break;
}
}
/// <summary>Returns collection of hosted views.</summary>
public IEnumerable<ViewBase> Views
{
get { return _views; }
}
/// <summary>Active view.</summary>
/// <remarks>
/// Host can have only one active view.
/// Root host has no active view if it hosts no views.
/// </remarks>
public ViewBase ActiveView
{
get { return _activeView; }
}
/// <summary>Active view's caption.</summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override string Text
{
get { return _activeView != null ? _activeView.Text : string.Empty; }
set { throw new InvalidOperationException(); }
}
/// <summary>Dock inside another host as tabbed views.</summary>
/// <param name="viewHost">Host to dock into.</param>
/// <remarks>
/// This ViewHost will be destroyed in the process of docking.
/// </remarks>
internal void DockInto(ViewHost viewHost)
{
Verify.Argument.IsNotNull(viewHost, "viewHost");
Verify.Argument.IsTrue(viewHost != this, "viewHost", "Cannot dock to itself.");
ReturnFromFloatingMode();
PreventActiveViewDispose();
foreach(var view in Views)
{
view.Host = null;
viewHost.AddView(view);
}
if(ActiveView != null)
{
viewHost.SetActiveView(_activeView);
_activeView = null;
}
Dispose();
Status = ViewHostStatus.Disposed;
}
/// <summary>Gets the <see cref="ViewDockSide"/> which hosts this in auto-hide state.
/// <value><see cref="ViewDockSide"/> which hosts this in auto-hide state.
internal ViewDockSide DockSide
{
get { return _dockSide; }
set { _dockSide = value; }
}
/// <summary>Dock to side of specified host.</summary>
/// <param name="origin">Relative control.</param>
/// <param name="side">Side to dock into.</param>
internal void DockToSide(Control origin, AnchorStyles side)
{
bool wasActive = ActiveView != null && IsActive;
ReturnFromFloatingMode();
ViewSplit.Replace(origin, this, side);
Status = ViewHostStatus.Docked;
if(wasActive)
{
SelectNextControl(this, true, true, true, false);
}
}
/// <summary>Converts this <see cref="ViewHost"/> to document well.
private void ConvertToDocumentWell()
{
if(!IsDocumentWell)
{
_isDocumentWell = true;
SuspendLayout();
RemoveHeader();
RemoveTabs();
var size = Size;
SpawnTabs(size);
SpawnFooter(size);
UpdateContentPanelBounds();
ResumeLayout(true);
}
}
/// <summary>Dock to side of specified host as document.</summary>
/// <param name="origin">Relative control.</param>
/// <param name="side">Side to dock into.</param>
internal void DockToSideAsDocument(Control origin, AnchorStyles side)
{
bool wasActive = ActiveView != null && IsActive;
ReturnFromFloatingMode();
ConvertToDocumentWell();
ViewSplit.Replace(origin, this, side);
Status = ViewHostStatus.Docked;
if(wasActive)
{
SelectNextControl(this, true, true, true, false);
}
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Control"/> and its child controls and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if(disposing)
{
if(_ownerForm != null)
{
_ownerForm.Activated -= OnOwnerFormActivated;
_ownerForm.Deactivate -= OnOwnerFormDeactivated;
_ownerForm = null;
}
foreach(var view in _views)
{
view.TextChanged -= OnViewTextChanged;
}
RemoveHeader();
RemoveTabs();
RemoveFooter();
_dockingProcess.Dispose();
_resizingProcess.Dispose();
_viewContainer.Dispose();
_dockMarkers.Dispose();
_dockSide = null;
_status = ViewHostStatus.Disposed;
}
lock(_viewHosts)
{
_viewHosts.Remove(this);
}
base.Dispose(disposing);
}
#region IDockHost
/// <summary>Provides dock helper markers.</summary>
public IDockMarkerProvider DockMarkers
{
get { return _dockMarkers; }
}
/// <summary>
/// Determines if <see cref="ViewHost"/> cn be docked into this <see cref="IDockHost"/>.
/// </summary>
/// <param name="viewHost"><see cref="ViewHost"/> to dock.
/// <param name="dockResult">Position for docking.</param>
/// <returns>true if docking is possible.</returns>
public bool CanDock(ViewHost viewHost, DockResult dockResult)
{
Verify.Argument.IsNotNull(viewHost, "viewHost");
switch(dockResult)
{
case DockResult.Left:
case DockResult.Top:
case DockResult.Right:
case DockResult.Bottom:
case DockResult.Fill:
return true;
case DockResult.DocumentLeft:
case DockResult.DocumentTop:
case DockResult.DocumentRight:
case DockResult.DocumentBottom:
return _isDocumentWell;
default:
return false;
}
}
/// <summary>
/// Docks <paramref name="viewHost"/> into this <see cref="IDockHost"/>.
/// </summary>
/// <param name="viewHost"><see cref="ViewHost"/> to dock.
/// <param name="dockResult">Position for docking.</param>
public void PerformDock(ViewHost viewHost, DockResult dockResult)
{
Verify.Argument.IsNotNull(viewHost, "viewHost");
switch(dockResult)
{
case DockResult.Left:
viewHost.DockToSide(this, AnchorStyles.Left);
if(_status == ViewHostStatus.DockedOnFloat || _status == ViewHostStatus.Floating)
{
viewHost.Status = ViewHostStatus.DockedOnFloat;
}
break;
case DockResult.Top:
viewHost.DockToSide(this, AnchorStyles.Top);
if(_status == ViewHostStatus.DockedOnFloat || _status == ViewHostStatus.Floating)
{
viewHost.Status = ViewHostStatus.DockedOnFloat;
}
break;
case DockResult.Right:
viewHost.DockToSide(this, AnchorStyles.Right);
if(_status == ViewHostStatus.DockedOnFloat || _status == ViewHostStatus.Floating)
{
viewHost.Status = ViewHostStatus.DockedOnFloat;
}
break;
case DockResult.Bottom:
viewHost.DockToSide(this, AnchorStyles.Bottom);
if(_status == ViewHostStatus.DockedOnFloat || _status == ViewHostStatus.Floating)
{
viewHost.Status = ViewHostStatus.DockedOnFloat;
}
break;
case DockResult.DocumentLeft:
viewHost.DockToSideAsDocument(this, AnchorStyles.Left);
break;
case DockResult.DocumentTop:
viewHost.DockToSideAsDocument(this, AnchorStyles.Top);
break;
case DockResult.DocumentRight:
viewHost.DockToSideAsDocument(this, AnchorStyles.Right);
break;
case DockResult.DocumentBottom:
viewHost.DockToSideAsDocument(this, AnchorStyles.Bottom);
break;
case DockResult.Fill:
viewHost.DockInto(this);
Activate();
break;
default:
throw new ArgumentException(
"Unsupported DockResult value: {0}".UseAsFormat(dockResult),
"dockResult");
}
}
/// <summary>Get bounding rectangle for docked view.</summary>
/// <param name="viewHost">Tested <see cref="ViewHost"/>.
/// <param name="dockResult">Position for docking.</param>
/// <returns>Bounding rectangle for docked view.</returns>
public Rectangle GetDockBounds(ViewHost viewHost, DockResult dockResult)
{
Verify.Argument.IsNotNull(viewHost, "viewHost");
Rectangle bounds;
var size = Size;
switch(dockResult)
{
case DockResult.Left:
case DockResult.DocumentLeft:
{
var w1 = viewHost.Width;
var w2 = size.Width;
ViewSplit.OptimalSizes(w2, ViewConstants.MinimumHostWidth, ref w1, ref w2);
bounds = new Rectangle(0, 0, w1, size.Height);
}
break;
case DockResult.Top:
case DockResult.DocumentTop:
{
var h1 = viewHost.Height;
var h2 = size.Height;
ViewSplit.OptimalSizes(h2, ViewConstants.MinimumHostHeight, ref h1, ref h2);
bounds = new Rectangle(0, 0, size.Width, h1);
}
break;
case DockResult.Right:
case DockResult.DocumentRight:
{
var w1 = size.Width;
var w2 = viewHost.Width;
ViewSplit.OptimalSizes(w1, ViewConstants.MinimumHostWidth, ref w1, ref w2);
bounds = new Rectangle(size.Width - w2, 0, w2, size.Height);
}
break;
case DockResult.Bottom:
case DockResult.DocumentBottom:
{
var h1 = size.Height;
var h2 = viewHost.Height;
ViewSplit.OptimalSizes(h1, ViewConstants.MinimumHostHeight, ref h1, ref h2);
bounds = new Rectangle(0, size.Height - h2, size.Width, h2);
}
break;
case DockResult.Fill:
bounds = new Rectangle(0, 0, size.Width, size.Height);
bounds.Intersect(_viewContainer.Bounds);
break;
default:
throw new ArgumentException(
"Unsupported DockResult value: {0}".UseAsFormat(dockResult),
"dockResult");
}
return RectangleToScreen(bounds);
}
#endregion
}
}