Prechádzať zdrojové kódy

Fixes #3149: UICatalog - Notepad example improvements (#3150)

* Fix the application crashing when closing the save file dialog without saving

* Allow user to write in multiple tabs without saving file

* Maintain code consistency

---------

Co-authored-by: Tig <[email protected]>
Maciej 1 rok pred
rodič
commit
0aff2f1c87
1 zmenil súbory, kde vykonal 317 pridanie a 313 odobranie
  1. 317 313
      UICatalog/Scenarios/Notepad.cs

+ 317 - 313
UICatalog/Scenarios/Notepad.cs

@@ -1,413 +1,417 @@
-using System;
-using System.IO;
+using System.IO;
 using System.Linq;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
+namespace UICatalog.Scenarios;
 
-	[ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor using the TabView control.")]
-	[ScenarioCategory ("Controls"), ScenarioCategory ("TabView"), ScenarioCategory ("TextView")]
-	public class Notepad : Scenario {
-		TabView tabView;
+[ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor using the TabView control.")]
+[ScenarioCategory ("Controls"), ScenarioCategory ("TabView"), ScenarioCategory ("TextView")]
+public class Notepad : Scenario {
+	TabView tabView;
 
-		private int numbeOfNewTabs = 1;
-		private TabView focusedTabView;
-		private StatusItem lenStatusItem;
+	private int _numbeOfNewTabs = 1;
+	private TabView _focusedTabView;
+	private StatusItem _lenStatusItem;
 
-		// Don't create a Window, just return the top-level view
-		public override void Init ()
-		{
-			Application.Init ();
-			Application.Top.ColorScheme = Colors.Base;
-		}
+	// Don't create a Window, just return the top-level view
+	public override void Init ()
+	{
+		Application.Init ();
+		Application.Top.ColorScheme = Colors.Base;
+	}
 
-		public override void Setup ()
-		{
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_New", "", () => New(), null, null, KeyCode.N | KeyCode.CtrlMask | KeyCode.AltMask),
-					new MenuItem ("_Open", "", () => Open()),
-					new MenuItem ("_Save", "", () => Save()),
-					new MenuItem ("Save _As", "", () => SaveAs()),
-					new MenuItem ("_Close", "", () => Close()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
-				new MenuBarItem ("_About", "", () => MessageBox.Query("Notepad", "About Notepad...", "Ok"))
-				});
-			Application.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;
+	public override void Setup ()
+	{
+		var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_New", "", () => New(), null, null, KeyCode.N | KeyCode.CtrlMask | KeyCode.AltMask),
+				new MenuItem ("_Open", "", () => Open()),
+				new MenuItem ("_Save", "", () => Save()),
+				new MenuItem ("Save _As", "", () => SaveAs()),
+				new MenuItem ("_Close", "", () => Close()),
+				new MenuItem ("_Quit", "", () => Quit()),
+			}),
+			new MenuBarItem ("_About", "", () => MessageBox.Query("Notepad", "About Notepad...", "Ok"))
+			});
+		Application.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;
+
+		Application.Top.Add (split);
+
+		_lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
+		var statusBar = new StatusBar (new StatusItem [] {
+			new StatusItem(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 StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
+			new StatusItem(KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close()),
+			_lenStatusItem,
+		});
+		_focusedTabView = tabView;
+		tabView.SelectedTabChanged += TabView_SelectedTabChanged;
+		tabView.Enter += (s, e) => _focusedTabView = tabView;
+
+		Application.Top.Add (statusBar);
+		Application.Top.Ready += (s, e) => New ();
+	}
 
-			Application.Top.Add (split);
+	private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
+	{
+		_lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
+		e.NewTab?.View?.SetFocus ();
+	}
 
-			lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
+	private void TabView_TabClicked (object sender, TabMouseEventArgs e)
+	{
+		// we are only interested in right clicks
+		if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+			return;
+		}
 
-				// 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()),
+		MenuBarItem items;
 
-				new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
-				new StatusItem(KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close()),
-				lenStatusItem,
+		if (e.Tab == null) {
+			items = new MenuBarItem (new MenuItem [] {
+				new MenuItem ($"Open", "", () => Open()),
 			});
-			focusedTabView = tabView;
-			tabView.SelectedTabChanged += TabView_SelectedTabChanged;
-			tabView.Enter += (s, e) => focusedTabView = tabView;
 
-			Application.Top.Add (statusBar);
-			Application.Top.Ready += (s, e) => New ();
-		}
+		} else {
 
-		private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
-		{
-			lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
-		}
+			var tv = (TabView)sender;
+			var t = (OpenedFile)e.Tab;
 
-		private void TabView_TabClicked (object sender, TabMouseEventArgs e)
-		{
-			// we are only interested in right clicks
-			if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
-				return;
-			}
+			items = new MenuBarItem (new MenuItem [] {
+				new MenuItem ($"Save", "", () => Save(_focusedTabView, e.Tab)),
+				new MenuItem ($"Close", "", () => Close(tv, e.Tab)),
+				null,
+				new MenuItem ($"Split Up", "", () => SplitUp(tv,t)),
+				new MenuItem ($"Split Down", "", () => SplitDown(tv,t)),
+				new MenuItem ($"Split Right", "", () => SplitRight(tv,t)),
+				new MenuItem ($"Split Left", "", () => SplitLeft(tv,t)),
+			});
+		}
 
