chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
 
namespace OpenTap
{
    /// <summary>
    /// This annotation represents a context menu for a given setting. Multiple of these might be present in a single
    /// AnnotationCollection. 
    /// </summary>
    public sealed class MenuAnnotation : IOwnedAnnotation
    {
        /// <summary> Creates a 'member' menu annotation. </summary>
        internal MenuAnnotation(IMemberData member, ITypeData type, AnnotationCollection annotations)
        {
            this.member = member;
            this.type = type;
            rootAnnotations = annotations;
        }
        
        /// <summary> Creates a 'member' menu annotation. </summary>
        /// <param name="member"> Which member of an object should the menu be generated for.</param>
        /// <param name="type"> Set this if menu items for the type should be generated. </param>
        internal MenuAnnotation(IMemberData member, ITypeData type)
        {
            this.member = member;
            this.type = type;
        }
 
        /// <summary> Create a 'type' menu annotation. </summary>
        /// <param name="type"> The type for which the menu should be generated. </param>
        internal MenuAnnotation(ITypeData type) => this.type = type;
 
        /// <summary> The member used to construct menu items. This maybe be null if 'type' has a value.</summary>
        readonly IMemberData member;
        
        /// <summary> The type used to construct menu items. This maybe be null if 'model' has a value.</summary>
        readonly ITypeData type;
        
        List<AnnotationCollection> annotations;
        List<IMenuModel> models;
        object[] source;
        readonly AnnotationCollection rootAnnotations;
 
        List<IMenuModel> getModels()
        {
            var member = this.member;
            var type = this.type;
            if (rootAnnotations != null)
            {
                var val2 = rootAnnotations.Get<IObjectValueAnnotation>()?.Value;
                if (val2 != null)
                    type = TypeData.GetTypeData(val2);
            }
            
            if (models == null)
            {
                models = new List<IMenuModel>();
                
                // get models for type menu annotations
                if (type != null)
                {
                    var factoryTypes = TypeData.GetDerivedTypes<ITypeMenuModelFactory>();
                    
                    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<IMenuModelFactory>();
                    
                    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<BrowsableAttribute>();
 
            // Browsable overrides everything
            if (browsable != null) return browsable.Browsable;
 
            var xmlIgnore = m.GetAttribute<XmlIgnoreAttribute>();
            if (xmlIgnore != null)
                return false;
 
            if (m is IMemberData mem)
            {
                if (m.HasAttribute<OutputAttribute>())
                    return true;
                if (!mem.Writable || !mem.Readable)
                    return false;
                return true;
            }
            return false;
        }
        
        void generateAnnotations()
        {
            List<IMenuModel> models = getModels();
            List<AnnotationCollection> items = new List<AnnotationCollection>();
            foreach (var model in models)
            {
                var a = AnnotationCollection.Annotate(model);
 
                var members = a.Get<IMembersAnnotation>()?.Members
                    .Where(x =>
                    {
                        var member = x.Get<IMemberAnnotation>()?.Member;
                        if (member == null) return false;
                        return FilterDefault2(member);
                    });
                if(members == null) continue;
                items.AddRange(members);
            }
            annotations = items;
        }
 
        /// <summary> Gets the menu items associated with this Menu. </summary>
        public IEnumerable<AnnotationCollection> 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();
        }
    }
 
    /// <summary> Factory class for build menus. This can be used to extend member with additional menu annotations.</summary>
    public interface IMenuModelFactory : ITapPlugin
    {
        /// <summary> Create model should create exactly one IMenuItemModel whose members will be used in the MenuAnnotation. </summary>
        /// <param name="member">The member to show the menu for.</param>
        /// <returns>Shall return null if the model does not support the member.</returns>
        IMenuModel CreateModel(IMemberData member);
    }
 
    /// <summary> Factory class for build menus. This can be used to extend a type with additional menu annotations.</summary>
    public interface ITypeMenuModelFactory : ITapPlugin
    {
        /// <summary> Create model should create exactly one IMenuItemModel whose members will be used in the MenuAnnotation. </summary>
        /// <param name="type">The member to show the menu for.</param>
        /// <returns>Shall return null if the model does not support the member.</returns>
        ITypeMenuModel CreateModel(ITypeData type);
    }
 
    /// <summary> Base class for things in a menu item. </summary>
    public interface IMenuModel
    {
        /// <summary> The source for the menu item. This may be more than one element.
        /// It is strongly recommended to explicitly implement this property. </summary>
        object[] Source { get; set; }
    }
 
    /// <summary> menu model for members </summary>
    public interface IMemberMenuModel : IMenuModel
    {
        /// <summary> The member this menu model represents. </summary>
        IMemberData Member { get; }
    }
    
    /// <summary> menu model for types </summary>
    public interface ITypeMenuModel : IMenuModel
    {
        
    }
    
 
    /// <summary>
    /// A MenuModel that can signal about it's current state (Enabled/Disabled). This is useful to dynamically disable the menu model.
    /// </summary>
    public interface IMenuModelState : IMenuModel
    {
        /// <summary> Gets if the menu model is enabled or disabled. </summary>
        bool Enabled { get; }
    }
 
    /// <summary> Menu models for test step properties. </summary>
    public interface ITestStepMenuModel
    {
        /// <summary> The selected test steps. </summary>
        ITestStepParent[] Source { get; }
        /// <summary> Selected Member </summary>
        IMemberData Member { get; }
    }
 
    /// <summary>
    /// Icon names defined here others might be defined by plugins. These names are used by IconAnnotationAttribute. 
    /// </summary>
    public static class IconNames
    {
        const string Common = nameof(OpenTap)+ "." + nameof(IconNames) + ".";
        /// <summary> Parameterize command </summary>
        public const string Parameterize = Common + nameof(Parameterize);
        /// <summary> Parameterize command </summary>
        public const string ParameterizeOnTestPlan = Common + nameof(ParameterizeOnTestPlan);
        /// <summary> Parameterize command </summary>
        public const string ParameterizeOnParent = Common + nameof(ParameterizeOnParent);
        /// <summary> Unparameterize command </summary>
        public const string Unparameterize = Common + nameof(Unparameterize);
        /// <summary> Parameterize command </summary>
        public const string EditParameter = Common + nameof(EditParameter);
        
        /// <summary> Parameterized icon name.</summary>
        public const string Parameterized = Common + nameof(Parameterized);
        /// <summary> Remove Parameter Command.</summary>
        public const string RemoveParameter = Common + nameof(RemoveParameter);
        /// <summary> Command to assign an output. </summary>
        public const string AssignOutput = Common + nameof(AssignOutput);
        /// <summary> Command to unassign an output. </summary>
        public const string UnassignOutput = Common + nameof(UnassignOutput);
        /// <summary> This is an output.</summary>
        public const string Output = Common + nameof(Output);
        /// <summary> This is an output.</summary>
        public const string OutputAssigned = Common + nameof(OutputAssigned);
        /// <summary> This is an input</summary>
        public const string Input = Common + nameof(Input);
        /// <summary> Add a mixin. </summary>
        public const string AddMixin = Common + nameof(AddMixin);
        /// <summary> Modifies a mixin. </summary>
        public const string ModifyMixin = Common + nameof(ModifyMixin);
        /// <summary> Remove a mixin. </summary>
        public const string RemoveMixin = Common + nameof(RemoveMixin);
        
    }
}