瀏覽代碼

Fixes #1475. Selection ending with a white space error. (#1478)

* Fixes #1475. Selection ending with a white space error.

* Prevents the mouse  double click processing twice.

* Removing unnecessary variable.

* Sets ScrollViewBar CanFocus to false to ensure the host always focused.

* Only navigates through TabView if winDialog is not null and ensures TextView being focused.

* Fixes both dynamic menu and status bar broken scenarios.

* Fix a bug where the subviews oldEnabled can be overridden, even the superview Enable property hasn't changed.

* Fixes CanFocus when set to false and HasFocus is true.

* Fixes the broken TextView DesiredCursorVisibility.

* Prevents TextField being focused by mouse if CanFocus is false.

* Fixes the CanFocus on content views.

* Fixes #1470. Not all WindowsConsole.InputRecord are caught in WindowsDriver.

* Changing the input for a Queue object.

* Suppress warnings.

* Fixed yet the visibility cursor and adding more unit tests.

* Suppressing more warnings.
BDisp 3 年之前
父節點
當前提交
58e7698f4c

+ 64 - 63
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -904,7 +904,7 @@ namespace Terminal.Gui {
 		bool isButtonReleased = false;
 		bool isButtonDoubleClicked = false;
 		Point point;
-		int buttonPressedCount;
+		//int buttonPressedCount;
 		bool isOneFingerDoubleClicked = false;
 		bool processButtonClick;
 
@@ -937,66 +937,67 @@ namespace Terminal.Gui {
 				Y = mouseEvent.MousePosition.Y
 			};
 
-			if (!isButtonPressed && buttonPressedCount < 2
-				&& mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
-				&& (mouseEvent.ButtonState == WindowsConsole.ButtonState.Button1Pressed
-				|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button2Pressed
-				|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button3Pressed)) {
+			//if (!isButtonPressed && buttonPressedCount < 2
+			//	&& mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
+			//	&& (mouseEvent.ButtonState == WindowsConsole.ButtonState.Button1Pressed
+			//	|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button2Pressed
+			//	|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button3Pressed)) {
 
-				lastMouseButtonPressed = mouseEvent.ButtonState;
-				buttonPressedCount++;
-			} else if (!isButtonPressed && buttonPressedCount > 0 && mouseEvent.ButtonState == 0
-				&& mouseEvent.EventFlags == 0) {
+			//	lastMouseButtonPressed = mouseEvent.ButtonState;
+			//	buttonPressedCount++;
+			//} else if (!isButtonPressed && buttonPressedCount > 0 && mouseEvent.ButtonState == 0
+			//	&& mouseEvent.EventFlags == 0) {
 
-				buttonPressedCount++;
-			}
+			//	buttonPressedCount++;
+			//}
 			//System.Diagnostics.Debug.WriteLine ($"isButtonPressed: {isButtonPressed};buttonPressedCount: {buttonPressedCount};lastMouseButtonPressed: {lastMouseButtonPressed}");
 			//System.Diagnostics.Debug.WriteLine ($"isOneFingerDoubleClicked: {isOneFingerDoubleClicked}");
 
-			if (buttonPressedCount == 1 && lastMouseButtonPressed != null
-				&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
-				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
-				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
-
-				switch (lastMouseButtonPressed) {
-				case WindowsConsole.ButtonState.Button1Pressed:
-					mouseFlag = MouseFlags.Button1DoubleClicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button2Pressed:
-					mouseFlag = MouseFlags.Button2DoubleClicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button3Pressed:
-					mouseFlag = MouseFlags.Button3DoubleClicked;
-					break;
-				}
-				isOneFingerDoubleClicked = true;
-
-			} else if (buttonPressedCount == 3 && lastMouseButtonPressed != null && isOneFingerDoubleClicked
-				&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
-				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
-				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
+			//if (buttonPressedCount == 1 && lastMouseButtonPressed != null && p == point
+			//	&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
+			//	|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
+			//	|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
+
+			//	switch (lastMouseButtonPressed) {
+			//	case WindowsConsole.ButtonState.Button1Pressed:
+			//		mouseFlag = MouseFlags.Button1DoubleClicked;
+			//		break;
+
+			//	case WindowsConsole.ButtonState.Button2Pressed:
+			//		mouseFlag = MouseFlags.Button2DoubleClicked;
+			//		break;
+
+			//	case WindowsConsole.ButtonState.Button3Pressed:
+			//		mouseFlag = MouseFlags.Button3DoubleClicked;
+			//		break;
+			//	}
+			//	isOneFingerDoubleClicked = true;
+
+			//} else if (buttonPressedCount == 3 && lastMouseButtonPressed != null && isOneFingerDoubleClicked && p == point
+			//	&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
+			//	|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
+			//	|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
+
+			//	switch (lastMouseButtonPressed) {
+			//	case WindowsConsole.ButtonState.Button1Pressed:
+			//		mouseFlag = MouseFlags.Button1TripleClicked;
+			//		break;
+
+			//	case WindowsConsole.ButtonState.Button2Pressed:
+			//		mouseFlag = MouseFlags.Button2TripleClicked;
+			//		break;
+
+			//	case WindowsConsole.ButtonState.Button3Pressed:
+			//		mouseFlag = MouseFlags.Button3TripleClicked;
+			//		break;
+			//	}
+			//	buttonPressedCount = 0;
+			//	lastMouseButtonPressed = null;
+			//	isOneFingerDoubleClicked = false;
+			//	isButtonReleased = false;
 
-				switch (lastMouseButtonPressed) {
-				case WindowsConsole.ButtonState.Button1Pressed:
-					mouseFlag = MouseFlags.Button1TripleClicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button2Pressed:
-					mouseFlag = MouseFlags.Button2TripleClicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button3Pressed:
-					mouseFlag = MouseFlags.Button3TripleClicked;
-					break;
-				}
-				buttonPressedCount = 0;
-				lastMouseButtonPressed = null;
-				isOneFingerDoubleClicked = false;
-				isButtonReleased = false;
-
-			} else if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) ||
+			//}
+			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) ||
 				 (lastMouseButtonPressed == null && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
 				 mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) {
 				switch (mouseEvent.ButtonState) {
@@ -1173,7 +1174,7 @@ namespace Terminal.Gui {
 			await Task.Delay (300);
 			isButtonDoubleClicked = false;
 			isOneFingerDoubleClicked = false;
-			buttonPressedCount = 0;
+			//buttonPressedCount = 0;
 		}
 
 		async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
@@ -1732,7 +1733,7 @@ namespace Terminal.Gui {
 		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 
 		// The records that we keep fetching
-		WindowsConsole.InputRecord [] result = new WindowsConsole.InputRecord [1];
+		Queue<WindowsConsole.InputRecord []> resultQueue = new Queue<WindowsConsole.InputRecord []> ();
 
 		/// <summary>
 		/// Invoked when a Key is pressed or released.
@@ -1763,7 +1764,9 @@ namespace Terminal.Gui {
 				waitForProbe.Wait ();
 				waitForProbe.Reset ();
 
-				result = winConsole.ReadConsoleInput ();
+				if (resultQueue?.Count == 0) {
+					resultQueue.Enqueue (winConsole.ReadConsoleInput ());
+				}
 
 				eventReady.Set ();
 			}
@@ -1807,7 +1810,6 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			result = null;
 			waitForProbe.Set ();
 			winChange.Set ();
 
@@ -1822,7 +1824,7 @@ namespace Terminal.Gui {
 			}
 
 			if (!tokenSource.IsCancellationRequested) {
-				return result != null || CheckTimers (wait, out _) || winChanged;
+				return resultQueue.Count > 0 || CheckTimers (wait, out _) || winChanged;
 			}
 
 			tokenSource.Dispose ();
@@ -1855,9 +1857,8 @@ namespace Terminal.Gui {
 
 		void IMainLoopDriver.MainIteration ()
 		{
-			if (result != null) {
-				var inputEvent = result [0];
-				result = null;
+			while (resultQueue.Count > 0) {
+				var inputEvent = resultQueue.Dequeue()[0];
 				ProcessInput?.Invoke (inputEvent);
 			}
 			if (winChanged) {

+ 9 - 0
Terminal.Gui/Core/Border.cs

@@ -257,6 +257,15 @@ namespace Terminal.Gui {
 					SuperView.SetNeedsDisplay ();
 				}
 			}
+
+			/// <inheritdoc/>
+			public override void OnCanFocusChanged ()
+			{
+				if (Border.Child != null) {
+					Border.Child.CanFocus = CanFocus;
+				}
+				base.OnCanFocusChanged ();
+			}
 		}
 
 		private class ChildContentView : View {

+ 60 - 38
Terminal.Gui/Core/View.cs

@@ -344,36 +344,45 @@ namespace Terminal.Gui {
 						TabIndex = SuperView != null ? SuperView.tabIndexes.IndexOf (this) : -1;
 					}
 					TabStop = value;
-				}
-				if (subviews != null && IsInitialized) {
-					foreach (var view in subviews) {
-						if (view.CanFocus != value) {
-							if (!value) {
-								view.oldCanFocus = view.CanFocus;
-								view.oldTabIndex = view.tabIndex;
-								view.CanFocus = value;
-								view.tabIndex = -1;
+
+					if (!value && Application.Top?.Focused == this) {
+						Application.Top.focused = null;
+					}
+					if (!value && HasFocus) {
+						SetHasFocus (false, this);
+						EnsureFocus ();
+						if (Focused == null) {
+							if (Application.Top.Focused == null) {
+								Application.Top.FocusNext ();
 							} else {
-								if (addingView) {
-									view.addingView = true;
-								}
-								view.CanFocus = view.oldCanFocus;
-								view.tabIndex = view.oldTabIndex;
-								view.addingView = false;
+								var v = Application.Top.GetMostFocused (Application.Top.Focused);
+								v.SetHasFocus (true, null, true);
 							}
+							Application.EnsuresTopOnFront ();
 						}
 					}
-				}
-				if (!value && HasFocus) {
-					SetHasFocus (false, this);
-					EnsureFocus ();
-					if (Focused == null) {
-						Application.Top.FocusNext ();
-						Application.EnsuresTopOnFront ();
+					if (subviews != null && IsInitialized) {
+						foreach (var view in subviews) {
+							if (view.CanFocus != value) {
+								if (!value) {
+									view.oldCanFocus = view.CanFocus;
+									view.oldTabIndex = view.tabIndex;
+									view.CanFocus = value;
+									view.tabIndex = -1;
+								} else {
+									if (addingView) {
+										view.addingView = true;
+									}
+									view.CanFocus = view.oldCanFocus;
+									view.tabIndex = view.oldTabIndex;
+									view.addingView = false;
+								}
+							}
+						}
 					}
+					OnCanFocusChanged ();
+					SetNeedsDisplay ();
 				}
-				OnCanFocusChanged ();
-				SetNeedsDisplay ();
 			}
 		}
 
@@ -1195,9 +1204,9 @@ namespace Terminal.Gui {
 			}
 		}
 
-		void SetHasFocus (bool value, View view)
+		void SetHasFocus (bool value, View view, bool force = false)
 		{
-			if (hasFocus != value) {
+			if (hasFocus != value || force) {
 				hasFocus = value;
 				if (value) {
 					OnEnter (view);
@@ -1789,6 +1798,19 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		View GetMostFocused (View view)
+		{
+			if (view == null) {
+				return view;
+			}
+
+			if (view.focused != null) {
+				return GetMostFocused (view.focused);
+			} else {
+				return view;
+			}
+		}
+
 		/// <summary>
 		/// Sets the View's <see cref="Frame"/> to the relative coordinates if its container, given the <see cref="Frame"/> for its container.
 		/// </summary>
@@ -1875,19 +1897,19 @@ namespace Terminal.Gui {
 				}
 			}
 
-            if (edges.Any ()) {
-                var (from, to) = edges.First ();
-                if (from != Application.Top) {
-                    if (!ReferenceEquals (from, to)) {
-                        throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {from} linked with {to}. Did you forget to add it to {this}?");
-                    } else {
-                        throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this);
-                    }
-                }
-            }
+			if (edges.Any ()) {
+				var (from, to) = edges.First ();
+				if (from != Application.Top) {
+					if (!ReferenceEquals (from, to)) {
+						throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {from} linked with {to}. Did you forget to add it to {this}?");
+					} else {
+						throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this);
+					}
+				}
+			}
 
-            // return L (a topologically sorted order)
-            return result;
+			// return L (a topologically sorted order)
+			return result;
 		}
 
 		/// <summary>

+ 9 - 0
Terminal.Gui/Core/Window.cs

@@ -293,6 +293,15 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <inheritdoc/>
+		public override void OnCanFocusChanged ()
+		{
+			if (contentView != null) {
+				contentView.CanFocus = CanFocus;
+			}
+			base.OnCanFocusChanged ();
+		}
+
 		/// <summary>
 		///   The text displayed by the <see cref="Label"/>.
 		/// </summary>

+ 9 - 0
Terminal.Gui/Views/FrameView.cs

@@ -268,5 +268,14 @@ namespace Terminal.Gui {
 
 			return base.OnEnter (view);
 		}
+
+		/// <inheritdoc/>
+		public override void OnCanFocusChanged ()
+		{
+			if (contentView != null) {
+				contentView.CanFocus = CanFocus;
+			}
+			base.OnCanFocusChanged ();
+		}
 	}
 }

+ 10 - 10
Terminal.Gui/Views/ScrollBarView.cs

@@ -85,10 +85,10 @@ namespace Terminal.Gui {
 			X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
 			Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
 			Host = host;
-			CanFocus = host.CanFocus;
+			CanFocus = false;
 			Enabled = host.Enabled;
 			Visible = host.Visible;
-			Host.CanFocusChanged += Host_CanFocusChanged;
+			//Host.CanFocusChanged += Host_CanFocusChanged;
 			Host.EnabledChanged += Host_EnabledChanged;
 			Host.VisibleChanged += Host_VisibleChanged;
 			Host.SuperView.Add (this);
@@ -97,7 +97,7 @@ namespace Terminal.Gui {
 				OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
 					ColorScheme = host.ColorScheme,
 					Host = host,
-					CanFocus = host.CanFocus,
+					CanFocus = false,
 					Enabled = host.Enabled,
 					Visible = host.Visible,
 					OtherScrollBarView = this
@@ -140,13 +140,13 @@ namespace Terminal.Gui {
 			contentBottomRightCorner.Enabled = Enabled;
 		}
 
-		private void Host_CanFocusChanged ()
-		{
-			CanFocus = Host.CanFocus;
-			if (otherScrollBarView != null) {
-				otherScrollBarView.CanFocus = CanFocus;
-			}
-		}
+		//private void Host_CanFocusChanged ()
+		//{
+		//	CanFocus = Host.CanFocus;
+		//	if (otherScrollBarView != null) {
+		//		otherScrollBarView.CanFocus = CanFocus;
+		//	}
+		//}
 
 		void ContentBottomRightCorner_MouseClick (MouseEventArgs me)
 		{

+ 22 - 8
Terminal.Gui/Views/TextField.cs

@@ -613,6 +613,9 @@ namespace Terminal.Gui {
 				return text.Count;
 
 			var ti = text [i];
+			if (Rune.IsLetterOrDigit (ti) && Rune.IsWhiteSpace (text [p]))
+				return i;
+
 			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
 				for (; i < text.Count; i++) {
 					if (Rune.IsLetterOrDigit (text [i]))
@@ -624,7 +627,8 @@ namespace Terminal.Gui {
 						break;
 				}
 				for (; i < text.Count; i++) {
-					if (Rune.IsLetterOrDigit (text [i]))
+					if (Rune.IsLetterOrDigit (text [i]) || 
+						(Rune.IsPunctuation (text [i]) && Rune.IsWhiteSpace (text [i - 1])))
 						break;
 				}
 			}
@@ -724,13 +728,12 @@ namespace Terminal.Gui {
 				return false;
 			}
 
+			if (!CanFocus) {
+				return true;
+			}
+
 			if (ev.Flags == MouseFlags.Button1Pressed) {
-				if (!CanFocus) {
-					return true;
-				}
-				if (!HasFocus) {
-					SetFocus ();
-				}
+				EnsureHasFocus ();
 				PositionCursor (ev);
 				if (isButtonReleased) {
 					ClearAllSelection ();
@@ -749,9 +752,12 @@ namespace Terminal.Gui {
 				isButtonPressed = false;
 				Application.UngrabMouse ();
 			} else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
+				EnsureHasFocus ();
 				int x = PositionCursor (ev);
 				int sbw = x;
-				if (x > 0 && (char)Text [x - 1] != ' ') {
+				if (x == text.Count || (x > 0 && (char)Text [x - 1] != ' '
+					|| (x > 0 && (char)Text [x] == ' '))) {
+
 					sbw = WordBackward (x);
 				}
 				if (sbw != -1) {
@@ -765,6 +771,7 @@ namespace Terminal.Gui {
 				}
 				PrepareSelection (sbw, sfw - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
+				EnsureHasFocus ();
 				PositionCursor (0);
 				ClearAllSelection ();
 				PrepareSelection (0, text.Count);
@@ -772,6 +779,13 @@ namespace Terminal.Gui {
 
 			SetNeedsDisplay ();
 			return true;
+
+			void EnsureHasFocus ()
+			{
+				if (!HasFocus) {
+					SetFocus ();
+				}
+			}
 		}
 
 		int PositionCursor (MouseEvent ev)

+ 33 - 25
Terminal.Gui/Views/TextView.cs

@@ -1272,14 +1272,9 @@ namespace Terminal.Gui {
 
 		void ResetCursorVisibility ()
 		{
-			if (savedCursorVisibility == 0) {
-				savedCursorVisibility = desiredCursorVisibility;
-			}
-			if (savedCursorVisibility != desiredCursorVisibility && !HasFocus) {
+			if (savedCursorVisibility != 0) {
 				DesiredCursorVisibility = savedCursorVisibility;
-				savedCursorVisibility = CursorVisibility.Default;
-			} else if (desiredCursorVisibility != CursorVisibility.Underline) {
-				DesiredCursorVisibility = CursorVisibility.Underline;
+				savedCursorVisibility = 0;
 			}
 		}
 
@@ -1362,8 +1357,10 @@ namespace Terminal.Gui {
 					}
 				}
 			}
-			if (col >= leftColumn && currentColumn - leftColumn + RightOffset < Frame.Width
-				&& topRow <= currentRow && currentRow - topRow + BottomOffset < Frame.Height) {
+			var posX = currentColumn - leftColumn;
+			var posY = currentRow - topRow;
+			if ( posX > -1 && col >= posX && posX < Frame.Width - RightOffset
+				&& topRow <= currentRow && posY < Frame.Height - BottomOffset) {
 				ResetCursorVisibility ();
 				Move (col, currentRow - topRow);
 			} else {
@@ -1446,7 +1443,7 @@ namespace Terminal.Gui {
 		public CursorVisibility DesiredCursorVisibility {
 			get => desiredCursorVisibility;
 			set {
-				if (desiredCursorVisibility != value && HasFocus) {
+				if (HasFocus) {
 					Application.Driver.SetCursorVisibility (value);
 				}
 
@@ -2885,10 +2882,13 @@ namespace Terminal.Gui {
 			if (col + 1 < line.Count) {
 				col++;
 				rune = line [col];
-				if (col + 1 == line.Count) {
+				if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune)
+					&& !Rune.IsWhiteSpace (line [col - 1])) {
 					col++;
 				}
 				return true;
+			} else if (col + 1 == line.Count) {
+				col++;
 			}
 			while (row + 1 < model.Count) {
 				col = 0;
@@ -3060,7 +3060,8 @@ namespace Terminal.Gui {
 				&& !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)
 				&& !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)
 				&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
-				&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)) {
+				&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)
+				&& !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)) {
 				return false;
 			}
 
@@ -3160,31 +3161,38 @@ namespace Terminal.Gui {
 					StopSelecting ();
 				}
 				ProcessMouseClick (ev, out List<Rune> line);
-				(int col, int row)? newPos = null;
-				if (currentColumn > 0 && line [currentColumn - 1] != ' ') {
+				(int col, int row)? newPos;
+				if (currentColumn == line.Count || (currentColumn > 0 && (line [currentColumn - 1] != ' '
+					|| line [currentColumn] == ' '))) {
+
 					newPos = WordBackward (currentColumn, currentRow);
 					if (newPos.HasValue) {
-						currentColumn = newPos.Value.col;
-						currentRow = newPos.Value.row;
+						currentColumn = currentRow == newPos.Value.row ? newPos.Value.col : 0;
 					}
 				}
 				if (!selecting) {
 					StartSelecting ();
 				}
-				if (currentRow < selectionStartRow || currentRow == selectionStartRow && currentColumn < selectionStartColumn) {
-					if (currentColumn > 0 && line [currentColumn - 1] != ' ') {
-						newPos = WordBackward (currentColumn, currentRow);
-					}
-				} else {
-					newPos = WordForward (currentColumn, currentRow);
-				}
+				newPos = WordForward (currentColumn, currentRow);
 				if (newPos != null && newPos.HasValue) {
-					currentColumn = newPos.Value.col;
-					currentRow = newPos.Value.row;
+					currentColumn = currentRow == newPos.Value.row ? newPos.Value.col : line.Count;
 				}
 				PositionCursor ();
 				lastWasKill = false;
 				columnTrack = currentColumn;
+			} else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)) {
+				if (selecting) {
+					StopSelecting ();
+				}
+				ProcessMouseClick (ev, out List<Rune> line);
+				currentColumn = 0;
+				if (!selecting) {
+					StartSelecting ();
+				}
+				currentColumn = line.Count;
+				PositionCursor ();
+				lastWasKill = false;
+				columnTrack = currentColumn;
 			}
 
 			return true;

+ 81 - 20
UICatalog/Scenarios/Editor.cs

@@ -83,19 +83,7 @@ namespace UICatalog {
 					new MenuItem ("_Select All", "", () => SelectAll(),null,null, Key.CtrlMask | Key.T)
 				}),
 				new MenuBarItem ("_ScrollBarView", CreateKeepChecked ()),
-				new MenuBarItem ("_Cursor", new MenuItem [] {
-					new MenuItem ("_Invisible", "", () => SetCursor(CursorVisibility.Invisible)),
-					new MenuItem ("_Box", "", () => SetCursor(CursorVisibility.Box)),
-					new MenuItem ("_Underline", "", () => SetCursor(CursorVisibility.Underline)),
-					new MenuItem ("", "", () => {}, () => { return false; }),
-					new MenuItem ("xTerm :", "", () => {}, () => { return false; }),
-					new MenuItem ("", "", () => {}, () => { return false; }),
-					new MenuItem ("  _Default", "", () => SetCursor(CursorVisibility.Default)),
-					new MenuItem ("  _Vertical", "", () => SetCursor(CursorVisibility.Vertical)),
-					new MenuItem ("  V_ertical Fix", "", () => SetCursor(CursorVisibility.VerticalFix)),
-					new MenuItem ("  B_ox Fix", "", () => SetCursor(CursorVisibility.BoxFix)),
-					new MenuItem ("  U_nderline Fix","", () => SetCursor(CursorVisibility.UnderlineFix))
-				}),
+				new MenuBarItem ("_Cursor", CreateCursorRadio ()),
 				new MenuBarItem ("Forma_t", new MenuItem [] {
 					CreateWrapChecked (),
 					CreateAutocomplete(),
@@ -171,14 +159,14 @@ namespace UICatalog {
 				} else if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
 					Quit ();
 					e.Handled = true;
-				} else if (keys == (Key.Tab | Key.CtrlMask)) {
+				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (0);
 					} else {
 						_tabView.SwitchTabBy (1);
 					}
 					e.Handled = true;
-				} else if (keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
+				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (0)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1);
 					} else {
@@ -330,11 +318,6 @@ namespace UICatalog {
 			}
 		}
 
-		private void SetCursor (CursorVisibility visibility)
-		{
-			_textView.DesiredCursorVisibility = visibility;
-		}
-
 		private bool CanCloseFile ()
 		{
 			if (_textView.Text == _originalText) {
@@ -580,6 +563,84 @@ namespace UICatalog {
 			return item;
 		}
 
+		MenuItem [] CreateCursorRadio ()
+		{
+			List<MenuItem> menuItems = new List<MenuItem> ();
+			menuItems.Add (new MenuItem ("_Invisible", "", () => SetCursor (CursorVisibility.Invisible)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.Invisible
+			});
+			menuItems.Add (new MenuItem ("_Box", "", () => SetCursor (CursorVisibility.Box)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.Box
+			});
+			menuItems.Add (new MenuItem ("_Underline", "", () => SetCursor (CursorVisibility.Underline)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.Underline
+			});
+			menuItems.Add (new MenuItem ("", "", () => { }, () => false));
+			menuItems.Add (new MenuItem ("xTerm :", "", () => { }, () => false));
+			menuItems.Add (new MenuItem ("", "", () => { }, () => false));
+			menuItems.Add (new MenuItem ("  _Default", "", () => SetCursor (CursorVisibility.Default)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.Default
+			});
+			menuItems.Add (new MenuItem ("  _Vertical", "", () => SetCursor (CursorVisibility.Vertical)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.Vertical
+			});
+			menuItems.Add (new MenuItem ("  V_ertical Fix", "", () => SetCursor (CursorVisibility.VerticalFix)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.VerticalFix
+			});
+			menuItems.Add (new MenuItem ("  B_ox Fix", "", () => SetCursor (CursorVisibility.BoxFix)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.BoxFix
+			});
+			menuItems.Add (new MenuItem ("  U_nderline Fix", "", () => SetCursor (CursorVisibility.UnderlineFix)) {
+				CheckType = MenuItemCheckStyle.Radio,
+				Checked = _textView.DesiredCursorVisibility == CursorVisibility.UnderlineFix
+			});
+
+			void SetCursor (CursorVisibility visibility)
+			{
+				_textView.DesiredCursorVisibility = visibility;
+				var title = "";
+				switch (visibility) {
+				case CursorVisibility.Default:
+					title = "  _Default";
+					break;
+				case CursorVisibility.Invisible:
+					title = "_Invisible";
+					break;
+				case CursorVisibility.Underline:
+					title = "_Underline";
+					break;
+				case CursorVisibility.UnderlineFix:
+					title = "  U_nderline Fix";
+					break;
+				case CursorVisibility.Vertical:
+					title = "  _Vertical";
+					break;
+				case CursorVisibility.VerticalFix:
+					title = "  V_ertical Fix";
+					break;
+				case CursorVisibility.Box:
+					title = "_Box";
+					break;
+				case CursorVisibility.BoxFix:
+					title = "  B_ox Fix";
+					break;
+				}
+
+				foreach (var menuItem in menuItems) {
+					menuItem.Checked = menuItem.Title.Equals (title) && visibility == _textView.DesiredCursorVisibility;
+				}
+			}
+
+			return menuItems.ToArray ();
+		}
+
 		private void CreateFindReplace (bool isFind = true)
 		{
 			if (winDialog != null) {

+ 6 - 0
UnitTests/ApplicationTests.cs

@@ -1234,6 +1234,12 @@ namespace Terminal.Gui.Core {
 
 			Application.Begin (top);
 
+			Assert.True (win.CanFocus);
+			Assert.True (win.HasFocus);
+			Assert.True (win2.CanFocus);
+			Assert.False (win2.HasFocus);
+			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+
 			win.CanFocus = false;
 			Assert.False (win.CanFocus);
 			Assert.False (win.HasFocus);

+ 101 - 0
UnitTests/TextFieldTests.cs

@@ -457,6 +457,12 @@ namespace Terminal.Gui.Views {
 					Assert.Null (_textField.SelectedText);
 					break;
 				case 9:
+					Assert.Equal (54, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 10:
 					Assert.Equal (55, _textField.CursorPosition);
 					Assert.Equal (-1, _textField.SelectedStart);
 					Assert.Equal (0, _textField.SelectedLength);
@@ -785,5 +791,100 @@ namespace Terminal.Gui.Views {
 			_textField.Paste ();
 			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
 		}
+
+		[Fact]
+		[InitShutdown]
+		public void TextField_SpaceHandling ()
+		{
+			var tf = new TextField () {
+				Width = 10,
+				Text = " "
+			};
+
+			MouseEvent ev = new MouseEvent () {
+				X = 0,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked,
+			};
+
+			tf.MouseEvent (ev);
+			Assert.Equal (1, tf.SelectedLength);
+
+			ev = new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked,
+			};
+
+			tf.MouseEvent (ev);
+			Assert.Equal (1, tf.SelectedLength);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void CanFocus_False_Wont_Focus_With_Mouse ()
+		{
+			var top = Application.Top;
+			var tf = new TextField () {
+				Width = Dim.Fill (),
+				CanFocus = false,
+				ReadOnly = true,
+				Text = "some text"
+			};
+			var fv = new FrameView ("I shouldn't get focus") {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				CanFocus = false,
+			};
+			fv.Add (tf);
+			top.Add (fv);
+
+			Application.Begin (top);
+
+			Assert.False (tf.CanFocus);
+			Assert.False (tf.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+
+			tf.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Null (tf.SelectedText);
+			Assert.False (tf.CanFocus);
+			Assert.False (tf.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+
+			Assert.Throws<InvalidOperationException> (() => tf.CanFocus = true);
+			fv.CanFocus = true;
+			tf.CanFocus = true;
+			tf.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Equal ("some ", tf.SelectedText);
+			Assert.True (tf.CanFocus);
+			Assert.True (tf.HasFocus);
+			Assert.True (fv.CanFocus);
+			Assert.True (fv.HasFocus);
+
+			fv.CanFocus = false;
+			tf.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Equal ("some ", tf.SelectedText); // Setting CanFocus to false don't change the SelectedText
+			Assert.False (tf.CanFocus);
+			Assert.False (tf.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+		}
 	}
 }

+ 183 - 2
UnitTests/TextViewTests.cs

@@ -573,6 +573,14 @@ namespace Terminal.Gui.Views {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 9:
+					Assert.Equal (54, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 10:
 					Assert.Equal (55, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
@@ -2009,8 +2017,8 @@ line.
 			Assert.True (gaveFullTurn);
 
 			Assert.Equal ((new Point (9, 1), true), tm.ReplaceAllText ("is", false, false, "really"));
-			Assert.Equal (TextModel.ToRunes ("Threally really first line."),  tm.GetLine (0));
-			Assert.Equal (TextModel.ToRunes ("Threally really last line."),  tm.GetLine (1));
+			Assert.Equal (TextModel.ToRunes ("Threally really first line."), tm.GetLine (0));
+			Assert.Equal (TextModel.ToRunes ("Threally really last line."), tm.GetLine (1));
 			tm = new TextModel ();
 			tm.AddLine (0, TextModel.ToRunes ("This is first line."));
 			tm.AddLine (1, TextModel.ToRunes ("This is last line."));
@@ -2078,5 +2086,178 @@ line.
 			Assert.Equal (3, tv.LeftColumn);
 			Assert.Equal (0, tv.RightOffset);
 		}
+
+		[Fact]
+		[InitShutdown]
+		public void TextView_SpaceHandling ()
+		{
+			var tv = new TextView () {
+				Width = 10,
+				Text = " "
+			};
+
+			MouseEvent ev = new MouseEvent () {
+				X = 0,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked,
+			};
+
+			tv.MouseEvent (ev);
+			Assert.Equal (1, tv.SelectedLength);
+
+			ev = new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked,
+			};
+
+			tv.MouseEvent (ev);
+			Assert.Equal (1, tv.SelectedLength);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void CanFocus_False_Wont_Focus_With_Mouse ()
+		{
+			var top = Application.Top;
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				CanFocus = false,
+				ReadOnly = true,
+				Text = "some text"
+			};
+			var fv = new FrameView ("I shouldn't get focus") {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				CanFocus = false,
+			};
+			fv.Add (tv);
+			top.Add (fv);
+
+			Application.Begin (top);
+
+			Assert.False (tv.CanFocus);
+			Assert.False (tv.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+
+			tv.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Empty (tv.SelectedText);
+			Assert.False (tv.CanFocus);
+			Assert.False (tv.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+
+			Assert.Throws<InvalidOperationException> (() => tv.CanFocus = true);
+			fv.CanFocus = true;
+			tv.CanFocus = true;
+			tv.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Equal ("some ", tv.SelectedText);
+			Assert.True (tv.CanFocus);
+			Assert.True (tv.HasFocus);
+			Assert.True (fv.CanFocus);
+			Assert.True (fv.HasFocus);
+
+			fv.CanFocus = false;
+			tv.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1DoubleClicked
+			});
+
+			Assert.Equal ("some ", tv.SelectedText); // Setting CanFocus to false don't change the SelectedText
+			Assert.False (tv.CanFocus);
+			Assert.False (tv.HasFocus);
+			Assert.False (fv.CanFocus);
+			Assert.False (fv.HasFocus);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void DesiredCursorVisibility_Vertical_Navigation ()
+		{
+			string text = "";
+
+			for (int i = 0; i < 12; i++) {
+				text += $"This is the line {i}\n";
+			}
+			var tv = new TextView () { Width = 10, Height = 10 };
+			tv.Text = text;
+
+			Assert.Equal (0, tv.TopRow);
+			tv.PositionCursor ();
+			Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
+
+			for (int i = 0; i < 12; i++) {
+				tv.MouseEvent (new MouseEvent () {
+					Flags = MouseFlags.WheeledDown
+				});
+				tv.PositionCursor ();
+				Assert.Equal (i + 1, tv.TopRow);
+				Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
+			}
+
+			for (int i = 12; i > 0; i--) {
+				tv.MouseEvent (new MouseEvent () {
+					Flags = MouseFlags.WheeledUp
+				});
+				tv.PositionCursor ();
+				Assert.Equal (i - 1, tv.TopRow);
+				if (i - 1 == 0) {
+					Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
+				} else {
+					Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
+				}
+			}
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void DesiredCursorVisibility_Horizontal_Navigation ()
+		{
+			string text = "";
+
+			for (int i = 0; i < 12; i++) {
+				text += $"{i.ToString () [^1]}";
+			}
+			var tv = new TextView () { Width = 10, Height = 10 };
+			tv.Text = text;
+
+			Assert.Equal (0, tv.LeftColumn);
+			tv.PositionCursor ();
+			Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
+
+			for (int i = 0; i < 12; i++) {
+				tv.MouseEvent (new MouseEvent () {
+					Flags = MouseFlags.WheeledRight
+				});
+				tv.PositionCursor ();
+				Assert.Equal (Math.Min (i + 1, 11), tv.LeftColumn);
+				Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
+			}
+
+			for (int i = 11; i > 0; i--) {
+				tv.MouseEvent (new MouseEvent () {
+					Flags = MouseFlags.WheeledLeft
+				});
+				tv.PositionCursor ();
+				Assert.Equal (i - 1, tv.LeftColumn);
+				if (i - 1 == 0) {
+					Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
+				} else {
+					Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
+				}
+			}
+		}
 	}
 }