using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace OpenTap { class TestStepMenuModel : IMemberMenuModel, ITestStepMenuModel, IMenuModelState { public static TestStepMenuModel FromSource(IMemberData member, object source) { if (source is ITestStepParent step) return new TestStepMenuModel(member, new [] {step}); if (source is IEnumerable array) return new TestStepMenuModel(member, array.OfType().ToArray()); return null; } public TestStepMenuModel(IMemberData member) => this.member = member; public TestStepMenuModel(IMemberData member, ITestStepParent[] source) { this.member = member; this.source = source; } ITestStepParent[] source; readonly IMemberData member; object[] IMenuModel.Source { get => source; set => source = value?.OfType().ToArray() ?? Array.Empty(); } /// Multiple item can be selected at once. ITestStepParent[] ITestStepMenuModel.Source => source; public IMemberData Member => member; public bool CanExecuteParameterize => ParameterManager.CanParameter(this) && (IsAnyOutputAssigned == false); [EnabledIf(nameof(CanExecuteParameterize), true, HideIfDisabled = true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] [Browsable(true)] [IconAnnotation(IconNames.Parameterize)] [Display("Parameterize...", "Parameterize this setting by creating, or adding to, an existing parameter.", Order: 1.0, Group: "Parameters")] public void Parameterize() => ParameterManager.CreateParameter(this, getCommonParent(), true); public bool HasTestPlanParent => source.FirstOrDefault()?.GetParent() != null; public bool CanAutoParameterize => ParameterManager.CanAutoParameterize(this.member, this.source); [EnabledIf(nameof(CanExecuteParameterize), true, HideIfDisabled = true)] [EnabledIf(nameof(HasTestPlanParent), true, HideIfDisabled = true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] [EnabledIf(nameof(CanAutoParameterize), true)] [Browsable(true)] [IconAnnotation(IconNames.ParameterizeOnTestPlan)] [Display("Parameterize On Test Plan", "Parameterize this setting by creating, or adding to, an existing external test plan parameter.", Order: 1.0, Group: "Parameters")] public void ParameterizeOnTestPlan() { var plan = source.FirstOrDefault().GetParent(); if (plan == null) return; ParameterManager.CreateParameter(this, plan, false); } // note: ParameterizeOnParent only works for things that does not have the test plan as a parent. // for steps with the test plan as a parent, ParameterizeOnTestPlan should be used. public bool HasSameParents => source.Select(x => x.Parent).OfType().Distinct().Take(2).Count() == 1; public bool CanExecutedParameterizeOnParent => CanExecuteParameterize && HasSameParents; [EnabledIf(nameof(CanExecutedParameterizeOnParent), true, HideIfDisabled = true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] [EnabledIf(nameof(CanAutoParameterize), true)] [Browsable(true)] [IconAnnotation(IconNames.ParameterizeOnParent)] [Display("Parameterize On Parent", "Parameterize this setting by creating, or adding to, an existing parameter.", Order: 1.0, Group: "Parameters")] public void ParameterizeOnParent() { var parents = source.Select(x => x.Parent); if(parents.Distinct().Count() == 1) ParameterManager.CreateParameter(this, parents.FirstOrDefault(), false); } bool isParameterized() => source.Any(x => DynamicMember.IsParameterized(member, x)); public bool IsParameterized => isParameterized(); public bool IsParameter => member is ParameterMemberData; public bool MultiSelect => source?.Length != 1; public bool TestPlanAllowsEdit => source .FirstNonDefault(step => step as TestPlan ?? step.GetParent()) ?.AllowEdit ?? true; public bool CanExecuteUnparameterize => ParameterManager.CanUnparameter(this) && (IsAnyOutputAssigned == false); [EnabledIf(nameof(CanExecuteUnparameterize), true, HideIfDisabled = true)] [EnabledIf(nameof(IsParameterized), true, HideIfDisabled = true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] [Display("Unparameterize", "Removes the parameterization of this setting.", Order: 1.0, Group: "Parameters")] [IconAnnotation(IconNames.Unparameterize)] [Browsable(true)] public void Unparameterize() => ParameterManager.Unparameterize(this); public bool CanEditParameter => ParameterManager.CanEditParameter(this); [Display("Edit Parameter", "Edit an existing parameterization.", Order: 1.0, Group: "Parameters")] [EnabledIf(nameof(CanEditParameter), true, HideIfDisabled = true)] [Browsable(true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] [IconAnnotation(IconNames.EditParameter)] [EnabledIf(nameof(IsParameter), true, HideIfDisabled = true)] [EnabledIf(nameof(MultiSelect), false, HideIfDisabled = true)] public void EditParameter() => ParameterManager.EditParameter(this); public bool CanRemoveParameter => member is IParameterMemberData && source.All(x => x is TestPlan); [Browsable(true)] [Display("Remove Parameter", "Remove a parameter.", Order: 1.0, Group: "Parameters")] [IconAnnotation(IconNames.RemoveParameter)] [EnabledIf(nameof(CanRemoveParameter), true, HideIfDisabled = true)] [EnabledIf(nameof(TestPlanAllowsEdit), true)] public void RemoveParameter() => ParameterManager.RemoveParameter(this); bool CalcAnyAvailableOutputs() { var scope = getCommonParent(); if(scope == null) return false; return new[] {scope}.Concat(scope.GetParents()) .SelectMany(x => AssignOutputDialog.GetAvailableOutputs(x, source, member.TypeDescriptor)) .Any(); } bool? anyAvailableOutputs; public bool AnyAvailableOutputs => (anyAvailableOutputs ?? (anyAvailableOutputs = CalcAnyAvailableOutputs())) ?? false; // Input/Output public bool CanAssignOutput => TestPlanAllowsEdit && source.Length > 0 && IsReadOnly == false && member.Writable && IsSweepable && !CanUnassignOutput && !IsParameterized && !IsAnyOutputAssigned; [Display("Assign Output", "Control this setting using an output.", Order: 2.0, Group: "Outputs")] [Browsable(true)] [IconAnnotation(IconNames.AssignOutput)] [EnabledIf(nameof(CanAssignOutput), true, HideIfDisabled = true)] [EnabledIf(nameof(AnyAvailableOutputs), true)] public void ControlUsingOutput() { var commonParent = getCommonParent(); var question = new AssignOutputDialog(member, commonParent, source); UserInput.Request(question); if (question.Response == ParameterManager.OkCancel.Cancel) return; var outputObject = question.Output?.Step; var outputMember = question.Output?.Member; if (outputObject != null && outputMember != null) { foreach(var srcItem in source) InputOutputRelation.Assign(srcItem, member, outputObject, outputMember); } } ITestStepParent getCommonParent() { // If the parents are as such: // A B C D [step a] // A B E F [step b] // A B C G H [step c] // A ist the root (Test Plan). The first common parent is then B. // notice the first two parents are the same for all, so the first common // parent is B. // Zip is being used to fetch only the similar ones. var parentlists = source .Select(x => (x?.GetParents() ?? Array.Empty()).Reverse()); return parentlists.Aggregate((x, y) => x.Zip(y, (x1,y1) => x1 == y1 ? x1 : null) .Where(x2 => x2 != null).ToArray()) .LastOrDefault(); } public bool IsSweepable => member.HasAttribute() == false; public bool IsReadOnly => source.Length > 0 && source?.Any(p => p is TestStep t && t.IsReadOnly) == true; public bool IsAnyOutputAssigned => source.Any(x => InputOutputRelation.IsInput(x, member)); public bool IsAnyInputAssigned => source.Any(x => InputOutputRelation.IsOutput(x, member)); public bool IsOutput => member.HasAttribute(); public bool CanUnassignOutput => TestPlanAllowsEdit && source.Length > 0 && IsReadOnly == false && member.Writable && IsAnyOutputAssigned; [Display("Unassign Output", "Unassign the output controlling this property.", Order: 2.0, Group: "Outputs")] [Browsable(true)] [IconAnnotation(IconNames.UnassignOutput)] [EnabledIf(nameof(CanUnassignOutput), true, HideIfDisabled = true)] public void UnassignOutput() { foreach (var step in source) { var con2 = InputOutputRelation.GetRelations(step) .FirstOrDefault(con => con.InputMember == member && con.InputObject == step); if(con2 != null) InputOutputRelation.Unassign(con2); } } bool IMenuModelState.Enabled => (source?.Length ?? 0) > 0; } class TestStepMenuItemsModelFactory : IMenuModelFactory { public IMenuModel CreateModel(IMemberData member) { if(member.DeclaringType.DescendsTo(typeof(ITestStepParent))) return new TestStepMenuModel(member); return null; } } }