-			MenuBarItem items;
+	((View)sender).BoundsToScreen (e.MouseEvent.X, e.MouseEvent.Y, out int screenX, out int screenY, true);
 
-			if (e.Tab == null) {
-				items = new MenuBarItem (new MenuItem [] {
-					new MenuItem ($"Open", "", () => Open()),
-				});
+		var contextMenu = new ContextMenu (screenX, screenY, items);
 
-			} else {
+		contextMenu.Show ();
+		e.MouseEvent.Handled = true;
+	}
 
-				var tv = (TabView)sender;
-				var t = (OpenedFile)e.Tab;
+	private void SplitUp (TabView sender, OpenedFile tab)
+	{
+		Split (0, Orientation.Horizontal, sender, tab);
+	}
+	private void SplitDown (TabView sender, OpenedFile tab)
+	{
+		Split (1, Orientation.Horizontal, sender, tab);
 
-				items = new MenuBarItem (new MenuItem [] {
-					new MenuItem ($"Save", "", () => Save(focusedTabView, e.Tab)),
-					new MenuItem ($"Close", "", () => Close(tv, e.Tab)),
-					null,
-					new MenuItem ($"Split Up", "", () => SplitUp(tv,t)),
-					new MenuItem ($"Split Down", "", () => SplitDown(tv,t)),
-					new MenuItem ($"Split Right", "", () => SplitRight(tv,t)),
-					new MenuItem ($"Split Left", "", () => SplitLeft(tv,t)),
-				});
-			}
+	}
+	private void SplitLeft (TabView sender, OpenedFile tab)
+	{
+		Split (0, Orientation.Vertical, sender, tab);
+	}
+	private void SplitRight (TabView sender, OpenedFile tab)
+	{
+		Split (1, Orientation.Vertical, sender, tab);
+	}
 
