Index: Core/Common/src/Core.Common.Gui/Forms/ViewManager/ViewList.cs =================================================================== diff -u -r1e9f415cb615bf84526faeecb51f9a70498f9ffc -r9ff4b863b9bcb7f2d746520708d9f98c53456fb8 --- Core/Common/src/Core.Common.Gui/Forms/ViewManager/ViewList.cs (.../ViewList.cs) (revision 1e9f415cb615bf84526faeecb51f9a70498f9ffc) +++ Core/Common/src/Core.Common.Gui/Forms/ViewManager/ViewList.cs (.../ViewList.cs) (revision 9ff4b863b9bcb7f2d746520708d9f98c53456fb8) @@ -25,9 +25,11 @@ using System.Drawing; using System.Linq; using System.Windows.Forms; + using Core.Common.Controls.Views; using Core.Common.Gui.Properties; using Core.Common.Utils.Events; + using log4net; namespace Core.Common.Gui.Forms.ViewManager @@ -37,19 +39,13 @@ /// public class ViewList : IViewList { - public event EventHandler ActiveViewChanging; + private static readonly ILog log = LogManager.GetLogger(typeof(ViewList)); - public event EventHandler ActiveViewChanged; - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - private static readonly ILog Log = LogManager.GetLogger(typeof(ViewList)); private readonly ViewLocation? defaultLocation; private readonly IDockingManager dockingManager; private readonly IList views; private IView activeView; - //private bool clearing; // used to skip view activation when it is not necessary /// /// Initializes a new instance of the class. @@ -67,6 +63,124 @@ this.dockingManager.ViewActivated += DockingManagerViewActivated; } + /// + /// Gets or sets the action to be used to update the name of the view. + /// + public Action UpdateViewNameAction { get; set; } + + /// + /// Sets up the controller for context menus on docked views. + /// + public void EnableTabContextMenus() + { + new ViewSelectionMouseController(dockingManager, this); + } + + /// + /// Removes all views in the list except for those specified. + /// + /// The views to keep. + public void Remove(IView[] viewsToKeep) + { + foreach (var view in views.ToArray()) + { + if (!viewsToKeep.Contains(view)) + { + views.Remove(view); + } + } + } + + /// + /// Updates the image used for the docking panel hosting the view. + /// + /// The view. + /// The image. + public void SetImage(IView view, Image image) + { + dockingManager.SetImage(view, image); + } + + private bool Close(IView view, bool removeTabFromDockingManager, bool activateNextView) + { + if (ViewResolver.IsViewOpening) + { + throw new InvalidOperationException(Resources.ViewList_Close_View_is_being_closed_while_it_is_being_opened); + } + + if (!Contains(view)) + { + return false; + } + + if (activateNextView && ActiveView == view) + { + ChangeActiveViewToPrevious(); + } + + int oldIndex = views.IndexOf(view); + + var succesfullyRemovedView = views.Remove(view); + + FireCollectionChangedEvent(NotifyCollectionChangeEventArgs.CreateCollectionRemoveArgs(view, oldIndex)); + + // remove from docking manager + dockingManager.Remove(view, removeTabFromDockingManager); + + ForceViewCleanup(view); + + return succesfullyRemovedView; + } + + private void ChangeActiveViewToPrevious() + { + var activeViewIndex = views.IndexOf(activeView); + + if (Count > 1) + { + var viewIndexToActivate = activeViewIndex > 0 ? + activeViewIndex - 1 : + 1; // There is no previous, so use next instead + + ActiveView = views[viewIndexToActivate]; + } + else + { + ActiveView = null; + } + } + + private static void ForceViewCleanup(IView view) + { + view.Data = null; // reset data for view + + view.Dispose(); // get rid of view. + } + + private void DockingManagerViewActivated(object sender, ActiveViewChangeEventArgs e) + { + if (Count == 0) + { + return; + } + + ActiveView = e.View; + } + + private void DockingManagerViewBarClosing(object sender, DockTabClosingEventArgs e) + { + log.DebugFormat(Resources.ViewList_DockingManagerViewBarClosing_Closing_view_0_, e.View); + Close(e.View, false, true); + } + + #region Implementation: IViewList + + public event EventHandler ActiveViewChanging; + + public event EventHandler ActiveViewChanged; + + public event NotifyCollectionChangedEventHandler CollectionChanged; + public IView this[int index] { get @@ -81,8 +195,6 @@ } } - public Action UpdateViewNameAction { get; set; } - public IView ActiveView { get @@ -121,28 +233,6 @@ } } - public void EnableTabContextMenus() - { - new ViewSelectionMouseController(dockingManager, this); - } - - // bug in Fluent ribbon (views removed during load layout are not cleared - no events), synchronize them manually - public void SynchronizeViews(IView[] openedViews) - { - foreach (var view in views.ToArray()) - { - if (!openedViews.Contains(view)) - { - views.Remove(view); - } - } - } - - public void SetImage(IView view, Image image) - { - dockingManager.SetImage(view, image); - } - public void Dispose() { dockingManager.ViewBarClosing -= DockingManagerViewBarClosing; @@ -216,7 +306,7 @@ Resources.ViewList_Insert_No_default_location_specified_Cannot_add_a_view_without_location_parameter_); } - Insert(index, view, (ViewLocation) defaultLocation); + Insert(index, view, (ViewLocation)defaultLocation); } public bool Remove(IView view) @@ -252,11 +342,11 @@ return; } - if (activeView == view && activeView != null && ((Control) activeView).Visible) + if (activeView == view && activeView != null && ((Control)activeView).Visible) { if (activeView is Control) { - ((Control) activeView).Focus(); + ((Control)activeView).Focus(); } return; @@ -274,7 +364,7 @@ { if (!Contains(view)) { - Log.Debug(Resources.ViewList_ActivateView_Item_not_found_in_list_of_views); + log.Debug(Resources.ViewList_ActivateView_Item_not_found_in_list_of_views); return; } } @@ -288,78 +378,29 @@ FireActiveViewChangedEvent(oldView); } - private void ChangeActiveViewToPrevious() + private void Insert(int index, IView view, ViewLocation viewLocation) { - var activeViewIndex = views.IndexOf(activeView); - - if (Count > 1) + // activate view only if it is already added + if (Contains(view)) { - var viewIndexToActivate = activeViewIndex > 0 ? - activeViewIndex - 1 : - 1; // There is no previous, so use next instead - - ActiveView = views[viewIndexToActivate]; + ActiveView = view; + return; } - else - { - ActiveView = null; - } - } - private bool Close(IView view, bool removeTabFromDockingManager, bool activateNextView) - { - if (ViewResolver.IsViewOpening) + if (UpdateViewNameAction != null) { - throw new InvalidOperationException(Resources.ViewList_Close_View_is_being_closed_while_it_is_being_opened); + UpdateViewNameAction(view); } - if (!Contains(view)) - { - return false; - } + views.Insert(index, view); - if (activateNextView && ActiveView == view) - { - ChangeActiveViewToPrevious(); - } + dockingManager.Add(view, viewLocation); - int oldIndex = views.IndexOf(view); + FireCollectionChangedEvent(NotifyCollectionChangeEventArgs.CreateCollectionAddArgs(view, index)); - var succesfullyRemovedView = views.Remove(view); - - FireCollectionChangedEvent(NotifyCollectionChangeEventArgs.CreateCollectionRemoveArgs(view, oldIndex)); - - // remove from docking manager - dockingManager.Remove(view, removeTabFromDockingManager); - - ForceViewCleanup(view); - - return succesfullyRemovedView; + ActiveView = view; } - private static void ForceViewCleanup(IView view) - { - view.Data = null; // reset data for view - - view.Dispose(); // get rid of view. - } - - private void DockingManagerViewActivated(object sender, ActiveViewChangeEventArgs e) - { - if (Count == 0) - { - return; - } - - ActiveView = e.View; - } - - private void DockingManagerViewBarClosing(object sender, DockTabClosingEventArgs e) - { - Log.DebugFormat(Resources.ViewList_DockingManagerViewBarClosing_Closing_view_0_, e.View); - Close(e.View, false, true); - } - private void FireActiveViewChangingEvent(IView oldView, IView newView) { if (ActiveViewChanging != null) @@ -384,27 +425,6 @@ } } - private void Insert(int index, IView view, ViewLocation viewLocation) - { - // activate view only if it is already added - if (Contains(view)) - { - ActiveView = view; - return; - } - - if (UpdateViewNameAction != null) - { - UpdateViewNameAction(view); - } - - views.Insert(index, view); - - dockingManager.Add(view, viewLocation); - - FireCollectionChangedEvent(NotifyCollectionChangeEventArgs.CreateCollectionAddArgs(view, index)); - - ActiveView = view; - } + #endregion } } \ No newline at end of file Index: Core/Common/src/Core.Common.Gui/RingtoetsGui.cs =================================================================== diff -u -r91f159bc90faf3c55a62ae1441e9ff2bc6fd180b -r9ff4b863b9bcb7f2d746520708d9f98c53456fb8 --- Core/Common/src/Core.Common.Gui/RingtoetsGui.cs (.../RingtoetsGui.cs) (revision 91f159bc90faf3c55a62ae1441e9ff2bc6fd180b) +++ Core/Common/src/Core.Common.Gui/RingtoetsGui.cs (.../RingtoetsGui.cs) (revision 9ff4b863b9bcb7f2d746520708d9f98c53456fb8) @@ -497,7 +497,7 @@ toolWindowViewsDockingManager.UpdateLayout(); // bug in Fluent ribbon (views removed during load layout are not cleared - no events), synchronize them manually - toolWindowViews.SynchronizeViews(toolWindowViewsDockingManager.Views.ToArray()); + toolWindowViews.Remove(toolWindowViewsDockingManager.Views.ToArray()); // make sure these windows come on top if (ToolWindowViews.Contains(mainWindow.PropertyGrid)) Index: Core/Common/test/Core.Common.Gui.Test/Forms/ViewManager/ViewListTest.cs =================================================================== diff -u -r1e9f415cb615bf84526faeecb51f9a70498f9ffc -r9ff4b863b9bcb7f2d746520708d9f98c53456fb8 --- Core/Common/test/Core.Common.Gui.Test/Forms/ViewManager/ViewListTest.cs (.../ViewListTest.cs) (revision 1e9f415cb615bf84526faeecb51f9a70498f9ffc) +++ Core/Common/test/Core.Common.Gui.Test/Forms/ViewManager/ViewListTest.cs (.../ViewListTest.cs) (revision 9ff4b863b9bcb7f2d746520708d9f98c53456fb8) @@ -1268,5 +1268,299 @@ mocks.VerifyAll(); // Expect calls on IDockingManager } } + + [Test] + [TestCase(ViewLocation.Top)] + [TestCase(ViewLocation.Bottom)] + [TestCase(ViewLocation.Document)] + [TestCase(ViewLocation.Floating)] + [TestCase(ViewLocation.Left)] + [TestCase(ViewLocation.Right)] + public void IndexProperty_SetNewView_ReplaceViewWithEventsAndMakeActiveWithEvents(ViewLocation defaultLocation) + { + // Setup + using (var view1 = new ToolWindowTestControl()) + using (var view2 = new ToolWindowTestControl()) + { + var mocks = new MockRepository(); + var dockingManager = mocks.Stub(); + dockingManager.Stub(dm => dm.ViewBarClosing += null).IgnoreArguments(); + dockingManager.Stub(dm => dm.ViewActivated += null).IgnoreArguments(); + dockingManager.Stub(dm => dm.ViewBarClosing -= null).IgnoreArguments(); + dockingManager.Stub(dm => dm.ViewActivated -= null).IgnoreArguments(); + dockingManager.Stub(dm => dm.Dispose()); + dockingManager.Stub(dm => dm.Add(view1, defaultLocation)); + dockingManager.Stub(dm => dm.ActivateView(view1)); + dockingManager.Expect(dm => dm.ActivateView(null)); + dockingManager.Expect(dm => dm.Remove(view1, true)); + dockingManager.Expect(dm => dm.Add(view2, defaultLocation)); + dockingManager.Expect(dm => dm.ActivateView(view2)); + mocks.ReplayAll(); + + using (var viewList = new ViewList(dockingManager, defaultLocation)) + { + viewList.Add(view1); + + var updateViewNameActionCallCount = 0; + viewList.UpdateViewNameAction = v => updateViewNameActionCallCount++; + + var changingEventCount = 0; + viewList.ActiveViewChanging += (sender, args) => + { + Assert.AreSame(viewList, sender); + if (changingEventCount == 0) + { + Assert.IsNull(args.View); + Assert.AreSame(view1, args.OldView); + } + else if (changingEventCount == 1) + { + Assert.AreSame(view2, args.View); + Assert.IsNull(args.OldView); + } + changingEventCount++; + }; + + var changedEventCount = 0; + viewList.ActiveViewChanged += (sender, args) => + { + Assert.AreSame(viewList, sender); + if (changedEventCount == 0) + { + Assert.IsNull(args.View); + Assert.AreSame(view1, args.OldView); + } + else if (changedEventCount == 1) + { + Assert.AreSame(view2, args.View); + Assert.IsNull(args.OldView); + } + changedEventCount++; + }; + var collectionChangedEventCount = 0; + viewList.CollectionChanged += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.AreEqual(0, args.Index); + if (args.Action == NotifyCollectionChangeAction.Remove) + { + Assert.AreSame(view1, args.Item); + } + else if (args.Action == NotifyCollectionChangeAction.Add) + { + Assert.AreSame(view2, args.Item); + } + else + { + Assert.Fail("Not expecting any other type of event action!"); + } + Assert.AreEqual(-1, args.OldIndex); + Assert.IsNull(args.OldItem); + Assert.IsFalse(args.Cancel); + collectionChangedEventCount++; + }; + + // Call + viewList[0] = view2; + + // Assert + Assert.AreEqual(1, viewList.Count); + CollectionAssert.AreEqual(new[] { view2 }, viewList); + CollectionAssert.AreEqual(new[] { view2 }, viewList.AllViews); + Assert.AreSame(view2, viewList[0]); + + Assert.AreEqual(1, updateViewNameActionCallCount); + + Assert.AreEqual(2, changingEventCount); + Assert.AreEqual(2, changedEventCount); + Assert.AreEqual(2, collectionChangedEventCount); + + Assert.AreSame(view2, viewList.ActiveView); + } + + mocks.VerifyAll(); + } + } + + [Test] + public void SynchronizeViews_SomeViewsInCollection_RemoveOtherViews() + { + // Setup + using (var view1 = new ToolWindowTestControl()) + using (var view2 = new ToolWindowTestControl()) + using (var view3 = new ToolWindowTestControl()) + using (var view4 = new ToolWindowTestControl()) + { + var mocks = new MockRepository(); + var dockingManager = mocks.Stub(); + mocks.ReplayAll(); + + using (var viewList = new ViewList(dockingManager, ViewLocation.Bottom)) + { + viewList.Add(view1); + viewList.Add(view2); + viewList.Add(view3); + viewList.Add(view4); + + viewList.CollectionChanged += (sender, args) => + { + Assert.Fail("No events expected"); + }; + + var viewsToKeep = new IView[] + { + view2, + view4 + }; + + // Call + viewList.Remove(viewsToKeep); + + // Assert + CollectionAssert.AreEqual(viewsToKeep, viewList); + } + mocks.VerifyAll(); + } + } + + [Test] + public void GivenViewListWithDockingManager_WhenActiveViewChangeEventFired_ThenUpdateActiveViewWithEvents() + { + // Scenario + using (var view1 = new ToolWindowTestControl()) + using (var view2 = new ToolWindowTestControl()) + { + var mocks = new MockRepository(); + var dockingManager = mocks.Stub(); + mocks.ReplayAll(); + + using (var viewList = new ViewList(dockingManager, ViewLocation.Document)) + { + viewList.Add(view1); + viewList.Add(view2); + + var changingEventCount = 0; + viewList.ActiveViewChanging += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.AreSame(view1, args.View); + Assert.AreSame(view2, args.OldView); + changingEventCount++; + }; + var changedEventCount = 0; + viewList.ActiveViewChanged += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.AreSame(view1, args.View); + Assert.AreSame(view2, args.OldView); + changedEventCount++; + }; + + // Event + dockingManager.Raise(manager => manager.ViewActivated += null, + dockingManager, + new ActiveViewChangeEventArgs(view1)); + + // Result + Assert.AreSame(view1, viewList.ActiveView); + + Assert.AreEqual(1, changingEventCount); + Assert.AreEqual(1, changedEventCount); + } + mocks.VerifyAll(); + } + } + + [Test] + public void GivenEmptyViewListWithDockingManager_WhenActiveViewChangeEventFired_DoNothing() + { + // Scenario + using (var view1 = new ToolWindowTestControl()) + { + var mocks = new MockRepository(); + var dockingManager = mocks.Stub(); + mocks.ReplayAll(); + + using (var viewList = new ViewList(dockingManager, ViewLocation.Document)) + { + viewList.ActiveViewChanging += (sender, args) => + { + Assert.Fail("Should not react when not having views."); + }; + viewList.ActiveViewChanged += (sender, args) => + { + Assert.Fail("Should not react when not having views."); + }; + + // Event + dockingManager.Raise(manager => manager.ViewActivated += null, + dockingManager, + new ActiveViewChangeEventArgs(view1)); + + // Result + Assert.IsNull(viewList.ActiveView); + } + mocks.VerifyAll(); + } + } + + [Test] + public void GivenViewListWithDockingManager_WhenViewBarClosingEventFired_ThenRemoveItemAndUpdateActiveViewWithEvents() + { + // Scenario + using (var view1 = new ToolWindowTestControl()) + { + var mocks = new MockRepository(); + var dockingManager = mocks.Stub(); + mocks.ReplayAll(); + + using (var viewList = new ViewList(dockingManager, ViewLocation.Document)) + { + viewList.Add(view1); + + var changingEventCount = 0; + viewList.ActiveViewChanging += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.IsNull(args.View); + Assert.AreSame(view1, args.OldView); + changingEventCount++; + }; + var changedEventCount = 0; + viewList.ActiveViewChanged += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.IsNull(args.View); + Assert.AreSame(view1, args.OldView); + changedEventCount++; + }; + var collectionChangedEventCount = 0; + viewList.CollectionChanged += (sender, args) => + { + Assert.AreSame(viewList, sender); + Assert.AreEqual(NotifyCollectionChangeAction.Remove, args.Action); + Assert.AreEqual(0, args.Index); + Assert.AreSame(view1, args.Item); + Assert.AreEqual(-1, args.OldIndex); + Assert.IsNull(args.OldItem); + Assert.IsFalse(args.Cancel); + collectionChangedEventCount++; + }; + + // Event + dockingManager.Raise(manager => manager.ViewBarClosing += null, + dockingManager, + new DockTabClosingEventArgs { View = view1 }); + + // Result + Assert.IsNull(viewList.ActiveView); + + Assert.AreEqual(1, changingEventCount); + Assert.AreEqual(1, changedEventCount); + Assert.AreEqual(1, collectionChangedEventCount); + } + mocks.VerifyAll(); + } + } } } \ No newline at end of file