Index: Application/Application.Ringtoets/App.xaml.cs =================================================================== diff -u -re071372c05b65a79f5155b0fca6f37f6fd3ccc86 -reee6c7815d1e418eac38c1c552fb279c0887ef55 --- Application/Application.Ringtoets/App.xaml.cs (.../App.xaml.cs) (revision e071372c05b65a79f5155b0fca6f37f6fd3ccc86) +++ Application/Application.Ringtoets/App.xaml.cs (.../App.xaml.cs) (revision eee6c7815d1e418eac38c1c552fb279c0887ef55) @@ -12,6 +12,8 @@ using Core.Common.Base; using Core.Common.Base.Workflow; using Core.Common.Controls.Swf; +using Core.Common.Gui; +using Core.Common.Gui.Properties; using Core.Common.Gui.Forms.MainWindow; using Core.Plugins.CommonTools; using Core.Plugins.CommonTools.Gui; @@ -51,14 +53,14 @@ static App() { - DeltaShellApplication.SetLanguageAndRegionalSettions(Ringtoets.Properties.Settings.Default); + DeltaShellApplication.SetLanguageAndRegionalSettions(Settings.Default); - log.Info(Ringtoets.Properties.Resources.App_App_Starting_Delta_Shell____); + log.Info(Core.Common.Gui.Properties.Resources.App_App_Starting_Delta_Shell____); } - public static void RunDeltaShell(IMainWindow mainWindow) + public static void RunDeltaShell() { - log.Info(Ringtoets.Properties.Resources.App_RunDeltaShell_Starting_Delta_Shell_Gui____); + log.Info(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Starting_Delta_Shell_Gui____); var loaderDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var startupDirectory = Directory.GetCurrentDirectory(); @@ -67,27 +69,7 @@ Environment.CurrentDirectory = loaderDirectory; } - gui = new DeltaShellGui - { - MainWindow = mainWindow, - Plugins = - { - new ProjectExplorerGuiPlugin(), - new CommonToolsGuiPlugin(), - new SharpMapGisGuiPlugin(), - new WtiGuiPlugin() - }, - Application = - { - Plugins = - { - new CommonToolsApplicationPlugin(), - new SharpMapGisApplicationPlugin(), - new WtiApplicationPlugin() - } - } - }; - + //gui.Application.ProjectRepositoryFactory.SpeedUpSessionCreationUsingParallelThread = true; //gui.Application.ProjectRepositoryFactory.SpeedUpConfigurationCreationUsingCaching = true; //gui.Application.ProjectRepositoryFactory.ConfigurationCacheDirectory = gui.Application.GetUserSettingsDirectoryPath(); @@ -115,7 +97,7 @@ } else { - log.ErrorFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_Specified_project___0___was_not_found_, projectFilePath); + log.ErrorFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Specified_project___0___was_not_found_, projectFilePath); } } @@ -125,7 +107,7 @@ { if (gui.Application.Project == null) { - log.ErrorFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_No_project_found__load_project_first); + log.ErrorFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_No_project_found__load_project_first); return; } @@ -137,19 +119,19 @@ if (activity == null) { log.ErrorFormat( - Ringtoets.Properties.Resources.App_RunDeltaShell_Activity___0___not_found_in_project__Typo__or_did_you_forget_to_load_a_project_, + Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Activity___0___not_found_in_project__Typo__or_did_you_forget_to_load_a_project_, runActivity); return; } - log.InfoFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_Starting_activity___0__, runActivity); + log.InfoFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Starting_activity___0__, runActivity); gui.Application.RunActivity(activity); - log.InfoFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_Activity___0___ended_with_status__1_, runActivity, activity.Status); + log.InfoFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Activity___0___ended_with_status__1_, runActivity, activity.Status); - log.InfoFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_Saving_project___0__, gui.Application.Project.Name); + log.InfoFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Saving_project___0__, gui.Application.Project.Name); gui.Application.SaveProject(); // Necessary for persisting the output of the activity run (e.g. netCDF files). - log.InfoFormat(Ringtoets.Properties.Resources.App_RunDeltaShell_Saved_project___0__, gui.Application.Project.Name); + log.InfoFormat(Core.Common.Gui.Properties.Resources.App_RunDeltaShell_Saved_project___0__, gui.Application.Project.Name); } }; @@ -177,7 +159,35 @@ Resources.Add(SystemParameters.MenuPopupAnimationKey, PopupAnimation.None); ParseArguments(e.Args); - StartupUri = new Uri("Forms/MainWindow/MainWindow.xaml", UriKind.Relative); + + + + gui = new DeltaShellGui + { + Plugins = + { + new ProjectExplorerGuiPlugin(), + new CommonToolsGuiPlugin(), + new SharpMapGisGuiPlugin(), + new WtiGuiPlugin() + }, + Application = + { + Plugins = + { + new CommonToolsApplicationPlugin(), + new SharpMapGisApplicationPlugin(), + new WtiApplicationPlugin() + } + } + }; + + var mainWindow = new MainWindow(gui); + gui.MainWindow = mainWindow; + + RunDeltaShell(); + + mainWindow.Show(); } private bool ShutdownIfNotFirstInstance() @@ -190,7 +200,7 @@ { if (!AcquireSingleInstancePerUserMutex()) { - MessageBox.Show(Ringtoets.Properties.Resources.App_ShutdownIfNotFirstInstance_Cannot_start_multiple_instances_of_DeltaShell__Please_close_the_other_instance_first_); + MessageBox.Show(Core.Common.Gui.Properties.Resources.App_ShutdownIfNotFirstInstance_Cannot_start_multiple_instances_of_DeltaShell__Please_close_the_other_instance_first_); Shutdown(1); return true; //done here } @@ -253,7 +263,7 @@ if (exception == null) { - exception = new Exception(Ringtoets.Properties.Resources.App_AppDomain_UnhandledException_Unknown_exception_); + exception = new Exception(Core.Common.Gui.Properties.Resources.App_AppDomain_UnhandledException_Unknown_exception_); } HandleExceptionOnMainThread(exception, e.IsTerminating); @@ -309,7 +319,7 @@ previousExceptionsCount++; var s = previousExceptionsText; previousExceptionsText = - string.Format(Ringtoets.Properties.Resources.App_HandleException_, + string.Format(Core.Common.Gui.Properties.Resources.App_HandleException_, previousExceptionsCount, dialog.ExceptionText, s); @@ -357,13 +367,13 @@ } }, { - "r|run-activity=", Ringtoets.Properties.Resources.App_ParseArguments_Run_activity_or_model_available_in_the_project_, v => runActivity = v + "r|run-activity=", Core.Common.Gui.Properties.Resources.App_ParseArguments_Run_activity_or_model_available_in_the_project_, v => runActivity = v }, { - "f|run-file=", Ringtoets.Properties.Resources.App_ParseArguments_Run_script_from_file, v => runScriptFilePath = Path.GetFullPath(v) + "f|run-file=", Core.Common.Gui.Properties.Resources.App_ParseArguments_Run_script_from_file, v => runScriptFilePath = Path.GetFullPath(v) }, { - "c|run-command=", Ringtoets.Properties.Resources.App_ParseArguments_Run_specified_command__Python__, v => runCommand = v + "c|run-command=", Core.Common.Gui.Properties.Resources.App_ParseArguments_Run_specified_command__Python__, v => runCommand = v }, { "p|project=", delegate(string projectPath) { projectFilePath = projectPath; } Index: Application/Application.Ringtoets/Application.Ringtoets.csproj =================================================================== diff -u -re071372c05b65a79f5155b0fca6f37f6fd3ccc86 -reee6c7815d1e418eac38c1c552fb279c0887ef55 --- Application/Application.Ringtoets/Application.Ringtoets.csproj (.../Application.Ringtoets.csproj) (revision e071372c05b65a79f5155b0fca6f37f6fd3ccc86) +++ Application/Application.Ringtoets/Application.Ringtoets.csproj (.../Application.Ringtoets.csproj) (revision eee6c7815d1e418eac38c1c552fb279c0887ef55) @@ -79,9 +79,6 @@ ..\..\lib\NDesk.Options.dll - - ..\..\packages\PostSharp.2.1.7.28\lib\net20\PostSharp.dll - @@ -121,111 +118,7 @@ App.xaml - - - - - - MainWindow.xaml - - - UserControl - - - GeneralOptionsControl.cs - - - - Form - - - ProgressDialog.cs - - - Component - - - - SplashScreen.xaml - - - - - - - - - Form - - - HelpAboutBox.cs - - - UserControl - - - MessageWindow.cs - - - MessageWindowData.xsd - Component - - - MessageWindowData.xsd - True - True - - - - Form - - - SelectItemDialog.cs - - - Form - - - OptionsDialog.cs - - - UserControl - - - RichTextView.cs - - - Form - - - SelectViewDialog.cs - - - UserControl - - - ViewSelectionContextMenuController.cs - - - - - - True - True - Resources.resx - - - True - True - Resources.nl-NL.resx - - - Settings.settings - True - True - - @@ -240,12 +133,8 @@ {9A2D67E6-26AC-4D17-B11A-2B4372F2F572} Core.Common.Controls - - {08205620-d756-4533-922c-d6a4c0038535} - Core.Common.Gui.Swf - - {30E4C2AE-719E-4D70-9FA9-668A9767FBFA} + {30e4c2ae-719e-4d70-9fa9-668a9767fbfa} Core.Common.Gui @@ -260,30 +149,10 @@ {ffb69466-79de-466a-ada7-5c47c5c5ca3a} Core.GIS.GeoAPI - - {8a3636fb-31b4-4f4b-9165-f718f87a92df} - Core.GIS.NetTopologySuite.Extensions - {5770daa9-84e5-4770-af43-f6b815894368} Core.GIS.NetTopologySuite - - {a3c9df74-978a-44b1-b55d-a72ac4221e3a} - Core.GIS.SharpMap.Api - - - {a4140c12-53f5-438c-8d24-9e48c504fecf} - Core.GIS.SharpMap.Extensions - - - {dd1cc1db-4bf9-4c88-a100-733d84795f3a} - Core.GIS.SharpMap.UI - - - {c83777fc-aabb-47d9-911f-d76255d4d541} - Core.GIS.SharpMap - {c90b77da-e421-43cc-b82e-529651bc21ac} Core.Common.Version @@ -309,248 +178,16 @@ {696DAAEE-D1D8-42D0-86AC-471B970FB17E} Core.Plugins.SharpMapGis - - {d64e4f0e-e341-496f-82b2-941ad202b4e3} - Ringtoets.Piping.Calculation - {1D3D58B6-EF7E-401E-92A0-104067D222EE} Ringtoets.Piping.Plugin - - {10b8d63d-87e8-46df-aca9-a8cf22ee8fb5} - Ringtoets.Piping.Service - - - {ce994cc9-6f6a-48ac-b4be-02c30a21f4db} - Ringtoets.Piping.Data - - - {7cd038e1-e111-4969-aced-22c5bd2974e1} - Ringtoets.Piping.Forms - - - {35b87b7a-7f50-4139-b563-589ee522b1ed} - Ringtoets.Piping.IO - - - HelpAboutBox.cs - Designer - - - MessageWindow.cs - - - MessageWindow.cs - Designer - - - GeneralOptionsControl.cs - - - GeneralOptionsControl.cs - Designer - - - OptionsDialog.cs - - - ProgressDialog.cs - - - SelectItemDialog.cs - - - SelectItemDialog.cs - Designer - - - OptionsDialog.cs - Designer - - - RichTextView.cs - Designer - - - SelectViewDialog.cs - - - SelectViewDialog.cs - Designer - - - PublicResXFileCodeGenerator - Designer - Resources.Designer.cs - - - + - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - MessageWindowData.xsd - - - MSDataSetGenerator - MessageWindowData.Designer.cs - Designer - - - MessageWindowData.xsd - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HelpAboutBox.cs - Designer - - - ProgressDialog.cs - - - ViewSelectionContextMenuController.cs - Designer - - - Designer - PublicResXFileCodeGenerator - Resources.nl-NL.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -585,18 +222,9 @@ MSBuild:Compile Designer - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - todo, remove the duplication + + string mainWindowTitle = Application.Settings["mainWindowTitle"]; + + string projectTitle = ""; + if (Application.Project != null) + { + projectTitle = Application.Project.Name; + } + + // TODO: this must be moved to MainWindow which should listen to project changes + if (mainWindow != null) + { + mainWindow.Title = projectTitle + " - " + mainWindowTitle; + } + } + + private void ActiveViewChanging(object sender, ActiveViewChangeEventArgs e) + { + if (e.View == null || mainWindow == null || mainWindow.IsWindowDisposed) + { + return; + } + + if (mainWindow.ProjectExplorer != null) + { + SetToolTipForView(e.View); + } + } + + // TODO: incapsulate any knowledge of the plugin XML inside plugin configurator, the rest of the system should not know about it! + private void InitToolWindows() + { + log.Info(Resources.DeltaShellGui_InitToolWindows_Creating_document_window_manager____); + + var allowedDocumentWindowLocations = new[] + { + ViewLocation.Document, + ViewLocation.Floating + }; + + var documentDockingManager = new AvalonDockDockingManager(mainWindow.DockingManager, allowedDocumentWindowLocations); + + var documentViewManager = new ViewList(documentDockingManager, ViewLocation.Document) + { + IgnoreActivation = true, + UpdateViewNameAction = v => UpdateViewName(v), + Gui = this + }; + + documentViewManager.EnableTabContextMenus(); + + documentViewManager.ActiveViewChanging += ActiveViewChanging; + documentViewManager.ActiveViewChanged += OnActiveViewChanged; + documentViewManager.CollectionChanged += DocumentViewsCollectionChanged; + documentViewManager.ChildViewChanged += DocumentViewManagerOnChildViewChanged; + + documentViews = documentViewManager; + + DocumentViewsResolver = new ViewResolver(documentViews, Plugins.SelectMany(p => p.GetViewInfoObjects())); + + var allowedToolWindowLocations = new[] + { + ViewLocation.Left, + ViewLocation.Right, + ViewLocation.Top, + ViewLocation.Bottom, + ViewLocation.Floating + }; + + toolWindowViewsDockingManager = new AvalonDockDockingManager(mainWindow.DockingManager, allowedToolWindowLocations); + + toolWindowViews = new ViewList(toolWindowViewsDockingManager, ViewLocation.Left) + { + IgnoreActivation = true, + Gui = this + }; + + toolWindowViews.CollectionChanged += ToolWindowViewsOnCollectionChanged; + + log.Info(Resources.DeltaShellGui_InitToolWindows_Creating_tool_window_manager____); + + mainWindow.InitializeToolWindows(); + + log.Debug(Resources.DeltaShellGui_InitToolWindows_Finished_InitToolWindows); + + mainWindow.SubscribeToGui(); + } + + private void OnActiveViewChanged(object sender, ActiveViewChangeEventArgs e) + { + if (e.View == null || mainWindow == null || mainWindow.IsWindowDisposed) + { + return; + } + + mainWindow.ValidateItems(); + } + + private void DocumentViewManagerOnChildViewChanged(object sender, NotifyCollectionChangingEventArgs notifyCollectionChangingEventArgs) + { + if (isExiting) + { + return; + } + + mainWindow.ValidateItems(); + } + + private static string GetViewName(IView view) + { + var name = view.ViewInfo != null ? view.ViewInfo.GetViewName(view, view.Data) : null; + if (name != null) + { + return name; + } + + var enumerable = view.Data as IEnumerable; + var data = enumerable == null ? view.Data : enumerable.Cast().FirstOrDefault(); + + var nameableItem = data as INameable; + return nameableItem != null ? nameableItem.Name : (view.Data == null ? "" : view.Data.ToString()); + } + + private void UpdateViewName(IView view) + { + view.Text = GetViewName(view); + SetToolTipForView(view); + } + + private void ToolWindowViewsOnCollectionChanged(object sender, NotifyCollectionChangingEventArgs notifyCollectionChangingEventArgs) + { + if (isExiting) + { + return; + } + mainWindow.ValidateItems(); + } + + private void DocumentViewsCollectionChanged(object sender, NotifyCollectionChangingEventArgs e) + { + if (isExiting || documentViews.Count != 0) + { + return; + } + + // if no new active view is set update toolbars + mainWindow.ValidateItems(); + } + + private void InitializeMenusAndToolbars() + { + log.Info(Resources.DeltaShellGui_InitializeMenusAndToolbars_Setting_up_menus_and_toolbars____); + mainWindow.SuspendLayout(); + + // Validate once when loading is completed + mainWindow.ValidateItems(); + + mainWindow.ResumeLayout(); + log.Info(Resources.DeltaShellGui_InitializeMenusAndToolbars_Menus_and_toolbars_are_ready_); + } + + private void ActivatePlugins() + { + var problematicPlugins = new List(); + + mainWindow.SuspendLayout(); + foreach (var plugin in Plugins) + { + try + { + plugin.Activate(); + } + catch (Exception) + { + problematicPlugins.Add(plugin); + } + } + + // remove problematic plugins + for (int i = 0; i < problematicPlugins.Count; i++) + { + try + { + problematicPlugins[i].Deactivate(); + } + catch (Exception exception) + { + log.Error(Resources.DeltaShellGui_ActivatePlugins_Exception_during_plugin_gui_deactivation, exception); + } + + try + { + problematicPlugins[i].Deactivate(); + } + catch (Exception exception) + { + log.Error(Resources.DeltaShellGui_ActivatePlugins_Exception_during_plugin_deactivation, exception); + } + + Plugins.Remove(problematicPlugins[i]); + } + + mainWindow.ResumeLayout(); + } + + private void CopyDefaultViewsFromUserSettings() + { + StringCollection defaultViews; + StringCollection defaultViewDataTypes; + if (Application.UserSettings["defaultViews"] != null) + { + defaultViews = (StringCollection) Application.UserSettings["defaultViews"]; + defaultViewDataTypes = (StringCollection) Application.UserSettings["defaultViewDataTypes"]; + } + else + { + return; + } + + for (int i = 0; i < defaultViews.Count; i++) + { + string skey = defaultViewDataTypes[i]; + string sview = defaultViews[i]; + if (AssemblyUtils.GetTypeByName(skey) != null) + { + DocumentViewsResolver.DefaultViewTypes.Add(AssemblyUtils.GetTypeByName(skey), AssemblyUtils.GetTypeByName(sview)); + } + } + } + + private void CopyDefaultViewsToUserSettings() + { + StringCollection defaultViews = new StringCollection(); + StringCollection defaultViewDataTypes = new StringCollection(); + + foreach (Type objectType in DocumentViewsResolver.DefaultViewTypes.Keys) + { + if (DocumentViewsResolver.DefaultViewTypes[objectType] == null) + { + continue; + } + defaultViews.Add(DocumentViewsResolver.DefaultViewTypes[objectType].ToString()); + defaultViewDataTypes.Add(objectType.ToString()); + } + + Application.UserSettings["defaultViews"] = defaultViews; + Application.UserSettings["defaultViewDataTypes"] = defaultViewDataTypes; + } + + ~DeltaShellGui() + { + Dispose(false); + } + } +} \ No newline at end of file Index: Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml =================================================================== diff -u --- Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml (revision 0) +++ Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml (revision eee6c7815d1e418eac38c1c552fb279c0887ef55) @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml.cs =================================================================== diff -u --- Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml.cs (revision 0) +++ Core/Common/src/Core.Common.Gui/Forms/MainWindow/MainWindow.xaml.cs (revision eee6c7815d1e418eac38c1c552fb279c0887ef55) @@ -0,0 +1,1316 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Configuration; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Interop; +using Core.Common.Base; +using Core.Common.Controls; +using Core.Common.Controls.Swf; +using Core.Common.Gui.Forms.MessageWindow; +using Core.Common.Gui.Forms.OptionsDialog; +using Core.Common.Utils; +using Core.Common.Utils.Aop; +using Core.Common.Utils.Collections; +using Core.Common.Utils.Interop; +using Fluent; +using log4net; +using Microsoft.Win32; +using Xceed.Wpf.AvalonDock; +using Xceed.Wpf.AvalonDock.Controls; +using Xceed.Wpf.AvalonDock.Layout; +using Xceed.Wpf.AvalonDock.Layout.Serialization; +using Xceed.Wpf.AvalonDock.Themes; +using WindowsFormApplication = System.Windows.Forms.Application; +using Button = Fluent.Button; +using Cursors = System.Windows.Input.Cursors; +using IWin32Window = System.Windows.Forms.IWin32Window; + +namespace Core.Common.Gui.Forms.MainWindow +{ + /// + /// Main windows of Delta Shell + /// + public partial class MainWindow : IMainWindow, IDisposable, IWin32Window, ISynchronizeInvoke + { + private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow)); + + private static Form synchronizationForm; + + /// + /// Remember last active contextual tab per view. + /// + private readonly IDictionary lastActiveContextTabNamePerViewType = new Dictionary(); + + private bool resetDefaultLayout; + + private MessageWindow.MessageWindow messageWindow; + private PropertyGridView.PropertyGridView propertyGrid; + private WindowInteropHelper windowInteropHelper; + + private IEnumerable ribbonCommandHandlers; + + /// + /// This is used when user selects non-contextual tab explicitly. Then we won't activate contextual tab on the next view activation. + /// + private bool activateContextualTab; + + /// + /// Used when contextual tab was activated and we switch back to view which does not support contextual tabs. + /// + private string lastNonContextualTab; + + public MainWindow() + { + InitializeComponent(); + + windowInteropHelper = new WindowInteropHelper(this); + ModalHelper.MainWindow = this; + + InitializeInvokeRequired(); + + log.Info(Properties.Resources.MainWindow_MainWindow_Main_window_created_); + } + + public MainWindow(DeltaShellGui gui) + { + DeltaShellApplication.SetLanguageAndRegionalSettions(Properties.Settings.Default); + + Gui = gui; + + InitializeComponent(); + + windowInteropHelper = new WindowInteropHelper(this); + ModalHelper.MainWindow = this; + + InitializeInvokeRequired(); + + log.Info(Properties.Resources.MainWindow_MainWindow_Main_window_created_); + } + + public DeltaShellGui Gui { get; set; } + + public bool IsWindowDisposed { get; private set; } + + public bool Enabled + { + get + { + return IsEnabled; + } + set + { + IsEnabled = value; + NativeWin32.EnableWindow(new HandleRef(this, windowInteropHelper.Handle), value); // prevents resize etc + } + } + + public DockingManager DockingManager + { + get + { + return dockingManager; + } + } + + public IMessageWindow MessageWindow + { + get + { + return messageWindow; + } + } + + public bool Visible + { + get + { + return IsVisible; + } + set + { + if (value) + { + if (IsVisible) + { + return; + } + + Show(); + } + else + { + if (!IsVisible) + { + return; + } + + Hide(); + } + } + } + + public string StatusBarMessage + { + get + { + return StatusMessageTextBlock.Text; + } + set + { + StatusMessageTextBlock.Text = value; + } + } + + public IProjectExplorer ProjectExplorer + { + get + { + return Enumerable.OfType(Gui.ToolWindowViews).FirstOrDefault(); + } + } + + public IPropertyGrid PropertyGrid + { + get + { + return propertyGrid; + } + } + + public bool InvokeRequired + { + get + { + return !Dispatcher.CheckAccess(); + } + } + + public IntPtr Handle + { + get + { + return windowInteropHelper.Handle; + } + } + + public void SubscribeToGui() + { + if (Gui != null) + { + Gui.ToolWindowViews.CollectionChanged += ToolWindowViews_CollectionChanged; + Gui.DocumentViews.ActiveViewChanged += DocumentViewsOnActiveViewChanged; + Gui.DocumentViews.ActiveViewChanging += DocumentViewsOnActiveViewChanging; + } + } + + public void UnsubscribeFromGui() + { + if (Gui != null) + { + Gui.ToolWindowViews.CollectionChanged -= ToolWindowViews_CollectionChanged; + Gui.DocumentViews.ActiveViewChanged -= DocumentViewsOnActiveViewChanged; + Gui.DocumentViews.ActiveViewChanging -= DocumentViewsOnActiveViewChanging; + } + } + + public void RestoreLayout() + { + if (Assembly.GetEntryAssembly() == null) // API, not a real exe + { + return; // make sure we don't mess layout + } + + OnLoadLayout("normal"); + RestoreWindowAppearance(); + } + + public void SaveLayout() + { + if (Properties.Settings.Default.autosaveWindowLayout) + { + SaveWindowAppearance(); + OnSaveLayout("normal"); + } + } + + public void InitializeToolWindows() + { + InitMessagesWindowOrActivate(); + InitPropertiesWindowAndActivate(); + } + + public void SuspendLayout() {} + + public void ResumeLayout() {} + + public void InitPropertiesWindowAndActivate() + { + if ((propertyGrid == null) || (propertyGrid.IsDisposed)) + { + propertyGrid = new PropertyGridView.PropertyGridView(Gui); + } + + propertyGrid.Text = Properties.Resources.Properties; + propertyGrid.Data = propertyGrid.GetObjectProperties(Gui.Selection); + + Gui.ToolWindowViews.Add(propertyGrid, ViewLocation.Right | ViewLocation.Bottom); + + Gui.ToolWindowViews.ActiveView = null; + Gui.ToolWindowViews.ActiveView = Gui.MainWindow.PropertyGrid; + } + + public void ShowStartPage(bool checkUseSettings = true) + { + if (!checkUseSettings || Convert.ToBoolean((object) Gui.Application.UserSettings["showStartPage"], CultureInfo.InvariantCulture)) + { + log.Info(Properties.Resources.MainWindow_ShowStartPage_Adding_welcome_page____); + OpenStartPage(); + } + } + + public void ClearDocumentTabs() + { + foreach (var contentToClose in dockingManager.Layout.Descendents().OfType().Where(d => (d.Parent is LayoutDocumentPane || d.Parent is LayoutDocumentFloatingWindow)).ToArray()) + { + if (!contentToClose.CanClose) + { + continue; + } + + if (contentToClose is LayoutDocument) + { + CloseContent(contentToClose as LayoutDocument); + } + else if (contentToClose is LayoutAnchorable) + { + CloseContent(contentToClose as LayoutAnchorable); + } + } + + foreach (var child in LayoutDocumentPaneGroup.Children.OfType()) + { + child.Children.Clear(); + } + + while (LayoutDocumentPaneGroup.Children.Count != 1) + { + LayoutDocumentPaneGroup.Children.RemoveAt(0); + } + } + + public void Dispose() + { + Close(); + Close(); + + if (IsWindowDisposed) + { + return; + } + + // TODO: add dispose code + + IsWindowDisposed = true; + + if (Equals(InvokeRequiredInfo.SynchronizeObject, this)) + { + InvokeRequiredInfo.SynchronizeObject = null; + } + + if (dockingManager.AutoHideWindow != null) + { + var m = typeof(LayoutAutoHideWindowControl).GetField("_manager", BindingFlags.Instance | BindingFlags.NonPublic); + m.SetValue(dockingManager.AutoHideWindow, null); + dockingManager.AutoHideWindow.Dispose(); + } + + Content = null; + + if (Ribbon != null) + { + foreach (var tab in Ribbon.Tabs) + { + foreach (var group in tab.Groups) + { + group.Items.Clear(); + } + tab.Groups.Clear(); + } + Ribbon.Tabs.Clear(); + Ribbon = null; + } + + dockingManager = null; + + if (propertyGrid != null) + { + propertyGrid.Dispose(); + propertyGrid = null; + } + + if (messageWindow != null) + { + messageWindow.OnError -= MessageWindowOnError; + messageWindow.Dispose(); + messageWindow = null; + } + + // I pulled this code from some internet sources combined with the reflector to remove a well-known leak + var handlers = typeof(SystemEvents).GetField("_handlers", BindingFlags.Static | BindingFlags.NonPublic) + .GetValue(null); + var upcHandler = typeof(SystemEvents).GetField("OnUserPreferenceChangedEvent", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); + var eventLockObject = typeof(SystemEvents).GetField("eventLockObject", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); + + lock (eventLockObject) + { + var upcHandlerList = (IList) ((IDictionary) handlers)[upcHandler]; + for (int i = upcHandlerList.Count - 1; i >= 0; i--) + { + var target = (Delegate) upcHandlerList[i].GetType().GetField("_delegate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(upcHandlerList[i]); + upcHandlerList.RemoveAt(i); + } + } + + ribbonCommandHandlers = null; + windowInteropHelper = null; + + Gui = null; + + // Dispatcher.InvokeShutdown(); + //System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown(); + } + + public void ValidateItems() + { + if (Gui == null) + { + return; + } + + ValidateMainWindowRibbonItems(); + + if (ribbonCommandHandlers == null) + { + return; + } + + // activate all context-specific groups (ValidateItems implementations should activate them) + foreach (var tabGroup in Ribbon.ContextualGroups) + { + var showGroup = false; + foreach (var tabItem in tabGroup.Items) + { + var tabItemVisible = ribbonCommandHandlers.Any(h => h.IsContextualTabVisible(tabGroup.Name, tabItem.Name)); + tabItem.Visibility = tabItemVisible ? Visibility.Visible : Visibility.Collapsed; + + if (tabItemVisible && !showGroup) + { + showGroup = true; + } + } + + tabGroup.Visibility = showGroup ? Visibility.Visible : Visibility.Collapsed; + } + + foreach (var ribbonCommandHandler in ribbonCommandHandlers) + { + ribbonCommandHandler.ValidateItems(); + } + + foreach (var ribbonGroupBox in Ribbon.Tabs.SelectMany(tab => tab.Groups)) + { + // Colapse all groups without visible items + ribbonGroupBox.Visibility = ribbonGroupBox.Items.OfType().All(e => e.Visibility == Visibility.Collapsed || e is Separator) + ? Visibility.Collapsed + : Visibility.Visible; + } + } + + public void SetWaitCursorOn() + { + Mouse.OverrideCursor = Cursors.Wait; + } + + public void SetWaitCursorOff() + { + Mouse.OverrideCursor = null; + } + + public IAsyncResult BeginInvoke(Delegate method, object[] args) + { + Dispatcher.BeginInvoke(method, args); + return null; + } + + public object EndInvoke(IAsyncResult result) + { + throw new NotImplementedException(); + } + + public object Invoke(Delegate method, object[] args) + { + return Dispatcher.Invoke(method, args); + } + + private void ToolWindowViews_CollectionChanged(object sender, NotifyCollectionChangingEventArgs e) + { + if (e.Action != NotifyCollectionChangeAction.Remove) + { + return; + } + + if (e.Item == propertyGrid) + { + propertyGrid = null; + } + + if (e.Item == MessageWindow) + { + messageWindow = null; + } + } + + private void DocumentViewsOnActiveViewChanging(object sender, ActiveViewChangeEventArgs e) + { + if (Ribbon.SelectedTabItem != null && !Ribbon.SelectedTabItem.IsContextual) + { + lastNonContextualTab = Ribbon.SelectedTabItem.Header.ToString(); + } + + // remember active contextual tab per view type, when view is activated back - activate contextual item + if (Ribbon.SelectedTabItem != null && e.OldView != null) + { + if (Ribbon.SelectedTabItem.IsContextual) + { + lastActiveContextTabNamePerViewType[e.OldView.GetType()] = Ribbon.SelectedTabItem.Header.ToString(); + activateContextualTab = true; + } + else + { + // user has clicked on non-contextual tab before switching active view + if (lastActiveContextTabNamePerViewType.ContainsKey(e.OldView.GetType())) + { + activateContextualTab = false; + } + } + } + else + { + activateContextualTab = true; + } + } + + private void DocumentViewsOnActiveViewChanged(object sender, ActiveViewChangeEventArgs e) + { + // activate contextual tab which was active for this view type + if (activateContextualTab && Ribbon.SelectedTabItem != null && e.View != null && + Ribbon.Tabs.Any(t => t.IsContextual && t.Visibility == Visibility.Visible)) + { + string lastActiveTabForActiveView; + if (lastActiveContextTabNamePerViewType.TryGetValue(e.View.GetType(), out lastActiveTabForActiveView)) + { + var tab = Ribbon.Tabs.First(t => t.Header.ToString() == lastActiveTabForActiveView); + if (tab.IsVisible) + { + Ribbon.SelectedTabItem = tab; + } + } + else // activate first contextual group tab + { + var tab = Ribbon.Tabs.FirstOrDefault(t => t.IsContextual && t.Visibility == Visibility.Visible); + Ribbon.SelectedTabItem = tab; + } + } + else if (!string.IsNullOrEmpty(lastNonContextualTab)) // reactivate last non-contextual tab + { + var tab = Ribbon.Tabs.First(t => t.Header.ToString() == lastNonContextualTab); + Ribbon.SelectedTabItem = tab; + } + } + + private void InitializeInvokeRequired() + { + if (Assembly.GetEntryAssembly() != null) // HACK: when assembly is non-empty - we run from real exe (not test) + { + if (InvokeRequiredInfo.SynchronizeObject == null) + { + InvokeRequiredInfo.SynchronizeObject = this; + InvokeRequiredInfo.WaitMethod = System.Windows.Forms.Application.DoEvents; + } + + return; // uses MainWindow + } + + // use static form for synchronization + + if (synchronizationForm == null) + { + synchronizationForm = new Form + { + ShowInTaskbar = false, WindowState = FormWindowState.Minimized + }; + var handle = synchronizationForm.Handle; //force get handle + synchronizationForm.Show(); + } + + if (InvokeRequiredInfo.SynchronizeObject == null) + { + InvokeRequiredInfo.SynchronizeObject = synchronizationForm; + InvokeRequiredInfo.WaitMethod = System.Windows.Forms.Application.DoEvents; + } + } + + private void AddRecentlyOpenedProjectsToFileMenu() + { + var mruList = Properties.Settings.Default["mruList"] as StringCollection; + + foreach (var recent in mruList) + { + AddNewMruItem(recent, false); + } + } + + private void AddNewMruItem(string path, bool putOnTop = true) + { + var newItem = new TabItem + { + Header = path + }; + newItem.MouseDoubleClick += (sender, args) => + { + try + { + Gui.CommandHandler.TryOpenExistingWTIProject(path); + RecentProjectsTabControl.Items.Remove(newItem); + RecentProjectsTabControl.Items.Insert(1, newItem); + } + catch (Exception) + { + //remove item from the list if it cannot be retrieved from file + RecentProjectsTabControl.Items.Remove(newItem); + log.WarnFormat("{0} {1}", Properties.Resources.MainWindow_AddNewMruItem_Can_t_open_project, path); + } + finally + { + CommitMruToSettings(); + ValidateItems(); + Menu.IsOpen = false; + } + }; + if (RecentProjectsTabControl.Items.OfType().Any(i => Equals(i.Header, path))) + { + return; + } + + if (putOnTop) + { + RecentProjectsTabControl.Items.Insert(1, newItem); + } + else + { + RecentProjectsTabControl.Items.Add(newItem); + } + } + + private void CommitMruToSettings() + { + var mruList = (StringCollection) Properties.Settings.Default["mruList"]; + + mruList.Clear(); + + foreach (TabItem item in RecentProjectsTabControl.Items) + { + if (item is SeparatorTabItem) //header + { + continue; + } + + mruList.Add(item.Header.ToString()); + } + } + + private void OnLayoutRootPropertyChanged(object sender, PropertyChangedEventArgs e) {} + + private void ValidateMainWindowRibbonItems() + { + if (Gui.ToolWindowViews != null) + { + ButtonShowMessages.IsChecked = Gui.ToolWindowViews.Contains(MessageWindow); + ButtonShowProperties.IsChecked = Gui.ToolWindowViews.Contains(PropertyGrid); + } + + var appHasProject = (Gui.Application.Project != null); + var isActivityRunning = Gui.Application.IsActivityRunning(); + + // filemenu items dependent on existence of project and if processes are running + /* + ButtonMenuFileNewProject.IsEnabled = !isActivityRunning; + ButtonMenuFileOpenProject.IsEnabled = !isActivityRunning; + ButtonMenuFileSaveProject.IsEnabled = appHasProject && !isActivityRunning; + ButtonMenuFileSaveProjectAs.IsEnabled = appHasProject && !isActivityRunning; + ButtonMenuFileCloseProject.IsEnabled = appHasProject && !isActivityRunning; + * */ + + // TODO: remove when implemented + ButtonMenuFileNewProject.IsEnabled = false; + ButtonMenuFileOpenProject.IsEnabled = false; + ButtonMenuFileSaveProject.IsEnabled = false; + ButtonMenuFileSaveProjectAs.IsEnabled = false; + ButtonMenuFileCloseProject.IsEnabled = false; + } + + private void InitMessagesWindowOrActivate() + { + if (messageWindow == null || messageWindow.IsDisposed) + { + if (messageWindow != null && messageWindow.IsDisposed) + { + messageWindow.OnError -= MessageWindowOnError; + } + + messageWindow = new MessageWindow.MessageWindow + { + Text = Properties.Resources.Messages + }; + + messageWindow.OnError += MessageWindowOnError; + } + + if (Gui == null || Gui.ToolWindowViews == null) + { + return; + } + + if (Gui.ToolWindowViews.Contains(messageWindow)) + { + Gui.ToolWindowViews.ActiveView = messageWindow; + return; + } + + Gui.ToolWindowViews.Add(messageWindow, ViewLocation.Bottom); + messageWindow.Visible = true; //doesn't always work (eg, remains false) + } + + [InvokeRequired] + private void MessageWindowOnError(object sender, EventArgs e) + { + // activates messageWindow when error occurs + InitMessagesWindowOrActivate(); + } + + private void OnFileSaveClicked(object sender, RoutedEventArgs e) + { + //TODO: Implement + return; + + // Original code: + //var saveProject = Gui.CommandHandler.SaveProject(); + //OnAfterProjectSaveOrOpen(saveProject); + } + + private void OnFileSaveAsClicked(object sender, RoutedEventArgs e) + { + //TODO: Implement + return; + + // Original code: + //var saveProject = Gui.CommandHandler.SaveProjectAs(); + //OnAfterProjectSaveOrOpen(saveProject); + } + + private void OnAfterProjectSaveOrOpen(bool actionSuccesful) + { + //TODO: Implement + return; + + // Original code: + /* + if (actionSuccesful) + { + AddNewMruItem(Gui.Application.ProjectFilePath); + CommitMruToSettings(); + } + ValidateItems(); + */ + } + + private void OnFileOpenClicked(object sender, RoutedEventArgs e) + { + //TODO: Implement + return; + + // Original code: + //var succesful = Gui.CommandHandler.TryOpenExistingWTIProject(); + //OnAfterProjectSaveOrOpen(succesful); + } + + private void OnFileCloseClicked(object sender, RoutedEventArgs e) + { + //TODO: Implement + return; + + // Original code: + //Gui.CommandHandler.TryCloseWTIProject(); + //ValidateItems(); + } + + private void OnFileNewClicked(object sender, RoutedEventArgs e) + { + //TODO: Implement + return; + + // Original code: + //Gui.CommandHandler.TryCreateNewWTIProject(); + //ValidateItems(); + } + + private void OnFileExitClicked(object sender, RoutedEventArgs e) + { + Gui.Exit(); + } + + private string GetLayoutFilePath(string perspective) + { + string localUserSettingsDirectory = Gui.Application.GetUserSettingsDirectoryPath(); + string layoutFileName = GetLayoutFileName(perspective); + return Path.Combine(localUserSettingsDirectory, layoutFileName); + } + + private static string GetLayoutFileName(string perspective) + { + return "WindowLayout_" + perspective + ".xml"; + } + + private void OnLoadLayout(string perspective) + { + string layoutFilePath = GetLayoutFilePath(perspective); + + if (!File.Exists(layoutFilePath)) + { + // try to load the default file directly from the current directory + string layoutFileName = GetLayoutFileName(perspective); + UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase); + string path = Uri.UnescapeDataString(uri.Path); + string assemblyDir = Path.GetDirectoryName(path); + layoutFilePath = Path.Combine(assemblyDir, layoutFileName); + + if (!File.Exists(layoutFilePath)) + { + return; + } + } + + var layoutSerializer = new XmlLayoutSerializer(dockingManager); + //Here I've implemented the LayoutSerializationCallback just to show + // a way to feed layout desarialization with content loaded at runtime + //Actually I could in this case let AvalonDock to attach the contents + //from current layout using the content ids + //LayoutSerializationCallback should anyway be handled to attach contents + //not currently loaded + layoutSerializer.LayoutSerializationCallback += (s, e) => + { + var c = e.Content; +/* + if (e.Model.ContentId == FileStatsViewModel.ToolContentId) + e.Content = Workspace.This.FileStats; + else if (!string.IsNullOrWhiteSpace(e.Model.ContentId) && + File.Exists(e.Model.ContentId)) + e.Content = Workspace.This.Open(e.Model.ContentId); +*/ + }; + + try + { + layoutSerializer.Deserialize(layoutFilePath); + } + catch (InvalidOperationException) + { + // the xml was not valid. Too bad. + log.Warn(Properties.Resources.MainWindow_OnLoadLayout_Could_not_load_the_requested_dock_layout__The_settings_are_invalid_and_will_be_reset_to_the_default_state_); + return; + } + + // load ribbon state + var ribbonSettingsPath = layoutFilePath + ".ribbon"; + + if (!File.Exists(ribbonSettingsPath)) + { + return; + } + + try + { + var settings = File.ReadAllText(ribbonSettingsPath).Split('|'); + if (settings.Length != 2) + { + return; + } + + var ribbonStateSettings = settings[0].Split(','); + Ribbon.IsMinimized = bool.Parse(ribbonStateSettings[0]); + Ribbon.ShowQuickAccessToolBarAboveRibbon = bool.Parse(ribbonStateSettings[1]); + + // disabled LoadState because the ribbon elements are not correctly detected during Ribbon.LoadState, + // resulting in disappearing quickaccess items + //Ribbon.LoadState(ribbonStream); + } + catch (Exception) + { + log.Warn(Properties.Resources.MainWindow_OnLoadLayout_Could_not_restore_the_ribbon_state__The_settings_are_invalid_and_will_be_reset_to_the_default_state_); + } + } + + private void OnSaveLayout(string perspective) + { + string layoutFilePath = GetLayoutFilePath(perspective); + var ribbonLayoutFilePath = layoutFilePath + ".ribbon"; + if (resetDefaultLayout) + { + if (File.Exists(layoutFilePath)) + { + File.Delete(layoutFilePath); + } + if (File.Exists(ribbonLayoutFilePath)) + { + File.Delete(ribbonLayoutFilePath); + } + return; // Do not save when resetting + } + + var layoutSerializer = new XmlLayoutSerializer(dockingManager); + layoutSerializer.Serialize(layoutFilePath); + + var ribbonStream = new FileStream(ribbonLayoutFilePath, FileMode.Create); + Ribbon.SaveState(ribbonStream); + } + + private void RestoreWindowAppearance() + { + WindowStartupLocation = WindowStartupLocation.Manual; + var x = Properties.Settings.Default.MainWindow_X; + var y = Properties.Settings.Default.MainWindow_Y; + var width = Properties.Settings.Default.MainWindow_Width; + var height = Properties.Settings.Default.MainWindow_Height; + var rec = new Rectangle(x, y, width, height); + + if (!IsVisibleOnAnyScreen(rec)) + { + width = Screen.PrimaryScreen.Bounds.Width - 200; + height = Screen.PrimaryScreen.Bounds.Height - 200; + } + + var fs = Properties.Settings.Default.MainWindow_FullScreen; + Width = width; + Height = height; + if (fs) + { + WindowState = WindowState.Maximized; + } + } + + private bool IsVisibleOnAnyScreen(Rectangle rect) + { + foreach (Screen screen in Screen.AllScreens) + { + if (screen.WorkingArea.IntersectsWith(rect)) + { + return true; + } + } + + return false; + } + + private void SaveWindowAppearance() + { + if (WindowState == WindowState.Maximized) + { + Properties.Settings.Default.MainWindow_FullScreen = true; + } + else + { + Properties.Settings.Default.MainWindow_Width = (int) Width; + Properties.Settings.Default.MainWindow_Height = (int) Height; + Properties.Settings.Default.MainWindow_FullScreen = false; + } + Properties.Settings.Default.Save(); + } + + private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) + { + AddRecentlyOpenedProjectsToFileMenu(); + + SetColorTheme((string) Gui.Application.UserSettings["colorTheme"]); + FileManualButton.IsEnabled = File.Exists(ConfigurationManager.AppSettings["manualFileName"]); + + // TODO: Enable as soon as relevant/implemented + AboutButton.IsEnabled = false; + LicenseButton.IsEnabled = false; + FeedbackButton.IsEnabled = false; + + ButtonQuickAccessNewProject.IsEnabled = false; + ButtonQuickAccessOpenProject.IsEnabled = false; + ButtonQuickAccessSaveProject.IsEnabled = false; + + UpdateMainWindowRibbonElements(); + UpdateRibbonExtensions(); + + ValidateItems(); + } + + private void UpdateMainWindowRibbonElements() + { + resetUIButton.ToolTip = new ScreenTip + { + Title = Properties.Resources.MainWindow_UpdateMainWindowRibbonElements_Reset_layout__restart_, + Text = Properties.Resources.MainWindow_UpdateMainWindowRibbonElements_When_this_option_is_turned_on__the_default_layout_will_be_used_when_restarting_DeltaShell_, + MaxWidth = 250 + }; + } + + private void UpdateRibbonExtensions() + { + // get all ribbon controls + ribbonCommandHandlers = Enumerable.Where(Gui.Plugins, p => p.RibbonCommandHandler != null).Select(p => p.RibbonCommandHandler).ToArray(); + + foreach (var ribbonExtension in ribbonCommandHandlers) + { + var ribbonControl = (Ribbon) ribbonExtension.GetRibbonControl(); + + // fill contextual groups from plugins + foreach (var group in ribbonControl.ContextualGroups) + { + if (Ribbon.ContextualGroups.Any(g => g.Name == group.Name)) + { + continue; + } + + Ribbon.ContextualGroups.Add(group); + } + + // fill tabs from plugins + foreach (var tab in ribbonControl.Tabs) + { + var existingTab = Ribbon.Tabs.FirstOrDefault(t => t.Header.Equals(tab.Header)); + + if (existingTab == null) // add new tab + { + Ribbon.Tabs.Add(tab); + existingTab = tab; + } + else + { + if (!string.IsNullOrEmpty(tab.ReduceOrder)) + { + existingTab.ReduceOrder += "," + tab.ReduceOrder; // Naive implementation; Can cause duplicates to occur + } + + foreach (var group in tab.Groups) + { + var existingGroup = existingTab.Groups.FirstOrDefault(g => g.Header.Equals(group.Header)); + + if (existingGroup == null) // add new group + { + var newGroup = new RibbonGroupBox + { + Header = group.Header, + Name = group.Name // Ensure ReduceOrder is working properly + }; + + // Set KeyTip for keyboard navigation: + var keys = KeyTip.GetKeys(group); + if (!string.IsNullOrEmpty(keys)) + { + KeyTip.SetKeys(newGroup, keys); + } + + existingTab.Groups.Add(newGroup); + existingGroup = newGroup; + } + + // Set group icon if not yet set: + if (existingGroup.Icon == null && group.Icon != null) + { + existingGroup.Icon = group.Icon; + } + + foreach (var item in group.Items.Cast().ToArray()) + { + // HACK: remember and restore button size (looks like a bug in Fluent) + var iconSize = RibbonControlSize.Small; + if (item is Button) + { + var button = (Button) item; + iconSize = button.Size; + } + + group.Items.Remove(item); + existingGroup.Items.Add(item); + + if (item is Button) + { + var button = existingGroup.Items.OfType