#nullable enable using System.Diagnostics; using System.Globalization; using System.Reflection; 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; } internal static List GetSupportedCultures () { CultureInfo [] culture = 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 = $"{Path.GetFileNameWithoutExtension (AppContext.BaseDirectory)}.resources.dll"; // Return all culture for which satellite folder found with culture code. return culture.Where ( cultureInfo => Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) ) .ToList (); } // 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) { // 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 (); Current = null; #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 { }) { 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; ClearKeyBindings (); Colors.Reset (); // 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); } // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. // This field is set in `End` in this case so that `Begin` correctly sets `Top`. // TODO: Determine if this is really needed. The only code that calls WakeUp I can find // is ProgressBarStyles, and it's not clear it needs to. #region Toplevel handling /// Holds the stack of TopLevel views. // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What // about TopLevels that are just a SubView of another View? internal static readonly Stack _topLevels = new (); /// The object used for the application on startup () /// The top. public static Toplevel? Top { get; private set; } /// /// The current object. This is updated in enters and leaves to /// point to the current /// . /// /// /// Only relevant in scenarios where is . /// /// The current. public static Toplevel? Current { get; private set; } private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) { if (!topLevel.Running || (topLevel == Current && topLevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) { return; } foreach (Toplevel? top in _topLevels.Reverse ()) { if (top.Modal && top != Current) { MoveCurrent (top); return; } } if (!topLevel.Visible && topLevel == Current) { OverlappedMoveNext (); } } #nullable enable private static Toplevel? FindDeepestTop (Toplevel start, in Point location) { if (!start.Frame.Contains (location)) { return null; } if (_topLevels is { Count: > 0 }) { int rx = location.X - start.Frame.X; int ry = location.Y - start.Frame.Y; foreach (Toplevel? t in _topLevels) { if (t != Current) { if (t != start && t.Visible && t.Frame.Contains (rx, ry)) { start = t; break; } } } } return start; } #nullable restore private static View FindTopFromView (View view) { View top = view?.SuperView is { } && view?.SuperView != Top ? view.SuperView : view; while (top?.SuperView is { } && top?.SuperView != Top) { top = top.SuperView; } return top; } #nullable enable // Only return true if the Current has changed. private static bool MoveCurrent (Toplevel top) { // The Current is modal and the top is not modal Toplevel then // the Current must be moved above the first not modal Toplevel. if (OverlappedTop is { } && top != OverlappedTop && top != Current && Current?.Modal == true && !_topLevels.Peek ().Modal) { lock (_topLevels) { _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); } var index = 0; Toplevel? [] savedToplevels = _topLevels.ToArray (); foreach (Toplevel? t in savedToplevels) { if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) { lock (_topLevels) { _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); } } index++; } return false; } // The Current and the top are both not running Toplevel then // the top must be moved above the first not running Toplevel. if (OverlappedTop is { } && top != OverlappedTop && top != Current && Current?.Running == false && top?.Running == false) { lock (_topLevels) { _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); } var index = 0; foreach (Toplevel? t in _topLevels.ToArray ()) { if (!t.Running && t != Current && index > 0) { lock (_topLevels) { _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); } } index++; } return false; } if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) || (OverlappedTop is { } && Current?.Modal == false && top != Current) || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) { lock (_topLevels) { _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); Current = top; } } return true; } #nullable restore /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// /// Event handlers can set to to prevent /// from changing it's size to match the new terminal size. /// public static event EventHandler SizeChanging; /// /// Called when the application's size changes. Sets the size of all s and fires the /// event. /// /// The new size. /// if the size was changed. public static bool OnSizeChanging (SizeChangedEventArgs args) { SizeChanging?.Invoke (null, args); if (args.Cancel || args.Size is null) { return false; } foreach (Toplevel t in _topLevels) { t.SetRelativeLayout (args.Size.Value); t.LayoutSubviews (); t.PositionToplevels (); t.OnSizeChanging (new (args.Size)); if (PositionCursor (t)) { Driver.UpdateCursor (); } } Refresh (); return true; } #endregion Toplevel handling /// /// Gets a string representation of the Application as rendered by . /// /// A string representation of the Application public new static string ToString () { ConsoleDriver 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 (ConsoleDriver driver) { 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 (); } }