#nullable enable
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Terminal.Gui.Resources;
namespace Terminal.Gui;
/// A static, singleton class representing the application. This class is the entry point for the application.
///
///
/// Application.Init();
/// var win = new Window()
/// {
/// Title = $"Example App ({Application.QuitKey} to quit)"
/// };
/// Application.Run(win);
/// win.Dispose();
/// Application.Shutdown();
///
///
///
public static partial class Application
{
/// Gets all cultures supported by the application without the invariant language.
public static List? SupportedCultures { get; private set; } = GetSupportedCultures ();
///
/// Gets a string representation of the Application as rendered by .
///
/// A string representation of the Application
public new static string ToString ()
{
IConsoleDriver? driver = Driver;
if (driver is null)
{
return string.Empty;
}
return ToString (driver);
}
///
/// Gets a string representation of the Application rendered by the provided .
///
/// The driver to use to render the contents.
/// A string representation of the Application
public static string ToString (IConsoleDriver? driver)
{
if (driver is null)
{
return string.Empty;
}
var sb = new StringBuilder ();
Cell [,] contents = driver?.Contents!;
for (var r = 0; r < driver!.Rows; r++)
{
for (var c = 0; c < driver.Cols; c++)
{
Rune rune = contents [r, c].Rune;
if (rune.DecodeSurrogatePair (out char [] sp))
{
sb.Append (sp);
}
else
{
sb.Append ((char)rune.Value);
}
if (rune.GetColumns () > 1)
{
c++;
}
// See Issue #2616
//foreach (var combMark in contents [r, c].CombiningMarks) {
// sb.Append ((char)combMark.Value);
//}
}
sb.AppendLine ();
}
return sb.ToString ();
}
internal static List GetAvailableCulturesFromEmbeddedResources ()
{
ResourceManager rm = new (typeof (Strings));
CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
return cultures.Where (
cultureInfo =>
!cultureInfo.Equals (CultureInfo.InvariantCulture)
&& rm.GetResourceSet (cultureInfo, true, false) is { }
)
.ToList ();
}
internal static List GetSupportedCultures ()
{
CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
// Get the assembly
var assembly = Assembly.GetExecutingAssembly ();
//Find the location of the assembly
string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
// Find the resource file name of the assembly
var resourceFilename = $"{assembly.GetName ().Name}.resources.dll";
if (cultures.Length > 1 && Directory.Exists (Path.Combine (assemblyLocation, "pt-PT")))
{
// Return all culture for which satellite folder found with culture code.
return cultures.Where (
cultureInfo =>
Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
&& File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
)
.ToList ();
}
// It's called from a self-contained single-file and get available cultures from the embedded resources strings.
return GetAvailableCulturesFromEmbeddedResources ();
}
// 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 (bool ignoreDisposed = false)
{
Application.Navigation = new ApplicationNavigation ();
// 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 (Toplevel? t in TopLevels)
{
t!.Running = false;
}
TopLevels.Clear ();
#if DEBUG_IDISPOSABLE
// Don't dispose the Top. It's up to caller dispose it
if (!ignoreDisposed && Top is { })
{
Debug.Assert (Top.WasDisposed);
// If End wasn't called _cachedRunStateToplevel may be null
if (_cachedRunStateToplevel is { })
{
Debug.Assert (_cachedRunStateToplevel.WasDisposed);
Debug.Assert (_cachedRunStateToplevel == Top);
}
}
#endif
Top = null;
_cachedRunStateToplevel = null;
// MainLoop stuff
MainLoop?.Dispose ();
MainLoop = null;
MainThreadId = -1;
Iteration = null;
EndAfterFirstIteration = false;
// Driver stuff
if (Driver is { })
{
UnsubscribeDriverEvents ();
Driver?.End ();
Driver = null;
}
_screen = 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
_lastMousePosition = null;
_cachedViewsUnderMouse.Clear ();
WantContinuousButtonPressedView = null;
MouseEvent = null;
GrabbedMouse = null;
UnGrabbingMouse = null;
GrabbedMouse = null;
UnGrabbedMouse = null;
// Keyboard
KeyDown = null;
KeyUp = null;
SizeChanging = null;
Navigation = null;
ClearScreenNextIteration = false;
KeyBindings.Clear ();
AddKeyBindings ();
// 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 (null);
}
///
/// Adds specified idle handler function to main iteration processing. The handler function will be called
/// once per iteration of the main loop after other events have been handled.
///
public static void AddIdle (Func func) => ApplicationImpl.Instance.AddIdle (func);
}