// 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
{
///
/// Identifies settings, properties, or methods that should only be valid/enabled when another property or setting has a certain value.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]
public class EnabledIfAttribute : Attribute
{
///
/// Gets or sets if the property should be hidden when disabled.
///
public bool HideIfDisabled { get; set; }
/// Gets or sets if the enabling value is individual flags from an enum.
public bool Flags { get; set; }
/// Gets or sets if the value should enable or disable(inverted) the setting.
public bool Invert { get; set; }
static readonly TraceSource log = Log.CreateSource("EnabledIf");
///
/// Name of the property to enable. Must exactly match a name of a property in the current class.
///
public string PropertyName { get; }
///
/// 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.
///
// 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().ToArray();
///
/// 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.
///
public object[] Values { get; }
///
/// Identifies settings, properties, or methods that are only valid/enabled when another property or setting has a certain value.
///
/// Name of the property to enable. Must exactly match a name of a property in the current class.
/// 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.
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);
}
/// Returns true if a member is enabled.
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();
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);
}
/// Calculate if an enabled if is enabled by a given value.
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;
}
///
/// Checks whether a given property is enabled according to the .
///
/// The attribute enabling this property.
/// Instance of the object that has 'property'.
/// true if property dependent property has the correct value.
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;
}
}
///
/// Checks whether a given property is enabled according to the .
///
/// Instance that has property.
/// Property to be checked for if it is enabled.
/// True if property is enabled.
public static bool IsEnabled(IMemberData property, object instance)
{
return IsEnabled(property, instance, out _, out _, out _);
}
}
}