using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Xml.Serialization; namespace OpenTap { /// /// This annotation represents a context menu for a given setting. Multiple of these might be present in a single /// AnnotationCollection. /// public sealed class MenuAnnotation : IOwnedAnnotation { /// Creates a 'member' menu annotation. internal MenuAnnotation(IMemberData member, ITypeData type, AnnotationCollection annotations) { this.member = member; this.type = type; rootAnnotations = annotations; } /// Creates a 'member' menu annotation. /// Which member of an object should the menu be generated for. /// Set this if menu items for the type should be generated. internal MenuAnnotation(IMemberData member, ITypeData type) { this.member = member; this.type = type; } /// Create a 'type' menu annotation. /// The type for which the menu should be generated. internal MenuAnnotation(ITypeData type) => this.type = type; /// The member used to construct menu items. This maybe be null if 'type' has a value. readonly IMemberData member; /// The type used to construct menu items. This maybe be null if 'model' has a value. readonly ITypeData type; List annotations; List models; object[] source; readonly AnnotationCollection rootAnnotations; List getModels() { var member = this.member; var type = this.type; if (rootAnnotations != null) { var val2 = rootAnnotations.Get()?.Value; if (val2 != null) type = TypeData.GetTypeData(val2); } if (models == null) { models = new List(); // get models for type menu annotations if (type != null) { var factoryTypes = TypeData.GetDerivedTypes(); foreach (var factoryType in factoryTypes) { if (factoryType.CanCreateInstance == false) continue; try { var factory = (ITypeMenuModelFactory)factoryType.CreateInstance(); IMenuModel model = factory.CreateModel(type); if (model == null) continue; model.Source = source; if (model is IMenuModelState active && active.Enabled == false) continue; models.Add(model); } catch { } } } if(member != null) { // get models for member menu annotations var factoryTypes = TypeData.GetDerivedTypes(); foreach (var factoryType in factoryTypes) { if (factoryType.CanCreateInstance == false) continue; try { var factory = (IMenuModelFactory)factoryType.CreateInstance(); IMenuModel model = factory.CreateModel(member); if (model == null) continue; model.Source = source; if (model is IMenuModelState active && active.Enabled == false) continue; models.Add(model); } catch { } } } } return models; } private static bool FilterDefault2(IReflectionData m) { var browsable = m.GetAttribute(); // Browsable overrides everything if (browsable != null) return browsable.Browsable; var xmlIgnore = m.GetAttribute(); if (xmlIgnore != null) return false; if (m is IMemberData mem) { if (m.HasAttribute()) return true; if (!mem.Writable || !mem.Readable) return false; return true; } return false; } void generateAnnotations() { List models = getModels(); List items = new List(); foreach (var model in models) { var a = AnnotationCollection.Annotate(model); var members = a.Get()?.Members .Where(x => { var member = x.Get()?.Member; if (member == null) return false; return FilterDefault2(member); }); if(members == null) continue; items.AddRange(members); } annotations = items; } /// Gets the menu items associated with this Menu. public IEnumerable MenuItems { get { if (annotations == null) generateAnnotations(); return annotations; } } void IOwnedAnnotation.Read(object source) { this.source = source as object[] ?? new []{source}; if (annotations == null) return; foreach (var model in models) model.Source = this.source; foreach(var annotation in annotations) annotation.Read(); } void IOwnedAnnotation.Write(object source) { this.source = source as object[] ?? new []{source}; if (annotations == null) return; foreach (var model in models) model.Source = this.source; foreach(var annotation in annotations) annotation.Write(); } } /// Factory class for build menus. This can be used to extend member with additional menu annotations. public interface IMenuModelFactory : ITapPlugin { /// Create model should create exactly one IMenuItemModel whose members will be used in the MenuAnnotation. /// The member to show the menu for. /// Shall return null if the model does not support the member. IMenuModel CreateModel(IMemberData member); } /// Factory class for build menus. This can be used to extend a type with additional menu annotations. public interface ITypeMenuModelFactory : ITapPlugin { /// Create model should create exactly one IMenuItemModel whose members will be used in the MenuAnnotation. /// The member to show the menu for. /// Shall return null if the model does not support the member. ITypeMenuModel CreateModel(ITypeData type); } /// Base class for things in a menu item. public interface IMenuModel { /// The source for the menu item. This may be more than one element. /// It is strongly recommended to explicitly implement this property. object[] Source { get; set; } } /// menu model for members public interface IMemberMenuModel : IMenuModel { /// The member this menu model represents. IMemberData Member { get; } } /// menu model for types public interface ITypeMenuModel : IMenuModel { } /// /// A MenuModel that can signal about it's current state (Enabled/Disabled). This is useful to dynamically disable the menu model. /// public interface IMenuModelState : IMenuModel { /// Gets if the menu model is enabled or disabled. bool Enabled { get; } } /// Menu models for test step properties. public interface ITestStepMenuModel { /// The selected test steps. ITestStepParent[] Source { get; } /// Selected Member IMemberData Member { get; } } /// /// Icon names defined here others might be defined by plugins. These names are used by IconAnnotationAttribute. /// public static class IconNames { const string Common = nameof(OpenTap)+ "." + nameof(IconNames) + "."; /// Parameterize command public const string Parameterize = Common + nameof(Parameterize); /// Parameterize command public const string ParameterizeOnTestPlan = Common + nameof(ParameterizeOnTestPlan); /// Parameterize command public const string ParameterizeOnParent = Common + nameof(ParameterizeOnParent); /// Unparameterize command public const string Unparameterize = Common + nameof(Unparameterize); /// Parameterize command public const string EditParameter = Common + nameof(EditParameter); /// Parameterized icon name. public const string Parameterized = Common + nameof(Parameterized); /// Remove Parameter Command. public const string RemoveParameter = Common + nameof(RemoveParameter); /// Command to assign an output. public const string AssignOutput = Common + nameof(AssignOutput); /// Command to unassign an output. public const string UnassignOutput = Common + nameof(UnassignOutput); /// This is an output. public const string Output = Common + nameof(Output); /// This is an output. public const string OutputAssigned = Common + nameof(OutputAssigned); /// This is an input public const string Input = Common + nameof(Input); /// Add a mixin. public const string AddMixin = Common + nameof(AddMixin); /// Modifies a mixin. public const string ModifyMixin = Common + nameof(ModifyMixin); /// Remove a mixin. public const string RemoveMixin = Common + nameof(RemoveMixin); } }