diff --git a/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextGroupTest.cs b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextGroupTest.cs new file mode 100644 index 00000000..fc1e8118 --- /dev/null +++ b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextGroupTest.cs @@ -0,0 +1,120 @@ +using Moq; +using NUnit.Framework; +using System.Collections.Generic; + +namespace CADBooster.SolidDna.Test; + +[TestFixture] +internal class CommandContextGroupTest +{ + [Test] + public void Create_WithNullInfo_ThrowsException() + { + var group = new CommandContextGroup + { + Name = "TestGroup", + Items = new List + { + new CommandContextItem { Name = "Item1" } + } + }; + + Assert.Throws(() => group.Create(null)); + } + + [Test] + public void Create_WithNullOrEmptyName_ThrowsException() + { + var group = new CommandContextGroup + { + Name = null, + Items = new List + { + new CommandContextItem { Name = "Item1" } + } + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => group.Create(info.Object)); + } + + [Test] + public void Create_WithWhitespaceName_ThrowsException() + { + var group = new CommandContextGroup + { + Name = " ", + Items = new List + { + new CommandContextItem { Name = "Item1" } + } + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => group.Create(info.Object)); + } + + [Test] + public void Create_WithNullItems_ThrowsException() + { + var group = new CommandContextGroup + { + Name = "TestGroup", + Items = null + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => group.Create(info.Object)); + } + + [Test] + public void Create_WithEmptyItems_ThrowsException() + { + var group = new CommandContextGroup + { + Name = "TestGroup", + Items = [] + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => group.Create(info.Object)); + } + + [Test] + public void ToString_ReturnsCorrectFormat() + { + var group = new CommandContextGroup + { + Name = "TestGroup", + Items = new List + { + new CommandContextItem { Name = "Item1" }, + new CommandContextItem { Name = "Item2" } + } + }; + + var result = group.ToString(); + + Assert.That(result, Does.Contain("TestGroup")); + Assert.That(result, Does.Contain("2")); + } + + [Test] + public void ToString_WithNullItems_DoesNotThrow() + { + var group = new CommandContextGroup + { + Name = "TestGroup", + Items = null + }; + + var result = group.ToString(); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Does.Contain("TestGroup")); + } +} diff --git a/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextIconTest.cs b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextIconTest.cs new file mode 100644 index 00000000..c30ba96f --- /dev/null +++ b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextIconTest.cs @@ -0,0 +1,120 @@ +using Moq; +using NUnit.Framework; + +namespace CADBooster.SolidDna.Test; + +[TestFixture] +internal class CommandContextIconTest +{ + [Test] + public void Create_WithNullInfo_ThrowsException() + { + var icon = new CommandContextIcon + { + Hint = "TestIcon", + IconPathFormat = @"C:\Icons\icon{0}.png" + }; + + Assert.Throws(() => icon.Create(null)); + } + + [Test] + public void Create_WithNullOrEmptyHint_ThrowsException() + { + var icon = new CommandContextIcon + { + Hint = null, + IconPathFormat = @"C:\Icons\icon{0}.png" + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => icon.Create(info.Object)); + } + + [Test] + public void Create_WithWhitespaceHint_ThrowsException() + { + var icon = new CommandContextIcon + { + Hint = " ", + IconPathFormat = @"C:\Icons\icon{0}.png" + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => icon.Create(info.Object)); + } + + [Test] + public void Create_WithNullOrEmptyIconPathFormat_ThrowsException() + { + var icon = new CommandContextIcon + { + Hint = "TestIcon", + IconPathFormat = null + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => icon.Create(info.Object)); + } + + [Test] + public void Create_WithWhitespaceIconPathFormat_ThrowsException() + { + var icon = new CommandContextIcon + { + Hint = "TestIcon", + IconPathFormat = " " + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => icon.Create(info.Object)); + } + + [Test] + public void VisibleForAssemblies_DefaultValue_IsTrue() + { + var icon = new CommandContextIcon(); + + Assert.That(icon.VisibleForAssemblies, Is.True); + } + + [Test] + public void VisibleForDrawings_DefaultValue_IsTrue() + { + var icon = new CommandContextIcon(); + + Assert.That(icon.VisibleForDrawings, Is.True); + } + + [Test] + public void VisibleForParts_DefaultValue_IsTrue() + { + var icon = new CommandContextIcon(); + + Assert.That(icon.VisibleForParts, Is.True); + } + + [Test] + public void SelectionType_DefaultValue_IsEverything() + { + var icon = new CommandContextIcon(); + + Assert.That(icon.SelectionType, Is.EqualTo(SelectionType.Everything)); + } + + [Test] + public void Name_ReturnsHintValue() + { + var icon = new CommandContextIcon + { + Hint = "TestHint" + }; + + ICommandCreatable creatable = icon; + Assert.That(creatable.Name, Is.EqualTo("TestHint")); + } +} diff --git a/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextItemTest.cs b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextItemTest.cs new file mode 100644 index 00000000..44f8075c --- /dev/null +++ b/SolidDna/CADBooster.SolidDna.Test/SolidWorks/CommandManager/ContextMenuItems/CommandContextItemTest.cs @@ -0,0 +1,80 @@ +using Moq; +using NUnit.Framework; + +namespace CADBooster.SolidDna.Test; + +[TestFixture] +internal class CommandContextItemTest +{ + [Test] + public void Create_WithNullInfo_ThrowsException() + { + var item = new CommandContextItem + { + Name = "TestItem", + Hint = "Test hint" + }; + + Assert.Throws(() => item.Create(null)); + } + + [Test] + public void Create_WithNullOrEmptyName_ThrowsException() + { + var item = new CommandContextItem + { + Name = null, + Hint = "Test hint" + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => item.Create(info.Object)); + } + + [Test] + public void Create_WithWhitespaceName_ThrowsException() + { + var item = new CommandContextItem + { + Name = " ", + Hint = "Test hint" + }; + var info = new Mock(); + info.Setup(x => x.SolidWorksCookie).Returns(1); + + Assert.Throws(() => item.Create(info.Object)); + } + + [Test] + public void VisibleForAssemblies_DefaultValue_IsTrue() + { + var item = new CommandContextItem(); + + Assert.That(item.VisibleForAssemblies, Is.True); + } + + [Test] + public void VisibleForDrawings_DefaultValue_IsTrue() + { + var item = new CommandContextItem(); + + Assert.That(item.VisibleForDrawings, Is.True); + } + + [Test] + public void VisibleForParts_DefaultValue_IsTrue() + { + var item = new CommandContextItem(); + + Assert.That(item.VisibleForParts, Is.True); + } + + [Test] + public void SelectionType_DefaultValue_IsEverything() + { + var item = new CommandContextItem(); + + Assert.That(item.SelectionType, Is.EqualTo(SelectionType.Everything)); + } +} diff --git a/SolidDna/CADBooster.SolidDna/Errors/SolidDnaErrorCode.cs b/SolidDna/CADBooster.SolidDna/Errors/SolidDnaErrorCode.cs index 539b1cc2..c5ea9ea4 100644 --- a/SolidDna/CADBooster.SolidDna/Errors/SolidDnaErrorCode.cs +++ b/SolidDna/CADBooster.SolidDna/Errors/SolidDnaErrorCode.cs @@ -1,4 +1,4 @@ -namespace CADBooster.SolidDna; +namespace CADBooster.SolidDna; /// /// A list of all known types of error codes in SolidDNA @@ -238,6 +238,21 @@ public enum SolidDnaErrorCode /// SolidWorksCommandItemPositionError = 12010, + /// + /// There was an error while trying to activate a Context Menu Item that was already activated + /// + SolidWorksCommandContextMenuItemReActivateError = 12011, + + /// + /// There was an unknown error while trying to add a context menu icon. + /// + SolidWorksCommandCreateContextIconError = 12012, + + /// + /// There was an unknown error while trying to add a context menu item. + /// + SolidWorksCommandCreateContextItemError = 12013, + #endregion #region Export Data (13,000) diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/Application/SolidWorksApplication.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/Application/SolidWorksApplication.cs index b18022c3..28f313f5 100644 --- a/SolidDna/CADBooster.SolidDna/SolidWorks/Application/SolidWorksApplication.cs +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/Application/SolidWorksApplication.cs @@ -151,7 +151,7 @@ public SolidWorksApplication(SldWorks solidWorks, int cookie) : base(solidWorks) // If we have a cookie... if (cookie > 0) // Get command manager - CommandManager = new CommandManager(UnsafeObject.GetCommandManager(mSwCookie)); + CommandManager = new CommandManager(UnsafeObject.GetCommandManager(mSwCookie), mSwCookie); // Get whatever the current model is on load ReloadActiveModelInformation(); diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/CommandManager.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/CommandManager.cs index 8efc465f..b6af99c6 100644 --- a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/CommandManager.cs +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/CommandManager.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Windows.Input; namespace CADBooster.SolidDna; @@ -23,16 +24,39 @@ public class CommandManager : SolidDnaObject /// private readonly List mCommandFlyouts = []; + /// + /// A list of all created command context menu items + /// + private readonly List mCommandContextItems = []; + /// /// Unique ID for flyouts (just increment every time we add one) /// private int mFlyoutIdCount = 1000; + /// + /// The current SolidWorks Add-in cookie ID + /// + private readonly int mCookie; + /// /// Creates a command manager which let us create and access custom toolbars/tabs/ribbons and menus. /// Every add-in has its own command manager. This is how SOLIDWORKS knows which menus and toolbars belong to which add-in. /// - public CommandManager(ICommandManager commandManager) : base(commandManager) { } + [Obsolete("Use overload with Add-in Cookie argument")] + public CommandManager(ICommandManager commandManager) : base(commandManager) + { + mCookie = SolidWorksEnvironment.Application.SolidWorksCookie; + } + + /// + /// Creates a command manager which let us create and access custom toolbars/tabs/ribbons and menus. + /// Every add-in has its own command manager. This is how SOLIDWORKS knows which menus and toolbars belong to which add-in. + /// + public CommandManager(ICommandManager commandManager, int cookie) : base(commandManager) + { + mCookie = cookie; + } /// /// Create an item in the Tools menu with a list of items. Only uses items, so no separators or flyouts. @@ -81,6 +105,26 @@ public CommandManagerGroup CreateCommandTab(string title, int id, List + /// Creates context menu items from the provided collection of objects. + /// + /// Pass here , or . + /// + /// + /// You can also implement and pass your command related object that will be disposed with + /// + /// + /// The collection of command items to create + public void CreateContextMenuItems(IEnumerable commandItems) + { + // Create base info with the add-in cookie + // Items and groups will create CommandContextItemCreateInfo if needed (with empty path for root level) + var createInfo = new CommandContextCreateInfoBase(mCookie); + + foreach (var item in commandItems) + mCommandContextItems.AddRange(item.Create(createInfo)); + } + /// /// Create a command group from a list of items. Uses a single list of items, separators and flyouts. /// NOTE: If you set to false, you should pick a new ID every time you change your tab. @@ -123,7 +167,7 @@ public CommandManagerGroup CreateCommandGroupAndTabs(string title, int id, List< // Track all flyouts for all add-ins that use SolidDNA mCommandFlyouts.AddRange(commandManagerItems.OfType()); - + // Create the group group.Create(this, title); @@ -326,6 +370,9 @@ public override void Dispose() // Remove all command flyouts mCommandFlyouts?.ForEach(RemoveCommandFlyout); + // Dispose all command context menu items + mCommandContextItems?.ForEach(x => x.Dispose()); + mCommandContextItems.Clear(); base.Dispose(); } } \ No newline at end of file diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemExtensions.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemExtensions.cs new file mode 100644 index 00000000..710893fe --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemExtensions.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CADBooster.SolidDna; + +/// +/// Provides extension methods for converting objects to +/// +public static class CommandManagerItemExtensions +{ + /// + /// Converts a collection of objects to objects + /// + /// The collection of objects to convert + /// A collection of objects + public static IEnumerable AsCommandContextItems(this IEnumerable items) + => items.AsCommandContextItems(SelectionType.Everything); + + /// + /// Converts a collection of objects to objects + /// + /// The collection of objects to convert + /// The selection type for all items + /// A collection of objects + public static IEnumerable AsCommandContextItems(this IEnumerable items, + SelectionType selectType) + => items.Select(x => x.AsCommandContextItem(selectType)); + + /// + /// Converts a single object to an object + /// + /// The object to convert + /// An object + public static CommandContextItem AsCommandContextItem(this CommandManagerItem item) + => item.AsCommandContextItem(SelectionType.Everything); + + /// + /// Converts a single object to the object + /// + /// The object to convert + /// The selection type for the item (defaults to ) + /// A . Cloned new item + public static CommandContextItem AsCommandContextItem(this CommandManagerItem item, SelectionType selectType) + => new CommandContextItem() + { + Name = item.Name, + Hint = item.Hint, + OnClick = item.OnClick, + OnStateCheck = item.OnStateCheck, + SelectionType = selectType, + VisibleForAssemblies = item.VisibleForAssemblies, + VisibleForDrawings = item.VisibleForDrawings, + VisibleForParts = item.VisibleForParts + }; + + /// + /// Converts a single object to an object + /// + /// The object to convert + /// + /// Absolute path to the image files that contain the single icon. + /// Based on a string format, replacing {0} with the size. For example C:\Folder\Icon{0}.png + /// If batch icon files are provided, SolidWorks uses the first icon (no index support). + /// + /// An object + public static CommandContextIcon AsCommandContextIcon(this CommandManagerItem item, string iconPathFormat) + => item.AsCommandContextIcon(iconPathFormat, SelectionType.Everything); + + /// + /// Converts a single object to the object + /// + /// The object to convert + /// + /// Absolute path to the image files that contain the single icon. + /// Based on a string format, replacing {0} with the size. For example C:\Folder\Icon{0}.png + /// If batch icon files are provided, SolidWorks uses the first icon (no index support). + /// + /// The selection type for the item (defaults to ) + /// A . Cloned new item + public static CommandContextIcon AsCommandContextIcon(this CommandManagerItem item, string iconPathFormat, SelectionType selectType) + => new CommandContextIcon() + { + Hint = item.Hint, + IconPathFormat = iconPathFormat, + OnClick = item.OnClick, + OnStateCheck = item.OnStateCheck, + SelectionType = selectType, + VisibleForAssemblies = item.VisibleForAssemblies, + VisibleForDrawings = item.VisibleForDrawings, + VisibleForParts = item.VisibleForParts + }; + + /// + /// Converts a collection of objects to objects + /// + /// The collection of objects to convert + /// + /// Absolute path to the image files that contain the single icon. + /// Based on a string format, replacing {0} with the size. For example C:\Folder\Icon{0}.png + /// If batch icon files are provided, SolidWorks uses the first icon (no index support). + /// + /// A collection of objects + public static IEnumerable AsCommandContextIcons(this IEnumerable items, + string iconPathFormat) + => items.AsCommandContextIcons(iconPathFormat, SelectionType.Everything); + + /// + /// Converts a collection of objects to objects + /// + /// The collection of objects to convert + /// + /// Absolute path to the image files that contain the single icon. + /// Based on a string format, replacing {0} with the size. For example C:\Folder\Icon{0}.png + /// If batch icon files are provided, SolidWorks uses the first icon (no index support). + /// + /// The selection type for all items + /// A collection of objects + public static IEnumerable AsCommandContextIcons(this IEnumerable items, + string iconPathFormat, + SelectionType selectType) + => items.Select(x => x.AsCommandContextIcon(iconPathFormat, selectType)); +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemState.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemState.cs index dca32b4a..4c04f886 100644 --- a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemState.cs +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/CommandManagerItemState.cs @@ -1,4 +1,4 @@ -namespace CADBooster.SolidDna; +namespace CADBooster.SolidDna; /// /// States for command manager items, flyout items and flyout groups. @@ -10,13 +10,13 @@ public enum CommandManagerItemState { /// /// Deselect and disable the item. - /// Valid for items, flyout items and flyout groups. + /// Valid for items, flyout items, flyout groups, context icons, and context items. /// DeselectedDisabled = 0, /// /// Deselect and enable the item. This is the default state if no update function is specified. - /// Valid for items, flyout items and flyout groups. + /// Valid for items, flyout items, flyout groups, context icons, and context items. /// DeselectedEnabled = 1, @@ -24,7 +24,7 @@ public enum CommandManagerItemState /// Select and disable the item. /// The default behavior of SolidWorks is to select a (flyout) item if the property manager page for that item is active, like while creating a sketch or drawing a rectangle. /// Also used in the Tools menu to show a selected option with a check mark. - /// Valid for items and flyout items. + /// Valid for items, flyout items, and context items. /// SelectedDisabled = 2, @@ -32,12 +32,12 @@ public enum CommandManagerItemState /// Select and enable the item. /// The default behavior of SolidWorks is to select a (flyout) item if the property manager page for that item is active, like while creating a sketch or drawing a rectangle. /// Also used in the Tools menu to show a selected option with a check mark. - /// Valid for items and flyout items. + /// Valid for items, flyout items, and context items. /// SelectedEnabled = 3, /// - /// Hide the item. Valid for flyout items. + /// Hide the item. Valid for flyout items, context icons, and context items. /// Hidden = 4, } \ No newline at end of file diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextBase.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextBase.cs new file mode 100644 index 00000000..65bafcf1 --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextBase.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CADBooster.SolidDna; + +/// +/// A command context menu item base class +/// +public abstract class CommandContextBase +{ + private bool _isCreated; + + #region Public Properties + + /// + /// True to show this item in the context menu when an assembly is open. + /// + public bool VisibleForAssemblies { get; set; } = true; + + /// + /// True to show this item in the context menu when a drawing is open. + /// + public bool VisibleForDrawings { get; set; } = true; + + /// + /// True to show this item in the context menu when a part is open. + /// + public bool VisibleForParts { get; set; } = true; + + /// + /// The action to call when the item is clicked + /// + public Action OnClick { get; set; } + + /// + /// The action to call when the item state requested + /// + public abstract Action OnStateCheck { get; set; } + + /// + /// The selection type that determines with which selection context the item will be shown + /// + public abstract SelectionType SelectionType { get; set; } + + #endregion + + /// + /// Creates the command context item for the specified document types in derived classes. + /// The base class only implements restriction of multiple creation; it does not create anything. + /// + /// Create information containing cookie and other context + /// Base class method returns empty enumerable + /// Thrown if the item has already been created + public virtual IEnumerable Create(ICommandContextCreateInfo info) + { + if (_isCreated) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandContextMenuItemReActivateError)); + + _isCreated = true; + + return Enumerable.Empty(); + } +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextCreatedBase.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextCreatedBase.cs new file mode 100644 index 00000000..0d8a1a49 --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextCreatedBase.cs @@ -0,0 +1,115 @@ +using System; + +namespace CADBooster.SolidDna; + +internal abstract class CommandContextCreatedBase : ICommandCreated, ICommandItem +{ + /// + /// Gets the unique callback ID for this command context item + /// + public string CallbackId { get; } = Guid.NewGuid().ToString("N"); + + /// + /// Gets the name for identification of this command context item + /// + public abstract string Name { get; } + + /// + /// Gets the selection type that determines where this item is shown + /// + public SelectionType SelectionType { get; } + + /// + /// Gets the document type (Assembly, Part, or Drawing) for which this item is created + /// + public DocumentType DocumentType { get; } + + /// + /// Gets the action to call when this item is clicked + /// + public Action OnClick { get; } + + /// + /// Gets the action to call when the state of this item is checked + /// + public Action OnStateCheck { get; private set; } + + private bool _isDisposed = false; + + /// + /// Initializes a new instance of the class + /// + /// The command context item to create + /// The full name of the item, including its hierarchical path + /// The document type (Assembly, Part, or Drawing) for which this item is created + public CommandContextCreatedBase(CommandContextBase commandContextBase, DocumentType documentType) + { + if (commandContextBase is null) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Command context base cannot be null")); + + OnClick = commandContextBase.OnClick; + OnStateCheck = commandContextBase.OnStateCheck; + SelectionType = commandContextBase.SelectionType; + DocumentType = documentType; + + // Listen out for callbacks + PlugInIntegration.CallbackFired += PlugInIntegration_CallbackFired; + + // Listen out for EnableMethod + PlugInIntegration.ItemStateCheckFired += PlugInIntegration_EnableMethodFired; + } + + /// + /// Fired when a SolidWorks callback is fired + /// + /// The name of the callback that was fired + private void PlugInIntegration_CallbackFired(string name) + { + if (CallbackId != name) + return; + + // Call the action + OnClick?.Invoke(); + } + + /// + /// Fired when a SolidWorks UpdateCallbackFunction is fired + /// + /// The arguments for user handling + private void PlugInIntegration_EnableMethodFired(CommandManagerItemStateCheckArgs args) + { + if (CallbackId != args.CallbackId) + return; + + // Removal/Dispose strategy: + // SolidWorks provides no reliable way to remove context menu items/icons once registered. + // So "Dispose" in SolidDNA means: keep the state-check callback active and force Hidden. + if (_isDisposed) + { + args.Result = CommandManagerItemState.Hidden; + return; + } + + // Call the action + OnStateCheck?.Invoke(args); + } + + /// + /// Disposing + /// + public virtual void Dispose() + { + // Stop listening out for callbacks + PlugInIntegration.CallbackFired -= PlugInIntegration_CallbackFired; + + // NOTE: We intentionally do not unsubscribe from ItemStateCheckFired. + // We need to keep receiving state checks so we can return Hidden after disposal, + // because SolidWorks does not reliably support removing registered context entries. + // PlugInIntegration.ItemStateCheckFired -= PlugInIntegration_EnableMethodFired; + + _isDisposed = true; + } +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroup.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroup.cs new file mode 100644 index 00000000..871d86cc --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroup.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CADBooster.SolidDna; + +/// +/// Represents a command context group in SolidWorks +/// +public class CommandContextGroup : ICommandCreatable +{ + private bool _isCreated; + + #region Public Properties + + /// + /// The name of this command group that is displayed in the context menu + /// + public string Name { get; set; } + + /// + /// Context menu items in this group + /// + public IEnumerable Items { get; set; } + + #endregion + + /// + /// Creates the command context group and its items + /// + /// Create information. Should be to create nested groups with proper path hierarchy + /// A list of created command context items + /// Thrown if the group has already been created + public IEnumerable Create(ICommandContextCreateInfo info) + { + if (info is null) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu create info cannot be null")); + + if (_isCreated) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandContextMenuItemReActivateError)); + + if (string.IsNullOrWhiteSpace(Name)) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu group name cannot be null or empty")); + + if (Items is null) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu group items cannot be null")); + + if (!Items.Any()) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu group items cannot be empty")); + + // Ensure we have CommandContextItemCreateInfo with path, create one if needed + // Empty path means this is a root-level group (not nested under another group) + var itemInfo = info as CommandContextItemCreateInfo + ?? new CommandContextItemCreateInfo(info.SolidWorksCookie, string.Empty); + + _isCreated = true; + + return Enumerable.Repeat(new CommandContextGroupCreated(Name, itemInfo, Items), 1); + } + + public override string ToString() => $"ContextGroup with name: {Name}. Count of sub items: {Items?.Count() ?? 0}"; +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroupCreated.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroupCreated.cs new file mode 100644 index 00000000..ecab9594 --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextGroupCreated.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CADBooster.SolidDna; + +/// +/// Represents a created command context group in SolidWorks +/// This class handles the creation and disposal of a group of context menu items +/// +internal class CommandContextGroupCreated : ICommandCreated +{ + /// + /// The name of this command context group that is displayed in the context menu + /// + public string Name { get; } + + /// + /// A list of created command items within this group + /// + private readonly List _createdItems; + + /// + /// Initializes a new instance of the class + /// + /// The name of the group + /// Create information containing cookie and path + /// The list of command items to include in the group + public CommandContextGroupCreated(string name, CommandContextItemCreateInfo info, IEnumerable items) + { + Name = name; + + // Construct the full name for the group using the provided path + var fullName = string.IsNullOrEmpty(info.Path) ? $"{Name}" : $"{info.Path}@{Name}"; + + // Create child info with the full name for nested items + var childInfo = new CommandContextItemCreateInfo(info.SolidWorksCookie, fullName); + + // Create all child items and store them in the list + _createdItems = items + .SelectMany(x => x.Create(childInfo)) + .ToList(); + } + + /// + /// Disposing + /// + public void Dispose() => _createdItems.ForEach(x => x.Dispose()); +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIcon.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIcon.cs new file mode 100644 index 00000000..9e13808b --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIcon.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; + +namespace CADBooster.SolidDna; + +/// +/// Represents a context icon in SolidWorks. +/// +/// It shows in the SolidWorks context popup. +/// If no popup for shows, this means the icon will not add a new popup and will be inaccessible, so make sure that a popup shows for the selection. +/// +/// +public class CommandContextIcon : CommandContextBase, ICommandCreatable +{ + /// + /// Absolute path to the image files that contain the single icon. + /// Based on a string format, replacing {0} with the size. For example C:\Folder\Icon{0}.png + /// If batch icon files are provided, SolidWorks uses the first icon (no index support). + /// + public string IconPathFormat { get; set; } + + /// + /// Text displayed in the SolidWorks status bar and as a tooltip when the user moves the pointer over the icon + /// + public string Hint { get; set; } + + /// + /// The name to identify the command. For icons, Hint is used as the name since it's the only text that the icon has. + /// + string ICommandCreatable.Name => Hint; + + /// + /// The action to call when the item state requested + /// SolidWorks calls it each time when a context popup in the specified selection context shows + /// Try to avoid long operations on this callback + /// + public override Action OnStateCheck { get; set; } + + /// + /// The selection type that determines with which selection context the icon will be shown. + /// + /// + /// + /// Use predefined selection type constants from the class (e.g., , ). + /// + /// + public override SelectionType SelectionType { get; set; } = SelectionType.Everything; + + /// + /// Creates the command context icon + /// + /// Create information containing cookie + /// A list of created command context icons + /// Thrown if the item has already been created + public sealed override IEnumerable Create(ICommandContextCreateInfo info) + { + if (info is null) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu create info cannot be null")); + + if (string.IsNullOrWhiteSpace(Hint)) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu icon hint cannot be null or empty")); + + if (string.IsNullOrWhiteSpace(IconPathFormat)) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu icon path format cannot be null or empty")); + + // Call base class method to ensure that object isnt created and move object to created state if not + _ = base.Create(info); + + List created = []; + + // Create commands for each SW document type + if (VisibleForAssemblies) + created.Add(new CommandContextIconCreated(this, info.SolidWorksCookie, DocumentType.Assembly)); + if (VisibleForDrawings) + created.Add(new CommandContextIconCreated(this, info.SolidWorksCookie, DocumentType.Drawing)); + if (VisibleForParts) + created.Add(new CommandContextIconCreated(this, info.SolidWorksCookie, DocumentType.Part)); + + return created; + } +} diff --git a/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIconCreated.cs b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIconCreated.cs new file mode 100644 index 00000000..f7a0105a --- /dev/null +++ b/SolidDna/CADBooster.SolidDna/SolidWorks/CommandManager/Item/ContextMenuItems/CommandContextIconCreated.cs @@ -0,0 +1,139 @@ +using SolidWorks.Interop.sldworks; +using System.Linq; + +namespace CADBooster.SolidDna; + +/// +/// Represents a created command context icon in the SolidWorks +/// +internal class CommandContextIconCreated : CommandContextCreatedBase +{ + /// + /// Text displayed in the SolidWorks status bar and as a tooltip when the user moves the pointer over the icon + /// + public string Hint { get; } + + /// + /// The name for identification of this created context icon. This is the Hint text displayed as a tooltip on mouse hover. + /// + public sealed override string Name => Hint; + + /// + /// Initializes a new command context icon in the SolidWorks UI + /// + /// The icon configuration + /// The SolidWorks add-in cookie + /// The document type this icon applies to + public CommandContextIconCreated(CommandContextIcon commandContextIcon, + int solidWorksCookie, + DocumentType documentType) : base(commandContextIcon, documentType) + { + if (solidWorksCookie <= 0) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + $"Invalid SolidWorks cookie value: {solidWorksCookie}. Cookie must be positive")); + + if (commandContextIcon is null) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Command context icon cannot be null")); + + if (string.IsNullOrWhiteSpace(commandContextIcon.IconPathFormat)) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu icon path format cannot be null or empty")); + + if (SelectionType.IsSpecificFeatureType) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + $"Context icons do not support specific feature selection types. " + + $"Selection type '{SelectionType.EnumValue}_{SelectionType.StringValue}' is a specific feature type. " + + $"Only {nameof(CommandContextItem)} supports specific feature types from {nameof(SpecificFeatureSelectionType)}")); + + Hint = commandContextIcon.Hint; + + // The list of icons. There should be a one multi sized icon. + var icons = Icons.GetArrayFromDictionary(Icons.GetFormattedPathDictionary(commandContextIcon.IconPathFormat)); + + // Validate that we have at least one icon and all paths are valid + if (icons is null || icons.Length == 0) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu icon must have at least one icon")); + + if (icons.Any(string.IsNullOrWhiteSpace)) + throw new SolidDnaException( + SolidDnaErrors.CreateError(SolidDnaErrorTypeCode.SolidWorksCommandManager, + SolidDnaErrorCode.SolidWorksCommandManagerError, + "Context menu icon contains invalid (null or empty) icon paths")); + + // Get the SolidWorks frame to add the menu icon + using var frame = new SolidDnaObject