diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs
index 837881b44..8973d8edb 100644
--- a/Terminal.Gui/Application.cs
+++ b/Terminal.Gui/Application.cs
@@ -32,6 +32,77 @@ namespace Terminal.Gui;
/// TODO: Flush this out.
///
public static partial class Application {
+
+ // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+ // Encapsulate all setting of initial state for Application; Having
+ // this in a function like this ensures we don't make mistakes in
+ // guaranteeing that the state of this singleton is deterministic when Init
+ // starts running and after Shutdown returns.
+ internal static void ResetState ()
+ {
+ // Shutdown is the bookend for Init. As such it needs to clean up all resources
+ // Init created. Apps that do any threading will need to code defensively for this.
+ // e.g. see Issue #537
+ foreach (var t in _topLevels) {
+ t.Running = false;
+ t.Dispose ();
+ }
+ _topLevels.Clear ();
+ Current = null;
+ Top?.Dispose ();
+ Top = null;
+
+ // MainLoop stuff
+ MainLoop?.Dispose ();
+ MainLoop = null;
+ _mainThreadId = -1;
+ Iteration = null;
+ EndAfterFirstIteration = false;
+
+ // Driver stuff
+ if (Driver != null) {
+ Driver.SizeChanged -= Driver_SizeChanged;
+ Driver.KeyDown -= Driver_KeyDown;
+ Driver.KeyUp -= Driver_KeyUp;
+ Driver.MouseEvent -= Driver_MouseEvent;
+ Driver?.End ();
+ Driver = null;
+ }
+ // Don't reset ForceDriver; it needs to be set before Init is called.
+ //ForceDriver = string.Empty;
+ Force16Colors = false;
+ _forceFakeConsole = false;
+
+ // Run State stuff
+ NotifyNewRunState = null;
+ NotifyStopRunState = null;
+ MouseGrabView = null;
+ _initialized = false;
+
+ // Mouse
+ _mouseEnteredView = null;
+ WantContinuousButtonPressedView = null;
+ MouseEvent = null;
+ GrabbedMouse = null;
+ UnGrabbingMouse = null;
+ GrabbedMouse = null;
+ UnGrabbedMouse = null;
+
+ // Keyboard
+ AlternateBackwardKey = Key.Empty;
+ AlternateForwardKey = Key.Empty;
+ QuitKey = Key.Empty;
+ KeyDown = null;
+ KeyUp = null;
+ SizeChanging = null;
+
+ // Reset synchronization context to allow the user to run async/await,
+ // as the main loop has been ended, the synchronization context from
+ // gui.cs does no longer process any callbacks. See #1084 for more details:
+ // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+ SynchronizationContext.SetSynchronizationContext (syncContext: null);
+ }
+
///
/// Gets the that has been selected. See also .
///
@@ -66,7 +137,7 @@ public static partial class Application {
///
public static List SupportedCultures => _cachedSupportedCultures;
- static List GetSupportedCultures ()
+ internal static List GetSupportedCultures ()
{
var culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
@@ -241,55 +312,6 @@ public static partial class Application {
ResetState ();
PrintJsonErrors ();
}
-
- // Encapsulate all setting of initial state for Application; Having
- // this in a function like this ensures we don't make mistakes in
- // guaranteeing that the state of this singleton is deterministic when Init
- // starts running and after Shutdown returns.
- static void ResetState ()
- {
- // Shutdown is the bookend for Init. As such it needs to clean up all resources
- // Init created. Apps that do any threading will need to code defensively for this.
- // e.g. see Issue #537
- foreach (var t in _topLevels) {
- t.Running = false;
- t.Dispose ();
- }
- _topLevels.Clear ();
- Current = null;
- Top?.Dispose ();
- Top = null;
-
- // BUGBUG: OverlappedTop is not cleared here, but it should be?
-
- MainLoop?.Dispose ();
- MainLoop = null;
- if (Driver != null) {
- Driver.SizeChanged -= Driver_SizeChanged;
- Driver.KeyDown -= Driver_KeyDown;
- Driver.KeyUp -= Driver_KeyUp;
- Driver.MouseEvent -= Driver_MouseEvent;
- Driver?.End ();
- Driver = null;
- }
- Iteration = null;
- MouseEvent = null;
- KeyDown = null;
- KeyUp = null;
- SizeChanging = null;
- _mainThreadId = -1;
- NotifyNewRunState = null;
- NotifyStopRunState = null;
- _initialized = false;
- MouseGrabView = null;
- _mouseEnteredView = null;
-
- // Reset synchronization context to allow the user to run async/await,
- // as the main loop has been ended, the synchronization context from
- // gui.cs does no longer process any callbacks. See #1084 for more details:
- // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
- SynchronizationContext.SetSynchronizationContext (syncContext: null);
- }
#endregion Initialization (Init/Shutdown)
#region Run (Begin, Run, End, Stop)
@@ -881,7 +903,7 @@ public static partial class Application {
///
// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
// about TopLevels that are just a SubView of another View?
- static readonly Stack _topLevels = new Stack ();
+ internal static readonly Stack _topLevels = new Stack ();
///
/// The object used for the application on startup ()
@@ -1141,7 +1163,7 @@ public static partial class Application {
}
// Used by OnMouseEvent to track the last view that was clicked on.
- static View _mouseEnteredView;
+ internal static View _mouseEnteredView;
///
/// Event fired when a mouse move or click occurs. Coordinates are screen relative.
@@ -1333,7 +1355,7 @@ public static partial class Application {
#endregion Mouse handling
#region Keyboard handling
- static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask);
+ static Key _alternateForwardKey = Key.Empty; // Defined in config.json
///
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
@@ -1358,7 +1380,7 @@ public static partial class Application {
}
}
- static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask);
+ static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
///
/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
@@ -1383,7 +1405,7 @@ public static partial class Application {
}
}
- static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask);
+ static Key _quitKey = Key.Empty; // Defined in config.json
///
/// Gets or sets the key to quit the application.
diff --git a/Terminal.Gui/Configuration/ConfigProperty.cs b/Terminal.Gui/Configuration/ConfigProperty.cs
index 7ce4e7737..8800f4e4f 100644
--- a/Terminal.Gui/Configuration/ConfigProperty.cs
+++ b/Terminal.Gui/Configuration/ConfigProperty.cs
@@ -82,7 +82,9 @@ public class ConfigProperty {
{
if (PropertyValue != null) {
try {
- PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+ if (PropertyInfo?.GetValue (null) != null) {
+ PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+ }
} catch (TargetInvocationException tie) {
// Check if there is an inner exception
if (tie.InnerException != null) {
diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs
index 4593f7bed..77025948c 100644
--- a/Terminal.Gui/Views/ToplevelOverlapped.cs
+++ b/Terminal.Gui/Views/ToplevelOverlapped.cs
@@ -42,7 +42,7 @@ public static partial class Application {
///
public static Toplevel OverlappedTop {
get {
- if (Top.IsOverlappedContainer) {
+ if (Top is { IsOverlappedContainer: true }) {
return Top;
}
return null;
diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs
index fd93680b7..6488ae1b2 100644
--- a/UnitTests/Application/ApplicationTests.cs
+++ b/UnitTests/Application/ApplicationTests.cs
@@ -1,7 +1,10 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -665,6 +668,90 @@ public class ApplicationTests {
Assert.False (Application.MoveToOverlappedChild (Application.Top));
}
+ [Fact]
+ public void Init_ResetState_Resets_Properties ()
+ {
+ ConfigurationManager.ThrowOnJsonErrors = true;
+ // For all the fields/properties of Application, check that they are reset to their default values
+
+ // Set some values
+
+ Application.Init ();
+ Application._initialized = true;
+
+ // Reset
+ Application.ResetState ();
+
+ void CheckReset ()
+ {
+ // Check that all fields and properties are set to their default values
+
+ // Public Properties
+ Assert.Null (Application.Top);
+ Assert.Null (Application.Current);
+ Assert.Null (Application.MouseGrabView);
+ Assert.Null (Application.WantContinuousButtonPressedView);
+ // Don't check Application.ForceDriver
+ // Assert.Empty (Application.ForceDriver);
+ Assert.False (Application.Force16Colors);
+ Assert.Null (Application.Driver);
+ Assert.Null (Application.MainLoop);
+ Assert.False (Application.EndAfterFirstIteration);
+ Assert.Equal (Key.Empty, Application.AlternateBackwardKey);
+ Assert.Equal (Key.Empty, Application.AlternateForwardKey);
+ Assert.Equal (Key.Empty, Application.QuitKey);
+ Assert.Null (Application.OverlappedChildren);
+ Assert.Null (Application.OverlappedTop);
+
+ // Internal properties
+ Assert.False (Application._initialized);
+ Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures);
+ Assert.False (Application._forceFakeConsole);
+ Assert.Equal (-1, Application._mainThreadId);
+ Assert.Empty (Application._topLevels);
+ Assert.Null (Application._mouseEnteredView);
+
+ // Events - Can't check
+ //Assert.Null (Application.NotifyNewRunState);
+ //Assert.Null (Application.NotifyNewRunState);
+ //Assert.Null (Application.Iteration);
+ //Assert.Null (Application.SizeChanging);
+ //Assert.Null (Application.GrabbedMouse);
+ //Assert.Null (Application.UnGrabbingMouse);
+ //Assert.Null (Application.GrabbedMouse);
+ //Assert.Null (Application.UnGrabbedMouse);
+ //Assert.Null (Application.MouseEvent);
+ //Assert.Null (Application.KeyDown);
+ //Assert.Null (Application.KeyUp);
+ }
+
+ CheckReset ();
+
+ // Set the values that can be set
+ Application._initialized = true;
+ Application._forceFakeConsole = true;
+ Application._mainThreadId = 1;
+ //Application._topLevels = new List ();
+ Application._mouseEnteredView = new View ();
+ //Application.SupportedCultures = new List ();
+ Application.Force16Colors = true;
+ //Application.ForceDriver = "driver";
+ Application.EndAfterFirstIteration = true;
+ Application.AlternateBackwardKey = Key.A;
+ Application.AlternateForwardKey = Key.B;
+ Application.QuitKey = Key.C;
+ //Application.OverlappedChildren = new List ();
+ //Application.OverlappedTop =
+ Application._mouseEnteredView = new View ();
+ //Application.WantContinuousButtonPressedView = new View ();
+
+ Application.ResetState ();
+ CheckReset ();
+
+ ConfigurationManager.ThrowOnJsonErrors = false;
+
+ }
+
// Invoke Tests
// TODO: Test with threading scenarios
[Fact]
diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs
index 946cafd82..5e022c73e 100644
--- a/UnitTests/Views/StatusBarTests.cs
+++ b/UnitTests/Views/StatusBarTests.cs
@@ -14,12 +14,14 @@ namespace Terminal.Gui.ViewsTests {
[Fact]
public void StatusItem_Constructor ()
{
+ Application.Init ();
var si = new StatusItem (Application.QuitKey, $"{Application.QuitKey} to Quit", null);
Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, si.Shortcut);
Assert.Equal ($"{Application.QuitKey} to Quit", si.Title);
Assert.Null (si.Action);
si = new StatusItem (Application.QuitKey, $"{Application.QuitKey} to Quit", () => { });
Assert.NotNull (si.Action);
+ Application.Shutdown ();
}
[Fact]
diff --git a/pull_request_template.md b/pull_request_template.md
index 26e706a53..8834dd8c1 100644
--- a/pull_request_template.md
+++ b/pull_request_template.md
@@ -1,4 +1,10 @@
-Fixes #_____ - Include a terse summary of the change or which issue is fixed.
+## Fixes
+
+- Fixes #_____
+
+## Proposed Changes/Todos
+
+- [ ] Todo 1
## Pull Request checklist: