Browse Source

Progress on refactoring nav

Charlie Kindel 3 years ago
parent
commit
1bc2c79938
4 changed files with 361 additions and 39 deletions
  1. 1 1
      Terminal.Gui/Core/View.cs
  2. 114 20
      Terminal.Gui/Windows/Wizard.cs
  3. 31 18
      UICatalog/Scenarios/Wizards.cs
  4. 215 0
      UnitTests/WizardTests.cs

+ 1 - 1
Terminal.Gui/Core/View.cs

@@ -2399,7 +2399,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		bool oldEnabled;
+		bool oldEnabled = true;
 
 		/// <inheritdoc/>
 		public override bool Enabled {

+ 114 - 20
Terminal.Gui/Windows/Wizard.cs

@@ -22,6 +22,11 @@ namespace Terminal.Gui {
 		/// <remarks>
 		/// If <see cref="Button"/>s are added, do not set <see cref="Button.IsDefault"/> to true as this will conflict
 		/// with the Next button of the Wizard.
+		/// 
+		/// Subscribe to the <see cref="View.VisibleChanged"/> event to be notified when the step is active; see also: <see cref="Wizard.StepChanged"/>.
+		/// 
+		/// To enable or disable a step from being shown to the user, set <see cref="View.Enabled"/>.
+		/// 
 		/// </remarks>
 		public class WizardStep : View {
 			/// <summary>
@@ -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 ();
+			//}
+
+
 			/// <summary>
 			/// 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,12 +294,51 @@ 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 ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Causes the wizad to move to the next enabled step (or last step if <see cref="CurrentStep"/> is not set). 
+		/// If there is no previous step, does nothing.
+		/// </summary>
+		public void GoNext ()
+		{
+			var nextStep = GetNextStep ();
+			if (nextStep != null) {
+				GoToStep (nextStep);
+			}
+		}
+
+		/// <summary>
+		/// Returns the next enabled <see cref="WizardStep"/> after the current step. Takes into account steps which
+		/// are disabled. If <see cref="CurrentStep"/> is `null` returns the first enabled step.
+		/// </summary>
+		/// <returns>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.</returns>
+		public WizardStep GetNextStep ()
+		{
+			LinkedListNode<WizardStep> 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 ()
@@ -294,11 +346,50 @@ namespace Terminal.Gui {
 			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 ();
+			}
+		}
+
+		/// <summary>
+		/// Causes the wizad to move to the previous enabled step (or first step if <see cref="CurrentStep"/> is not set). 
+		/// If there is no previous step, does nothing.
+		/// </summary>
+		public void GoBack ()
+		{
+			var previous = GetPreviousStep ();
+			if (previous != null) {
+				GoToStep (previous);
+			}
+		}
+
+		/// <summary>
+		/// Returns the first enabled <see cref="WizardStep"/> before the current step. Takes into account steps which
+		/// are disabled. If <see cref="CurrentStep"/> is `null` returns the last enabled step.
+		/// </summary>
+		/// <returns>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.</returns>
+		public WizardStep GetPreviousStep ()
+		{
+			LinkedListNode<WizardStep> 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<WizardStep> steps = new LinkedList<WizardStep> ();
@@ -449,7 +540,7 @@ namespace Terminal.Gui {
 		public WizardStep CurrentStep {
 			get => currentStep;
 			set {
-				GotoStep (value);
+				GoToStep (value);
 			}
 		}
 
@@ -484,9 +575,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="newStep">The step to go to.</param>
 		/// <returns>True if the transition to the step succeeded. False if the step was not found or the operation was cancelled.</returns>
-		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 ();

+ 31 - 18
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.StepChanged += (args) => {
-						if (args.NewStep == fourthStep) {
-							var btn = MessageBox.ErrorQuery ("Wizards", "Yay. Moved to Step Four", "Ok");
-						}
-					};
+					//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");
+					//	}
+					//};
 
 					scrollBar.ChangedPosition += () => {
 						someText.TopRow = scrollBar.Position;

+ 215 - 0
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
+		}
 	}
 }