From 131e8d089cd80539504c311ddee0b31e68106eb0 Mon Sep 17 00:00:00 2001 From: oguzhankoral Date: Wed, 20 Nov 2024 02:00:17 +0300 Subject: [PATCH 1/3] Fix multiple problems --- .../Bindings/TeklaBasicConnectorBinding.cs | 9 +- .../HostApp/TeklaDocumentModelStore.cs | 95 +++++++++++++++++-- .../SpeckleTeklaPanelHost.cs | 48 +++++++--- 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index 23fba0b59..e20d5e886 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -12,6 +12,7 @@ namespace Speckle.Connector.Tekla2024.Bindings; public class TeklaBasicConnectorBinding : IBasicConnectorBinding { + public BasicConnectorBindingCommands Commands { get; } private readonly ISpeckleApplication _speckleApplication; private readonly DocumentModelStore _store; public string Name => "baseBinding"; @@ -32,6 +33,12 @@ TSM.Model model Parent = parent; _logger = logger; _model = model; + Commands = new BasicConnectorBindingCommands(parent); + _store.DocumentChanged += (_, _) => + parent.TopLevelExceptionHandler.FireAndForget(async () => + { + await Commands.NotifyDocumentChanged().ConfigureAwait(false); + }); } public string GetSourceApplicationName() => _speckleApplication.Slug; @@ -141,6 +148,4 @@ await Task.Run(() => _logger.LogError(ex, "Failed to highlight objects"); } } - - public BasicConnectorBindingCommands Commands { get; } } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index 891d0972b..c995af7ae 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -1,17 +1,100 @@ -using Speckle.Connectors.DUI.Models; +using System.IO; +using Microsoft.Extensions.Logging; +using Speckle.Connectors.DUI.Models; using Speckle.Newtonsoft.Json; +using Speckle.Sdk; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Logging; namespace Speckle.Connector.Tekla2024.HostApp; public class TeklaDocumentModelStore : DocumentModelStore { + private readonly ISpeckleApplication _speckleApplication; + private readonly ILogger _logger; + private readonly TSM.Model _model; + private readonly TSM.Events _events; + private string HostAppUserDataPath { get; set; } + private string DocumentStateFile { get; set; } + private string ModelPathHash { get; set; } + public TeklaDocumentModelStore( - JsonSerializerSettings jsonSerializerSettings - // ITopLevelExceptionHandler topLevelExceptionHandler + JsonSerializerSettings jsonSerializerSettings, + ISpeckleApplication speckleApplication, + ILogger logger ) - : base(jsonSerializerSettings, true) { } + : base(jsonSerializerSettings, true) + { + _speckleApplication = speckleApplication; + _logger = logger; + _model = new TSM.Model(); + SetPaths(); + _events = new TSM.Events(); + _events.ModelLoad += () => + { + SetPaths(); + ReadFromFile(); + OnDocumentChanged(); + }; + _events.Register(); + if (SpeckleTeklaPanelHost.IsInitialized) + { + ReadFromFile(); + OnDocumentChanged(); + } + } + + private void SetPaths() + { + ModelPathHash = Crypt.Md5(_model.GetInfo().ModelPath, length: 32); + HostAppUserDataPath = Path.Combine( + SpecklePathProvider.UserSpeckleFolderPath, + "Connectors", + _speckleApplication.Slug + ); + DocumentStateFile = Path.Combine(HostAppUserDataPath, $"{ModelPathHash}.json"); + } + + public override void WriteToFile() + { + string serializedState = Serialize(); + try + { + if (!Directory.Exists(HostAppUserDataPath)) + { + Directory.CreateDirectory(HostAppUserDataPath); + } + File.WriteAllText(DocumentStateFile, serializedState); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex.Message); + } + } + + public override void ReadFromFile() + { + try + { + if (!Directory.Exists(HostAppUserDataPath)) + { + Models = new(); + return; + } - public override void WriteToFile() { } + if (!File.Exists(DocumentStateFile)) + { + Models = new(); + return; + } - public override void ReadFromFile() { } + string serializedState = File.ReadAllText(DocumentStateFile); + Models = Deserialize(serializedState) ?? new(); + } + catch (Exception ex) when (!ex.IsFatal()) + { + Models = new(); + _logger.LogError(ex.Message); + } + } } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs index d051c6bf2..4933c6bf8 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs @@ -14,13 +14,44 @@ namespace Speckle.Connector.Tekla2024; public class SpeckleTeklaPanelHost : PluginFormBase { - private ElementHost Host { get; } + private static SpeckleTeklaPanelHost? s_instance; + private ElementHost Host { get; set; } public Model Model { get; private set; } public static new ServiceProvider? Container { get; private set; } - private static readonly List s_instances = new(); + public static bool IsFirst { get; private set; } = true; + public static bool IsInitialized { get; private set; } public SpeckleTeklaPanelHost() { + if (IsFirst) + { + IsFirst = false; + Close(); + } + else + { + if (IsInitialized) + { + s_instance?.BringToFront(); + Close(); + return; + } + IsInitialized = true; + InitializeInstance(); + s_instance?.BringToFront(); + } + } + + protected override void OnClosed(EventArgs e) + { + s_instance?.Dispose(); + IsInitialized = false; + } + + private void InitializeInstance() + { + s_instance = this; // Assign the current instance to the static field + this.Text = "Speckle (Beta)"; this.Name = "Speckle (Beta)"; @@ -37,17 +68,6 @@ public SpeckleTeklaPanelHost() this.Icon = Icon.FromHandle(bmp.GetHicon()); } - // adds instances to tracking list - s_instances.Add(this); - - if (s_instances.Count > 1) - { - var firstInstance = s_instances[0]; - s_instances.RemoveAt(0); - // hides the first instance if there is more than one - firstInstance.Hide(); - } - var services = new ServiceCollection(); services.Initialize(HostApplications.TeklaStructures, GetVersion()); services.AddTekla(); @@ -66,10 +86,12 @@ public SpeckleTeklaPanelHost() ); } var webview = Container.GetRequiredService(); + webview.RenderSize = new System.Windows.Size(800, 600); Host = new() { Child = webview, Dock = DockStyle.Fill }; Controls.Add(Host); Operation.DisplayPrompt("Speckle connector initialized."); + this.TopLevel = true; Show(); Activate(); Focus(); From 45acd1494d9d4d68661caaf4e07f07847358c009 Mon Sep 17 00:00:00 2001 From: oguzhankoral Date: Wed, 20 Nov 2024 02:17:18 +0300 Subject: [PATCH 2/3] Connect windows each other --- .../SpeckleTeklaPanelHost.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs index 4933c6bf8..6adec0ab5 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.CodeAnalysis; using System.Drawing; +using System.Runtime.InteropServices; using System.Windows.Forms; using System.Windows.Forms.Integration; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +23,13 @@ public class SpeckleTeklaPanelHost : PluginFormBase public static bool IsFirst { get; private set; } = true; public static bool IsInitialized { get; private set; } + //window owner call + [DllImport("user32.dll", SetLastError = true)] + [SuppressMessage("Security", "CA5392:Use DefaultDllImportSearchPaths attribute for P/Invokes")] + private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value); + + private const int GWL_HWNDPARENT = -8; + public SpeckleTeklaPanelHost() { if (IsFirst) @@ -92,6 +101,7 @@ private void InitializeInstance() Operation.DisplayPrompt("Speckle connector initialized."); this.TopLevel = true; + SetWindowLongPtr(Handle, GWL_HWNDPARENT, MainWindow.Frame.Handle); Show(); Activate(); Focus(); From 9528a11dbbd98c719494e290fa62d6c87ed34f04 Mon Sep 17 00:00:00 2001 From: oguzhankoral Date: Wed, 20 Nov 2024 02:26:36 +0300 Subject: [PATCH 3/3] Add note --- .../SpeckleTeklaPanelHost.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs index 6adec0ab5..b535cd82b 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs @@ -20,7 +20,13 @@ public class SpeckleTeklaPanelHost : PluginFormBase private ElementHost Host { get; set; } public Model Model { get; private set; } public static new ServiceProvider? Container { get; private set; } - public static bool IsFirst { get; private set; } = true; + + // NOTE: Somehow tekla triggers this class twice at the beginning and on first dialog our webview appears + // with small size of render in Host even if we set it as Dock.Fill. But on second trigger dialog initializes as expected. + // So, we do not init our plugin at first attempt, we just close it at first. + // On second, we init plugin and mark plugin as 'Initialized' to handle later init attempts nicely. + // We make 'IsInitialized' as 'false' only whenever our main dialog is closed explicitly by user. + private static bool IsFirst { get; set; } = true; public static bool IsInitialized { get; private set; } //window owner call