// Copyright (C) Stichting Deltares 2017. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using Core.Common.Base;
using Core.Common.Base.Data;
using Core.Common.Base.Storage;
using Core.Common.Controls.TreeView;
using Core.Common.Controls.Views;
using Core.Common.Gui.Commands;
using Core.Common.Gui.ContextMenu;
using Core.Common.Gui.Forms;
using Core.Common.Gui.Forms.MainWindow;
using Core.Common.Gui.Forms.MessageWindow;
using Core.Common.Gui.Forms.PropertyGridView;
using Core.Common.Gui.Forms.ViewHost;
using Core.Common.Gui.Plugin;
using Core.Common.Gui.Properties;
using Core.Common.Gui.Settings;
using Core.Common.Utils.Extensions;
using Core.Common.Utils.Settings;
using log4net;
using log4net.Appender;
using log4net.Repository.Hierarchy;
using SplashScreen = Core.Common.Gui.Forms.SplashScreen.SplashScreen;
using WindowsApplication = System.Windows.Forms.Application;
namespace Core.Common.Gui
{
///
/// Gui class provides graphical user functionality for the application.
///
public class GuiCore : IGui
{
private static readonly ILog log = LogManager.GetLogger(typeof(GuiCore));
private static bool isAlreadyRunningInstanceOfIGui;
private static string instanceCreationStackTrace;
private readonly Observer projectObserver;
private ISelectionProvider currentSelectionProvider;
private bool isExiting;
private bool runFinished;
private SplashScreen splashScreen;
///
/// Initializes a new instance of the class.
///
/// The main window.
/// The project store.
/// The project migrator.
/// The project factory.
/// The fixed settings.
/// Thrown when another
/// instance is running.
/// Thrown when any parameter is null.
public GuiCore(IMainWindow mainWindow, IStoreProject projectStore, IMigrateProject projectMigrator, IProjectFactory projectFactory, GuiCoreSettings fixedSettings)
{
// error detection code, make sure we use only a single instance of GuiCore at a time
if (isAlreadyRunningInstanceOfIGui)
{
isAlreadyRunningInstanceOfIGui = false; // reset to that the consecutive creations won't fail.
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture,
Resources.GuiCore_Only_a_single_instance_of_Ringtoets_is_allowed_at_the_same_time_per_process_Make_sure_that_the_previous_instance_was_disposed_correctly_stack_trace_0,
instanceCreationStackTrace));
}
if (mainWindow == null)
{
throw new ArgumentNullException(nameof(mainWindow));
}
if (projectStore == null)
{
throw new ArgumentNullException(nameof(projectStore));
}
if (projectMigrator == null)
{
throw new ArgumentNullException(nameof(projectMigrator));
}
if (projectFactory == null)
{
throw new ArgumentNullException(nameof(projectFactory));
}
if (fixedSettings == null)
{
throw new ArgumentNullException(nameof(fixedSettings));
}
MainWindow = mainWindow;
FixedSettings = fixedSettings;
isAlreadyRunningInstanceOfIGui = true;
instanceCreationStackTrace = new StackTrace().ToString();
Plugins = new List();
viewCommandHandler = new ViewCommandHandler(this, this, this);
StorageCommands = new StorageCommandHandler(projectStore, projectMigrator, projectFactory,
this, dialogBasedInquiryHelper, MainWindow);
importCommandHandler = new GuiImportHandler(MainWindow, Plugins.SelectMany(p => p.GetImportInfos()), dialogBasedInquiryHelper);
exportCommandHandler = new GuiExportHandler(MainWindow, Plugins.SelectMany(p => p.GetExportInfos()));
updateCommandHandler = new GuiUpdateHandler(MainWindow, Plugins.SelectMany(p => p.GetUpdateInfos()), dialogBasedInquiryHelper);
WindowsApplication.EnableVisualStyles();
ViewPropertyEditor.ViewCommands = ViewCommands;
ProjectOpened += ApplicationProjectOpened;
BeforeProjectOpened += ApplicationBeforeProjectOpened;
projectObserver = new Observer(UpdateTitle);
Project = projectFactory.CreateNewProject();
}
public IPropertyResolver PropertyResolver { get; private set; }
#region Implementation: ISettingsOwner
public GuiCoreSettings FixedSettings { get; }
#endregion
///
/// Runs the user interface, causing all user interface components to initialize,
/// loading plugins, opening a saved project and displaying the main window.
///
/// Path to the project to be opened. (optional)
public void Run(string projectPath = null)
{
DateTime startTime = DateTime.Now;
ConfigureLogging();
ShowSplashScreen();
InitializeProjectFromPath(projectPath);
Initialize();
log.InfoFormat(Resources.GuiCore_Run_Started_in_0_f2_sec, (DateTime.Now - startTime).TotalSeconds);
runFinished = true;
HideSplashScreen();
MessageWindowLogAppender.Instance.Enabled = true;
}
public void Dispose()
{
Dispose(true);
}
public void ExitApplication()
{
if (isExiting)
{
return; //already got here before
}
// Store project?
if (!StorageCommands.HandleUnsavedChanges())
{
// User pressed cancel
return;
}
isExiting = true;
if (Application.Current != null)
{
Application.Current.Shutdown();
}
}
#region Implementation: IContextMenuBuilderProvider
public IContextMenuBuilder Get(object value, TreeViewControl treeViewControl)
{
if (applicationFeatureCommands == null)
{
throw new InvalidOperationException("Call IGui.Run in order to initialize dependencies before getting the ContextMenuBuilder.");
}
return new ContextMenuBuilder(applicationFeatureCommands,
importCommandHandler,
exportCommandHandler,
updateCommandHandler,
ViewCommands,
value,
treeViewControl);
}
#endregion
private IProjectExplorer ProjectExplorer
{
get
{
return ViewHost.ToolViews.OfType().FirstOrDefault();
}
}
private void Dispose(bool disposing)
{
if (disposing)
{
projectObserver.Dispose();
if (Plugins != null)
{
foreach (PluginBase plugin in Plugins.ToArray())
{
DeactivatePlugin(plugin);
}
}
isExiting = true;
mainWindow?.UnsubscribeFromGui();
Selection = null;
if (ViewHost != null)
{
ViewHost.Dispose();
ViewHost.ViewClosed -= OnViewClosed;
ViewHost.ViewClosed -= OnActiveDocumentViewChanged;
ViewHost.ActiveDocumentViewChanged -= OnActiveDocumentViewChanged;
ViewHost.ActiveViewChanged -= OnActiveViewChanged;
}
if (currentSelectionProvider != null)
{
currentSelectionProvider.SelectionChanged -= OnSelectionChanged;
}
// Dispose managed resources. TODO: double check if we need to dispose managed resources?
if (mainWindow != null && !mainWindow.IsWindowDisposed)
{
mainWindow.Dispose();
mainWindow = null;
}
DocumentViewController = null;
splashScreen = null;
MessageWindowLogAppender.Instance.MessageWindow = null;
RemoveLogging();
Plugins = null;
}
#region Prevent nasty Windows.Forms memory leak (keeps references to databinding objects / controls
Assembly systemAssembly = typeof(Component).Assembly;
Type reflectTypeDescriptionProviderType =
systemAssembly.GetType("System.ComponentModel.ReflectTypeDescriptionProvider");
FieldInfo propertyCacheInfo = reflectTypeDescriptionProviderType.GetField("_propertyCache",
BindingFlags.Static |
BindingFlags.NonPublic);
var propertyCache = (Hashtable) propertyCacheInfo.GetValue(null);
propertyCache?.Clear();
FieldInfo extendedPropertyCacheInfo = reflectTypeDescriptionProviderType.GetField(
"_extendedPropertyCache", BindingFlags.Static | BindingFlags.NonPublic);
var extendedPropertyCache = extendedPropertyCacheInfo.GetValue(null) as Hashtable;
extendedPropertyCache?.Clear();
FieldInfo eventCacheInfo = reflectTypeDescriptionProviderType.GetField("_eventCache",
BindingFlags.Static |
BindingFlags.NonPublic);
var eventCache = eventCacheInfo.GetValue(null) as Hashtable;
eventCache?.Clear();
FieldInfo attributeCacheInfo = reflectTypeDescriptionProviderType.GetField("_attributeCache",
BindingFlags.Static |
BindingFlags.NonPublic);
var attributeCache = attributeCacheInfo.GetValue(null) as Hashtable;
attributeCache?.Clear();
Type typeDescriptorType = systemAssembly.GetType("System.ComponentModel.TypeDescriptor");
FieldInfo providerTableInfo = typeDescriptorType.GetField("_providerTable",
BindingFlags.Static | BindingFlags.NonPublic);
var providerTable = providerTableInfo.GetValue(null) as Hashtable;
providerTable?.Clear();
FieldInfo providerTypeTableInfo = typeDescriptorType.GetField("_providerTypeTable",
BindingFlags.Static | BindingFlags.NonPublic);
var providerTypeTable = providerTypeTableInfo.GetValue(null) as Hashtable;
providerTypeTable?.Clear();
FieldInfo defaultProvidersInfo = typeDescriptorType.GetField("_defaultProviders",
BindingFlags.Static | BindingFlags.NonPublic);
var defaultProviders = defaultProvidersInfo.GetValue(null) as Hashtable;
defaultProviders?.Clear();
#endregion
GC.Collect();
instanceCreationStackTrace = "";
isAlreadyRunningInstanceOfIGui = false;
}
private void InitializeProjectFromPath(string projectPath)
{
bool isPathGiven = !string.IsNullOrWhiteSpace(projectPath);
if (isPathGiven)
{
StorageCommands.OpenExistingProject(projectPath);
}
}
private void DeactivatePlugin(PluginBase plugin)
{
try
{
plugin.Deactivate();
}
catch (Exception exception)
{
log.Error(Resources.GuiCore_ActivatePlugins_Exception_during_plugin_gui_deactivation, exception);
}
plugin.Dispose();
Plugins.Remove(plugin);
}
private void ApplicationProjectOpened(IProject newProject)
{
mainWindow?.ValidateItems();
projectObserver.Observable = newProject;
UpdateTitle();
}
private void ApplicationBeforeProjectOpened(IProject oldProject)
{
ViewCommands.RemoveAllViewsForItem(project);
}
private static void ConfigureLogging()
{
// configure logging
Logger rootLogger = ((Hierarchy) LogManager.GetRepository()).Root;
if (!rootLogger.Appenders.Cast().Any(a => a is MessageWindowLogAppender))
{
rootLogger.AddAppender(new MessageWindowLogAppender());
rootLogger.Repository.Configured = true;
}
}
private static void RemoveLogging()
{
Logger rootLogger = ((Hierarchy) LogManager.GetRepository()).Root;
MessageWindowLogAppender messageWindowLogAppender = rootLogger.Appenders.Cast().OfType().FirstOrDefault();
if (messageWindowLogAppender != null)
{
rootLogger.RemoveAppender(messageWindowLogAppender);
}
}
private void Initialize()
{
InitializeWindows();
InitializePlugins();
}
private void ShowSplashScreen()
{
splashScreen = new SplashScreen
{
VersionText = SettingsHelper.Instance.ApplicationVersion
};
splashScreen.IsVisibleChanged += delegate
{
if (splashScreen.IsVisible)
{
return;
}
if (!runFinished) // splash screen was closed before gui started.
{
log.Info(Resources.GuiCore_ShowSplashScreen_User_has_canceled_start_Exiting);
Environment.Exit(1);
}
};
splashScreen.Show();
}
private void HideSplashScreen()
{
splashScreen.Shutdown();
}
private void InitializeMainWindow()
{
mainWindow.Loaded += delegate
{
if (ViewHost.ToolViews.Contains(mainWindow.PropertyGrid))
{
ViewHost.BringToFront(mainWindow.PropertyGrid);
}
if (ViewHost.ToolViews.Contains(mainWindow.MessageWindow))
{
ViewHost.BringToFront(mainWindow.MessageWindow);
}
if (ViewHost.ToolViews.Contains(ProjectExplorer))
{
ViewHost.BringToFront(ProjectExplorer);
}
mainWindow.ValidateItems();
};
mainWindow.Closing += delegate(object sender, CancelEventArgs e)
{
if (isExiting)
{
return;
}
e.Cancel = true; //cancel closing: let Exit handle it
ExitApplication();
};
}
private void InitializeWindows()
{
InitializeMainWindow();
ViewHost = mainWindow.ViewHost;
ViewHost.ViewClosed += OnViewClosed;
ViewHost.ViewClosed += OnActiveDocumentViewChanged;
ViewHost.ActiveDocumentViewChanged += OnActiveDocumentViewChanged;
ViewHost.ActiveViewChanged += OnActiveViewChanged;
DocumentViewController = new DocumentViewController(ViewHost, Plugins.SelectMany(p => p.GetViewInfos()), mainWindow);
PropertyResolver = new PropertyResolver(Plugins.SelectMany(p => p.GetPropertyInfos()));
applicationFeatureCommands = new ApplicationFeatureCommandHandler(PropertyResolver, mainWindow);
mainWindow.InitializeToolWindows();
mainWindow.SubscribeToGui();
UpdateTitle();
}
private void OnViewClosed(object sender, ViewChangeEventArgs e)
{
if (ReferenceEquals(currentSelectionProvider, e.View))
{
currentSelectionProvider.SelectionChanged -= OnSelectionChanged;
currentSelectionProvider = null;
Selection = null;
}
}
private void OnActiveDocumentViewChanged(object sender, EventArgs e)
{
if (mainWindow != null && !mainWindow.IsWindowDisposed)
{
mainWindow.ValidateItems();
}
}
private void OnActiveViewChanged(object sender, ViewChangeEventArgs e)
{
if (HandleActivatingCurrentSelectionProvidingDocumentView(e.View))
{
return;
}
var selectionProvider = e.View as ISelectionProvider;
if (selectionProvider == null)
{
HandleDeactivatingCurrentSelectionProvidingDocumentView();
return;
}
if (currentSelectionProvider != null)
{
currentSelectionProvider.SelectionChanged -= OnSelectionChanged;
}
currentSelectionProvider = selectionProvider;
currentSelectionProvider.SelectionChanged += OnSelectionChanged;
Selection = currentSelectionProvider.Selection;
}
private bool HandleActivatingCurrentSelectionProvidingDocumentView(IView activeView)
{
var handled = false;
if (ReferenceEquals(currentSelectionProvider, activeView) && ViewHost.DocumentViews.Contains(activeView))
{
currentSelectionProvider.SelectionChanged += OnSelectionChanged;
handled = true;
}
return handled;
}
private void HandleDeactivatingCurrentSelectionProvidingDocumentView()
{
if (currentSelectionProvider != null && ViewHost.DocumentViews.Contains((IView) currentSelectionProvider))
{
currentSelectionProvider.SelectionChanged -= OnSelectionChanged;
}
}
private void OnSelectionChanged(object sender, EventArgs eventArgs)
{
Selection = currentSelectionProvider.Selection;
}
private void InitializePlugins()
{
Plugins.ForEachElementDo(p => p.Gui = this);
var problematicPlugins = new List();
// Try to activate all plugins
foreach (PluginBase plugin in Plugins)
{
try
{
plugin.Activate();
}
catch
{
problematicPlugins.Add(plugin);
}
}
// Deactivate and remove all problematic plugins
foreach (PluginBase problematicPlugin in problematicPlugins)
{
DeactivatePlugin(problematicPlugin);
}
}
~GuiCore()
{
Dispose(false);
}
#region Implementation: IProjectOwner
private IProject project;
public event Action BeforeProjectOpened;
public event Action ProjectOpened;
public string ProjectFilePath { get; private set; }
public IProject Project
{
get
{
return project;
}
private set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), @"There should always be a project.");
}
if (!ReferenceEquals(project, value))
{
OnBeforeProjectOpened();
project = value;
OnProjectOpened();
}
}
}
private void OnBeforeProjectOpened()
{
BeforeProjectOpened?.Invoke(project);
}
private void OnProjectOpened()
{
ProjectOpened?.Invoke(project);
}
public void SetProject(IProject newProject, string projectPath)
{
Project = newProject;
ProjectFilePath = projectPath;
}
#endregion
#region Implementation: IApplicationSelection
private object selection;
public object Selection
{
get
{
return selection;
}
set
{
if (selection == value)
{
return;
}
selection = value;
if (mainWindow == null)
{
return;
}
if (mainWindow.PropertyGrid != null)
{
mainWindow.PropertyGrid.Data = selection;
}
if (!isExiting && !mainWindow.IsWindowDisposed)
{
mainWindow.ValidateItems();
}
}
}
#endregion
#region Implementation: ICommandsOwner
private ApplicationFeatureCommandHandler applicationFeatureCommands;
private readonly ViewCommandHandler viewCommandHandler;
private readonly GuiImportHandler importCommandHandler;
private readonly GuiExportHandler exportCommandHandler;
private readonly GuiUpdateHandler updateCommandHandler;
public IApplicationFeatureCommands ApplicationCommands
{
get
{
return applicationFeatureCommands;
}
}
public IStorageCommands StorageCommands { get; }
public IViewCommands ViewCommands
{
get
{
return viewCommandHandler;
}
}
#endregion
#region Implementation: IViewController
public IViewHost ViewHost { get; private set; }
public IDocumentViewController DocumentViewController { get; private set; }
#endregion
#region Implementation: IPluginHost
public IList Plugins { get; private set; }
public IEnumerable GetTreeNodeInfos()
{
return Plugins.SelectMany(pluginGui => pluginGui.GetTreeNodeInfos());
}
public IEnumerable GetAllDataWithViewDefinitionsRecursively(object rootValue)
{
var resultSet = new HashSet