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);
}
}