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
//            Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.ComponentModel;
using System.Linq;
 
namespace OpenTap
{
    /// <summary>
    /// Identifies settings, properties, or methods that should only be valid/enabled when another property or setting has a certain value. 
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]
    public class EnabledIfAttribute : Attribute
    {
        /// <summary>
        /// Gets or sets if the property should be hidden when disabled.
        /// </summary>
        public bool HideIfDisabled { get; set; }
 
        /// <summary> Gets or sets if the enabling value is individual flags from an enum. </summary>
        public bool Flags { get; set; }
        
        /// <summary>  Gets or sets if the value should enable or disable(inverted) the setting. </summary>
        public bool Invert { get; set; }
 
        static readonly TraceSource log = Log.CreateSource("EnabledIf");
 
        /// <summary>
        /// Name of the property to enable. Must exactly match a name of a property in the current class. 
        /// </summary>
        public string PropertyName { get; }
 
        /// <summary>
        /// Value(s) the property must have for the item to be valid/enabled. If multiple values are specified, the item is enabled if just one value is equal. 
        /// If no values are specified, 'true' is the assumed value.
        /// </summary>
        // note, IComparable is not for equality comparing. It is meant for sorting, hence it does not really matter here.
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [Obsolete("Use Values instead.")]
        public IComparable[] PropertyValues => Values.OfType<IComparable>().ToArray();
        
        /// <summary>
        /// Value(s) the property must have for the item to be valid/enabled. If multiple values are specified, the item is enabled if just one value is equal. 
        /// If no values are specified, 'true' is the assumed value.
        /// </summary>
        public object[] Values { get; }
        /// <summary>
        /// Identifies settings, properties, or methods that are only valid/enabled when another property or setting has a certain value. 
        /// </summary>
        /// <param name="propertyName">Name of the property to enable. Must exactly match a name of a property in the current class. </param>
        /// <param name="propertyValues">Value(s) the property must have for the item to be valid/enabled. If multiple values are specified, the item is enabled if just one value is equal. 
        /// If no values are specified, 'true' is the assumed value.</param>
        public EnabledIfAttribute(string propertyName, params object[] propertyValues)
        {
            PropertyName = propertyName;
            if ((propertyValues == null) || (propertyValues.Length <= 0))
                Values = new object[] {true};
            else
                Values = propertyValues;
        }
 
        internal virtual (bool enabled, bool hidden) IsEnabled(object instance, ITypeData instanceType, out IMemberData dependentProp)
        {
            bool newEnabled = true;
            dependentProp = instanceType.GetMember(PropertyName);
 
            if (dependentProp == null)
            {
                // We cannot be sure that the step developer has used this attribute correctly
                // (could just be a typo in the (weakly typed) property name), thus we need to 
                // provide a good error message that leads the developer to where the error is.
                log.Warning("Could not find property '{0}' on '{1}'. EnabledIfAttribute can only refer to properties of the same class as the property it is decorating.", PropertyName, instanceType.Name);
                return (false, false);
            }
 
            var depValue = dependentProp.GetValue(instance);
                
            try
            {
                newEnabled = calcEnabled(this, depValue);
            }
            catch (ArgumentException)
            {
                // CompareTo throws ArgumentException when obj is not the same type as this instance.
                newEnabled = false;
            }
            bool hidden = false;
            if (HideIfDisabled)
                hidden = !newEnabled;
            return (newEnabled, hidden);
        }
 
        /// <summary> Returns true if a member is enabled. </summary>
        public static bool IsEnabled(IMemberData property, object instance,
            out IMemberData dependentProp, out IComparable dependentValue, out bool hidden)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));
            if (instance == null)
                throw new ArgumentNullException(nameof(instance));
            ITypeData instanceType = TypeData.GetTypeData(instance);
            var dependencyAttrs = property.GetAttributes<EnabledIfAttribute>();
            dependentProp = null;
            dependentValue = 0;
            hidden = false;
            bool enabled = true;
            foreach (var at in dependencyAttrs)
            {
                var (enabled2, hidden2) = at.IsEnabled(instance, instanceType, out dependentProp);
                
                enabled &= enabled2;
                if (hidden2)
                    hidden = true;
            }
            return enabled;
        }
 
        static bool calcEnabled(EnabledIfAttribute at, object value)
        {
            if(at.Invert)
                return !calcEnabled2(at, value);
            return calcEnabled2(at, value);
        }
 
        /// <summary> Calculate if an enabled if is enabled by a given value. </summary>
        static bool calcEnabled2(EnabledIfAttribute at, object depValue)
        {
            if (at.Flags)
            {
                int value = (int)Convert.ChangeType(depValue, TypeCode.Int32);
                foreach (var flag in at.Values)
                {
                    int flagCode = (int) Convert.ChangeType(flag, TypeCode.Int32);
                    if ((value & flagCode) != 0)
                        return true;
                }
                return false;
            }
 
            if (depValue is IEnabled e)
                depValue = e.IsEnabled;
 
            foreach (var val in at.Values)
            {
                if (Equals(val, depValue))
                    return true;
            }
            
            return false;
        }        
 
        /// <summary>
        /// Checks whether a given property is enabled according to the <see cref="EnabledIfAttribute"/>.
        /// </summary>
        /// <param name="at">The attribute enabling this property.</param>
        /// <param name="instance">Instance of the object that has 'property'.</param>
        /// <returns>true if property dependent property has the correct value.</returns>
        internal static bool IsEnabled(EnabledIfAttribute at, object instance)
        {
            IMemberData dependentProp = TypeData.GetTypeData(instance).GetMember(at.PropertyName);
            if (dependentProp == null)
            {
                // We cannot be sure that the step developer has used this attribute correctly
                // (could just be a typo in the (weakly typed) property name), thus we need to 
                // provide a good error message that leads the developer to where the error is.
                log.Warning(
                    $"Could not find property '{at.PropertyName}' on '{instance.GetType().Name}'. " +
                    $"EnabledIfAttribute can only refer to properties of the same class as the property it is decorating.");
                return false;
            }
 
            var depValue = dependentProp.GetValue(instance);
            try
            {
                return calcEnabled(at, depValue);
            }
            catch (ArgumentException)
            {
                // CompareTo throws ArgumentException when obj is not the same type as this instance.
                return false;
            }
        }
 
 
        /// <summary>
        /// Checks whether a given property is enabled according to the <see cref="EnabledIfAttribute"/>.
        /// </summary>
        /// <param name="instance">Instance that has property.</param>
        /// <param name="property">Property to be checked for if it is enabled.</param>
        /// <returns>True if property is enabled.</returns>
        public static bool IsEnabled(IMemberData property, object instance)
        {
            return IsEnabled(property, instance, out _, out _, out _);
        }
    }
}