diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 1afbfb5b5..caae6f2ba 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -93,7 +93,7 @@ public static partial class Application { t.Running = false; #if DEBUG_IDISPOSABLE - // Don't dispose the tolevels. It's up to caller dispose them + // Don't dispose the toplevels. It's up to caller dispose them Debug.Assert (t.WasDisposed); #endif } @@ -105,7 +105,7 @@ public static partial class Application if (Top is { }) { Debug.Assert (Top.WasDisposed); - // If End wasn't called _latestClosedRunStateToplevel may be null + // If End wasn't called _cachedRunStateToplevel may be null if (_cachedRunStateToplevel is { }) { Debug.Assert (_cachedRunStateToplevel.WasDisposed); @@ -246,7 +246,8 @@ public static partial class Application // multiple times. We need to do this because some settings are only // valid after a Driver is loaded. In this cases we need just // `Settings` so we can determine which driver to use. - Load (true); + // Don't reset, so we can inherit the theme from the previous run. + Load (false); Apply (); // Ignore Configuration for ForceDriver if driverName is specified @@ -350,6 +351,7 @@ public static partial class Application /// public static void Shutdown () { + // TODO: Throw an exception if Init hasn't been called. ResetState (); PrintJsonErrors (); } diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 03c66a87f..2dbd2ba44 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -147,9 +147,8 @@ public static class ConfigurationManager { if (_settings is null) { - throw new InvalidOperationException ( - "ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property." - ); + // If Settings is null, we need to initialize it. + Reset (); } return _settings; diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index ab6a7c333..49c124aa0 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -88,18 +88,6 @@ public class Scenario : IDisposable /// public Window Win { get; set; } - public void Dispose () - { - // BUGBUG: Top should have already been disposed. We dispose it here until we can fix the scenarios that are doing it wrong. - Top?.Dispose (); - - // We created Win, so we Dispose it. - Win?.Dispose (); - - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose (true); - GC.SuppressFinalize (this); - } /// /// Helper function to get the list of categories a belongs to (defined in @@ -140,6 +128,15 @@ public class Scenario : IDisposable return objects.OrderBy (s => s.GetName ()).ToList (); } + + public virtual void Main () + { + Init (); + Setup (); + Run (); + } + + /// /// Helper that calls and creates the default implementation with a frame and label /// showing the name of the and logic to exit back to the Scenario picker UI. Override @@ -186,7 +183,7 @@ public class Scenario : IDisposable /// public virtual void Run () { - // Must explicit call Application.Shutdown method to shutdown. + // Must explicitly call Application.Shutdown method to shutdown. Application.Run (Top); } @@ -197,6 +194,13 @@ public class Scenario : IDisposable /// Gets the Scenario Name + Description with the Description padded based on the longest known Scenario name. /// public override string ToString () { return $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; } + + public void Dispose () + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose (true); + GC.SuppressFinalize (this); + } protected virtual void Dispose (bool disposing) { @@ -204,11 +208,10 @@ public class Scenario : IDisposable { if (disposing) { - // TODO: dispose managed state (managed objects) + Top?.Dispose (); + Win?.Dispose (); } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null _disposedValue = true; } } diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index ea51bece2..b2a78aea1 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -354,6 +354,20 @@ public class BackgroundWorkerCollection : Scenario Source = new ListWrapper (_log) }; Add (_listLog); + + Closing += WorkerApp_Closing; + } + private void WorkerApp_Closing (object sender, ToplevelClosingEventArgs e) + { + Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp"); + + if (Visible && top == this) + { + Visible = false; + e.Cancel = true; + + Application.OverlappedMoveNext (); + } } public void CancelWorker () diff --git a/UICatalog/Scenarios/ChineseUI.cs b/UICatalog/Scenarios/ChineseUI.cs index 05183e1c7..96c2ea56e 100644 --- a/UICatalog/Scenarios/ChineseUI.cs +++ b/UICatalog/Scenarios/ChineseUI.cs @@ -6,21 +6,18 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Unicode")] public class ChineseUI : Scenario { - public override void Init () + public override void Main () { Application.Init (); - Toplevel top = new (); - var win = new Window { - Title = "Test", + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; - top.Add (win); var buttonPanel = new FrameView { @@ -35,20 +32,20 @@ public class ChineseUI : Scenario var btn = new Button { X = 1, Y = 1, Text = "你" }; // v1: A btn.Accept += (s, e) => - { - int result = MessageBox.Query ( - "Confirm", - "Are you sure you want to quit ui?", - 0, - "Yes", - "No" - ); + { + int result = MessageBox.Query ( + "Confirm", + "Are you sure you want to quit ui?", + 0, + "Yes", + "No" + ); - if (result == 0) - { - Application.RequestStop (); - } - }; + if (result == 0) + { + Application.RequestStop (); + } + }; buttonPanel.Add ( btn, @@ -56,10 +53,10 @@ public class ChineseUI : Scenario new Button { X = 22, Y = 1, Text = "呀" } // v1: C ); - Application.Run (top); + Application.Run (win); - top.Dispose (); + win.Dispose (); + + Application.Shutdown (); } - - public override void Run () { } } diff --git a/UICatalog/Scenarios/Generic.cs b/UICatalog/Scenarios/Generic.cs index 4ba0e9f11..38e1086d7 100644 --- a/UICatalog/Scenarios/Generic.cs +++ b/UICatalog/Scenarios/Generic.cs @@ -4,37 +4,28 @@ namespace UICatalog.Scenarios; [ScenarioMetadata ("Generic", "Generic sample - A template for creating new Scenarios")] [ScenarioCategory ("Controls")] -public class MyScenario : Scenario +public sealed class MyScenario : Scenario { - public override void Init () + public override void Main () { - // The base `Scenario.Init` implementation: - // - Calls `Application.Init ()` - // - Adds a full-screen Window to Application.Top with a title - // that reads "Press to Quit". Access this Window with `this.Win`. - // - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`. - // To override this, implement an override of `Init`. - - //base.Init (); - - // A common, alternate, implementation where `this.Win` is not used is below. This code - // leverages ConfigurationManager to borrow the color scheme settings from UICatalog: - + // Init Application.Init (); - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - Top = new Toplevel (); - Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; - } - public override void Setup () - { - // Put scenario code here (in a real app, this would be the code - // that would setup the app before `Application.Run` is called`). - // With a Scenario, after UI Catalog calls `Scenario.Setup` it calls - // `Scenario.Run` which calls `Application.Run`. Example: + // Setup - Create a top-level application window and configure it. + Window appWindow = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" + }; var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" }; - Top.Add (button); + button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "Ok"); + appWindow.Add (button); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 20d5d52cd..4980d20c8 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -12,15 +12,92 @@ public class Notepad : Scenario { private TabView _focusedTabView; private StatusItem _lenStatusItem; - private int _numbeOfNewTabs = 1; + private int _numNewTabs = 1; private TabView _tabView; - // Don't create a Window, just return the top-level view - public override void Init () + public override void Main () { Application.Init (); - Top = new (); - Top.ColorScheme = Colors.ColorSchemes ["Base"]; + + Toplevel top = new (); + + var menu = new MenuBar + { + Menus = + [ + new ( + "_File", + new MenuItem [] + { + new ( + "_New", + "", + () => New (), + null, + null, + KeyCode.N + | KeyCode.CtrlMask + | KeyCode.AltMask + ), + new ("_Open", "", () => Open ()), + new ("_Save", "", () => Save ()), + new ("Save _As", "", () => SaveAs ()), + new ("_Close", "", () => Close ()), + new ("_Quit", "", () => Quit ()) + } + ), + new ( + "_About", + "", + () => MessageBox.Query ("Notepad", "About Notepad...", "Ok") + ) + ] + }; + top.Add (menu); + + _tabView = CreateNewTabView (); + + _tabView.Style.ShowBorder = true; + _tabView.ApplyStyleChanges (); + + // Start with only a single view but support splitting to show side by side + var split = new TileView (1) { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; + split.Tiles.ElementAt (0).ContentView.Add (_tabView); + split.LineStyle = LineStyle.None; + + top.Add (split); + + _lenStatusItem = new (KeyCode.CharMask, "Len: ", null); + + var statusBar = new StatusBar ( + new [] + { + new ( + Application.QuitKey, + $"{Application.QuitKey} to Quit", + () => Quit () + ), + + // These shortcut keys don't seem to work correctly in linux + //new StatusItem(Key.CtrlMask | Key.N, "~^O~ Open", () => Open()), + //new StatusItem(Key.CtrlMask | Key.N, "~^N~ New", () => New()), + + new (KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save ()), + new (KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close ()), + _lenStatusItem + } + ); + _focusedTabView = _tabView; + _tabView.SelectedTabChanged += TabView_SelectedTabChanged; + _tabView.Enter += (s, e) => _focusedTabView = _tabView; + + top.Add (statusBar); + top.Ready += (s, e) => New (); + + Application.Run (top); + top.Dispose (); + + Application.Shutdown (); } public void Save () { Save (_focusedTabView, _focusedTabView.SelectedTab); } @@ -58,99 +135,26 @@ public class Notepad : Scenario if (string.IsNullOrWhiteSpace (fd.Path)) { fd.Dispose (); + return false; } if (fd.Canceled) { fd.Dispose (); + return false; } - tab.File = new FileInfo (fd.Path); + tab.File = new (fd.Path); tab.Text = fd.FileName; tab.Save (); fd.Dispose (); + return true; } - public override void Setup () - { - var menu = new MenuBar - { - Menus = - [ - new MenuBarItem ( - "_File", - new MenuItem [] - { - new ( - "_New", - "", - () => New (), - null, - null, - KeyCode.N - | KeyCode.CtrlMask - | KeyCode.AltMask - ), - new ("_Open", "", () => Open ()), - new ("_Save", "", () => Save ()), - new ("Save _As", "", () => SaveAs ()), - new ("_Close", "", () => Close ()), - new ("_Quit", "", () => Quit ()) - } - ), - new MenuBarItem ( - "_About", - "", - () => MessageBox.Query ("Notepad", "About Notepad...", "Ok") - ) - ] - }; - Top.Add (menu); - - _tabView = CreateNewTabView (); - - _tabView.Style.ShowBorder = true; - _tabView.ApplyStyleChanges (); - - // Start with only a single view but support splitting to show side by side - var split = new TileView (1) { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; - split.Tiles.ElementAt (0).ContentView.Add (_tabView); - split.LineStyle = LineStyle.None; - - Top.Add (split); - - _lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null); - - var statusBar = new StatusBar ( - new [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ), - - // These shortcut keys don't seem to work correctly in linux - //new StatusItem(Key.CtrlMask | Key.N, "~^O~ Open", () => Open()), - //new StatusItem(Key.CtrlMask | Key.N, "~^N~ New", () => New()), - - new (KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save ()), - new (KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close ()), - _lenStatusItem - } - ); - _focusedTabView = _tabView; - _tabView.SelectedTabChanged += TabView_SelectedTabChanged; - _tabView.Enter += (s, e) => _focusedTabView = _tabView; - - Top.Add (statusBar); - Top.Ready += (s, e) => New (); - } - private void Close () { Close (_focusedTabView, _focusedTabView.SelectedTab); } private void Close (TabView tv, Tab tabToClose) @@ -244,7 +248,7 @@ public class Notepad : Scenario return tv; } - private void New () { Open (null, $"new {_numbeOfNewTabs++}"); } + private void New () { Open (null, $"new {_numNewTabs++}"); } private void Open () { @@ -264,7 +268,7 @@ public class Notepad : Scenario } // TODO should open in focused TabView - Open (new FileInfo (path), Path.GetFileName (path)); + Open (new (path), Path.GetFileName (path)); } } @@ -335,27 +339,27 @@ public class Notepad : Scenario if (e.Tab == null) { - items = new MenuBarItem ( - new MenuItem [] { new ("Open", "", () => Open ()) } - ); + items = new ( + new MenuItem [] { new ("Open", "", () => Open ()) } + ); } else { var tv = (TabView)sender; var t = (OpenedFile)e.Tab; - items = new MenuBarItem ( - new MenuItem [] - { - new ("Save", "", () => Save (_focusedTabView, e.Tab)), - new ("Close", "", () => Close (tv, e.Tab)), - null, - new ("Split Up", "", () => SplitUp (tv, t)), - new ("Split Down", "", () => SplitDown (tv, t)), - new ("Split Right", "", () => SplitRight (tv, t)), - new ("Split Left", "", () => SplitLeft (tv, t)) - } - ); + items = new ( + new MenuItem [] + { + new ("Save", "", () => Save (_focusedTabView, e.Tab)), + new ("Close", "", () => Close (tv, e.Tab)), + null, + new ("Split Up", "", () => SplitUp (tv, t)), + new ("Split Down", "", () => SplitDown (tv, t)), + new ("Split Right", "", () => SplitRight (tv, t)), + new ("Split Left", "", () => SplitLeft (tv, t)) + } + ); } Rectangle screen = ((View)sender).BoundsToScreen (new (e.MouseEvent.X, e.MouseEvent.Y, 0, 0)); @@ -368,14 +372,6 @@ public class Notepad : Scenario private class OpenedFile : Tab { - public FileInfo File { get; set; } - - /// The text of the tab the last time it was saved - /// - public string SavedText { get; set; } - - public bool UnsavedChanges => !string.Equals (SavedText, View.Text); - public OpenedFile CloneTo (TabView other) { var newTab = new OpenedFile { DisplayText = base.Text, File = File }; @@ -407,6 +403,8 @@ public class Notepad : Scenario }; } + public FileInfo File { get; set; } + public void RegisterTextViewEvents (TabView parent) { var textView = (TextView)View; @@ -436,6 +434,12 @@ public class Notepad : Scenario }; } + /// The text of the tab the last time it was saved + /// + public string SavedText { get; set; } + + public bool UnsavedChanges => !string.Equals (SavedText, View.Text); + internal void Save () { string newText = View.Text; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 92079c39a..a87de281b 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -290,11 +290,10 @@ internal class UICatalogApp Application.Init (driverName: _forceDriver); _selectedScenario.Theme = _cachedTheme; _selectedScenario.TopLevelColorScheme = _topLevelColorScheme; - _selectedScenario.Init (); - _selectedScenario.Setup (); - _selectedScenario.Run (); + _selectedScenario.Main (); _selectedScenario.Dispose (); _selectedScenario = null; + // TODO: Throw if shutdown was not called already Application.Shutdown (); VerifyObjectsWereDisposed (); @@ -322,13 +321,12 @@ internal class UICatalogApp Apply (); scenario.Theme = _cachedTheme; scenario.TopLevelColorScheme = _topLevelColorScheme; - scenario.Init (); - scenario.Setup (); - scenario.Run (); + scenario.Main (); scenario.Dispose (); // This call to Application.Shutdown brackets the Application.Init call // made by Scenario.Init() above + // TODO: Throw if shutdown was not called already Application.Shutdown (); VerifyObjectsWereDisposed ();