Browse Source

Fix #1120 (#1124)

* Added ObjectActivationButton (mouse)

* Fixed layout to match coding guidelines
Thomas Nind 4 years ago
parent
commit
522745f559
2 changed files with 145 additions and 10 deletions
  1. 55 10
      Terminal.Gui/Views/TreeView.cs
  2. 90 0
      UnitTests/TreeViewTests.cs

+ 55 - 10
Terminal.Gui/Views/TreeView.cs

@@ -119,6 +119,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		public Key ObjectActivationKey { get; set; } = Key.Enter;
 
+		/// <summary>
+		/// Mouse event to trigger <see cref="TreeView{T}.ObjectActivated"/>.
+		/// Defaults to double click (<see cref="MouseFlags.Button1DoubleClicked"/>).
+		/// Set to null to disable this feature.
+		/// </summary>
+		/// <value></value>
+		public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
+
 		/// <summary>
 		/// Secondary selected regions of tree when <see cref="MultiSelect"/> is true
 		/// </summary>
@@ -447,7 +455,7 @@ namespace Terminal.Gui {
 				toReturn.AddRange (AddToLineMap (root));
 			}
 
-			return cachedLineMap = new ReadOnlyCollection<Branch<T>>(toReturn);
+			return cachedLineMap = new ReadOnlyCollection<Branch<T>> (toReturn);
 		}
 
 		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch)
@@ -564,7 +572,7 @@ namespace Terminal.Gui {
 		{
 			// If it is not an event we care about
 			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
-				!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
+				!me.Flags.HasFlag (ObjectActivationButton ?? MouseFlags.Button1DoubleClicked) &&
 				!me.Flags.HasFlag (MouseFlags.WheeledDown) &&
 				!me.Flags.HasFlag (MouseFlags.WheeledUp) &&
 				!me.Flags.HasFlag (MouseFlags.WheeledRight) &&
@@ -607,18 +615,13 @@ namespace Terminal.Gui {
 
 			if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 
-				var map = BuildLineMap ();
-
-				var idx = me.Y + ScrollOffsetVertical;
+				// The line they clicked on a branch
+				var clickedBranch = HitTest (me.Y);
 
-				// click is outside any visible nodes
-				if (idx < 0 || idx >= map.Count) {
+				if (clickedBranch == null) {
 					return false;
 				}
 
-				// The line they clicked on
-				var clickedBranch = map.ElementAt (idx);
-
 				bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
 
 				// If we are already selected (double click)
@@ -651,9 +654,51 @@ namespace Terminal.Gui {
 				return true;
 			}
 
+			// If it is activation via mouse (e.g. double click)
+			if (ObjectActivationButton.HasValue && me.Flags.HasFlag (ObjectActivationButton.Value)) {
+				// The line they clicked on a branch
+				var clickedBranch = HitTest (me.Y);
+
+				if (clickedBranch == null) {
+					return false;
+				}
+
+				// Double click changes the selection to the clicked node as well as triggering
+				// activation otherwise it feels wierd
+				SelectedObject = clickedBranch.Model;
+				SetNeedsDisplay ();
+
+				// trigger activation event				
+				OnObjectActivated (new ObjectActivatedEventArgs<T> (this, clickedBranch.Model));
+
+				// mouse event is handled.
+				return true;
+			}
+
 			return false;
 		}
 
+		/// <summary>
+		/// Returns the branch at the given <paramref name="y"/> client
+		/// coordinate e.g. following a click event
+		/// </summary>
+		/// <param name="y">Client Y position in the controls bounds</param>
+		/// <returns>The clicked branch or null if outside of tree region</returns>
+		private Branch<T> HitTest (int y)
+		{
+			var map = BuildLineMap ();
+
+			var idx = y + ScrollOffsetVertical;
+
+			// click is outside any visible nodes
+			if (idx < 0 || idx >= map.Count) {
+				return null;
+			}
+
+			// The line they clicked on
+			return map.ElementAt (idx);
+		}
+
 		/// <summary>
 		/// Positions the cursor at the start of the selected objects line (if visible)
 		/// </summary>

+ 90 - 0
UnitTests/TreeViewTests.cs

@@ -567,6 +567,96 @@ namespace UnitTests {
 
 		}
 
+		[Fact]
+		public void ObjectActivationButton_DoubleClick ()
+		{
+			var tree = CreateTree (out Factory f, out Car car1, out _);
+
+			InitFakeDriver ();
+
+			object activated = null;
+			bool called = false;
+
+			// register for the event
+			tree.ObjectActivated += (s) => {
+				activated = s.ActivatedObject;
+				called = true;
+			};
+
+			Assert.False (called);
+
+			// double click triggers activation
+			tree.MouseEvent (new MouseEvent () { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
+
+			Assert.True (called);
+			Assert.Same (f, activated);
+			Assert.Same (f, tree.SelectedObject);
+		}
+
+		[Fact]
+		public void ObjectActivationButton_SetToNull ()
+		{
+			var tree = CreateTree (out Factory f, out Car car1, out _);
+
+			InitFakeDriver ();
+
+			// disable activation
+			tree.ObjectActivationButton = null;
+
+			object activated = null;
+			bool called = false;
+
+			// register for the event
+			tree.ObjectActivated += (s) => {
+				activated = s.ActivatedObject;
+				called = true;
+			};
+
+			Assert.False (called);
+
+			// double click does nothing because we changed button to null
+			tree.MouseEvent (new MouseEvent () { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
+
+			Assert.False (called);
+			Assert.Null (activated);
+			Assert.Null (tree.SelectedObject);
+		}
+
+
+		[Fact]
+		public void ObjectActivationButton_RightClick ()
+		{
+			var tree = CreateTree (out Factory f, out Car car1, out _);
+
+			InitFakeDriver ();
+
+			tree.ObjectActivationButton = MouseFlags.Button2Clicked;
+			tree.ExpandAll ();
+
+			object activated = null;
+			bool called = false;
+
+			// register for the event
+			tree.ObjectActivated += (s) => {
+				activated = s.ActivatedObject;
+				called = true;
+			};
+
+			Assert.False (called);
+
+			// double click does nothing because we changed button binding to right click
+			tree.MouseEvent (new MouseEvent () { Y = 1, Flags = MouseFlags.Button1DoubleClicked });
+
+			Assert.Null (activated);
+			Assert.False (called);
+
+			tree.MouseEvent (new MouseEvent () { Y = 1, Flags = MouseFlags.Button2Clicked });
+
+			Assert.True (called);
+			Assert.Same (car1, activated);
+			Assert.Same (car1, tree.SelectedObject);
+		}
+
 
 
 		/// <summary>