From bf1ed312817ee0d0a9ef3a2cae17a3247bd2b954 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 3 Nov 2022 15:58:15 -0600 Subject: [PATCH] Refactored UI Catalog and added tons of unit tests --- Terminal.Gui/Core/Application.cs | 202 ++- Terminal.Gui/Core/MainLoop.cs | 4 +- Terminal.Gui/Core/Toplevel.cs | 8 +- UICatalog/Properties/launchSettings.json | 4 + UICatalog/Scenario.cs | 22 +- UICatalog/Scenarios/AllViewsTester.cs | 34 +- .../Scenarios/BackgroundWorkerCollection.cs | 5 + UICatalog/Scenarios/BordersComparisons.cs | 10 +- UICatalog/Scenarios/Buttons.cs | 4 +- UICatalog/Scenarios/ClassExplorer.cs | 4 +- UICatalog/Scenarios/Clipping.cs | 11 +- .../Scenarios/CollectionNavigatorTester.cs | 17 +- UICatalog/Scenarios/ComputedLayout.cs | 4 +- UICatalog/Scenarios/ContextMenus.cs | 2 +- UICatalog/Scenarios/CsvEditor.cs | 6 +- UICatalog/Scenarios/Dialogs.cs | 4 +- UICatalog/Scenarios/DynamicMenuBar.cs | 5 +- UICatalog/Scenarios/DynamicStatusBar.cs | 5 +- UICatalog/Scenarios/Editor.cs | 11 +- UICatalog/Scenarios/GraphViewExample.cs | 6 +- UICatalog/Scenarios/HexEditor.cs | 4 +- UICatalog/Scenarios/InteractiveTree.cs | 6 +- UICatalog/Scenarios/Keys.cs | 13 +- UICatalog/Scenarios/LabelsAsButtons.cs | 6 +- UICatalog/Scenarios/LineViewExample.cs | 6 +- UICatalog/Scenarios/MessageBoxes.cs | 4 +- UICatalog/Scenarios/MultiColouredTable.cs | 6 +- UICatalog/Scenarios/Notepad.cs | 11 +- UICatalog/Scenarios/ProgressBarStyles.cs | 4 +- .../Scenarios/RuneWidthGreaterThanOne.cs | 2 +- UICatalog/Scenarios/Scrolling.cs | 8 +- UICatalog/Scenarios/SingleBackgroundWorker.cs | 4 +- UICatalog/Scenarios/SyntaxHighlighting.cs | 6 +- UICatalog/Scenarios/TabViewExample.cs | 10 +- UICatalog/Scenarios/TableEditor.cs | 8 +- UICatalog/Scenarios/TextFormatterDemo.cs | 2 +- .../Scenarios/TextViewAutocompletePopup.cs | 4 +- UICatalog/Scenarios/Threading.cs | 4 +- UICatalog/Scenarios/TreeUseCases.cs | 6 +- UICatalog/Scenarios/TreeViewFileSystem.cs | 6 +- UICatalog/Scenarios/Unicode.cs | 4 +- UICatalog/Scenarios/WindowsAndFrameViews.cs | 14 +- UICatalog/Scenarios/WizardAsView.cs | 10 +- UICatalog/Scenarios/Wizards.cs | 4 +- UICatalog/UICatalog.cs | 860 +++++------ UnitTests/ApplicationTests.cs | 1355 +++++------------ UnitTests/MainLoopTests.cs | 52 + UnitTests/MdiTests.cs | 662 ++++++++ UnitTests/RunStateTests.cs | 105 ++ UnitTests/ScenarioTests.cs | 4 +- UnitTests/SynchronizatonContextTests.cs | 83 + 51 files changed, 1977 insertions(+), 1664 deletions(-) create mode 100644 UnitTests/MdiTests.cs create mode 100644 UnitTests/RunStateTests.cs create mode 100644 UnitTests/SynchronizatonContextTests.cs diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 50906cfb9..4a030f433 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -223,19 +223,28 @@ namespace Terminal.Gui { public static bool ExitRunLoopAfterFirstIteration { get; set; } = false; /// - /// Notify that a new token was created, - /// used if is true. + /// Notify that a new was created ( was called). The token is created in + /// and this event will be fired before that function exits. /// + /// + /// If is callers to + /// must also subscribe to + /// and manually dispose of the token when the application is done. + /// public static event Action NotifyNewRunState; /// - /// Notify that a existent token is stopping, - /// used if is true. + /// Notify that a existent is stopping ( was called). /// + /// + /// If is callers to + /// must also subscribe to + /// and manually dispose of the token when the application is done. + /// public static event Action NotifyStopRunState; /// - /// This event is raised on each iteration of the + /// This event is raised on each iteration of the . /// /// /// See also @@ -315,8 +324,10 @@ namespace Terminal.Gui { /// returned) to ensure resources are cleaned up and terminal settings restored. /// /// - /// The function combines and - /// into a single call. An applciation cam use without explicitly calling . + /// The function + /// combines and + /// into a single call. An applciation cam use + /// without explicitly calling . /// /// /// Note, due to Issue #520, if Init is called and is not called, the @@ -326,7 +337,7 @@ namespace Terminal.Gui { /// The to use. If not specified the default driver for the /// platform will be used (see , , and ). /// Specifies the to use. - public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver); + public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver, resetState: true); internal static bool _initialized = false; internal static int _mainThreadId = -1; @@ -341,12 +352,16 @@ namespace Terminal.Gui { /// The to use. If not specified the default driver for the /// platform will be used (see , , and ). /// Specifies the to use. - static void Init (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) + /// If (default) all state will be reset. + /// Set to to not reset the state (for when this function is called via + /// when + /// has not already been called. f + internal static void Init (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool resetState = true) { if (_initialized && driver == null) return; if (_initialized) { - throw new InvalidOperationException ("Init must be bracketed by Shutdown"); + throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); } // Used only for start debugging on Unix. @@ -358,7 +373,9 @@ namespace Terminal.Gui { //#endif // Reset all class variables (Application is a singleton). - ResetState (); + if (resetState) { + ResetState (); + } // This supports Unit Tests and the passing of a mock driver/loopdriver if (driver != null) { @@ -366,9 +383,6 @@ namespace Terminal.Gui { throw new ArgumentNullException ("mainLoopDriver cannot be null if driver is provided."); } Driver = driver; - Driver.Init (TerminalResized); - MainLoop = new MainLoop (mainLoopDriver); - SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop)); } if (Driver == null) { @@ -383,10 +397,12 @@ namespace Terminal.Gui { mainLoopDriver = new UnixMainLoop (); Driver = new CursesDriver (); } - Driver.Init (TerminalResized); - MainLoop = new MainLoop (mainLoopDriver); - SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop)); } + MainLoop = new MainLoop (mainLoopDriver); + + Driver.Init (TerminalResized); + SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop)); + Top = topLevelFactory (); Current = Top; supportedCultures = GetSupportedCultures (); @@ -395,7 +411,7 @@ namespace Terminal.Gui { } /// - /// Captures the execution state for the provided view. + /// Captures the execution state for the provided view. /// public class RunState : IDisposable { /// @@ -411,31 +427,61 @@ namespace Terminal.Gui { /// public Toplevel Toplevel { get; internal set; } +#if DEBUG_IDISPOSABLE /// - /// Releases alTop = l resource used by the object. + /// For debug purposes to verify objects are being disposed properly /// - /// Call when you are finished using the . The + public bool WasDisposed = false; + /// + /// For debug purposes to verify objects are being disposed properly + /// + public int DisposedCount = 0; + /// + /// For debug purposes + /// + public static List Instances = new List (); + /// + /// For debug purposes + /// + public RunState () + { + Instances.Add (this); + } +#endif + + /// + /// Releases all resource used by the object. + /// + /// + /// Call when you are finished using the . + /// + /// /// method leaves the in an unusable state. After /// calling , you must release all references to the /// so the garbage collector can reclaim the memory that the - /// was occupying. + /// was occupying. + /// public void Dispose () { Dispose (true); GC.SuppressFinalize (this); +#if DEBUG_IDISPOSABLE + WasDisposed = true; +#endif } /// - /// Dispose the specified disposing. + /// Releases all resource used by the object. /// - /// The dispose. - /// If set to true disposing. + /// If set to we are disposing and should dispose held objects. protected virtual void Dispose (bool disposing) { if (Toplevel != null && disposing) { - End (Toplevel); - Toplevel.Dispose (); - Toplevel = null; + throw new InvalidOperationException ("You must clean up (Dispose) the Toplevel before calling Application.RunState.Dispose"); + // BUGBUG: It's insidious that we call EndFirstTopLevel here so I moved it to End. + //EndFirstTopLevel (Toplevel); + //Toplevel.Dispose (); + //Toplevel = null; } } } @@ -841,7 +887,6 @@ namespace Terminal.Gui { } var rs = new RunState (toplevel); - Init (); if (toplevel is ISupportInitializeNotification initializableNotification && !initializableNotification.IsInitialized) { @@ -913,6 +958,7 @@ namespace Terminal.Gui { Driver.Refresh (); } + NotifyNewRunState?.Invoke (rs); return rs; } @@ -930,6 +976,42 @@ namespace Terminal.Gui { } else { runState.Toplevel.OnUnloaded (); } + + // End the RunState.Toplevel + // First, take it off the toplevel Stack + if (toplevels.Count > 0) { + if (toplevels.Peek () != runState.Toplevel) { + // If there the top of the stack is not the RunState.Toplevel then + // this call to End is not balanced with the call to Begin that started the RunState + throw new ArgumentException ("End must be balanced with calls to Begin"); + } + toplevels.Pop (); + } + + // Notify that it is closing + runState.Toplevel?.OnClosed (runState.Toplevel); + + // If there is a MdiTop that is not the RunState.Toplevel then runstate.TopLevel + // is a child of MidTop and we should notify the MdiTop that it is closing + if (MdiTop != null && !(runState.Toplevel).Modal && runState.Toplevel != MdiTop) { + MdiTop.OnChildClosed (runState.Toplevel); + } + + // Set Current and Top to the next TopLevel on the stack + if (toplevels.Count == 0) { + Current = null; + } else { + Current = toplevels.Peek (); + if (toplevels.Count == 1 && Current == MdiTop) { + MdiTop.OnAllChildClosed (); + } else { + SetCurrentAsTop (); + } + Refresh (); + } + + runState.Toplevel?.Dispose (); + runState.Toplevel = null; runState.Dispose (); } @@ -937,7 +1019,7 @@ namespace Terminal.Gui { /// Shutdown an application initialized with . /// /// - /// Shutdown must be called for every call to or + /// Shutdown must be called for every call to or /// to ensure all resources are cleaned up (Disposed) and terminal settings are restored. /// public static void Shutdown () @@ -1014,40 +1096,17 @@ namespace Terminal.Gui { Driver.Refresh (); } - internal static void End (View view) - { - if (toplevels.Peek () != view) - throw new ArgumentException ("The view that you end with must be balanced"); - toplevels.Pop (); - (view as Toplevel)?.OnClosed ((Toplevel)view); - - if (MdiTop != null && !((Toplevel)view).Modal && view != MdiTop) { - MdiTop.OnChildClosed (view as Toplevel); - } - - if (toplevels.Count == 0) { - Current = null; - } else { - Current = toplevels.Peek (); - if (toplevels.Count == 1 && Current == MdiTop) { - MdiTop.OnAllChildClosed (); - } else { - SetCurrentAsTop (); - } - Refresh (); - } - } /// - /// Building block API: Runs the main loop for the created dialog. + /// Building block API: Runs the for the created . /// /// - /// Use the wait parameter to control whether this is a - /// blocking or non-blocking call. + /// Use the parameter to control whether this is a blocking or non-blocking call. /// - /// The state returned by the Begin method. - /// By default this is true which will execute the runloop waiting for events, if you pass false, you can use this method to run a single iteration of the events. + /// The state returned by the method. + /// By default this is which will execute the runloop waiting for events, + /// if set to , a single iteration will execute. public static void RunLoop (RunState state, bool wait = true) { if (state == null) @@ -1057,8 +1116,9 @@ namespace Terminal.Gui { bool firstIteration = true; for (state.Toplevel.Running = true; state.Toplevel.Running;) { - if (ExitRunLoopAfterFirstIteration && !firstIteration) + if (ExitRunLoopAfterFirstIteration && !firstIteration) { return; + } RunMainLoopIteration (ref state, wait, ref firstIteration); } } @@ -1186,17 +1246,21 @@ namespace Terminal.Gui { /// with a new instance of the specified -derived class. /// /// If has not arleady been called, this function will - /// call . + /// call . /// /// - /// must be called when the application is closing (typically after has + /// must be called when the application is closing (typically after Run> has /// returned) to ensure resources are cleaned up and terminal settings restored. /// /// /// /// See for more details. /// - public static void Run (Func errorHandler = null) where T : Toplevel, new() + /// + /// The to use. If not specified the default driver for the + /// platform will be used (see , , and ). + /// Specifies the to use. + public static void Run (Func errorHandler = null, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) where T : Toplevel, new() { if (_initialized && Driver != null) { var top = new T (); @@ -1207,9 +1271,11 @@ namespace Terminal.Gui { if (type != typeof (Toplevel)) { throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel"); } + // Run() will eventually cause Application.Top to be set, via Begin() and SetCurrentAsTop() Run (top, errorHandler); } else { - Init (() => new T ()); + // Note in this case, we don't verify the type of the Toplevel created by new T(). + Init (() => new T (), Driver == null ? driver : Driver, Driver == null ? mainLoopDriver : null, resetState: false); Run (Top, errorHandler); } } @@ -1255,13 +1321,12 @@ namespace Terminal.Gui { #endif resume = false; var runToken = Begin (view); + // If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken + // by using NotifyStopRunState event. RunLoop (runToken); - if (!ExitRunLoopAfterFirstIteration) + if (!ExitRunLoopAfterFirstIteration) { End (runToken); - else - // If ExitRunLoopAfterFirstIteration is true then the user must deal his disposing when it ends - // by using NotifyStopRunState event. - NotifyNewRunState?.Invoke (runToken); + } #if !DEBUG } catch (Exception error) @@ -1354,8 +1419,9 @@ namespace Terminal.Gui { static void OnNotifyStopRunState (Toplevel top) { - if (ExitRunLoopAfterFirstIteration) + if (ExitRunLoopAfterFirstIteration) { NotifyStopRunState?.Invoke (top); + } } /// diff --git a/Terminal.Gui/Core/MainLoop.cs b/Terminal.Gui/Core/MainLoop.cs index e71367646..9b6492fe0 100644 --- a/Terminal.Gui/Core/MainLoop.cs +++ b/Terminal.Gui/Core/MainLoop.cs @@ -94,8 +94,8 @@ namespace Terminal.Gui { public IMainLoopDriver Driver { get; } /// - /// Invoked when a new timeout is added to be used on the case - /// if is true, + /// Invoked when a new timeout is added. To be used in the case + /// when is . /// public event Action TimeoutAdded; diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index a922f729d..707a3e0a5 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -44,7 +44,7 @@ namespace Terminal.Gui { public bool Running { get; set; } /// - /// Invoked when the Toplevel has begin loaded. + /// Invoked when the Toplevel has begun to be loaded. /// A Loaded event handler is a good place to finalize initialization before calling /// . /// @@ -77,13 +77,13 @@ namespace Terminal.Gui { /// /// Invoked when a child of the Toplevel is closed by - /// . + /// . /// public event Action ChildClosed; /// /// Invoked when the last child of the Toplevel is closed from - /// by . + /// by . /// public event Action AllChildClosed; @@ -94,7 +94,7 @@ namespace Terminal.Gui { public event Action Closing; /// - /// Invoked when the Toplevel's is closed by . + /// Invoked when the Toplevel's is closed by . /// public event Action Closed; diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index a519b2cb4..b621c2df5 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -48,6 +48,10 @@ "WSL": { "commandName": "WSL2", "distributionName": "" + }, + "All Views Tester": { + "commandName": "Project", + "commandLineArgs": "\"All Views Tester\"" } } } \ No newline at end of file diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index 30767e190..e26ce2911 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -48,12 +48,7 @@ namespace UICatalog { private bool _disposedValue; /// - /// The Top level for the . This should be set to in most cases. - /// - public Toplevel Top { get; set; } - - /// - /// The Window for the . This should be set within the in most cases. + /// The Window for the . This should be set to in most cases. /// public Window Win { get; set; } @@ -63,22 +58,21 @@ namespace UICatalog { /// the Scenario picker UI. /// Override to provide any behavior needed. /// - /// The Toplevel created by the UI Catalog host. /// The colorscheme to use. /// /// - /// The base implementation calls , sets to the passed in , creates a for and adds it to . + /// The base implementation calls and creates a for + /// and adds it to . /// /// - /// Overrides that do not call the base., must call before creating any views or calling other Terminal.Gui APIs. + /// Overrides that do not call the base., must call + /// before creating any views or calling other Terminal.Gui APIs. /// /// - public virtual void Init (Toplevel top, ColorScheme colorScheme) + public virtual void Init (ColorScheme colorScheme) { Application.Init (); - Top = top != null ? top : Application.Top; - Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") { X = 0, Y = 0, @@ -86,7 +80,7 @@ namespace UICatalog { Height = Dim.Fill (), ColorScheme = colorScheme, }; - Top.Add (Win); + Application.Top.Add (Win); } /// @@ -201,7 +195,7 @@ namespace UICatalog { public virtual void Run () { // Must explicit call Application.Shutdown method to shutdown. - Application.Run (Top); + Application.Run (Application.Top); } /// diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 945b25df8..7e346f02d 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -14,7 +14,7 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("Tests")] [ScenarioCategory ("Top Level Windows")] public class AllViewsTester : Scenario { - Window _leftPane; + FrameView _leftPane; ListView _classListView; FrameView _hostPane; @@ -40,42 +40,33 @@ namespace UICatalog.Scenarios { TextField _hText; int _hVal = 0; - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - - Top = top != null ? top : Application.Top; - - //Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") { - // X = 0, - // Y = 0, - // Width = Dim.Fill (), - // Height = Dim.Fill () - //}; - //Top.Add (Win); + // Don't create a sub-win; just use Applicatiion.Top } - + public override void Setup () { var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), new StatusItem(Key.F2, "~F2~ Toggle Frame Ruler", () => { ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler; - Top.SetNeedsDisplay (); + Application.Top.SetNeedsDisplay (); }), new StatusItem(Key.F3, "~F3~ Toggle Frame Padding", () => { ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding; - Top.SetNeedsDisplay (); + Application.Top.SetNeedsDisplay (); }), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); _viewClasses = GetAllViewClassesCollection () .OrderBy (t => t.Name) .Select (t => new KeyValuePair (t.Name, t)) .ToDictionary (t => t.Key, t => t.Value); - _leftPane = new Window ("Classes") { + _leftPane = new FrameView ("Classes") { X = 0, Y = 0, Width = 15, @@ -241,9 +232,9 @@ namespace UICatalog.Scenarios { ColorScheme = Colors.Dialog, }; - Top.Add (_leftPane, _settingsPane, _hostPane); + Application.Top.Add (_leftPane, _settingsPane, _hostPane); - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); _curView = CreateClass (_viewClasses.First ().Value); } @@ -438,11 +429,6 @@ namespace UICatalog.Scenarios { UpdateTitle (_curView); } - public override void Run () - { - base.Run (); - } - private void Quit () { Application.RequestStop (); diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index 07eceb5f7..ec73bcf76 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -12,6 +12,11 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("Dialogs")] [ScenarioCategory ("Controls")] public class BackgroundWorkerCollection : Scenario { + public override void Init (ColorScheme colorScheme) + { + // Do not call Init as Application.Run will do it + } + public override void Run () { // BUGBUG: work around Issue #520: Ensuring the `Toplevel` created by `Init` gets Disposed... diff --git a/UICatalog/Scenarios/BordersComparisons.cs b/UICatalog/Scenarios/BordersComparisons.cs index 9ea462f52..1aac4dca5 100644 --- a/UICatalog/Scenarios/BordersComparisons.cs +++ b/UICatalog/Scenarios/BordersComparisons.cs @@ -5,12 +5,10 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("Layout")] [ScenarioCategory ("Borders")] public class BordersComparisons : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - top = Application.Top; - var borderStyle = BorderStyle.Double; var drawMarginFrame = false; var borderThickness = new Thickness (1, 2, 3, 4); @@ -53,7 +51,7 @@ namespace UICatalog.Scenarios { Width = 10 }; win.Add (tf1, button, label, tv, tf2); - top.Add (win); + Application.Top.Add (win); var top2 = new Border.ToplevelContainer (new Rect (50, 5, 40, 20), new Border () { @@ -92,7 +90,7 @@ namespace UICatalog.Scenarios { Width = 10 }; top2.Add (tf3, button2, label2, tv2, tf4); - top.Add (top2); + Application.Top.Add (top2); var frm = new FrameView (new Rect (95, 5, 40, 20), "Test3", null, new Border () { @@ -128,7 +126,7 @@ namespace UICatalog.Scenarios { Width = 10 }; frm.Add (tf5, button3, label3, tv3, tf6); - top.Add (frm); + Application.Top.Add (frm); Application.Run (); } diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index f66849d9e..7269d53d3 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -56,7 +56,7 @@ namespace UICatalog.Scenarios { //View prev = colorButtonsLabel; - //With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds); + //With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds); var x = Pos.Right (colorButtonsLabel) + 2; foreach (var colorScheme in Colors.ColorSchemes) { var colorButton = new Button ($"{colorScheme.Key}") { @@ -272,7 +272,7 @@ namespace UICatalog.Scenarios { } }; - Top.Ready += () => radioGroup.Refresh (); + Application.Top.Ready += () => radioGroup.Refresh (); } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/ClassExplorer.cs b/UICatalog/Scenarios/ClassExplorer.cs index c7b5798bd..3b96b18e0 100644 --- a/UICatalog/Scenarios/ClassExplorer.cs +++ b/UICatalog/Scenarios/ClassExplorer.cs @@ -58,7 +58,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { @@ -73,7 +73,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_Expand All", "", () => treeView.ExpandAll()), new MenuItem ("_Collapse All", "", () => treeView.CollapseAll()) }), }); - Top.Add (menu); + Application.Top.Add (menu); treeView = new TreeView () { X = 0, diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs index 9c137f03e..0d68229a9 100644 --- a/UICatalog/Scenarios/Clipping.cs +++ b/UICatalog/Scenarios/Clipping.cs @@ -7,13 +7,10 @@ namespace UICatalog.Scenarios { public class Clipping : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - - Top = top != null ? top : Application.Top; - - Top.ColorScheme = Colors.Base; + Application.Top.ColorScheme = Colors.Base; } public override void Setup () @@ -26,7 +23,7 @@ namespace UICatalog.Scenarios { X = 0, Y = 0, //ColorScheme = Colors.Dialog }; - Top.Add (label); + Application.Top.Add (label); var scrollView = new ScrollView (new Rect (3, 3, 50, 20)); scrollView.ColorScheme = Colors.Menu; @@ -69,7 +66,7 @@ namespace UICatalog.Scenarios { scrollView.Add (embedded1); - Top.Add (scrollView); + Application.Top.Add (scrollView); } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/CollectionNavigatorTester.cs b/UICatalog/Scenarios/CollectionNavigatorTester.cs index d97f6890c..8d444ff08 100644 --- a/UICatalog/Scenarios/CollectionNavigatorTester.cs +++ b/UICatalog/Scenarios/CollectionNavigatorTester.cs @@ -15,11 +15,10 @@ namespace UICatalog.Scenarios { public class CollectionNavigatorTester : Scenario { // Don't create a Window, just return the top-level view - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - Top = top != null ? top : Application.Top; - Top.ColorScheme = Colors.Base; + Application.Top.ColorScheme = Colors.Base; } System.Collections.Generic.List _items = new string [] { @@ -103,7 +102,7 @@ namespace UICatalog.Scenarios { new MenuBarItem("_Quit", "CTRL-Q", () => Quit()), }); - Top.Add (menu); + Application.Top.Add (menu); _items.Sort (StringComparer.OrdinalIgnoreCase); @@ -113,7 +112,7 @@ namespace UICatalog.Scenarios { Y = 1, Height = Dim.Fill () }; - Top.Add (vsep); + Application.Top.Add (vsep); CreateTreeView (); } @@ -129,7 +128,7 @@ namespace UICatalog.Scenarios { Width = Dim.Percent (50), Height = 1, }; - Top.Add (label); + Application.Top.Add (label); _listView = new ListView () { X = 0, @@ -140,7 +139,7 @@ namespace UICatalog.Scenarios { AllowsMultipleSelection = false, ColorScheme = Colors.TopLevel }; - Top.Add (_listView); + Application.Top.Add (_listView); _listView.SetSource (_items); @@ -161,7 +160,7 @@ namespace UICatalog.Scenarios { Width = Dim.Percent (50), Height = 1, }; - Top.Add (label); + Application.Top.Add (label); _treeView = new TreeView () { X = Pos.Right (_listView) + 1, @@ -170,7 +169,7 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), ColorScheme = Colors.TopLevel }; - Top.Add (_treeView); + Application.Top.Add (_treeView); var root = new TreeNode ("IsLetterOrDigit examples"); root.Children = _items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast ().ToList (); diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index 38300482f..af7d3ba28 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -25,12 +25,12 @@ namespace UICatalog.Scenarios { new MenuItem ("_Quit", "", () => Quit()), }), }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); //Top.LayoutStyle = LayoutStyle.Computed; // Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width diff --git a/UICatalog/Scenarios/ContextMenus.cs b/UICatalog/Scenarios/ContextMenus.cs index a9e9223d3..897f75acc 100644 --- a/UICatalog/Scenarios/ContextMenus.cs +++ b/UICatalog/Scenarios/ContextMenus.cs @@ -81,7 +81,7 @@ namespace UICatalog.Scenarios { Win.WantMousePositionReports = true; - Top.Closed += (_) => { + Application.Top.Closed += (_) => { Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); Application.RootMouseEvent -= Application_RootMouseEvent; }; diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index ab83b487d..f57d13e37 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -34,7 +34,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName(); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); this.tableView = new TableView () { X = 0, @@ -70,14 +70,14 @@ namespace UICatalog.Scenarios { miCentered = new MenuItem ("_Set Format Pattern", "", () => SetFormat()), }) }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.O, "~^O~ Open", () => Open()), new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()), new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); Win.Add (tableView); diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index c3aac85a4..f8a680046 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -116,9 +116,9 @@ namespace UICatalog.Scenarios { { frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (numButtonsEdit) + Dim.Height (styleRadioGroup) + Dim.Height(glyphsNotWords) + 2; - Top.Loaded -= Top_Loaded; + Application.Top.Loaded -= Top_Loaded; } - Top.Loaded += Top_Loaded; + Application.Top.Loaded += Top_Loaded; label = new Label ("Button Pressed:") { X = Pos.Center (), diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 82309b965..2ba8c3a07 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -13,11 +13,10 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("Top Level Windows")] [ScenarioCategory ("Menus")] public class DynamicMenuBar : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - Top = Application.Top; - Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}")); + Application.Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}")); } public class DynamicMenuItemList { diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index 5583afbcb..a61f366c0 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -12,11 +12,10 @@ namespace UICatalog.Scenarios { [ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")] [ScenarioCategory ("Top Level Windows")] public class DynamicStatusBar : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - Top = Application.Top; - Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}")); + Application.Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}")); } public class DynamicStatusItemList { diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index c14922d50..8bc930389 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -32,11 +32,10 @@ namespace UICatalog.Scenarios { private bool _forceMinimumPosToZero = true; private List _cultureInfos; - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); _cultureInfos = Application.SupportedCultures; - Top = top != null ? top : Application.Top; Win = new Window (_fileName ?? "Untitled") { X = 0, @@ -45,7 +44,7 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), ColorScheme = colorScheme, }; - Top.Add (Win); + Application.Top.Add (Win); _textView = new TextView () { X = 0, @@ -115,7 +114,7 @@ namespace UICatalog.Scenarios { }) }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { siCursorPosition, @@ -125,7 +124,7 @@ namespace UICatalog.Scenarios { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), new StatusItem(Key.Null, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null) }); - Top.Add (statusBar); + Application.Top.Add (statusBar); _scrollBar = new ScrollBarView (_textView, true); @@ -197,7 +196,7 @@ namespace UICatalog.Scenarios { } }; - Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); + Application.Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); } private void DisposeWinDialog () diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 8404f044b..e8f806046 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -23,7 +23,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); graphs = new Action [] { ()=>SetupPeriodicTableScatterPlot(), //0 @@ -59,7 +59,7 @@ namespace UICatalog.Scenarios { }), }); - Top.Add (menu); + Application.Top.Add (menu); graphView = new GraphView () { X = 1, @@ -92,7 +92,7 @@ namespace UICatalog.Scenarios { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), new StatusItem(Key.CtrlMask | Key.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } private void MultiBarGraph () diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs index 52d02b2ef..c83408ada 100644 --- a/UICatalog/Scenarios/HexEditor.cs +++ b/UICatalog/Scenarios/HexEditor.cs @@ -52,7 +52,7 @@ namespace UICatalog.Scenarios { miAllowEdits = new MenuItem ("_AllowEdits", "", () => ToggleAllowEdits ()){Checked = _hexView.AllowEdits, CheckType = MenuItemCheckStyle.Checked} }) }); - Top.Add (menu); + Application.Top.Add (menu); statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.F2, "~F2~ Open", () => Open()), @@ -61,7 +61,7 @@ namespace UICatalog.Scenarios { siPositionChanged = new StatusItem(Key.Null, $"Position: {_hexView.Position} Line: {_hexView.CursorPosition.Y} Col: {_hexView.CursorPosition.X} Line length: {_hexView.BytesPerLine}", () => {}) }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } private void _hexView_PositionChanged (HexView.HexViewEventArgs obj) diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index b3d63578d..f51f10238 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -20,14 +20,14 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Quit", "", () => Quit()), }) }); - Top.Add (menu); + Application.Top.Add (menu); treeView = new TreeView () { X = 0, @@ -45,7 +45,7 @@ namespace UICatalog.Scenarios { new StatusItem(Key.CtrlMask | Key.T, "~^T~ Add Root", () => AddRootNode()), new StatusItem(Key.CtrlMask | Key.R, "~^R~ Rename Node", () => RenameNode()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/Keys.cs b/UICatalog/Scenarios/Keys.cs index 35cafbc21..21b567f6d 100644 --- a/UICatalog/Scenarios/Keys.cs +++ b/UICatalog/Scenarios/Keys.cs @@ -48,10 +48,9 @@ namespace UICatalog.Scenarios { } } - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - Top = top != null ? top : Application.Top; Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") { X = 0, @@ -60,7 +59,7 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), ColorScheme = colorScheme, }; - Top.Add (Win); + Application.Top.Add (Win); } public override void Setup () @@ -107,7 +106,7 @@ namespace UICatalog.Scenarios { Shift = true }); var maxLogEntry = $"Key{"",-5}: {fakeKeyPress}".Length; - var yOffset = (Top == Application.Top ? 1 : 6); + var yOffset = (Application.Top == Application.Top ? 1 : 6); var keyStrokelist = new List (); var keyStrokeListView = new ListView (keyStrokelist) { X = 0, @@ -126,7 +125,7 @@ namespace UICatalog.Scenarios { Win.Add (processKeyLogLabel); maxLogEntry = $"{fakeKeyPress}".Length; - yOffset = (Top == Application.Top ? 1 : 6); + yOffset = (Application.Top == Application.Top ? 1 : 6); var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) { X = Pos.Left (processKeyLogLabel), Y = Pos.Top (processKeyLogLabel) + yOffset, @@ -144,7 +143,7 @@ namespace UICatalog.Scenarios { }; Win.Add (processHotKeyLogLabel); - yOffset = (Top == Application.Top ? 1 : 6); + yOffset = (Application.Top == Application.Top ? 1 : 6); var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) { X = Pos.Left (processHotKeyLogLabel), Y = Pos.Top (processHotKeyLogLabel) + yOffset, @@ -162,7 +161,7 @@ namespace UICatalog.Scenarios { }; Win.Add (processColdKeyLogLabel); - yOffset = (Top == Application.Top ? 1 : 6); + yOffset = (Application.Top == Application.Top ? 1 : 6); var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) { X = Pos.Left (processColdKeyLogLabel), Y = Pos.Top (processColdKeyLogLabel) + yOffset, diff --git a/UICatalog/Scenarios/LabelsAsButtons.cs b/UICatalog/Scenarios/LabelsAsButtons.cs index 395a042ec..29c2b98af 100644 --- a/UICatalog/Scenarios/LabelsAsButtons.cs +++ b/UICatalog/Scenarios/LabelsAsButtons.cs @@ -59,7 +59,7 @@ namespace UICatalog.Scenarios { }; Win.Add (colorLabelsLabel); - //With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds); + //With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds); var x = Pos.Right (colorLabelsLabel) + 2; foreach (var colorScheme in Colors.ColorSchemes) { var colorLabel = new Label ($"{colorScheme.Key}") { @@ -73,7 +73,7 @@ namespace UICatalog.Scenarios { Win.Add (colorLabel); x += colorLabel.Text.Length + 2; } - Top.Ready += () => Top.Redraw (Top.Bounds); + Application.Top.Ready += () => Application.Top.Redraw (Application.Top.Bounds); Label Label; Win.Add (Label = new Label ("A super long _Label that will probably expose a bug in clipping or wrapping of text. Will it?") { @@ -306,7 +306,7 @@ namespace UICatalog.Scenarios { } }; - Top.Ready += () => radioGroup.Refresh (); + Application.Top.Ready += () => radioGroup.Refresh (); } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index 4a8c4002a..cf537d952 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -17,14 +17,14 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Quit", "", () => Quit()), }) }); - Top.Add (menu); + Application.Top.Add (menu); Win.Add (new Label ("Regular Line") { Y = 0 }); @@ -94,7 +94,7 @@ namespace UICatalog.Scenarios { var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()) }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs index 0b9aaa702..b0fe23109 100644 --- a/UICatalog/Scenarios/MessageBoxes.cs +++ b/UICatalog/Scenarios/MessageBoxes.cs @@ -156,9 +156,9 @@ namespace UICatalog.Scenarios { { frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit) + Dim.Height (numButtonsEdit) + Dim.Height (defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2 + Dim.Height (ckbEffect3D); - Top.Loaded -= Top_Loaded; + Application.Top.Loaded -= Top_Loaded; } - Top.Loaded += Top_Loaded; + Application.Top.Loaded += Top_Loaded; label = new Label ("Button Pressed:") { X = Pos.Center (), diff --git a/UICatalog/Scenarios/MultiColouredTable.cs b/UICatalog/Scenarios/MultiColouredTable.cs index da772b0ec..d1d9b4059 100644 --- a/UICatalog/Scenarios/MultiColouredTable.cs +++ b/UICatalog/Scenarios/MultiColouredTable.cs @@ -16,7 +16,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); this.tableView = new TableViewColors () { X = 0, @@ -30,12 +30,12 @@ namespace UICatalog.Scenarios { new MenuItem ("_Quit", "", () => Quit()), }), }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); Win.Add (tableView); diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index d9d760175..a4fe1992e 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -11,11 +11,10 @@ namespace UICatalog.Scenarios { private int numbeOfNewTabs = 1; // Don't create a Window, just return the top-level view - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); - Top = top != null ? top : Application.Top; - Top.ColorScheme = Colors.Base; + Application.Top.ColorScheme = Colors.Base; } public override void Setup () @@ -30,7 +29,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_Quit", "", () => Quit()), }) }); - Top.Add (menu); + Application.Top.Add (menu); tabView = new TabView () { X = 0, @@ -42,7 +41,7 @@ namespace UICatalog.Scenarios { tabView.Style.ShowBorder = true; tabView.ApplyStyleChanges (); - Top.Add (tabView); + Application.Top.Add (tabView); var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null); var statusBar = new StatusBar (new StatusItem [] { @@ -59,7 +58,7 @@ namespace UICatalog.Scenarios { tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}"; - Top.Add (statusBar); + Application.Top.Add (statusBar); New (); } diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 0b8ee4bfe..d35949b5c 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -131,7 +131,7 @@ namespace UICatalog.Scenarios { Application.MainLoop.Driver.Wakeup (); }, null, 0, 300); - Top.Unloaded += Top_Unloaded; + Application.Top.Unloaded += Top_Unloaded; void Top_Unloaded () { @@ -143,7 +143,7 @@ namespace UICatalog.Scenarios { _pulseTimer.Dispose (); _pulseTimer = null; } - Top.Unloaded -= Top_Unloaded; + Application.Top.Unloaded -= Top_Unloaded; } } } diff --git a/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs b/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs index a6bd140f0..4d7ddfa44 100644 --- a/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs +++ b/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs @@ -16,7 +16,7 @@ namespace UICatalog.Scenarios { private Window _win; private string _lastRunesUsed; - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { Application.Init (); diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index a1b82282a..de28406ae 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -156,9 +156,9 @@ namespace UICatalog.Scenarios { horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)] + "\n" + "| ".Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)]; verticalRuler.Text = vrule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height * 2) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height * 2)]; - Top.Loaded -= Top_Loaded; + Application.Top.Loaded -= Top_Loaded; } - Top.Loaded += Top_Loaded; + Application.Top.Loaded += Top_Loaded; var pressMeButton = new Button ("Press me!") { X = 3, @@ -313,9 +313,9 @@ namespace UICatalog.Scenarios { void Top_Unloaded () { pulsing = false; - Top.Unloaded -= Top_Unloaded; + Application.Top.Unloaded -= Top_Unloaded; } - Top.Unloaded += Top_Unloaded; + Application.Top.Unloaded += Top_Unloaded; } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index a2705500c..e0268ab41 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -11,11 +11,11 @@ namespace UICatalog.Scenarios { public class SingleBackgroundWorker : Scenario { public override void Run () { - Top.Dispose (); + Application.Top.Dispose (); Application.Run (); - Top.Dispose (); + Application.Top.Dispose (); } public class MainApp : Toplevel { diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index cd3436341..3a696b14c 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -21,7 +21,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { @@ -29,7 +29,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_Quit", "", () => Quit()), }) }); - Top.Add (menu); + Application.Top.Add (menu); textView = new SqlTextView () { X = 0, @@ -49,7 +49,7 @@ namespace UICatalog.Scenarios { }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } private void WordWrap () diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 73e27ad69..2bab75e70 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -24,7 +24,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { @@ -50,7 +50,7 @@ namespace UICatalog.Scenarios { }) }); - Top.Add (menu); + Application.Top.Add (menu); tabView = new TabView () { X = 0, @@ -85,7 +85,6 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), }; - frameRight.Add (new TextView () { Text = "This demos the tabs control\nSwitch between tabs using cursor keys", Width = Dim.Fill (), @@ -94,8 +93,6 @@ namespace UICatalog.Scenarios { Win.Add (frameRight); - - var frameBelow = new FrameView ("Bottom Frame") { X = 0, Y = Pos.Bottom (tabView), @@ -103,7 +100,6 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), }; - frameBelow.Add (new TextView () { Text = "This frame exists to check you can still tab here\nand that the tab control doesn't overspill it's bounds", Width = Dim.Fill (), @@ -115,7 +111,7 @@ namespace UICatalog.Scenarios { var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); } private void AddBlankTab () diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index e3da3c964..2f71e30ae 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -38,7 +38,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName(); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); this.tableView = new TableView () { X = 0, @@ -78,9 +78,9 @@ namespace UICatalog.Scenarios { new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne), }), }); - - Top.Add (menu); + + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)), @@ -88,7 +88,7 @@ namespace UICatalog.Scenarios { new StatusItem(Key.F4, "~F4~ OpenSimple", () => OpenSimple(true)), new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); Win.Add (tableView); diff --git a/UICatalog/Scenarios/TextFormatterDemo.cs b/UICatalog/Scenarios/TextFormatterDemo.cs index fb5187673..bf18ed506 100644 --- a/UICatalog/Scenarios/TextFormatterDemo.cs +++ b/UICatalog/Scenarios/TextFormatterDemo.cs @@ -42,7 +42,7 @@ namespace UICatalog.Scenarios { blockText.Text = ustring.Make (block.ToString ()); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space Win.Add (blockText); - var unicodeCheckBox = new CheckBox ("Unicode", Top.HotKeySpecifier == (Rune)' ') { + var unicodeCheckBox = new CheckBox ("Unicode", Application.Top.HotKeySpecifier == (Rune)' ') { X = 0, Y = Pos.Bottom (blockText) + 1, }; diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs index a9518586f..5907a6a25 100644 --- a/UICatalog/Scenarios/TextViewAutocompletePopup.cs +++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs @@ -33,7 +33,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_Quit", "", () => Quit()) }) }); - Top.Add (menu); + Application.Top.Add (menu); textViewTopLeft = new TextView () { Width = width, @@ -89,7 +89,7 @@ namespace UICatalog.Scenarios { siMultiline = new StatusItem(Key.Null, "", null), siWrap = new StatusItem(Key.Null, "", null) }); - Top.Add (statusBar); + Application.Top.Add (statusBar); Win.LayoutStarted += Win_LayoutStarted; } diff --git a/UICatalog/Scenarios/Threading.cs b/UICatalog/Scenarios/Threading.cs index 99ff09b10..6bd159497 100644 --- a/UICatalog/Scenarios/Threading.cs +++ b/UICatalog/Scenarios/Threading.cs @@ -96,9 +96,9 @@ namespace UICatalog.Scenarios { void Top_Loaded () { _btnActionCancel.SetFocus (); - Top.Loaded -= Top_Loaded; + Application.Top.Loaded -= Top_Loaded; } - Top.Loaded += Top_Loaded; + Application.Top.Loaded += Top_Loaded; } private async void LoadData () diff --git a/UICatalog/Scenarios/TreeUseCases.cs b/UICatalog/Scenarios/TreeUseCases.cs index 4a63c25aa..aa1626c8d 100644 --- a/UICatalog/Scenarios/TreeUseCases.cs +++ b/UICatalog/Scenarios/TreeUseCases.cs @@ -17,7 +17,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { @@ -31,13 +31,13 @@ namespace UICatalog.Scenarios { }), }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); // Start with the most basic use case LoadSimpleNodes (); diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 57fa181c7..e2c1c905b 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -35,7 +35,7 @@ namespace UICatalog.Scenarios { Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { @@ -60,12 +60,12 @@ namespace UICatalog.Scenarios { miMultiSelect = new MenuItem ("_MultiSelect", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, }), }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); var lblFiles = new Label ("File Tree:") { X = 0, diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index 29779c4ac..c2813a0f5 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -34,14 +34,14 @@ namespace UICatalog.Scenarios { new MenuItem ("_Paste", "", null) }) }); - Top.Add (menu); + Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Выход", () => Application.RequestStop()), new StatusItem (Key.Unknown, "~F2~ Создать", null), new StatusItem(Key.Unknown, "~F3~ Со_хранить", null), }); - Top.Add (statusBar); + Application.Top.Add (statusBar); var label = new Label ("Label:") { X = 0, Y = 1 }; Win.Add (label); diff --git a/UICatalog/Scenarios/WindowsAndFrameViews.cs b/UICatalog/Scenarios/WindowsAndFrameViews.cs index ca3f613ef..616c20fca 100644 --- a/UICatalog/Scenarios/WindowsAndFrameViews.cs +++ b/UICatalog/Scenarios/WindowsAndFrameViews.cs @@ -6,13 +6,6 @@ namespace UICatalog.Scenarios { [ScenarioMetadata (Name: "Windows & FrameViews", Description: "Shows Windows, sub-Windows, and FrameViews.")] [ScenarioCategory ("Layout")] public class WindowsAndFrameViews : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) - { - Application.Init (); - - Top = top != null ? top : Application.Top; - } - public override void RequestStop () { base.RequestStop (); @@ -67,7 +60,7 @@ namespace UICatalog.Scenarios { Y = Pos.AnchorEnd (1), ColorScheme = Colors.Error }); - Top.Add (Win); + Application.Top.Add (Win); listWin.Add (Win); for (var i = 0; i < 3; i++) { @@ -114,11 +107,10 @@ namespace UICatalog.Scenarios { }); win.Add (frameView); - Top.Add (win); + Application.Top.Add (win); listWin.Add (win); } - FrameView frame = null; frame = new FrameView ($"This is a FrameView") { X = margin, @@ -176,7 +168,7 @@ namespace UICatalog.Scenarios { frame.Add (subFrameViewofFV); - Top.Add (frame); + Application.Top.Add (frame); listWin.Add (frame); } } diff --git a/UICatalog/Scenarios/WizardAsView.cs b/UICatalog/Scenarios/WizardAsView.cs index a53067885..af2239285 100644 --- a/UICatalog/Scenarios/WizardAsView.cs +++ b/UICatalog/Scenarios/WizardAsView.cs @@ -10,10 +10,8 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("Wizards")] public class WizardAsView : Scenario { - public override void Init (Toplevel top, ColorScheme colorScheme) + public override void Init (ColorScheme colorScheme) { - Top = Application.Top; - var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Restart Configuration...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reset the Wizard and start over?", "Ok", "Cancel")), @@ -21,7 +19,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_Shutdown Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to cancel setup and shutdown?", "Ok", "Cancel")), }) }); - Top.Add (menu); + Application.Top.Add (menu); // No need for a Title because the border is disabled var wizard = new Wizard () { @@ -93,8 +91,8 @@ namespace UICatalog.Scenarios { wizard.AddStep (lastStep); lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel."; - Top.Add (wizard); - Application.Run (Top); + Application.Top.Add (wizard); + Application.Run (Application.Top); } public override void Run () diff --git a/UICatalog/Scenarios/Wizards.cs b/UICatalog/Scenarios/Wizards.cs index e5f503414..268d9a422 100644 --- a/UICatalog/Scenarios/Wizards.cs +++ b/UICatalog/Scenarios/Wizards.cs @@ -73,9 +73,9 @@ namespace UICatalog.Scenarios { void Top_Loaded () { frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2; - Top.Loaded -= Top_Loaded; + Application.Top.Loaded -= Top_Loaded; } - Top.Loaded += Top_Loaded; + Application.Top.Loaded += Top_Loaded; label = new Label ("Action:") { X = Pos.Center (), diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 33f69f321..c5f896b77 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -44,35 +44,7 @@ namespace UICatalog { /// /// UI Catalog is a comprehensive sample app and scenario library for /// - public class UICatalogApp { - private static int _nameColumnWidth; - private static FrameView _leftPane; - private static List _categories; - private static ListView _categoryListView; - private static FrameView _rightPane; - private static List _scenarios; - private static ListView _scenarioListView; - private static StatusBar _statusBar; - private static StatusItem _capslock; - private static StatusItem _numlock; - private static StatusItem _scrolllock; - - // If set, holds the scenario the user selected - private static Scenario _selectedScenario = null; - - private static bool _useSystemConsole = false; - private static ConsoleDriver.DiagnosticFlags _diagnosticFlags; - private static bool _heightAsBuffer = false; - private static bool _isFirstRunning = true; - - // When a scenario is run, the main app is killed. These items - // are therefore cached so that when the scenario exits the - // main app UI can be restored to previous state - private static int _cachedScenarioIndex = 0; - private static int _cachedCategoryIndex = 0; - - private static StringBuilder _aboutMessage; - + class UICatalogApp { static void Main (string [] args) { Console.OutputEncoding = Encoding.Default; @@ -82,17 +54,22 @@ namespace UICatalog { } _scenarios = Scenario.GetScenarios (); + _categories = Scenario.GetAllCategories (); + _nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length; if (args.Length > 0 && args.Contains ("-usc")) { _useSystemConsole = true; args = args.Where (val => val != "-usc").ToArray (); } + + // If a Scenario name has been provided on the commandline + // run it and exit when done. if (args.Length > 0) { var item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase)); _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ()); Application.UseSystemConsole = _useSystemConsole; Application.Init (); - _selectedScenario.Init (Application.Top, _colorScheme); + _selectedScenario.Init (_colorScheme); _selectedScenario.Setup (); _selectedScenario.Run (); _selectedScenario = null; @@ -114,17 +91,8 @@ namespace UICatalog { Scenario scenario; while ((scenario = SelectScenario ()) != null) { -#if DEBUG_IDISPOSABLE - // Validate there are no outstanding Responder-based instances - // after a scenario was selected to run. This proves the main UI Catalog - // 'app' closed cleanly. - foreach (var inst in Responder.Instances) { - Debug.Assert (inst.WasDisposed); - } - Responder.Instances.Clear (); -#endif - - scenario.Init (Application.Top, _colorScheme); + VerifyObjectsWereDisposed (); + scenario.Init (_colorScheme); scenario.Setup (); scenario.Run (); @@ -132,23 +100,436 @@ namespace UICatalog { // made by Scenario.Init() Application.Shutdown (); -#if DEBUG_IDISPOSABLE - // After the scenario runs, validate all Responder-based instances - // were disposed. This proves the scenario 'app' closed cleanly. - foreach (var inst in Responder.Instances) { - Debug.Assert (inst.WasDisposed); - } - Responder.Instances.Clear (); -#endif + VerifyObjectsWereDisposed (); + } + VerifyObjectsWereDisposed (); + } + + static List _scenarios; + static List _categories; + static int _nameColumnWidth; + // When a scenario is run, the main app is killed. These items + // are therefore cached so that when the scenario exits the + // main app UI can be restored to previous state + static int _cachedScenarioIndex = 0; + static int _cachedCategoryIndex = 0; + static StringBuilder _aboutMessage; + + // If set, holds the scenario the user selected + static Scenario _selectedScenario = null; + + static bool _useSystemConsole = false; + static ConsoleDriver.DiagnosticFlags _diagnosticFlags; + static bool _heightAsBuffer = false; + static bool _isFirstRunning = true; + static ColorScheme _colorScheme; + + /// + /// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on + /// the command line) and each time a Scenario ends. + /// + class UICatalogTopLevel : Toplevel { + public MenuItem miIsMouseDisabled; + public MenuItem miHeightAsBuffer; + + public FrameView LeftPane; + public ListView CategoryListView; + public FrameView RightPane; + public ListView ScenarioListView; + + public StatusItem Capslock; + public StatusItem Numlock; + public StatusItem Scrolllock; + public StatusItem DriverName; + + public UICatalogTopLevel () + { + ColorScheme = _colorScheme; + MenuBar = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask) + }), + new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), + new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()), + new MenuBarItem ("_Help", new MenuItem [] { + new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1), + new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2), + new MenuItem ("_About...", + "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A), + }), + }); + + Capslock = new StatusItem (Key.CharMask, "Caps", null); + Numlock = new StatusItem (Key.CharMask, "Num", null); + Scrolllock = new StatusItem (Key.CharMask, "Scroll", null); + DriverName = new StatusItem (Key.CharMask, "Driver:", null); + + StatusBar = new StatusBar () { + Visible = true, + }; + StatusBar.Items = new StatusItem [] { + Capslock, + Numlock, + Scrolllock, + new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => { + if (_selectedScenario is null){ + // This causes GetScenarioToRun to return null + _selectedScenario = null; + RequestStop(); + } else { + _selectedScenario.RequestStop(); + } + }), + new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => { + StatusBar.Visible = !StatusBar.Visible; + LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0); + RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0); + LayoutSubviews(); + SetChildNeedsDisplay(); + }), + DriverName, + }; + + LeftPane = new FrameView ("Categories") { + X = 0, + Y = 1, // for menu + Width = 25, + Height = Dim.Fill (1), + CanFocus = true, + Shortcut = Key.CtrlMask | Key.C + }; + LeftPane.Title = $"{LeftPane.Title} ({LeftPane.ShortcutTag})"; + LeftPane.ShortcutAction = () => LeftPane.SetFocus (); + + CategoryListView = new ListView (_categories) { + X = 0, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (0), + AllowsMarking = false, + CanFocus = true, + }; + CategoryListView.OpenSelectedItem += (a) => { + RightPane.SetFocus (); + }; + CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged; + LeftPane.Add (CategoryListView); + + RightPane = new FrameView ("Scenarios") { + X = 25, + Y = 1, // for menu + Width = Dim.Fill (), + Height = Dim.Fill (1), + CanFocus = true, + Shortcut = Key.CtrlMask | Key.S + }; + RightPane.Title = $"{RightPane.Title} ({RightPane.ShortcutTag})"; + RightPane.ShortcutAction = () => RightPane.SetFocus (); + + ScenarioListView = new ListView () { + X = 0, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (0), + AllowsMarking = false, + CanFocus = true, + }; + + ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem; + RightPane.Add (ScenarioListView); + + KeyDown += KeyDownHandler; + Add (MenuBar); + Add (LeftPane); + Add (RightPane); + Add (StatusBar); + + Loaded += LoadedHandler; + + // Restore previous selections + CategoryListView.SelectedItem = _cachedCategoryIndex; + ScenarioListView.SelectedItem = _cachedScenarioIndex; } + void LoadedHandler () + { + Application.HeightAsBuffer = _heightAsBuffer; + + if (_colorScheme == null) { + ColorScheme = _colorScheme = Colors.Base; + } + + miIsMouseDisabled.Checked = Application.IsMouseDisabled; + miHeightAsBuffer.Checked = Application.HeightAsBuffer; + DriverName.Title = $"Driver: {Driver.GetType ().Name}"; + + if (_selectedScenario != null) { + _selectedScenario = null; + _isFirstRunning = false; + } + if (!_isFirstRunning) { + RightPane.SetFocus (); + } + Loaded -= LoadedHandler; + } + + /// + /// Launches the selected scenario, setting the global _selectedScenario + /// + /// + void ScenarioListView_OpenSelectedItem (EventArgs e) + { + if (_selectedScenario is null) { + // Save selected item state + _cachedCategoryIndex = CategoryListView.SelectedItem; + _cachedScenarioIndex = ScenarioListView.SelectedItem; + // Create new instance of scenario (even though Scenarios contains instances) + _selectedScenario = (Scenario)Activator.CreateInstance (ScenarioListView.Source.ToList () [ScenarioListView.SelectedItem].GetType ()); + + // Tell the main app to stop + Application.RequestStop (); + } + } + + List CreateDiagnosticMenuItems () + { + List menuItems = new List (); + menuItems.Add (CreateDiagnosticFlagsMenuItems ()); + menuItems.Add (new MenuItem [] { null }); + menuItems.Add (CreateHeightAsBufferMenuItems ()); + menuItems.Add (CreateDisabledEnabledMouseItems ()); + menuItems.Add (CreateKeybindingsMenuItems ()); + return menuItems; + } + + MenuItem [] CreateDisabledEnabledMouseItems () + { + List menuItems = new List (); + miIsMouseDisabled = new MenuItem (); + miIsMouseDisabled.Title = "_Disable Mouse"; + miIsMouseDisabled.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miIsMouseDisabled.Title.ToString ().Substring (1, 1) [0]; + miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked; + miIsMouseDisabled.Action += () => { + miIsMouseDisabled.Checked = Application.IsMouseDisabled = !miIsMouseDisabled.Checked; + }; + menuItems.Add (miIsMouseDisabled); + + return menuItems.ToArray (); + } + + MenuItem [] CreateKeybindingsMenuItems () + { + List menuItems = new List (); + var item = new MenuItem (); + item.Title = "_Key Bindings"; + item.Help = "Change which keys do what"; + item.Action += () => { + var dlg = new KeyBindingsDialog (); + Application.Run (dlg); + }; + + menuItems.Add (null); + menuItems.Add (item); + + return menuItems.ToArray (); + } + + MenuItem [] CreateHeightAsBufferMenuItems () + { + List menuItems = new List (); + miHeightAsBuffer = new MenuItem (); + miHeightAsBuffer.Title = "_Height As Buffer"; + miHeightAsBuffer.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miHeightAsBuffer.Title.ToString ().Substring (1, 1) [0]; + miHeightAsBuffer.CheckType |= MenuItemCheckStyle.Checked; + miHeightAsBuffer.Action += () => { + miHeightAsBuffer.Checked = !miHeightAsBuffer.Checked; + Application.HeightAsBuffer = miHeightAsBuffer.Checked; + }; + menuItems.Add (miHeightAsBuffer); + + return menuItems.ToArray (); + } + + MenuItem [] CreateDiagnosticFlagsMenuItems () + { + const string OFF = "Diagnostics: _Off"; + const string FRAME_RULER = "Diagnostics: Frame _Ruler"; + const string FRAME_PADDING = "Diagnostics: _Frame Padding"; + var index = 0; + + List menuItems = new List (); + foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) { + var item = new MenuItem (); + item.Title = GetDiagnosticsTitle (diag); + item.Shortcut = Key.AltMask + index.ToString () [0]; + index++; + item.CheckType |= MenuItemCheckStyle.Checked; + if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) { + item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding + | ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0; + } else { + item.Checked = _diagnosticFlags.HasFlag (diag); + } + item.Action += () => { + var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off); + if (item.Title == t && !item.Checked) { + _diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler); + item.Checked = true; + } else if (item.Title == t && item.Checked) { + _diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler); + item.Checked = false; + } else { + var f = GetDiagnosticsEnumValue (item.Title); + if (_diagnosticFlags.HasFlag (f)) { + SetDiagnosticsFlag (f, false); + } else { + SetDiagnosticsFlag (f, true); + } + } + foreach (var menuItem in menuItems) { + if (menuItem.Title == t) { + menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler) + && !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding); + } else if (menuItem.Title != t) { + menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title)); + } + } + ConsoleDriver.Diagnostics = _diagnosticFlags; + Application.Top.SetNeedsDisplay (); + }; + menuItems.Add (item); + } + return menuItems.ToArray (); + + string GetDiagnosticsTitle (Enum diag) + { + switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) { + case "Off": + return OFF; + case "FrameRuler": + return FRAME_RULER; + case "FramePadding": + return FRAME_PADDING; + } + return ""; + } + + Enum GetDiagnosticsEnumValue (ustring title) + { + switch (title.ToString ()) { + case FRAME_RULER: + return ConsoleDriver.DiagnosticFlags.FrameRuler; + case FRAME_PADDING: + return ConsoleDriver.DiagnosticFlags.FramePadding; + } + return null; + } + + void SetDiagnosticsFlag (Enum diag, bool add) + { + switch (diag) { + case ConsoleDriver.DiagnosticFlags.FrameRuler: + if (add) { + _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler; + } else { + _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler; + } + break; + case ConsoleDriver.DiagnosticFlags.FramePadding: + if (add) { + _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding; + } else { + _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding; + } + break; + default: + _diagnosticFlags = default; + break; + } + } + } + + MenuItem [] CreateColorSchemeMenuItems () + { + List menuItems = new List (); + foreach (var sc in Colors.ColorSchemes) { + var item = new MenuItem (); + item.Title = $"_{sc.Key}"; + item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0]; + item.CheckType |= MenuItemCheckStyle.Radio; + item.Checked = sc.Value == _colorScheme; + item.Action += () => { + ColorScheme = _colorScheme = sc.Value; + SetNeedsDisplay (); + foreach (var menuItem in menuItems) { + menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme; + } + }; + menuItems.Add (item); + } + return menuItems.ToArray (); + } + + void KeyDownHandler (View.KeyEventEventArgs a) + { + if (a.KeyEvent.IsCapslock) { + Capslock.Title = "Caps: On"; + StatusBar.SetNeedsDisplay (); + } else { + Capslock.Title = "Caps: Off"; + StatusBar.SetNeedsDisplay (); + } + + if (a.KeyEvent.IsNumlock) { + Numlock.Title = "Num: On"; + StatusBar.SetNeedsDisplay (); + } else { + Numlock.Title = "Num: Off"; + StatusBar.SetNeedsDisplay (); + } + + if (a.KeyEvent.IsScrolllock) { + Scrolllock.Title = "Scroll: On"; + StatusBar.SetNeedsDisplay (); + } else { + Scrolllock.Title = "Scroll: Off"; + StatusBar.SetNeedsDisplay (); + } + } + + void CategoryListView_SelectedChanged (ListViewItemEventArgs e) + { + var item = _categories [e.Item]; + List newlist; + if (e.Item == 0) { + // First category is "All" + newlist = _scenarios; + + } else { + newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList (); + } + ScenarioListView.SetSource (newlist.ToList ()); + } + } + + static void VerifyObjectsWereDisposed () + { #if DEBUG_IDISPOSABLE - // This proves that when the user exited the UI Catalog app - // it cleaned up properly. + // Validate there are no outstanding Responder-based instances + // after a scenario was selected to run. This proves the main UI Catalog + // 'app' closed cleanly. foreach (var inst in Responder.Instances) { Debug.Assert (inst.WasDisposed); } Responder.Instances.Clear (); + + // Validate there are no outstanding Application.RunState-based instances + // after a scenario was selected to run. This proves the main UI Catalog + // 'app' closed cleanly. + foreach (var inst in Application.RunState.Instances) { + Debug.Assert (inst.WasDisposed); + } + Application.RunState.Instances.Clear (); #endif } @@ -158,389 +539,20 @@ namespace UICatalog { /// When the Scenario exits, this function exits. /// /// - private static Scenario SelectScenario () + static Scenario SelectScenario () { Application.UseSystemConsole = _useSystemConsole; - Application.Init (); - if (_colorScheme == null) { - // `Colors` is not initilized until the ConsoleDriver is loaded by - // Application.Init. Set it only the first time though so it is - // preserved between running multiple Scenarios - _colorScheme = Colors.Base; - } - Application.HeightAsBuffer = _heightAsBuffer; - - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask) - }), - new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), - new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()), - new MenuBarItem ("_Help", new MenuItem [] { - new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1), - new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2), - new MenuItem ("_About...", - "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A), - }), - }); - - _leftPane = new FrameView ("Categories") { - X = 0, - Y = 1, // for menu - Width = 25, - Height = Dim.Fill (1), - CanFocus = true, - Shortcut = Key.CtrlMask | Key.C - }; - _leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})"; - _leftPane.ShortcutAction = () => _leftPane.SetFocus (); - - _categories = Scenario.GetAllCategories (); - _categoryListView = new ListView (_categories) { - X = 0, - Y = 0, - Width = Dim.Fill (0), - Height = Dim.Fill (0), - AllowsMarking = false, - CanFocus = true, - }; - _categoryListView.OpenSelectedItem += (a) => { - _rightPane.SetFocus (); - }; - _categoryListView.SelectedItemChanged += CategoryListView_SelectedChanged; - _leftPane.Add (_categoryListView); - - _rightPane = new FrameView ("Scenarios") { - X = 25, - Y = 1, // for menu - Width = Dim.Fill (), - Height = Dim.Fill (1), - CanFocus = true, - Shortcut = Key.CtrlMask | Key.S - }; - _rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})"; - _rightPane.ShortcutAction = () => _rightPane.SetFocus (); - - _nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length; - - _scenarioListView = new ListView () { - X = 0, - Y = 0, - Width = Dim.Fill (0), - Height = Dim.Fill (0), - AllowsMarking = false, - CanFocus = true, - }; - - _scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem; - _rightPane.Add (_scenarioListView); - - _capslock = new StatusItem (Key.CharMask, "Caps", null); - _numlock = new StatusItem (Key.CharMask, "Num", null); - _scrolllock = new StatusItem (Key.CharMask, "Scroll", null); - - _statusBar = new StatusBar () { - Visible = true, - }; - _statusBar.Items = new StatusItem [] { - _capslock, - _numlock, - _scrolllock, - new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => { - if (_selectedScenario is null){ - // This causes GetScenarioToRun to return null - _selectedScenario = null; - Application.RequestStop(); - } else { - _selectedScenario.RequestStop(); - } - }), - new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => { - _statusBar.Visible = !_statusBar.Visible; - _leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0); - _rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0); - Application.Top.LayoutSubviews(); - Application.Top.SetChildNeedsDisplay(); - }), - new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null), - }; - - Application.Top.ColorScheme = _colorScheme; - Application.Top.KeyDown += KeyDownHandler; - Application.Top.Add (menu); - Application.Top.Add (_leftPane); - Application.Top.Add (_rightPane); - Application.Top.Add (_statusBar); - - void TopHandler () - { - if (_selectedScenario != null) { - _selectedScenario = null; - _isFirstRunning = false; - } - if (!_isFirstRunning) { - _rightPane.SetFocus (); - } - Application.Top.Loaded -= TopHandler; - } - Application.Top.Loaded += TopHandler; - - // Restore previous selections - _categoryListView.SelectedItem = _cachedCategoryIndex; - _scenarioListView.SelectedItem = _cachedScenarioIndex; + //var top = new UICatalogTopLevel (); // Run UI Catalog UI. When it exits, if _selectedScenario is != null then // a Scenario was selected. Otherwise, the user wants to exit UI Catalog. - Application.Run (Application.Top); + Application.Run (); Application.Shutdown (); return _selectedScenario; } - - /// - /// Launches the selected scenario, setting the global _selectedScenario - /// - /// - private static void _scenarioListView_OpenSelectedItem (EventArgs e) - { - if (_selectedScenario is null) { - // Save selected item state - _cachedCategoryIndex = _categoryListView.SelectedItem; - _cachedScenarioIndex = _scenarioListView.SelectedItem; - // Create new instance of scenario (even though Scenarios contains instances) - _selectedScenario = (Scenario)Activator.CreateInstance (_scenarioListView.Source.ToList () [_scenarioListView.SelectedItem].GetType ()); - - // Tell the main app to stop - Application.RequestStop (); - } - } - - static List CreateDiagnosticMenuItems () - { - List menuItems = new List (); - menuItems.Add (CreateDiagnosticFlagsMenuItems ()); - menuItems.Add (new MenuItem [] { null }); - menuItems.Add (CreateSizeStyle ()); - menuItems.Add (CreateDisabledEnabledMouse ()); - menuItems.Add (CreateKeybindings ()); - return menuItems; - } - - private static MenuItem [] CreateDisabledEnabledMouse () - { - List menuItems = new List (); - var item = new MenuItem (); - item.Title = "_Disable Mouse"; - item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0]; - item.CheckType |= MenuItemCheckStyle.Checked; - item.Checked = Application.IsMouseDisabled; - item.Action += () => { - item.Checked = Application.IsMouseDisabled = !item.Checked; - }; - menuItems.Add (item); - - return menuItems.ToArray (); - } - private static MenuItem [] CreateKeybindings () - { - - List menuItems = new List (); - var item = new MenuItem (); - item.Title = "_Key Bindings"; - item.Help = "Change which keys do what"; - item.Action += () => { - var dlg = new KeyBindingsDialog (); - Application.Run (dlg); - }; - - menuItems.Add (null); - menuItems.Add (item); - - return menuItems.ToArray (); - } - - static MenuItem [] CreateSizeStyle () - { - List menuItems = new List (); - var item = new MenuItem (); - item.Title = "_Height As Buffer"; - item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0]; - item.CheckType |= MenuItemCheckStyle.Checked; - item.Checked = Application.HeightAsBuffer; - item.Action += () => { - item.Checked = !item.Checked; - _heightAsBuffer = item.Checked; - Application.HeightAsBuffer = _heightAsBuffer; - }; - menuItems.Add (item); - - return menuItems.ToArray (); - } - - static MenuItem [] CreateDiagnosticFlagsMenuItems () - { - const string OFF = "Diagnostics: _Off"; - const string FRAME_RULER = "Diagnostics: Frame _Ruler"; - const string FRAME_PADDING = "Diagnostics: _Frame Padding"; - var index = 0; - - List menuItems = new List (); - foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) { - var item = new MenuItem (); - item.Title = GetDiagnosticsTitle (diag); - item.Shortcut = Key.AltMask + index.ToString () [0]; - index++; - item.CheckType |= MenuItemCheckStyle.Checked; - if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) { - item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding - | ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0; - } else { - item.Checked = _diagnosticFlags.HasFlag (diag); - } - item.Action += () => { - var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off); - if (item.Title == t && !item.Checked) { - _diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler); - item.Checked = true; - } else if (item.Title == t && item.Checked) { - _diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler); - item.Checked = false; - } else { - var f = GetDiagnosticsEnumValue (item.Title); - if (_diagnosticFlags.HasFlag (f)) { - SetDiagnosticsFlag (f, false); - } else { - SetDiagnosticsFlag (f, true); - } - } - foreach (var menuItem in menuItems) { - if (menuItem.Title == t) { - menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler) - && !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding); - } else if (menuItem.Title != t) { - menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title)); - } - } - ConsoleDriver.Diagnostics = _diagnosticFlags; - Application.Top.SetNeedsDisplay (); - }; - menuItems.Add (item); - } - return menuItems.ToArray (); - - string GetDiagnosticsTitle (Enum diag) - { - switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) { - case "Off": - return OFF; - case "FrameRuler": - return FRAME_RULER; - case "FramePadding": - return FRAME_PADDING; - } - return ""; - } - - Enum GetDiagnosticsEnumValue (ustring title) - { - switch (title.ToString ()) { - case FRAME_RULER: - return ConsoleDriver.DiagnosticFlags.FrameRuler; - case FRAME_PADDING: - return ConsoleDriver.DiagnosticFlags.FramePadding; - } - return null; - } - - void SetDiagnosticsFlag (Enum diag, bool add) - { - switch (diag) { - case ConsoleDriver.DiagnosticFlags.FrameRuler: - if (add) { - _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler; - } else { - _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler; - } - break; - case ConsoleDriver.DiagnosticFlags.FramePadding: - if (add) { - _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding; - } else { - _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding; - } - break; - default: - _diagnosticFlags = default; - break; - } - } - } - - static ColorScheme _colorScheme; - static MenuItem [] CreateColorSchemeMenuItems () - { - List menuItems = new List (); - foreach (var sc in Colors.ColorSchemes) { - var item = new MenuItem (); - item.Title = $"_{sc.Key}"; - item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0]; - item.CheckType |= MenuItemCheckStyle.Radio; - item.Checked = sc.Value == _colorScheme; - item.Action += () => { - Application.Top.ColorScheme = _colorScheme = sc.Value; - Application.Top?.SetNeedsDisplay (); - foreach (var menuItem in menuItems) { - menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme; - } - }; - menuItems.Add (item); - } - return menuItems.ToArray (); - } - - private static void KeyDownHandler (View.KeyEventEventArgs a) - { - if (a.KeyEvent.IsCapslock) { - _capslock.Title = "Caps: On"; - _statusBar.SetNeedsDisplay (); - } else { - _capslock.Title = "Caps: Off"; - _statusBar.SetNeedsDisplay (); - } - - if (a.KeyEvent.IsNumlock) { - _numlock.Title = "Num: On"; - _statusBar.SetNeedsDisplay (); - } else { - _numlock.Title = "Num: Off"; - _statusBar.SetNeedsDisplay (); - } - - if (a.KeyEvent.IsScrolllock) { - _scrolllock.Title = "Scroll: On"; - _statusBar.SetNeedsDisplay (); - } else { - _scrolllock.Title = "Scroll: Off"; - _statusBar.SetNeedsDisplay (); - } - } - - private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e) - { - var item = _categories [e.Item]; - List newlist; - if (e.Item == 0) { - // First category is "All" - newlist = _scenarios; - - } else { - newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList (); - } - _scenarioListView.SetSource (newlist.ToList ()); - } - - private static void OpenUrl (string url) + static void OpenUrl (string url) { try { if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index 597534c74..c10aaf211 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -14,9 +14,47 @@ namespace Terminal.Gui.Core { { #if DEBUG_IDISPOSABLE Responder.Instances.Clear (); + Application.RunState.Instances.Clear (); #endif } + void Pre_Init_State () + { + Assert.Null (Application.Driver); + Assert.Null (Application.Top); + Assert.Null (Application.Current); + Assert.Throws (() => Application.HeightAsBuffer == true); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Iteration); + Assert.Null (Application.RootMouseEvent); + Assert.Null (Application.Resized); + } + + void Post_Init_State () + { + Assert.NotNull (Application.Driver); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); + Assert.False (Application.HeightAsBuffer); + Assert.NotNull (Application.MainLoop); + Assert.Null (Application.Iteration); + Assert.Null (Application.RootMouseEvent); + Assert.Null (Application.Resized); + } + + void Init () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + Assert.NotNull (Application.Driver); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (SynchronizationContext.Current); + } + + void Shutdown () + { + Application.Shutdown (); + } + [Fact] public void Init_Shutdown_Cleans_Up () { @@ -40,7 +78,7 @@ namespace Terminal.Gui.Core { // Verify state is back to initial Pre_Init_State (); - + // Validate there are no outstanding Responder-based instances // after a scenario was selected to run. This proves the main UI Catalog // 'app' closed cleanly. @@ -48,75 +86,69 @@ namespace Terminal.Gui.Core { Assert.True (inst.WasDisposed); } } - - void Pre_Init_State () - { - Assert.Null (Application.Driver); - Assert.Null (Application.Top); - Assert.Null (Application.Current); - Assert.Throws (() => Application.HeightAsBuffer == true); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Iteration); - Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.Resized); - } - - void Post_Init_State () - { - Assert.NotNull (Application.Driver); - Assert.NotNull (Application.Top); - Assert.NotNull (Application.Current); - Assert.False (Application.HeightAsBuffer); - Assert.NotNull (Application.MainLoop); - Assert.Null (Application.Iteration); - Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.Resized); - } [Fact] - public void RunState_Dispose_Cleans_Up () - { - var rs = new Application.RunState (null); - Assert.NotNull (rs); - - // Should not throw because Toplevel was null - rs.Dispose (); - - var top = new Toplevel (); - rs = new Application.RunState (top); - Assert.NotNull (rs); - - // Should throw because there's no stack - Assert.Throws (() => rs.Dispose ()); - } - - void Init () + public void Init_Unbalanced_Throwss () { Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - Assert.NotNull (Application.Driver); - Assert.NotNull (Application.MainLoop); - Assert.NotNull (SynchronizationContext.Current); + + Toplevel topLevel = null; + Assert.Throws (() => Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)))); + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + + // Now try the other way + topLevel = null; + Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + Assert.Throws (() => Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)))); + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + + class TestToplevel : Toplevel { + public TestToplevel () + { + IsMdiContainer = false; + } } - void Shutdown () - { - Application.Shutdown (); - } - [Fact] - public void Begin_End_Cleans_Up () + public void Init_Begin_End_Cleans_Up () { - // Setup Mock driver Init (); + + // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests + // if we don't stop + Application.Iteration = () => { + Application.RequestStop (); + }; - // Test null Toplevel - Assert.Throws (() => Application.Begin (null)); + Application.RunState runstate = null; + Action NewRunStateFn = (rs) => { + Assert.NotNull (rs); + runstate = rs; + }; + Application.NotifyNewRunState += NewRunStateFn; - var top = new Toplevel (); - var rs = Application.Begin (top); + Toplevel topLevel = new Toplevel(); + var rs = Application.Begin (topLevel); Assert.NotNull (rs); - Assert.Equal (top, Application.Current); - Application.End (rs); + Assert.NotNull (runstate); + Assert.Equal (rs, runstate); + + Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); + + Application.NotifyNewRunState -= NewRunStateFn; + Application.End (runstate); Assert.Null (Application.Current); Assert.NotNull (Application.Top); @@ -131,7 +163,141 @@ namespace Terminal.Gui.Core { } [Fact] - public void RequestStop_Stops () + public void InitWithTopLevelFactory_Begin_End_Cleans_Up () + { + // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests + // if we don't stop + Application.Iteration = () => { + Application.RequestStop (); + }; + + // NOTE: Run, when called after Init has been called behaves differently than + // when called if Init has not been called. + Toplevel topLevel = null; + Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + Application.RunState runstate = null; + Action NewRunStateFn = (rs) => { + Assert.NotNull (rs); + runstate = rs; + }; + Application.NotifyNewRunState += NewRunStateFn; + + var rs = Application.Begin (topLevel); + Assert.NotNull (rs); + Assert.NotNull (runstate); + Assert.Equal (rs, runstate); + + Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); + + Application.NotifyNewRunState -= NewRunStateFn; + Application.End (runstate); + + Assert.Null (Application.Current); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (Application.Driver); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Begin_Null_Toplevel_Throws () + { + // Setup Mock driver + Init (); + + // Test null Toplevel + Assert.Throws (() => Application.Begin (null)); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + #region RunTests + + [Fact] + public void Run_T_InitWithDriver_Throws_with_TopLevel () + { + // Setup Mock driver + Init (); + + // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) + Assert.Throws (() => Application.Run (errorHandler: null)); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_InitWithDriver_Works_with_TestTopLevel () + { + // Setup Mock driver + Init (); + + Application.Iteration = () => { + Application.RequestStop (); + }; + + // Run when already initialized with a Driver will work + Application.Run (errorHandler: null); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_NoInit_ThrowsInDefaultDriver () + { + Application.Iteration = () => { + Application.RequestStop (); + }; + + // Note that Init has NOT been called and we're passing no driver + // The platform-default driver will be selected and it will barf + Assert.ThrowsAny (() => Application.Run (errorHandler: null, driver: null, mainLoopDriver: null)); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_NoInit_Works_WithFakeDriver () + { + Application.Iteration = () => { + Application.RequestStop (); + }; + + // Note that Init has NOT been called and we're passing no driver + // The platform-default driver will be selected and it will barf + Application.Run (errorHandler: null, new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_RequestStop_Stops () { // Setup Mock driver Init (); @@ -155,7 +321,7 @@ namespace Terminal.Gui.Core { } [Fact] - public void RunningFalse_Stops () + public void Run_RunningFalse_Stops () { // Setup Mock driver Init (); @@ -178,7 +344,139 @@ namespace Terminal.Gui.Core { Assert.Null (Application.Driver); } + [Fact] + public void Run_Loaded_Ready_Unlodaded_Events () + { + Init (); + var top = Application.Top; + var count = 0; + top.Loaded += () => count++; + top.Ready += () => count++; + top.Unloaded += () => count++; + Application.Iteration = () => Application.RequestStop (); + Application.Run (); + Application.Shutdown (); + Assert.Equal (3, count); + } + #endregion + #region ShutdownTests + [Fact] + public void Shutdown_Allows_Async () + { + static async Task TaskWithAsyncContinuation () + { + await Task.Yield (); + await Task.Yield (); + } + + Init (); + Application.Shutdown (); + + var task = TaskWithAsyncContinuation (); + Thread.Sleep (20); + Assert.True (task.IsCompletedSuccessfully); + } + + [Fact] + public void Shutdown_Resets_SyncContext () + { + Init (); + Application.Shutdown (); + Assert.Null (SynchronizationContext.Current); + } + #endregion + + [Fact] + [AutoInitShutdown] + public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top () + { + var t1 = new Toplevel (); + var t2 = new Toplevel (); + var t3 = new Toplevel (); + var d = new Dialog (); + var t4 = new Toplevel (); + + // t1, t2, t3, d, t4 + var iterations = 5; + + t1.Ready += () => { + Assert.Equal (t1, Application.Top); + Application.Run (t2); + }; + t2.Ready += () => { + Assert.Equal (t2, Application.Top); + Application.Run (t3); + }; + t3.Ready += () => { + Assert.Equal (t3, Application.Top); + Application.Run (d); + }; + d.Ready += () => { + Assert.Equal (t3, Application.Top); + Application.Run (t4); + }; + t4.Ready += () => { + Assert.Equal (t4, Application.Top); + t4.RequestStop (); + d.RequestStop (); + t3.RequestStop (); + t2.RequestStop (); + }; + // Now this will close the MdiContainer when all MdiChildes was closed + t2.Closed += (_) => { + t1.RequestStop (); + }; + Application.Iteration += () => { + if (iterations == 5) { + // The Current still is t4 because Current.Running is false. + Assert.Equal (t4, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t4, Application.Top); + } else if (iterations == 4) { + // The Current is d and Current.Running is false. + Assert.Equal (d, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t4, Application.Top); + } else if (iterations == 3) { + // The Current is t3 and Current.Running is false. + Assert.Equal (t3, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t3, Application.Top); + } else if (iterations == 2) { + // The Current is t2 and Current.Running is false. + Assert.Equal (t2, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t2, Application.Top); + } else { + // The Current is t1. + Assert.Equal (t1, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t1, Application.Top); + } + iterations--; + }; + + Application.Run (t1); + + Assert.Equal (t1, Application.Top); + } + + [Fact] + [AutoInitShutdown] + public void Internal_Properties_Correct () + { + Assert.True (Application._initialized); + Assert.NotNull (Application.Top); + var rs = Application.Begin (Application.Top); + Assert.Equal (Application.Top, rs.Toplevel); + Assert.Null (Application.MouseGrabView); // public + Assert.Null (Application.WantContinuousButtonPressedView); // public + Assert.False (Application.DebugDrawBounds); + Assert.False (Application.ShowChild (Application.Top)); + } + + #region KeyboardTests [Fact] public void KeyUp_Event () { @@ -239,46 +537,6 @@ namespace Terminal.Gui.Core { Assert.Null (Application.Driver); } - [Fact] - public void Loaded_Ready_Unlodaded_Events () - { - Init (); - var top = Application.Top; - var count = 0; - top.Loaded += () => count++; - top.Ready += () => count++; - top.Unloaded += () => count++; - Application.Iteration = () => Application.RequestStop (); - Application.Run (); - Application.Shutdown (); - Assert.Equal (3, count); - } - - [Fact] - public void Shutdown_Allows_Async () - { - static async Task TaskWithAsyncContinuation () - { - await Task.Yield (); - await Task.Yield (); - } - - Init (); - Application.Shutdown (); - - var task = TaskWithAsyncContinuation (); - Thread.Sleep (20); - Assert.True (task.IsCompletedSuccessfully); - } - - [Fact] - public void Shutdown_Resets_SyncContext () - { - Init (); - Application.Shutdown (); - Assert.Null (SynchronizationContext.Current); - } - [Fact] public void AlternateForwardKey_AlternateBackwardKey_Tests () { @@ -392,776 +650,6 @@ namespace Terminal.Gui.Core { Application.Shutdown (); } - [Fact] - public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use_The_Application_Current () - { - Init (); - - var top1 = new Toplevel (); - var top2 = new Toplevel (); - var top3 = new Window (); - var top4 = new Window (); - var d = new Dialog (); - - // top1, top2, top3, d1 = 4 - var iterations = 4; - - top1.Ready += () => { - Assert.Null (Application.MdiChildes); - Application.Run (top2); - }; - top2.Ready += () => { - Assert.Null (Application.MdiChildes); - Application.Run (top3); - }; - top3.Ready += () => { - Assert.Null (Application.MdiChildes); - Application.Run (top4); - }; - top4.Ready += () => { - Assert.Null (Application.MdiChildes); - Application.Run (d); - }; - - d.Ready += () => { - Assert.Null (Application.MdiChildes); - // This will close the d because on a not MdiContainer the Application.Current it always used. - Application.RequestStop (top1); - Assert.True (Application.Current == d); - }; - - d.Closed += (e) => Application.RequestStop (top1); - - Application.Iteration += () => { - Assert.Null (Application.MdiChildes); - if (iterations == 4) { - Assert.True (Application.Current == d); - } else if (iterations == 3) { - Assert.True (Application.Current == top4); - } else if (iterations == 2) { - Assert.True (Application.Current == top3); - } else if (iterations == 1) { - Assert.True (Application.Current == top2); - } else { - Assert.True (Application.Current == top1); - } - Application.RequestStop (top1); - iterations--; - }; - - Application.Run (top1); - - Assert.Null (Application.MdiChildes); - - Application.Shutdown (); - } - - class Mdi : Toplevel { - public Mdi () - { - IsMdiContainer = true; - } - } - - [Fact] - public void MdiContainer_With_Toplevel_RequestStop_Balanced () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d = new Dialog (); - - // MdiChild = c1, c2, c3 - // d1 = 1 - var iterations = 4; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d); - }; - - // More easy because the Mdi Container handles all at once - d.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - // This will not close the MdiContainer because d is a modal toplevel and will be closed. - mdi.RequestStop (); - }; - - // Now this will close the MdiContainer propagating through the MdiChildes. - d.Closed += (e) => { - mdi.RequestStop (); - }; - - Application.Iteration += () => { - if (iterations == 4) { - // The Dialog was not closed before and will be closed now. - Assert.True (Application.Current == d); - Assert.False (d.Running); - } else { - Assert.Equal (iterations, Application.MdiChildes.Count); - for (int i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); - } - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d = new Dialog (); - - // MdiChild = c1, c2, c3 - // d1 = 1 - var iterations = 4; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d); - }; - - // Also easy because the Mdi Container handles all at once - d.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - // This will not close the MdiContainer because d is a modal toplevel - Application.RequestStop (mdi); - }; - - // Now this will close the MdiContainer propagating through the MdiChildes. - d.Closed += (e) => Application.RequestStop (mdi); - - Application.Iteration += () => { - if (iterations == 4) { - // The Dialog was not closed before and will be closed now. - Assert.True (Application.Current == d); - Assert.False (d.Running); - } else { - Assert.Equal (iterations, Application.MdiChildes.Count); - for (int i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); - } - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void MdiContainer_With_Application_RequestStop_MdiTop_Without_Params () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d = new Dialog (); - - // MdiChild = c1, c2, c3 = 3 - // d1 = 1 - var iterations = 4; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d); - }; - - //More harder because it's sequential. - d.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - // Close the Dialog - Application.RequestStop (); - }; - - // Now this will close the MdiContainer propagating through the MdiChildes. - d.Closed += (e) => Application.RequestStop (mdi); - - Application.Iteration += () => { - if (iterations == 4) { - // The Dialog still is the current top and we can't request stop to MdiContainer - // because we are not using parameter calls. - Assert.True (Application.Current == d); - Assert.False (d.Running); - } else { - Assert.Equal (iterations, Application.MdiChildes.Count); - for (int i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); - } - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void IsMdiChild_Testing () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d = new Dialog (); - - Application.Iteration += () => { - Assert.False (mdi.IsMdiChild); - Assert.True (c1.IsMdiChild); - Assert.True (c2.IsMdiChild); - Assert.True (c3.IsMdiChild); - Assert.False (d.IsMdiChild); - - mdi.RequestStop (); - }; - - Application.Run (mdi); - - Application.Shutdown (); - } - - [Fact] - public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d1 = new Dialog (); - var d2 = new Dialog (); - - // MdiChild = c1, c2, c3 = 3 - // d1, d2 = 2 - var iterations = 5; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d1); - }; - d1.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d2); - }; - - d2.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Assert.True (Application.Current == d2); - Assert.True (Application.Current.Running); - // Trying to close the Dialog1 - d1.RequestStop (); - }; - - // Now this will close the MdiContainer propagating through the MdiChildes. - d1.Closed += (e) => { - Assert.True (Application.Current == d1); - Assert.False (Application.Current.Running); - mdi.RequestStop (); - }; - - Application.Iteration += () => { - if (iterations == 5) { - // The Dialog2 still is the current top and we can't request stop to MdiContainer - // because Dialog2 and Dialog1 must be closed first. - // Dialog2 will be closed in this iteration. - Assert.True (Application.Current == d2); - Assert.False (Application.Current.Running); - Assert.False (d1.Running); - } else if (iterations == 4) { - // Dialog1 will be closed in this iteration. - Assert.True (Application.Current == d1); - Assert.False (Application.Current.Running); - } else { - Assert.Equal (iterations, Application.MdiChildes.Count); - for (int i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); - } - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void Modal_Toplevel_Can_Open_Another_Not_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - var d1 = new Dialog (); - var c4 = new Toplevel (); - - // MdiChild = c1, c2, c3, c4 = 4 - // d1 = 1 - var iterations = 5; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (d1); - }; - d1.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - Application.Run (c4); - }; - - c4.Ready += () => { - Assert.Equal (4, Application.MdiChildes.Count); - // Trying to close the Dialog1 - d1.RequestStop (); - }; - - // Now this will close the MdiContainer propagating through the MdiChildes. - d1.Closed += (e) => { - mdi.RequestStop (); - }; - - Application.Iteration += () => { - if (iterations == 5) { - // The Dialog2 still is the current top and we can't request stop to MdiContainer - // because Dialog2 and Dialog1 must be closed first. - // Using request stop here will call the Dialog again without need - Assert.True (Application.Current == d1); - Assert.False (Application.Current.Running); - Assert.True (c4.Running); - } else { - Assert.Equal (iterations, Application.MdiChildes.Count); - for (int i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (), - Application.MdiChildes [i].Id); - } - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - - // MdiChild = c1, c2, c3 - var iterations = 3; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - c3.RequestStop (); - c1.RequestStop (); - }; - // Now this will close the MdiContainer propagating through the MdiChildes. - c1.Closed += (e) => { - mdi.RequestStop (); - }; - Application.Iteration += () => { - if (iterations == 3) { - // The Current still is c3 because Current.Running is false. - Assert.True (Application.Current == c3); - Assert.False (Application.Current.Running); - // But the childes order were reorder by Running = false - Assert.True (Application.MdiChildes [0] == c3); - Assert.True (Application.MdiChildes [1] == c1); - Assert.True (Application.MdiChildes [^1] == c2); - } else if (iterations == 2) { - // The Current is c1 and Current.Running is false. - Assert.True (Application.Current == c1); - Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildes [0] == c1); - Assert.True (Application.MdiChildes [^1] == c2); - } else if (iterations == 1) { - // The Current is c2 and Current.Running is false. - Assert.True (Application.Current == c2); - Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildes [^1] == c2); - } else { - // The Current is mdi. - Assert.True (Application.Current == mdi); - Assert.Empty (Application.MdiChildes); - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void MdiContainer_Throws_If_More_Than_One () - { - Init (); - - var mdi = new Mdi (); - var mdi2 = new Mdi (); - - mdi.Ready += () => { - Assert.Throws (() => Application.Run (mdi2)); - mdi.RequestStop (); - }; - - Application.Run (mdi); - - Application.Shutdown (); - } - - [Fact] - public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly () - { - Init (); - - var mdi = new Mdi (); - var logger = new Toplevel (); - - var iterations = 1; // The logger - var running = true; - var stageCompleted = true; - var allStageClosed = false; - var mdiRequestStop = false; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (logger); - }; - - logger.Ready += () => Assert.Single (Application.MdiChildes); - - Application.Iteration += () => { - if (stageCompleted && running) { - stageCompleted = false; - var stage = new Window () { Modal = true }; - - stage.Ready += () => { - Assert.Equal (iterations, Application.MdiChildes.Count); - stage.RequestStop (); - }; - - stage.Closed += (_) => { - if (iterations == 11) { - allStageClosed = true; - } - Assert.Equal (iterations, Application.MdiChildes.Count); - if (running) { - stageCompleted = true; - - var rpt = new Window (); - - rpt.Ready += () => { - iterations++; - Assert.Equal (iterations, Application.MdiChildes.Count); - }; - - Application.Run (rpt); - } - }; - - Application.Run (stage); - - } else if (iterations == 11 && running) { - running = false; - Assert.Equal (iterations, Application.MdiChildes.Count); - - } else if (!mdiRequestStop && running && !allStageClosed) { - Assert.Equal (iterations, Application.MdiChildes.Count); - - } else if (!mdiRequestStop && !running && allStageClosed) { - Assert.Equal (iterations, Application.MdiChildes.Count); - mdiRequestStop = true; - mdi.RequestStop (); - } else { - Assert.Empty (Application.MdiChildes); - } - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void AllChildClosed_Event_Test () - { - Init (); - - var mdi = new Mdi (); - var c1 = new Toplevel (); - var c2 = new Window (); - var c3 = new Window (); - - // MdiChild = c1, c2, c3 - var iterations = 3; - - mdi.Ready += () => { - Assert.Empty (Application.MdiChildes); - Application.Run (c1); - }; - c1.Ready += () => { - Assert.Single (Application.MdiChildes); - Application.Run (c2); - }; - c2.Ready += () => { - Assert.Equal (2, Application.MdiChildes.Count); - Application.Run (c3); - }; - c3.Ready += () => { - Assert.Equal (3, Application.MdiChildes.Count); - c3.RequestStop (); - c2.RequestStop (); - c1.RequestStop (); - }; - // Now this will close the MdiContainer when all MdiChildes was closed - mdi.AllChildClosed += () => { - mdi.RequestStop (); - }; - Application.Iteration += () => { - if (iterations == 3) { - // The Current still is c3 because Current.Running is false. - Assert.True (Application.Current == c3); - Assert.False (Application.Current.Running); - // But the childes order were reorder by Running = false - Assert.True (Application.MdiChildes [0] == c3); - Assert.True (Application.MdiChildes [1] == c2); - Assert.True (Application.MdiChildes [^1] == c1); - } else if (iterations == 2) { - // The Current is c2 and Current.Running is false. - Assert.True (Application.Current == c2); - Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildes [0] == c2); - Assert.True (Application.MdiChildes [^1] == c1); - } else if (iterations == 1) { - // The Current is c1 and Current.Running is false. - Assert.True (Application.Current == c1); - Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildes [^1] == c1); - } else { - // The Current is mdi. - Assert.True (Application.Current == mdi); - Assert.False (Application.Current.Running); - Assert.Empty (Application.MdiChildes); - } - iterations--; - }; - - Application.Run (mdi); - - Assert.Empty (Application.MdiChildes); - - Application.Shutdown (); - } - - [Fact] - public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top () - { - Init (); - - var t1 = new Toplevel (); - var t2 = new Toplevel (); - var t3 = new Toplevel (); - var d = new Dialog (); - var t4 = new Toplevel (); - - // t1, t2, t3, d, t4 - var iterations = 5; - - t1.Ready += () => { - Assert.Equal (t1, Application.Top); - Application.Run (t2); - }; - t2.Ready += () => { - Assert.Equal (t2, Application.Top); - Application.Run (t3); - }; - t3.Ready += () => { - Assert.Equal (t3, Application.Top); - Application.Run (d); - }; - d.Ready += () => { - Assert.Equal (t3, Application.Top); - Application.Run (t4); - }; - t4.Ready += () => { - Assert.Equal (t4, Application.Top); - t4.RequestStop (); - d.RequestStop (); - t3.RequestStop (); - t2.RequestStop (); - }; - // Now this will close the MdiContainer when all MdiChildes was closed - t2.Closed += (_) => { - t1.RequestStop (); - }; - Application.Iteration += () => { - if (iterations == 5) { - // The Current still is t4 because Current.Running is false. - Assert.Equal (t4, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t4, Application.Top); - } else if (iterations == 4) { - // The Current is d and Current.Running is false. - Assert.Equal (d, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t4, Application.Top); - } else if (iterations == 3) { - // The Current is t3 and Current.Running is false. - Assert.Equal (t3, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t3, Application.Top); - } else if (iterations == 2) { - // The Current is t2 and Current.Running is false. - Assert.Equal (t2, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t2, Application.Top); - } else { - // The Current is t1. - Assert.Equal (t1, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t1, Application.Top); - } - iterations--; - }; - - Application.Run (t1); - - Assert.Equal (t1, Application.Top); - - Application.Shutdown (); - - Assert.Null (Application.Top); - } - - [Fact] - [AutoInitShutdown] - public void Internal_Tests () - { - Assert.True (Application._initialized); - Assert.NotNull (Application.Top); - var rs = Application.Begin (Application.Top); - Assert.Equal (Application.Top, rs.Toplevel); - Assert.Null (Application.MouseGrabView); - Assert.Null (Application.WantContinuousButtonPressedView); - Assert.False (Application.DebugDrawBounds); - Assert.False (Application.ShowChild (Application.Top)); - Application.End (Application.Top); - } - [Fact] [AutoInitShutdown] public void QuitKey_Getter_Setter () @@ -1290,6 +778,8 @@ namespace Terminal.Gui.Core { Assert.Null (Toplevel.dragPosition); } + #endregion + [Fact, AutoInitShutdown] public void GetSupportedCultures_Method () { @@ -1297,129 +787,7 @@ namespace Terminal.Gui.Core { Assert.Equal (cultures.Count, Application.SupportedCultures.Count); } - [Fact, AutoInitShutdown] - public void TestAddManyTimeouts () - { - int delegatesRun = 0; - int numberOfThreads = 100; - int numberOfTimeoutsPerThread = 100; - - - lock (Application.Top) { - // start lots of threads - for (int i = 0; i < numberOfThreads; i++) { - - var myi = i; - - Task.Run (() => { - Thread.Sleep (100); - - // each thread registers lots of 1s timeouts - for (int j = 0; j < numberOfTimeoutsPerThread; j++) { - - Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (1), (s) => { - - // each timeout delegate increments delegatesRun count by 1 every second - Interlocked.Increment (ref delegatesRun); - return true; - }); - } - - // if this is the first Thread created - if (myi == 0) { - - // let the timeouts run for a bit - Thread.Sleep (10000); - - // then tell the application to quit - Application.MainLoop.Invoke (() => Application.RequestStop ()); - } - }); - } - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - - // undershoot a bit to be on the safe side. The 5000 ms wait allows the timeouts to run - // a lot but all those timeout delegates could end up going slowly on a slow machine perhaps - // so the final number of delegatesRun might vary by computer. So for this assert we say - // that it should have run at least 2 seconds worth of delegates - Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2); - } - } - - [Fact] - public void SynchronizationContext_Post () - { - Init (); - var context = SynchronizationContext.Current; - - var success = false; - Task.Run (() => { - Thread.Sleep (1_000); - - // non blocking - context.Post ( - delegate (object o) { - success = true; - - // then tell the application to quit - Application.MainLoop.Invoke (() => Application.RequestStop ()); - }, null); - Assert.False (success); - }); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - Assert.True (success); - - Application.Shutdown (); - } - - [Fact] - public void SynchronizationContext_Send () - { - Init (); - var context = SynchronizationContext.Current; - - var success = false; - Task.Run (() => { - Thread.Sleep (1_000); - - // blocking - context.Send ( - delegate (object o) { - success = true; - - // then tell the application to quit - Application.MainLoop.Invoke (() => Application.RequestStop ()); - }, null); - Assert.True (success); - }); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - Assert.True (success); - - Application.Shutdown (); - } - - [Fact] - public void SynchronizationContext_CreateCopy () - { - Init (); - - var context = SynchronizationContext.Current; - Assert.NotNull (context); - - var contextCopy = context.CreateCopy (); - Assert.NotNull (contextCopy); - - Assert.NotEqual (context, contextCopy); - - Application.Shutdown (); - } - + #region mousegrabtests [Fact, AutoInitShutdown] public void MouseGrabView_WithNullMouseEventView () { @@ -1564,5 +932,6 @@ namespace Terminal.Gui.Core { Application.UnGrabbedMouse -= Application_UnGrabbedMouse; } } + #endregion } } diff --git a/UnitTests/MainLoopTests.cs b/UnitTests/MainLoopTests.cs index 01ba22316..57b1e8f1e 100644 --- a/UnitTests/MainLoopTests.cs +++ b/UnitTests/MainLoopTests.cs @@ -591,5 +591,57 @@ namespace Terminal.Gui.Core { Assert.Equal ((numIncrements * numPasses), tbCounter); } + + + [Fact, AutoInitShutdown] + public void TestAddManyTimeouts () + { + int delegatesRun = 0; + int numberOfThreads = 100; + int numberOfTimeoutsPerThread = 100; + + + lock (Application.Top) { + // start lots of threads + for (int i = 0; i < numberOfThreads; i++) { + + var myi = i; + + Task.Run (() => { + Thread.Sleep (100); + + // each thread registers lots of 1s timeouts + for (int j = 0; j < numberOfTimeoutsPerThread; j++) { + + Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (1), (s) => { + + // each timeout delegate increments delegatesRun count by 1 every second + Interlocked.Increment (ref delegatesRun); + return true; + }); + } + + // if this is the first Thread created + if (myi == 0) { + + // let the timeouts run for a bit + Thread.Sleep (10000); + + // then tell the application to quit + Application.MainLoop.Invoke (() => Application.RequestStop ()); + } + }); + } + + // blocks here until the RequestStop is processed at the end of the test + Application.Run (); + + // undershoot a bit to be on the safe side. The 5000 ms wait allows the timeouts to run + // a lot but all those timeout delegates could end up going slowly on a slow machine perhaps + // so the final number of delegatesRun might vary by computer. So for this assert we say + // that it should have run at least 2 seconds worth of delegates + Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2); + } + } } } diff --git a/UnitTests/MdiTests.cs b/UnitTests/MdiTests.cs new file mode 100644 index 000000000..4f4f13e11 --- /dev/null +++ b/UnitTests/MdiTests.cs @@ -0,0 +1,662 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.Core { + public class MdiTests { + public MdiTests () + { +#if DEBUG_IDISPOSABLE + Responder.Instances.Clear (); + Application.RunState.Instances.Clear (); +#endif + } + + [Fact, AutoInitShutdown] + public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use_Application_Current () + { + var top1 = new Toplevel (); + var top2 = new Toplevel (); + var top3 = new Window (); + var top4 = new Window (); + var d = new Dialog (); + + // top1, top2, top3, d1 = 4 + var iterations = 4; + + top1.Ready += () => { + Assert.Null (Application.MdiChildes); + Application.Run (top2); + }; + top2.Ready += () => { + Assert.Null (Application.MdiChildes); + Application.Run (top3); + }; + top3.Ready += () => { + Assert.Null (Application.MdiChildes); + Application.Run (top4); + }; + top4.Ready += () => { + Assert.Null (Application.MdiChildes); + Application.Run (d); + }; + + d.Ready += () => { + Assert.Null (Application.MdiChildes); + // This will close the d because on a not MdiContainer the Application.Current it always used. + Application.RequestStop (top1); + Assert.True (Application.Current == d); + }; + + d.Closed += (e) => Application.RequestStop (top1); + + Application.Iteration += () => { + Assert.Null (Application.MdiChildes); + if (iterations == 4) { + Assert.True (Application.Current == d); + } else if (iterations == 3) { + Assert.True (Application.Current == top4); + } else if (iterations == 2) { + Assert.True (Application.Current == top3); + } else if (iterations == 1) { + Assert.True (Application.Current == top2); + } else { + Assert.True (Application.Current == top1); + } + Application.RequestStop (top1); + iterations--; + }; + + Application.Run (top1); + + Assert.Null (Application.MdiChildes); + } + + class Mdi : Toplevel { + public Mdi () + { + IsMdiContainer = true; + } + } + + [Fact] + [AutoInitShutdown] + public void MdiContainer_With_Toplevel_RequestStop_Balanced () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d = new Dialog (); + + // MdiChild = c1, c2, c3 + // d1 = 1 + var iterations = 4; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d); + }; + + // More easy because the Mdi Container handles all at once + d.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + // This will not close the MdiContainer because d is a modal toplevel and will be closed. + mdi.RequestStop (); + }; + + // Now this will close the MdiContainer propagating through the MdiChildes. + d.Closed += (e) => { + mdi.RequestStop (); + }; + + Application.Iteration += () => { + if (iterations == 4) { + // The Dialog was not closed before and will be closed now. + Assert.True (Application.Current == d); + Assert.False (d.Running); + } else { + Assert.Equal (iterations, Application.MdiChildes.Count); + for (int i = 0; i < iterations; i++) { + Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); + } + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d = new Dialog (); + + // MdiChild = c1, c2, c3 + // d1 = 1 + var iterations = 4; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d); + }; + + // Also easy because the Mdi Container handles all at once + d.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + // This will not close the MdiContainer because d is a modal toplevel + Application.RequestStop (mdi); + }; + + // Now this will close the MdiContainer propagating through the MdiChildes. + d.Closed += (e) => Application.RequestStop (mdi); + + Application.Iteration += () => { + if (iterations == 4) { + // The Dialog was not closed before and will be closed now. + Assert.True (Application.Current == d); + Assert.False (d.Running); + } else { + Assert.Equal (iterations, Application.MdiChildes.Count); + for (int i = 0; i < iterations; i++) { + Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); + } + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void MdiContainer_With_Application_RequestStop_MdiTop_Without_Params () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d = new Dialog (); + + // MdiChild = c1, c2, c3 = 3 + // d1 = 1 + var iterations = 4; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d); + }; + + //More harder because it's sequential. + d.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + // Close the Dialog + Application.RequestStop (); + }; + + // Now this will close the MdiContainer propagating through the MdiChildes. + d.Closed += (e) => Application.RequestStop (mdi); + + Application.Iteration += () => { + if (iterations == 4) { + // The Dialog still is the current top and we can't request stop to MdiContainer + // because we are not using parameter calls. + Assert.True (Application.Current == d); + Assert.False (d.Running); + } else { + Assert.Equal (iterations, Application.MdiChildes.Count); + for (int i = 0; i < iterations; i++) { + Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); + } + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void IsMdiChild_Testing () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d = new Dialog (); + + Application.Iteration += () => { + Assert.False (mdi.IsMdiChild); + Assert.True (c1.IsMdiChild); + Assert.True (c2.IsMdiChild); + Assert.True (c3.IsMdiChild); + Assert.False (d.IsMdiChild); + + mdi.RequestStop (); + }; + + Application.Run (mdi); + } + + [Fact] + [AutoInitShutdown] + public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d1 = new Dialog (); + var d2 = new Dialog (); + + // MdiChild = c1, c2, c3 = 3 + // d1, d2 = 2 + var iterations = 5; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d1); + }; + d1.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d2); + }; + + d2.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Assert.True (Application.Current == d2); + Assert.True (Application.Current.Running); + // Trying to close the Dialog1 + d1.RequestStop (); + }; + + // Now this will close the MdiContainer propagating through the MdiChildes. + d1.Closed += (e) => { + Assert.True (Application.Current == d1); + Assert.False (Application.Current.Running); + mdi.RequestStop (); + }; + + Application.Iteration += () => { + if (iterations == 5) { + // The Dialog2 still is the current top and we can't request stop to MdiContainer + // because Dialog2 and Dialog1 must be closed first. + // Dialog2 will be closed in this iteration. + Assert.True (Application.Current == d2); + Assert.False (Application.Current.Running); + Assert.False (d1.Running); + } else if (iterations == 4) { + // Dialog1 will be closed in this iteration. + Assert.True (Application.Current == d1); + Assert.False (Application.Current.Running); + } else { + Assert.Equal (iterations, Application.MdiChildes.Count); + for (int i = 0; i < iterations; i++) { + Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id); + } + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void Modal_Toplevel_Can_Open_Another_Not_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + var d1 = new Dialog (); + var c4 = new Toplevel (); + + // MdiChild = c1, c2, c3, c4 = 4 + // d1 = 1 + var iterations = 5; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (d1); + }; + d1.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + Application.Run (c4); + }; + + c4.Ready += () => { + Assert.Equal (4, Application.MdiChildes.Count); + // Trying to close the Dialog1 + d1.RequestStop (); + }; + + // Now this will close the MdiContainer propagating through the MdiChildes. + d1.Closed += (e) => { + mdi.RequestStop (); + }; + + Application.Iteration += () => { + if (iterations == 5) { + // The Dialog2 still is the current top and we can't request stop to MdiContainer + // because Dialog2 and Dialog1 must be closed first. + // Using request stop here will call the Dialog again without need + Assert.True (Application.Current == d1); + Assert.False (Application.Current.Running); + Assert.True (c4.Running); + } else { + Assert.Equal (iterations, Application.MdiChildes.Count); + for (int i = 0; i < iterations; i++) { + Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (), + Application.MdiChildes [i].Id); + } + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + + // MdiChild = c1, c2, c3 + var iterations = 3; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + c3.RequestStop (); + c1.RequestStop (); + }; + // Now this will close the MdiContainer propagating through the MdiChildes. + c1.Closed += (e) => { + mdi.RequestStop (); + }; + Application.Iteration += () => { + if (iterations == 3) { + // The Current still is c3 because Current.Running is false. + Assert.True (Application.Current == c3); + Assert.False (Application.Current.Running); + // But the childes order were reorder by Running = false + Assert.True (Application.MdiChildes [0] == c3); + Assert.True (Application.MdiChildes [1] == c1); + Assert.True (Application.MdiChildes [^1] == c2); + } else if (iterations == 2) { + // The Current is c1 and Current.Running is false. + Assert.True (Application.Current == c1); + Assert.False (Application.Current.Running); + Assert.True (Application.MdiChildes [0] == c1); + Assert.True (Application.MdiChildes [^1] == c2); + } else if (iterations == 1) { + // The Current is c2 and Current.Running is false. + Assert.True (Application.Current == c2); + Assert.False (Application.Current.Running); + Assert.True (Application.MdiChildes [^1] == c2); + } else { + // The Current is mdi. + Assert.True (Application.Current == mdi); + Assert.Empty (Application.MdiChildes); + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void MdiContainer_Throws_If_More_Than_One () + { + var mdi = new Mdi (); + var mdi2 = new Mdi (); + + mdi.Ready += () => { + Assert.Throws (() => Application.Run (mdi2)); + mdi.RequestStop (); + }; + + Application.Run (mdi); + } + + [Fact] + [AutoInitShutdown] + public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly () + { + var mdi = new Mdi (); + var logger = new Toplevel (); + + var iterations = 1; // The logger + var running = true; + var stageCompleted = true; + var allStageClosed = false; + var mdiRequestStop = false; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (logger); + }; + + logger.Ready += () => Assert.Single (Application.MdiChildes); + + Application.Iteration += () => { + if (stageCompleted && running) { + stageCompleted = false; + var stage = new Window () { Modal = true }; + + stage.Ready += () => { + Assert.Equal (iterations, Application.MdiChildes.Count); + stage.RequestStop (); + }; + + stage.Closed += (_) => { + if (iterations == 11) { + allStageClosed = true; + } + Assert.Equal (iterations, Application.MdiChildes.Count); + if (running) { + stageCompleted = true; + + var rpt = new Window (); + + rpt.Ready += () => { + iterations++; + Assert.Equal (iterations, Application.MdiChildes.Count); + }; + + Application.Run (rpt); + } + }; + + Application.Run (stage); + + } else if (iterations == 11 && running) { + running = false; + Assert.Equal (iterations, Application.MdiChildes.Count); + + } else if (!mdiRequestStop && running && !allStageClosed) { + Assert.Equal (iterations, Application.MdiChildes.Count); + + } else if (!mdiRequestStop && !running && allStageClosed) { + Assert.Equal (iterations, Application.MdiChildes.Count); + mdiRequestStop = true; + mdi.RequestStop (); + } else { + Assert.Empty (Application.MdiChildes); + } + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + + [Fact] + [AutoInitShutdown] + public void AllChildClosed_Event_Test () + { + var mdi = new Mdi (); + var c1 = new Toplevel (); + var c2 = new Window (); + var c3 = new Window (); + + // MdiChild = c1, c2, c3 + var iterations = 3; + + mdi.Ready += () => { + Assert.Empty (Application.MdiChildes); + Application.Run (c1); + }; + c1.Ready += () => { + Assert.Single (Application.MdiChildes); + Application.Run (c2); + }; + c2.Ready += () => { + Assert.Equal (2, Application.MdiChildes.Count); + Application.Run (c3); + }; + c3.Ready += () => { + Assert.Equal (3, Application.MdiChildes.Count); + c3.RequestStop (); + c2.RequestStop (); + c1.RequestStop (); + }; + // Now this will close the MdiContainer when all MdiChildes was closed + mdi.AllChildClosed += () => { + mdi.RequestStop (); + }; + Application.Iteration += () => { + if (iterations == 3) { + // The Current still is c3 because Current.Running is false. + Assert.True (Application.Current == c3); + Assert.False (Application.Current.Running); + // But the childes order were reorder by Running = false + Assert.True (Application.MdiChildes [0] == c3); + Assert.True (Application.MdiChildes [1] == c2); + Assert.True (Application.MdiChildes [^1] == c1); + } else if (iterations == 2) { + // The Current is c2 and Current.Running is false. + Assert.True (Application.Current == c2); + Assert.False (Application.Current.Running); + Assert.True (Application.MdiChildes [0] == c2); + Assert.True (Application.MdiChildes [^1] == c1); + } else if (iterations == 1) { + // The Current is c1 and Current.Running is false. + Assert.True (Application.Current == c1); + Assert.False (Application.Current.Running); + Assert.True (Application.MdiChildes [^1] == c1); + } else { + // The Current is mdi. + Assert.True (Application.Current == mdi); + Assert.False (Application.Current.Running); + Assert.Empty (Application.MdiChildes); + } + iterations--; + }; + + Application.Run (mdi); + + Assert.Empty (Application.MdiChildes); + } + } +} diff --git a/UnitTests/RunStateTests.cs b/UnitTests/RunStateTests.cs new file mode 100644 index 000000000..afe6886df --- /dev/null +++ b/UnitTests/RunStateTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.Core { + /// + /// These tests focus on Application.RunState and the various ways it can be changed. + /// + public class RunStateTests { + public RunStateTests () + { +#if DEBUG_IDISPOSABLE + Responder.Instances.Clear (); + Application.RunState.Instances.Clear (); +#endif + } + + [Fact] + public void New_Creates_RunState () + { + var rs = new Application.RunState (null); + Assert.Null (rs.Toplevel); + + var top = new Toplevel (); + rs = new Application.RunState (top); + Assert.Equal (top, rs.Toplevel); + } + + [Fact] + public void Dispose_Cleans_Up_RunState () + { + var rs = new Application.RunState (null); + Assert.NotNull (rs); + + // Should not throw because Toplevel was null + rs.Dispose (); + Assert.True (rs.WasDisposed); + + var top = new Toplevel (); + rs = new Application.RunState (top); + Assert.NotNull (rs); + + // Should throw because Toplevel was not cleaned up + Assert.Throws (() => rs.Dispose ()); + + rs.Toplevel.Dispose (); + rs.Toplevel = null; + rs.Dispose (); + Assert.True (rs.WasDisposed); + Assert.True (top.WasDisposed); + } + + void Init () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + Assert.NotNull (Application.Driver); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (SynchronizationContext.Current); + } + + void Shutdown () + { + Application.Shutdown (); + // Validate there are no outstanding RunState-based instances left + foreach (var inst in Application.RunState.Instances) { + Assert.True (inst.WasDisposed); + } + } + + [Fact] + public void Begin_End_Cleans_Up_RunState () + { + // Setup Mock driver + Init (); + + // Test null Toplevel + Assert.Throws (() => Application.Begin (null)); + + var top = new Toplevel (); + var rs = Application.Begin (top); + Assert.NotNull (rs); + Assert.Equal (top, Application.Current); + Application.End (rs); + + Assert.Null (Application.Current); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (Application.Driver); + + Shutdown (); + + Assert.True (rs.WasDisposed); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + } +} diff --git a/UnitTests/ScenarioTests.cs b/UnitTests/ScenarioTests.cs index c2dbb6bdc..f5f1dc57b 100644 --- a/UnitTests/ScenarioTests.cs +++ b/UnitTests/ScenarioTests.cs @@ -66,7 +66,7 @@ namespace UICatalog { // Close after a short period of time var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback); - scenario.Init (Application.Top, Colors.Base); + scenario.Init (Colors.Base); scenario.Setup (); scenario.Run (); Application.Shutdown (); @@ -121,7 +121,7 @@ namespace UICatalog { Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key); }; - generic.Init (Application.Top, Colors.Base); + generic.Init (Colors.Base); generic.Setup (); // There is no need to call Application.Begin because Init already creates the Application.Top // If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run. diff --git a/UnitTests/SynchronizatonContextTests.cs b/UnitTests/SynchronizatonContextTests.cs new file mode 100644 index 000000000..fe492c386 --- /dev/null +++ b/UnitTests/SynchronizatonContextTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices.ComTypes; +using System.Threading; +using System.Threading.Tasks; +using Terminal.Gui; +using Xunit; +using Xunit.Sdk; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.Core { + public class SyncrhonizationContextTests { + + [Fact, AutoInitShutdown] + public void SynchronizationContext_Post () + { + var context = SynchronizationContext.Current; + + var success = false; + Task.Run (() => { + Thread.Sleep (1_000); + + // non blocking + context.Post ( + delegate (object o) { + success = true; + + // then tell the application to quit + Application.MainLoop.Invoke (() => Application.RequestStop ()); + }, null); + Assert.False (success); + }); + + // blocks here until the RequestStop is processed at the end of the test + Application.Run (); + Assert.True (success); + } + + [Fact, AutoInitShutdown] + public void SynchronizationContext_Send () + { + var context = SynchronizationContext.Current; + + var success = false; + Task.Run (() => { + Thread.Sleep (1_000); + + // blocking + context.Send ( + delegate (object o) { + success = true; + + // then tell the application to quit + Application.MainLoop.Invoke (() => Application.RequestStop ()); + }, null); + Assert.True (success); + }); + + // blocks here until the RequestStop is processed at the end of the test + Application.Run (); + Assert.True (success); + + } + + [Fact, AutoInitShutdown] + public void SynchronizationContext_CreateCopy () + { + var context = SynchronizationContext.Current; + Assert.NotNull (context); + + var contextCopy = context.CreateCopy (); + Assert.NotNull (contextCopy); + + Assert.NotEqual (context, contextCopy); + } + + } +}