diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 0c40f67c5..8de8708b3 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2399,7 +2399,7 @@ namespace Terminal.Gui { } } - bool oldEnabled; + bool oldEnabled = true; /// public override bool Enabled { diff --git a/Terminal.Gui/Windows/Wizard.cs b/Terminal.Gui/Windows/Wizard.cs index ea762233c..abb9de422 100644 --- a/Terminal.Gui/Windows/Wizard.cs +++ b/Terminal.Gui/Windows/Wizard.cs @@ -22,6 +22,11 @@ namespace Terminal.Gui { /// /// If s are added, do not set to true as this will conflict /// with the Next button of the Wizard. + /// + /// Subscribe to the event to be notified when the step is active; see also: . + /// + /// To enable or disable a step from being shown to the user, set . + /// /// public class WizardStep : View { /// @@ -94,6 +99,7 @@ namespace Terminal.Gui { helpTextView.ReadOnly = true; helpTextView.WordWrap = true; this.Add (helpTextView); + ShowHide (); var scrollBar = new ScrollBarView (helpTextView, true); @@ -143,6 +149,13 @@ namespace Terminal.Gui { this.Add (scrollBar); } + //public override void OnEnabledChanged() + //{ + // if (Enabled) { } + // base.OnEnabledChanged (); + //} + + /// /// If true (the default) the help will be visible. If false, the help will not be shown and the control pane will /// fill the wizard step. @@ -281,24 +294,102 @@ namespace Terminal.Gui { var args = new WizardButtonEventArgs (); MovingNext?.Invoke (args); if (!args.Cancel) { - var current = steps.Find (CurrentStep); - if (current != null && current.Next != null) { - GotoStep (current.Next.Value); - } + GoNext (); } } } + /// + /// Causes the wizad to move to the next enabled step (or last step if is not set). + /// If there is no previous step, does nothing. + /// + public void GoNext () + { + var nextStep = GetNextStep (); + if (nextStep != null) { + GoToStep (nextStep); + } + } + + /// + /// Returns the next enabled after the current step. Takes into account steps which + /// are disabled. If is `null` returns the first enabled step. + /// + /// The next step after the current step, if there is one; otherwise returns `null`, which + /// indicates either there are no enabled steps or the current step is the last enabled step. + public WizardStep GetNextStep () + { + LinkedListNode step = null; + if (CurrentStep == null) { + // Get last step, assume it is next + step = steps.First; + } else { + // Get the step after current + step = steps.Find (CurrentStep); + if (step != null) { + step = step.Next; + } + } + + // step now points to the potential next step + while (step != null) { + if (step.Value.Enabled) { + return step.Value; + } + step = step.Next; + } + return null; + } + private void BackBtn_Clicked () { var args = new WizardButtonEventArgs (); MovingBack?.Invoke (args); if (!args.Cancel) { - var current = steps.Find (CurrentStep); - if (current != null && current.Previous != null) { - GotoStep (current.Previous.Value); + GoBack (); + } + } + + /// + /// Causes the wizad to move to the previous enabled step (or first step if is not set). + /// If there is no previous step, does nothing. + /// + public void GoBack () + { + var previous = GetPreviousStep (); + if (previous != null) { + GoToStep (previous); + } + } + + /// + /// Returns the first enabled before the current step. Takes into account steps which + /// are disabled. If is `null` returns the last enabled step. + /// + /// The first step ahead of the current step, if there is one; otherwise returns `null`, which + /// indicates either there are no enabled steps or the current step is the first enabled step. + public WizardStep GetPreviousStep () + { + LinkedListNode step = null; + if (CurrentStep == null) { + // Get last step, assume it is previous + step = steps.Last; + } else { + // Get the step before current + step = steps.Find (CurrentStep); + if (step != null) { + step = step.Previous; } } + + // step now points to the potential previous step + while (step != null) { + if (step.Value.Enabled) { + return step.Value; + } + step = step.Previous; + } + return null; } private LinkedList steps = new LinkedList (); @@ -449,7 +540,7 @@ namespace Terminal.Gui { public WizardStep CurrentStep { get => currentStep; set { - GotoStep (value); + GoToStep (value); } } @@ -484,9 +575,9 @@ namespace Terminal.Gui { /// /// The step to go to. /// True if the transition to the step succeeded. False if the step was not found or the operation was cancelled. - public bool GotoStep (WizardStep newStep) + public bool GoToStep (WizardStep newStep) { - if (OnStepChanging (currentStep, newStep)) { + if (OnStepChanging (currentStep, newStep) || (newStep != null && !newStep.Enabled)) { return false; } @@ -495,17 +586,20 @@ namespace Terminal.Gui { step.Visible = (step == newStep); } - base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + newStep.Title : string.Empty)}"; + if (newStep != null) { - // Configure the Back button - backBtn.Text = newStep.BackButtonText != ustring.Empty ? newStep.BackButtonText : Strings.wzBack; // "_Back"; - backBtn.Visible = (newStep != steps.First.Value); + base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + newStep.Title : string.Empty)}"; - // Configure the Next/Finished button - if (newStep == steps.Last.Value) { - nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : Strings.wzFinish; // "Fi_nish"; - } else { - nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : Strings.wzNext; // "_Next..."; + // Configure the Back button + backBtn.Text = newStep.BackButtonText != ustring.Empty ? newStep.BackButtonText : Strings.wzBack; // "_Back"; + backBtn.Visible = (newStep != steps.First.Value); + + // Configure the Next/Finished button + if (newStep == steps.Last.Value) { + nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : Strings.wzFinish; // "Fi_nish"; + } else { + nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : Strings.wzNext; // "_Next..."; + } } // Set focus to the nav buttons @@ -515,7 +609,7 @@ namespace Terminal.Gui { nextfinishBtn.SetFocus (); } - var oldStep = currentStep; + var oldStep = currentStep; currentStep = newStep; LayoutSubviews (); diff --git a/UICatalog/Scenarios/Wizards.cs b/UICatalog/Scenarios/Wizards.cs index 80a178f78..c6f7e5c9b 100644 --- a/UICatalog/Scenarios/Wizards.cs +++ b/UICatalog/Scenarios/Wizards.cs @@ -176,15 +176,15 @@ namespace UICatalog.Scenarios { lbl = new Label () { Text = "Last Name: ", AutoSize = true, X = 1, Y = Pos.Bottom (lbl) }; var lastNameField = new TextField () { Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; viewForControls.Add (lbl, lastNameField); - var checkBox = new CheckBox () { Text = "Un-check me!", Checked = true, X = Pos.Left (lastNameField), Y = Pos.Bottom (lastNameField) }; - viewForControls.Add (checkBox); + var thirdStepEnabledCeckBox = new CheckBox () { Text = "Enable Step _3", Checked = false, X = Pos.Left (lastNameField), Y = Pos.Bottom (lastNameField) }; + viewForControls.Add (thirdStepEnabledCeckBox); // Add a frame to demonstrate difference between adding controls to // WizardStep.Controls vs. WizardStep directly. This is here to demonstrate why // adding to .Controls is preferred. var frame = new FrameView ($"A Broken Frame - {frameMsg}") { X = 0, - Y = Pos.Bottom (checkBox) + 2, + Y = Pos.Bottom (thirdStepEnabledCeckBox) + 2, Width = Dim.Fill (), Height = 4, //ColorScheme = Colors.Error, @@ -192,10 +192,23 @@ namespace UICatalog.Scenarios { frame.Add (new TextField ("This is a TextField inside of the frame.")); viewForControls.Add (frame); - // Add 3rd step - var thirdStep = new Wizard.WizardStep ("Third Step"); + // Add 3rd (optional) step + var thirdStep = new Wizard.WizardStep ("Third Step (Optional)"); + + thirdStep.Enabled = thirdStepEnabledCeckBox.Checked; + thirdStepEnabledCeckBox.Toggled += (args) => { + thirdStep.Enabled = thirdStepEnabledCeckBox.Checked; + }; + wizard.AddStep (thirdStep); - thirdStep.HelpText = "This is the help text for the Third Step."; + thirdStep.HelpText = "This is step is optional (WizardStep.Enabled = false). Enable it with the checkbox in Step 2."; + var step3Label = new Label () { + Text = "This step is optional.", + X = 0, + Y = 0, + AutoSize = true + }; + thirdStep.Controls.Add (step3Label); var progLbl = new Label () { Text = "Third Step ProgressBar: ", AutoSize = true, X = 1, Y = 10 }; var progressBar = new ProgressBar () { X = Pos.Right (progLbl), @@ -222,18 +235,18 @@ namespace UICatalog.Scenarios { //fourthStep.NextButtonText = "4"; var scrollBar = new ScrollBarView (someText, true); - wizard.StepChanging += (args) => { - if (args.NewStep == fourthStep) { - var btn = MessageBox.ErrorQuery ("Wizards", "Move to Step Four?", "Yes", "No"); - args.Cancel = btn == 1; - } - }; + //wizard.StepChanging += (args) => { + // if (args.NewStep == fourthStep) { + // var btn = MessageBox.ErrorQuery ("Wizards", "Move to Step Four?", "Yes", "No"); + // args.Cancel = btn == 1; + // } + //}; - wizard.StepChanged += (args) => { - if (args.NewStep == fourthStep) { - var btn = MessageBox.ErrorQuery ("Wizards", "Yay. Moved to Step Four", "Ok"); - } - }; + //wizard.StepChanged += (args) => { + // if (args.NewStep == fourthStep) { + // var btn = MessageBox.ErrorQuery ("Wizards", "Yay. Moved to Step Four", "Ok"); + // } + //}; scrollBar.ChangedPosition += () => { someText.TopRow = scrollBar.Position; diff --git a/UnitTests/WizardTests.cs b/UnitTests/WizardTests.cs index b76eeb9a0..15d80a960 100644 --- a/UnitTests/WizardTests.cs +++ b/UnitTests/WizardTests.cs @@ -191,5 +191,220 @@ namespace Terminal.Gui.Views { Application.End (Application.Begin (wizard)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output); } + + [Fact, AutoInitShutdown] + public void Navigate_GetPreviousStep_Correct () + { + var wizard = new Wizard (); + + // If no steps should be null + Assert.Null (wizard.GetPreviousStep ()); + + var step1 = new Wizard.WizardStep ("step1"); + wizard.AddStep (step1); + + // If no current step, should be last step + Assert.Equal (step1.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + wizard.CurrentStep = step1; + // If there is 1 step it's current step should be null + Assert.Null (wizard.GetPreviousStep ()); + + // If one disabled step should be null + step1.Enabled = false; + Assert.Null (wizard.GetPreviousStep ()); + + // If two steps and at 2 and step 1 is `Enabled = true`should be step1 + var step2 = new Wizard.WizardStep ("step2"); + wizard.AddStep (step2); + wizard.CurrentStep = step2; + step1.Enabled = true; + Assert.Equal (step1.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + // If two steps and at 2 and step 1 is `Enabled = false` should be null + step1.Enabled = false; + Assert.Null (wizard.GetPreviousStep ()); + + // If three steps with Step2.Enabled = true + // At step 1 should be null + // At step 2 should be step 1 + // At step 3 should be step 2 + var step3 = new Wizard.WizardStep ("step3"); + wizard.AddStep (step3); + step1.Enabled = true; + wizard.CurrentStep = step1; + step2.Enabled = true; + step3.Enabled = true; + Assert.Null (wizard.GetPreviousStep ()); + wizard.CurrentStep = step2; + Assert.Equal (step1.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + wizard.CurrentStep = step3; + Assert.Equal (step2.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + // If three steps with Step2.Enabled = false + // At step 1 should be null + // At step 3 should be step1 + step1.Enabled = true; + step2.Enabled = false; + step3.Enabled = true; + wizard.CurrentStep = step1; + Assert.Null (wizard.GetPreviousStep ()); + wizard.CurrentStep = step3; + Assert.Equal (step1.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + // If three steps with Step1.Enabled = false & Step2.Enabled = false + // At step 3 should be null + + // If no current step, GetPreviousStep provides equivalent to GetLastStep + wizard.CurrentStep = null; + step1.Enabled = true; + step2.Enabled = true; + step3.Enabled = true; + Assert.Equal (step3.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = true; + step3.Enabled = true; + Assert.Equal (step3.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = false; + step3.Enabled = true; + Assert.Equal (step3.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = true; + step3.Enabled = false; + Assert.Equal (step2.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + + step1.Enabled = true; + step2.Enabled = false; + step3.Enabled = false; + Assert.Equal (step1.Title.ToString(), wizard.GetPreviousStep ().Title.ToString()); + } + + [Fact, AutoInitShutdown] + public void Navigate_GetNextStep_Correct () + { + var wizard = new Wizard (); + + // If no steps should be null + Assert.Null (wizard.GetNextStep ()); + + var step1 = new Wizard.WizardStep ("step1"); + wizard.AddStep (step1); + + // If no current step, should be first step + Assert.Equal (step1.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + wizard.CurrentStep = step1; + // If there is 1 step it's current step should be null + Assert.Null (wizard.GetNextStep ()); + + // If one disabled step should be null + step1.Enabled = false; + Assert.Null (wizard.GetNextStep ()); + + // If two steps and at 1 and step 2 is `Enabled = true`should be step 2 + var step2 = new Wizard.WizardStep ("step2"); + wizard.AddStep (step2); + Assert.Equal (step2.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + // If two steps and at 1 and step 2 is `Enabled = false` should be null + step1.Enabled = true; + wizard.CurrentStep = step1; + step2.Enabled = false; + Assert.Null (wizard.GetNextStep ()); + + // If three steps with Step2.Enabled = true + // At step 1 should be step 2 + // At step 2 should be step 3 + // At step 3 should be null + var step3 = new Wizard.WizardStep ("step3"); + wizard.AddStep (step3); + step1.Enabled = true; + wizard.CurrentStep = step1; + step2.Enabled = true; + step3.Enabled = true; + Assert.Equal (step2.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + wizard.CurrentStep = step2; + Assert.Equal (step3.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + wizard.CurrentStep = step3; + Assert.Null (wizard.GetNextStep ()); + + // If three steps with Step2.Enabled = false + // At step 1 should be step 3 + // At step 3 should be null + step1.Enabled = true; + wizard.CurrentStep = step1; + step2.Enabled = false; + step3.Enabled = true; + Assert.Equal (step3.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + wizard.CurrentStep = step3; + Assert.Null (wizard.GetNextStep ()); + + // If three steps with Step2.Enabled = false & Step3.Enabled = false + // At step 1 should be null + step1.Enabled = true; + wizard.CurrentStep = step1; + step2.Enabled = false; + step3.Enabled = false; + Assert.Null (wizard.GetNextStep ()); + + // If no current step, GetNextStep provides equivalent to GetFirstStep + wizard.CurrentStep = null; + step1.Enabled = true; + step2.Enabled = true; + step3.Enabled = true; + Assert.Equal (step1.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = true; + step3.Enabled = true; + Assert.Equal (step2.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = false; + step3.Enabled = true; + Assert.Equal (step3.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + step1.Enabled = false; + step2.Enabled = true; + step3.Enabled = false; + Assert.Equal (step2.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + + step1.Enabled = true; + step2.Enabled = false; + step3.Enabled = false; + Assert.Equal (step1.Title.ToString(), wizard.GetNextStep ().Title.ToString()); + } + + [Fact, AutoInitShutdown] + public void Navigate_GoNext_Works () + { + // If zero steps do nothing + + // If one step do nothing (enabled or disabled) + + // If two steps + // If current is 1 + // If 2 is enabled 2 becomes current + // If 2 is disabled 1 stays current + // If current is 2 does nothing + } + + [Fact, AutoInitShutdown] + public void Navigate_GoBack_Works () + { + // If zero steps do nothing + + // If one step do nothing (enabled or disabled) + + // If two steps + // If current is 1 does nothing + // If current is 2 does nothing + // If 1 is enabled 2 becomes current + // If 1 is disabled 1 stays current + } } } \ No newline at end of file