Browse Source

Merge pull request #1706 from tznind/tree-view-context

Tree view context
Tig Kindel 3 years ago
parent
commit
a7db7cdec8
3 changed files with 223 additions and 19 deletions
  1. 38 0
      Terminal.Gui/Views/TreeView.cs
  2. 77 19
      UICatalog/Scenarios/TreeViewFileSystem.cs
  3. 108 0
      UnitTests/TreeViewTests.cs

+ 38 - 0
Terminal.Gui/Views/TreeView.cs

@@ -609,6 +609,29 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// <para>
+		/// Returns the Y coordinate within the <see cref="View.Bounds"/> of the
+		/// tree at which <paramref name="toFind"/> would be displayed or null if
+		/// it is not currently exposed (e.g. its parent is collapsed).
+		/// </para>
+		/// <para>
+		/// Note that the returned value can be negative if the TreeView is scrolled
+		/// down and the <paramref name="toFind"/> object is off the top of the view.
+		/// </para>
+		/// </summary>
+		/// <param name="toFind"></param>
+		/// <returns></returns>
+		public int? GetObjectRow(T toFind)
+		{
+			var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));
+
+			if (idx == -1)
+				return null;
+
+			return idx - ScrollOffsetVertical;
+		}
+
 		/// <summary>
 		/// <para>Moves the <see cref="SelectedObject"/> to the next item that begins with <paramref name="character"/></para>
 		/// <para>This method will loop back to the start of the tree if reaching the end without finding a match</para>
@@ -671,6 +694,21 @@ namespace Terminal.Gui {
 			ObjectActivated?.Invoke (e);
 		}
 
+		/// <summary>
+		/// Returns the object in the tree list that is currently visible
+		/// at the provided row.  Returns null if no object is at that location.
+		/// <remarks>
+		/// </remarks>
+		/// If you have screen coordinates then use <see cref="View.ScreenToView(int, int)"/>
+		/// to translate these into the client area of the <see cref="TreeView{T}"/>.
+		/// </summary>
+		/// <param name="row">The row of the <see cref="View.Bounds"/> of the <see cref="TreeView{T}"/></param>
+		/// <returns>The object currently displayed on this row or null</returns>
+		public T GetObjectOnRow (int row)
+		{
+			return HitTest (row)?.Model;
+		}
+
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent me)
 		{

+ 77 - 19
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -77,6 +77,8 @@ namespace UICatalog.Scenarios {
 			};
 
 			treeViewFiles.ObjectActivated += TreeViewFiles_ObjectActivated;
+			treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
+			treeViewFiles.KeyPress += TreeViewFiles_KeyPress;
 
 			SetupFileTree ();
 
@@ -88,6 +90,80 @@ namespace UICatalog.Scenarios {
 			red = Application.Driver.MakeAttribute (Color.Red, Color.Blue);
 		}
 
+		private void TreeViewFiles_KeyPress (View.KeyEventEventArgs obj)
+		{
+			if(obj.KeyEvent.Key == (Key.R | Key.CtrlMask)) {
+
+				var selected = treeViewFiles.SelectedObject;
+				
+				// nothing is selected
+				if (selected == null)
+					return;
+				
+				var location = treeViewFiles.GetObjectRow (selected);
+
+				//selected object is offscreen or somehow not found
+				if (location == null || location < 0 || location > treeViewFiles.Frame.Height)
+					return;
+
+				ShowContextMenu (new Point (
+					5 + treeViewFiles.Frame.X,
+					location.Value + treeViewFiles.Frame.Y + 2),
+					selected);
+			}
+		}
+
+		private void TreeViewFiles_MouseClick (View.MouseEventArgs obj)
+		{
+			// if user right clicks
+			if (obj.MouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) {
+
+				var rightClicked = treeViewFiles.GetObjectOnRow ( obj.MouseEvent.Y);
+
+				// nothing was clicked
+				if (rightClicked == null)
+					return;
+
+				ShowContextMenu (new Point (
+					obj.MouseEvent.X + treeViewFiles.Frame.X,
+					obj.MouseEvent.Y + treeViewFiles.Frame.Y + 2),
+					rightClicked);
+			}
+		}
+
+		private void ShowContextMenu (Point screenPoint, FileSystemInfo forObject)
+		{
+			var menu = new ContextMenu ();
+			menu.Position = screenPoint;
+
+			menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
+			
+			Application.MainLoop.Invoke(menu.Show);
+		}
+
+		private void ShowPropertiesOf (FileSystemInfo fileSystemInfo)
+		{
+			if (fileSystemInfo is FileInfo f) {
+				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
+				sb.AppendLine ($"Path:{f.DirectoryName}");
+				sb.AppendLine ($"Size:{f.Length:N0} bytes");
+				sb.AppendLine ($"Modified:{ f.LastWriteTime}");
+				sb.AppendLine ($"Created:{ f.CreationTime}");
+
+				MessageBox.Query (f.Name, sb.ToString (), "Close");
+			}
+
+			if (fileSystemInfo is DirectoryInfo dir) {
+
+				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
+				sb.AppendLine ($"Path:{dir.Parent?.FullName}");
+				sb.AppendLine ($"Modified:{ dir.LastWriteTime}");
+				sb.AppendLine ($"Created:{ dir.CreationTime}");
+
+				MessageBox.Query (dir.Name, sb.ToString (), "Close");
+			}
+		}
+
 		private void SetupScrollBar ()
 		{
 			// When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
@@ -140,25 +216,7 @@ namespace UICatalog.Scenarios {
 
 		private void TreeViewFiles_ObjectActivated (ObjectActivatedEventArgs<FileSystemInfo> obj)
 		{
-			if (obj.ActivatedObject is FileInfo f) {
-				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
-				sb.AppendLine ($"Path:{f.DirectoryName}");
-				sb.AppendLine ($"Size:{f.Length:N0} bytes");
-				sb.AppendLine ($"Modified:{ f.LastWriteTime}");
-				sb.AppendLine ($"Created:{ f.CreationTime}");
-
-				MessageBox.Query (f.Name, sb.ToString (), "Close");
-			}
-
-			if (obj.ActivatedObject is DirectoryInfo dir) {
-
-				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
-				sb.AppendLine ($"Path:{dir.Parent?.FullName}");
-				sb.AppendLine ($"Modified:{ dir.LastWriteTime}");
-				sb.AppendLine ($"Created:{ dir.CreationTime}");
-
-				MessageBox.Query (dir.Name, sb.ToString (), "Close");
-			}
+			ShowPropertiesOf (obj.ActivatedObject);
 		}
 
 		private void ShowLines ()

+ 108 - 0
UnitTests/TreeViewTests.cs

@@ -720,7 +720,115 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj2, child)));
 
 		}
