diff --git a/ExcelUI_Engine/ExcelUI_Engine.csproj b/ExcelUI_Engine/ExcelUI_Engine.csproj index ad709eeb..e89b6e40 100644 --- a/ExcelUI_Engine/ExcelUI_Engine.csproj +++ b/ExcelUI_Engine/ExcelUI_Engine.csproj @@ -10,7 +10,7 @@ BHoM Copyright � https://github.com/BHoM 9.0.0.0 - 9.1.0.0 + 9.2.0.0 diff --git a/Excel_UI/Addin/AddIn.cs b/Excel_UI/Addin/AddIn.cs index b9ab4a65..2cd9df78 100644 --- a/Excel_UI/Addin/AddIn.cs +++ b/Excel_UI/Addin/AddIn.cs @@ -28,6 +28,8 @@ using System.Collections.Generic; using System.Collections; using System.Linq.Expressions; +using BH.oM.UI; +using BH.UI.Excel.Global; using BH.UI.Excel.Templates; @@ -41,6 +43,8 @@ public partial class AddIn : IExcelAddIn public static Dictionary CallerShells { get; private set; } = new Dictionary(); + public static Dictionary CustomEntryShells { get; private set; } = new Dictionary(); + public static AddIn Instance { get; private set; } = null; @@ -57,12 +61,18 @@ public AddIn() static AddIn() { - // Collect the callers from assemblies + // Collect the callers from assemblies. + // Side effect: constructing each CallerFormula creates a Caller, which triggers + // static Caller() in BHoM_UI → Initialisation.Activate() → CustomRibbonEntries populated. CallerShells = ExcelIntegration.GetExportedAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => t.Namespace == "BH.UI.Excel.Components" && typeof(CallerFormula).IsAssignableFrom(t)) .Select(t => InstantiateCaller(t)) .ToDictionary(o => o.Caller.GetType().Name); + + // Subscribe to custom ribbon entries and replay those already loaded. + // Initialisation.CustomRibbonEntries is fully populated at this point. + CustomRibbon.Activate(); } /*******************************************/ diff --git a/Excel_UI/Excel_UI.csproj b/Excel_UI/Excel_UI.csproj index 6eacb90a..705444f5 100644 --- a/Excel_UI/Excel_UI.csproj +++ b/Excel_UI/Excel_UI.csproj @@ -9,11 +9,10 @@ ..\Build\ false en-US - true https://github.com/BHoM/Excel_UI Copyright © https://github.com/BHoM 9.0.0.0 - 9.1.0.0 + 9.2.0.0 @@ -84,12 +83,11 @@ False - - + diff --git a/Excel_UI/Global/CustomRibbon.cs b/Excel_UI/Global/CustomRibbon.cs new file mode 100644 index 00000000..8ab36d80 --- /dev/null +++ b/Excel_UI/Global/CustomRibbon.cs @@ -0,0 +1,90 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM 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.0 of the License, or + * (at your option) any later version. + * + * The BHoM 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 code. If not, see . + */ + +using BH.oM.UI; +using BH.UI.Base; +using BH.UI.Base.Global; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace BH.UI.Excel.Global +{ + public static class CustomRibbon + { + /*******************************************/ + /**** Public Methods ****/ + /*******************************************/ + + public static void Activate() + { + Initialisation.CustomRibbonEntryLoaded += OnCustomRibbonEntryLoaded; + + foreach (CustomRibbonEntry entry in Initialisation.CustomRibbonEntries) + OnCustomRibbonEntryLoaded(null, entry); + } + + /*******************************************/ + + public static string DeriveId(CustomRibbonEntry entry) + { + string seed = $"{entry.TabName}|{entry.Category}|{entry.ItemJson}"; + using (MD5 md5 = MD5.Create()) + return "c" + BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(seed))) + .Replace("-", "").ToLowerInvariant(); + } + + + /*******************************************/ + /**** Private Methods ****/ + /*******************************************/ + + private static void OnCustomRibbonEntryLoaded(object sender, CustomRibbonEntry entry) + { + try + { + string id = DeriveId(entry); + if (AddIn.CustomEntryShells.ContainsKey(id)) + return; + + // Validate: confirm CallerType can be instantiated and ItemJson deserialises correctly. + Caller temp = Activator.CreateInstance(entry.CallerType) as Caller; + if (temp == null) + { + BH.Engine.Base.Compute.RecordWarning($"Could not instantiate Caller for custom ribbon entry. Tab: {entry.TabName}, Category: {entry.Category}."); + return; + } + + object item = BH.Engine.Serialiser.Convert.FromJson(entry.ItemJson); + temp.SetItem(item); + + AddIn.CustomEntryShells[id] = entry; + } + catch (Exception e) + { + BH.Engine.Base.Compute.RecordWarning(e, $"Failed to register custom ribbon entry. Tab: {entry.TabName}, Category: {entry.Category}."); + } + } + + /*******************************************/ + } +} diff --git a/Excel_UI/Ribbon/Ribbon.cs b/Excel_UI/Ribbon/Ribbon.cs index 30edd8f6..cc1b98da 100644 --- a/Excel_UI/Ribbon/Ribbon.cs +++ b/Excel_UI/Ribbon/Ribbon.cs @@ -20,7 +20,12 @@ * along with this code. If not, see . */ +using BH.Engine.Serialiser; +using BH.oM.UI; +using BH.UI.Base; +using BH.UI.Excel.Global; using BH.UI.Excel.Templates; +using ExcelDna.Integration; using ExcelDna.Integration.CustomUI; using System; using System.Collections.Generic; @@ -40,6 +45,13 @@ public partial class Ribbon : ExcelRibbon public override string GetCustomUI(string RibbonID) { + IEnumerable customTabNames = AddIn.CustomEntryShells.Values + .Select(e => e.TabName) + .Distinct(); + + string customTabsXml = string.Concat(customTabNames.Select(tabName => + $"{GetCustomRibbonXml(tabName)}")); + string ribbonxml = $@" @@ -57,6 +69,7 @@ public override string GetCustomUI(string RibbonID)