-		((View)sender).BoundsToScreen (e.MouseEvent.X, e.MouseEvent.Y, out int screenX, out int screenY, true);
+	private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
+	{
 
-			var contextMenu = new ContextMenu (screenX, screenY, items);
+		var split = (TileView)sender.SuperView.SuperView;
+		var tileIndex = split.IndexOf (sender);
 
-			contextMenu.Show ();
-			e.MouseEvent.Handled = true;
+		if (tileIndex == -1) {
+			return;
 		}
 
-		private void SplitUp (TabView sender, OpenedFile tab)
-		{
-			Split (0, Orientation.Horizontal, sender, tab);
+		if (orientation != split.Orientation) {
+			split.TrySplitTile (tileIndex, 1, out split);
+			split.Orientation = orientation;
+			tileIndex = 0;
 		}
-		private void SplitDown (TabView sender, OpenedFile tab)
-		{
-			Split (1, Orientation.Horizontal, sender, tab);
 
-		}
-		private void SplitLeft (TabView sender, OpenedFile tab)
-		{
-			Split (0, Orientation.Vertical, sender, tab);
-		}
-		private void SplitRight (TabView sender, OpenedFile tab)
-		{
-			Split (1, Orientation.Vertical, sender, tab);
-		}
+		var newTile = split.InsertTile (tileIndex + offset);
+		var newTabView = CreateNewTabView ();
+		tab.CloneTo (newTabView);
+		newTile.ContentView.Add (newTabView);
 
-		private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
-		{
-
-			var split = (TileView)sender.SuperView.SuperView;
-			var tileIndex = split.IndexOf (sender);
+		newTabView.EnsureFocus ();
+		newTabView.FocusFirst ();
+		newTabView.FocusNext ();
+	}
 
-			if (tileIndex == -1) {
-				return;
-			}
+	private TabView CreateNewTabView ()
+	{
+		var tv = new TabView () {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+		};
+
+		tv.TabClicked += TabView_TabClicked;
+		tv.SelectedTabChanged += TabView_SelectedTabChanged;
+		tv.Enter += (s, e) => _focusedTabView = tv;
+		return tv;
+	}
 
-			if (orientation != split.Orientation) {
-				split.TrySplitTile (tileIndex, 1, out split);
-				split.Orientation = orientation;
-				tileIndex = 0;
-			}
+	private void New ()
+	{
+		Open (null, $"new {_numbeOfNewTabs++}");
+	}
 
-			var newTile = split.InsertTile (tileIndex + offset);
-			var newTabView = CreateNewTabView ();
-			tab.CloneTo (newTabView);
-			newTile.ContentView.Add (newTabView);
+	private void Close ()
+	{
+		Close (_focusedTabView, _focusedTabView.SelectedTab);
+	}
+	private void Close (TabView tv, Tab tabToClose)
+	{
+		var tab = tabToClose as OpenedFile;
 
-			newTabView.EnsureFocus ();
-			newTabView.FocusFirst ();
-			newTabView.FocusNext ();
+		if (tab == null) {
+			return;
 		}
 
-		private TabView CreateNewTabView ()
-		{
-			var tv = new TabView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
+		_focusedTabView = tv;
 
-			tv.TabClicked += TabView_TabClicked;
-			tv.SelectedTabChanged += TabView_SelectedTabChanged;
-			tv.Enter += (s, e) => focusedTabView = tv;
-			return tv;
-		}
+		if (tab.UnsavedChanges) {
 
-		private void New ()
-		{
-			Open (null, $"new {numbeOfNewTabs++}");
-		}
+			int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.TrimEnd ('*')}", "Yes", "No", "Cancel");
 
-		private void Close ()
-		{
-			Close (focusedTabView, focusedTabView.SelectedTab);
-		}
-		private void Close (TabView tv, Tab tabToClose)
-		{
-			var tab = tabToClose as OpenedFile;
+			if (result == -1 || result == 2) {
 
-			if (tab == null) {
+				// user cancelled
 				return;
 			}
 
-			focusedTabView = tv;
-
-			if (tab.UnsavedChanges) {
+			if (result == 0) {
+				if (tab.File == null) {
+					SaveAs ();
+				} else {
+					tab.Save ();
+				}
+			}
+		}
 
-				int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.TrimEnd ('*')}", "Yes", "No", "Cancel");
+		// close and dispose the tab
+		tv.RemoveTab (tab);
+		tab.View.Dispose ();
+		_focusedTabView = tv;
 
-				if (result == -1 || result == 2) {
+		if (tv.Tabs.Count == 0) {
 
-					// user cancelled
-					return;
-				}
+			var split = (TileView)tv.SuperView.SuperView;
 
-				if (result == 0) {
-					if (tab.File == null) {
-						SaveAs ();
-					} else {
-						tab.Save ();
-					}
-				}
+			// if it is the last TabView on screen don't drop it or we will
+			// be unable to open new docs!
+			if (split.IsRootTileView () && split.Tiles.Count == 1) {
+				return;
 			}
 
-			// close and dispose the tab
-			tv.RemoveTab (tab);
-			tab.View.Dispose ();
-			focusedTabView = tv;
-
-			if (tv.Tabs.Count == 0) {
+			var tileIndex = split.IndexOf (tv);
+			split.RemoveTile (tileIndex);
 
-				var split = (TileView)tv.SuperView.SuperView;
+			if (split.Tiles.Count == 0) {
+				var parent = split.GetParentTileView ();
 
-				// if it is the last TabView on screen don't drop it or we will
-				// be unable to open new docs!
-				if (split.IsRootTileView () && split.Tiles.Count == 1) {
+				if (parent == null) {
 					return;
 				}
 
-				var tileIndex = split.IndexOf (tv);
-				split.RemoveTile (tileIndex);
-
-				if (split.Tiles.Count == 0) {
-					var parent = split.GetParentTileView ();
-
-					if (parent == null) {
-						return;
-					}
-
-					var idx = parent.IndexOf (split);
+				var idx = parent.IndexOf (split);
 
-					if (idx == -1) {
-						return;
-					}
-
-					parent.RemoveTile (idx);
+				if (idx == -1) {
+					return;
 				}
+
+				parent.RemoveTile (idx);
 			}
 		}
+	}
 
-		private void Open ()
-		{
-			var open = new OpenDialog ("Open") { AllowsMultipleSelection = true };
+	private void Open ()
+	{
+		var open = new OpenDialog ("Open") { AllowsMultipleSelection = true };
 
-			Application.Run (open);
+		Application.Run (open);
 
-			if (!open.Canceled) {
+		if (!open.Canceled) {
 
-				foreach (var path in open.FilePaths) {
+			foreach (var path in open.FilePaths) {
 
-					if (string.IsNullOrEmpty (path) || !File.Exists (path)) {
-						return;
-					}
-
-					// TODO should open in focused TabView
-					Open (new FileInfo (path), Path.GetFileName (path));
+				if (string.IsNullOrEmpty (path) || !File.Exists (path)) {
+					return;
 				}
-			}
-		}
 
-		/// <summary>
-		/// Creates a new tab with initial text
-		/// </summary>
-		/// <param name="fileInfo">File that was read or null if a new blank document</param>
-		private void Open (FileInfo fileInfo, string tabName)
-		{
-			var tab = new OpenedFile (focusedTabView, tabName, fileInfo);
-			focusedTabView.AddTab (tab, true);
-		}
-
-		public void Save ()
-		{
-			Save (focusedTabView, focusedTabView.SelectedTab);
+				// TODO should open in focused TabView
+				Open (new FileInfo (path), Path.GetFileName (path));
+			}
 		}
-		public void Save (TabView tabViewToSave, Tab tabToSave)
-		{
-			var tab = tabToSave as OpenedFile;
+	}
 
-			if (tab == null) {
-				return;
-			}
+	/// <summary>
+	/// Creates a new tab with initial text
+	/// </summary>
+	/// <param name="fileInfo">File that was read or null if a new blank document</param>
+	private void Open (FileInfo fileInfo, string tabName)
+	{
+		var tab = new OpenedFile (_focusedTabView, tabName, fileInfo);
+		_focusedTabView.AddTab (tab, true);
+	}
 
-			if (tab.File == null) {
-				SaveAs ();
-			}
+	public void Save ()
+	{
+		Save (_focusedTabView, _focusedTabView.SelectedTab);
+	}
+	public void Save (TabView tabViewToSave, Tab tabToSave)
+	{
+		var tab = tabToSave as OpenedFile;
 
-			tab.Save ();
-			tabViewToSave.SetNeedsDisplay ();
+		if (tab == null) {
+			return;
 		}
 
-		public bool SaveAs ()
-		{
-			var tab = focusedTabView.SelectedTab as OpenedFile;
-
-			if (tab == null) {
-				return false;
-			}
+		if (tab.File == null) {
+			SaveAs ();
+		}
 
-			var fd = new SaveDialog ();
-			Application.Run (fd);
+		tab.Save ();
+		tabViewToSave.SetNeedsDisplay ();
+	}
 
-			if (string.IsNullOrWhiteSpace (fd.Path)) {
-				return false;
-			}
+	public bool SaveAs ()
+	{
+		var tab = _focusedTabView.SelectedTab as OpenedFile;
 
-			if (fd.Canceled) {
-				return false;
-			}
+		if (tab == null) {
+			return false;
+		}
 
-			tab.File = new FileInfo (fd.Path);
-			tab.Text = fd.FileName;
-			tab.Save ();
+		var fd = new SaveDialog ();
+		Application.Run (fd);
 
-			return true;
+		if (string.IsNullOrWhiteSpace (fd.Path)) {
+			return false;
 		}
 
-		private class OpenedFile : Tab {
-			public FileInfo File { get; set; }
+		if (fd.Canceled) {
+			return false;
+		}
 
-			/// <summary>
-			/// The text of the tab the last time it was saved
-			/// </summary>
-			/// <value></value>
-			public string SavedText { get; set; }
+		tab.File = new FileInfo (fd.Path);
+		tab.Text = fd.FileName;
+		tab.Save ();
 
-			public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
+		return true;
+	}
 
-			public OpenedFile (TabView parent, string name, FileInfo file)
-				: base (name, CreateTextView (file))
-			{
+	private class OpenedFile : Tab {
+		public FileInfo File { get; set; }
 
-				File = file;
-				SavedText = View.Text;
-				RegisterTextViewEvents (parent);
-			}
+		/// <summary>
+		/// The text of the tab the last time it was saved
+		/// </summary>
+		/// <value></value>
+		public string SavedText { get; set; }
 
-			private void RegisterTextViewEvents (TabView parent)
-			{
-				var textView = (TextView)View;
-				// when user makes changes rename tab to indicate unsaved
-				textView.KeyUp += (s, k) => {
+		public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
 
-					// if current text doesn't match saved text
-					var areDiff = this.UnsavedChanges;
+		public OpenedFile (TabView parent, string name, FileInfo file)
+			: base (name, CreateTextView (file))
+		{
+			File = file;
+			SavedText = View.Text;
+			RegisterTextViewEvents (parent);
+		}
 
-					if (areDiff) {
-						if (!this.Text.EndsWith ('*')) {
+		private void RegisterTextViewEvents (TabView parent)
+		{
+			var textView = (TextView)View;
+			// when user makes changes rename tab to indicate unsaved
+			textView.KeyUp += (s, k) => {
 
-							this.Text = this.Text + '*';
-							parent.SetNeedsDisplay ();
-						}
-					} else {
+				// if current text doesn't match saved text
+				var areDiff = this.UnsavedChanges;
 
-						if (Text.EndsWith ('*')) {
+				if (areDiff) {
+					if (!Text.EndsWith ('*')) {
 
-							Text = Text.TrimEnd ('*');
-							parent.SetNeedsDisplay ();
-						}
+						Text = Text + '*';
+						parent.SetNeedsDisplay ();
 					}
-				};
-			}
+				} else {
 
-			private static View CreateTextView (FileInfo file)
-			{
-				string initialText = string.Empty;
-				if (file != null && file.Exists) {
+					if (Text.EndsWith ('*')) {
 
-					initialText = System.IO.File.ReadAllText (file.FullName);
+						Text = Text.TrimEnd ('*');
+						parent.SetNeedsDisplay ();
+					}
 				}
+			};
+		}
 
-				return new TextView () {
-					X = 0,
-					Y = 0,
-					Width = Dim.Fill (),
-					Height = Dim.Fill (),
-					Text = initialText,
-					AllowsTab = false,
-				};
-			}
-			public OpenedFile CloneTo (TabView other)
-			{
-				var newTab = new OpenedFile (other, base.Text.ToString (), File);
-				other.AddTab (newTab, true);
-				return newTab;
+		private static View CreateTextView (FileInfo file)
+		{
+			string initialText = string.Empty;
+			if (file != null && file.Exists) {
+
+				initialText = System.IO.File.ReadAllText (file.FullName);
 			}
-			internal void Save ()
-			{
-				var newText = View.Text;
 
-				System.IO.File.WriteAllText (File.FullName, newText);
-				SavedText = newText;
+			return new TextView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = initialText,
+				AllowsTab = false,
+			};
+		}
 
-				Text = Text.TrimEnd ('*');
-			}
+		public OpenedFile CloneTo (TabView other)
+		{
+			var newTab = new OpenedFile (other, base.Text.ToString (), File);
+			other.AddTab (newTab, true);
+			return newTab;
 		}
 
-		private void Quit ()
+		internal void Save ()
 		{
-			Application.RequestStop ();
+			var newText = View.Text;
+
+			if (File is null || string.IsNullOrWhiteSpace (File.FullName)) {
+				return;
+			}
+
+			System.IO.File.WriteAllText (File.FullName, newText);
+			SavedText = newText;
+
+			Text = Text.TrimEnd ('*');
 		}
 	}
+
+	private void Quit ()
+	{
+		Application.RequestStop ();
+	}
 }