+		[Fact, AutoInitShutdown]
+		public void TestGetObjectOnRow ()
+		{
+			var tv = new TreeView { Width = 20, Height = 10 };
+
+			var n1 = new TreeNode ("normal");
+			var n1_1 = new TreeNode ("pink");
+			var n1_2 = new TreeNode ("normal");
+			n1.Children.Add (n1_1);
+			n1.Children.Add (n1_2);
+
+			var n2 = new TreeNode ("pink");
+			tv.AddObject (n1);
+			tv.AddObject (n2);
+			tv.Expand (n1);
+
+			tv.ColorScheme = new ColorScheme ();
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsAre (
+@"├-normal
+│ ├─pink
+│ └─normal
+└─pink
+", output);
+
+			Assert.Same (n1, tv.GetObjectOnRow (0));
+			Assert.Same (n1_1, tv.GetObjectOnRow (1));
+			Assert.Same (n1_2, tv.GetObjectOnRow (2));
+			Assert.Same (n2, tv.GetObjectOnRow (3));
+			Assert.Null (tv.GetObjectOnRow (4));
+
+			tv.Collapse (n1);
+
+			tv.Redraw (tv.Bounds);
+
+
+			GraphViewTests.AssertDriverContentsAre (
+@"├+normal
+└─pink
+", output);
+
+			Assert.Same (n1, tv.GetObjectOnRow (0));
+			Assert.Same (n2, tv.GetObjectOnRow (1));
+			Assert.Null (tv.GetObjectOnRow (2));
+			Assert.Null (tv.GetObjectOnRow (3));
+			Assert.Null (tv.GetObjectOnRow (4));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestGetObjectRow ()
+		{
+			var tv = new TreeView { Width = 20, Height = 10 };
+
+			var n1 = new TreeNode ("normal");
+			var n1_1 = new TreeNode ("pink");
+			var n1_2 = new TreeNode ("normal");
+			n1.Children.Add (n1_1);
+			n1.Children.Add (n1_2);
+
+			var n2 = new TreeNode ("pink");
+			tv.AddObject (n1);
+			tv.AddObject (n2);
+			tv.Expand (n1);
+
+			tv.ColorScheme = new ColorScheme ();
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsAre (
+@"├-normal
+│ ├─pink
+│ └─normal
+└─pink
+", output);
+
+			Assert.Equal (0, tv.GetObjectRow (n1));
+			Assert.Equal (1, tv.GetObjectRow (n1_1));
+			Assert.Equal (2, tv.GetObjectRow (n1_2));
+			Assert.Equal (3, tv.GetObjectRow (n2));
 
+			tv.Collapse (n1);
+
+			tv.Redraw (tv.Bounds);
+
+
+			GraphViewTests.AssertDriverContentsAre (
+@"├+normal
+└─pink
+", output);
+			Assert.Equal (0, tv.GetObjectRow (n1));
+			Assert.Null (tv.GetObjectRow (n1_1));
+			Assert.Null (tv.GetObjectRow (n1_2));
+			Assert.Equal (1, tv.GetObjectRow (n2));
+
+
+			// scroll down 1
+			tv.ScrollOffsetVertical = 1;
+
+			tv.Redraw (tv.Bounds);
+
+
+			GraphViewTests.AssertDriverContentsAre (
+@"└─pink
+", output);
+			Assert.Equal (-1, tv.GetObjectRow (n1));
+			Assert.Null (tv.GetObjectRow (n1_1));
+			Assert.Null (tv.GetObjectRow (n1_2));
+			Assert.Equal (0, tv.GetObjectRow (n2));
+		}
 		[Fact, AutoInitShutdown]
 		public void TestTreeViewColor()
 		{