diff --git a/Terminal.Gui/Views/TextInput/TextField/TextField.Commands.cs b/Terminal.Gui/Views/TextInput/TextField/TextField.Commands.cs index 35c2755a20..734a307deb 100644 --- a/Terminal.Gui/Views/TextInput/TextField/TextField.Commands.cs +++ b/Terminal.Gui/Views/TextInput/TextField/TextField.Commands.cs @@ -522,6 +522,14 @@ public bool Copy () App?.Clipboard?.SetClipboardData (SelectedText); + if (!ReadOnly) + { + return true; + } + _insertionPoint = 0; + ScrollOffset = 0; + ClearAllSelection (); + return true; } diff --git a/Terminal.Gui/Views/TextInput/TextField/TextField.cs b/Terminal.Gui/Views/TextInput/TextField/TextField.cs index 2bf1a1594e..25fa5e20f7 100644 --- a/Terminal.Gui/Views/TextInput/TextField/TextField.cs +++ b/Terminal.Gui/Views/TextInput/TextField/TextField.cs @@ -112,9 +112,12 @@ public TextField () private void TextField_Initialized (object? sender, EventArgs e) { - _insertionPoint = GraphemeHelper.GetGraphemeCount (Text); + if (!ReadOnly) + { + _insertionPoint = GraphemeHelper.GetGraphemeCount (Text); + } - if (Viewport.Width > 0) + if (!ReadOnly && Viewport.Width > 0) { int colsWidth = Text.GetColumns (); ScrollOffset = colsWidth > Viewport.Width + 1 ? colsWidth - Viewport.Width + 1 : 0; @@ -145,12 +148,27 @@ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocus App.Mouse.UngrabMouse (); } - // If gaining focus via keyboard (not mouse), select all text - if (newHasFocus && !_focusSetByMouse && _text.Count > 0) + // If gaining focus via keyboard (not mouse), select all text if not ReadOnly + if (newHasFocus && !ReadOnly && !_focusSetByMouse && _text.Count > 0) { SelectAll (); } + if (ReadOnly && InsertionPoint > 0) + { + _insertionPoint = 0; + } + + if (ReadOnly && ScrollOffset > 0) + { + ScrollOffset = 0; + } + + if (ReadOnly && SelectedLength > 0) + { + ClearAllSelection (); + } + // Reset the flag after handling focus change _focusSetByMouse = false; diff --git a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs index d5d3890fb3..ded262f9bf 100644 --- a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs +++ b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs @@ -1539,4 +1539,125 @@ public void CtrlKey_With_AssociatedText_Does_Not_Insert_Into_TextField () Assert.Equal ("", tf.Text); } + + [Fact] + public void ReadOnly_ShouldNotAllowAutomaticallyScrolling_AndMustSetInsertionPointToZeroAtInitialization () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + tf.BeginInit (); + tf.EndInit (); + + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + Assert.Null (tf.SelectedText); + } + + [Fact] + public void ReadOnly_ShouldNotSelectAllText_OnFocus () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + tf.BeginInit (); + tf.EndInit (); + + tf.SetFocus (); + + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + Assert.Null (tf.SelectedText); + } + + [Fact] + public void ReadOnly_ShouldSetInsertionPointAndScrollOffsetToZero_OnFocus () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + tf.BeginInit (); + tf.EndInit (); + + // Move insertion point to end of text + tf.InsertionPoint = tf.Text.Length; + Assert.Equal (11, tf.InsertionPoint); + Assert.Equal (7, tf.ScrollOffset); + + // Set focus and verify insertion point is still at zero + tf.SetFocus (); + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + } + + [Fact] + public void ReadOnly_ShouldSetInsertionPointAndScrollOffsetToZero_LeavingFocus () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + + // Create another view to take focus away + View otherView = new () { CanFocus = true }; + + // Create a container to hold both views + Runnable container = new () { Id = "container", Width = 20, Height = 5 }; + container.Add (tf, otherView); + container.BeginInit (); + container.EndInit (); + + // Set focus to TextField and verify insertion point and scroll offset are at zero + tf.SetFocus (); + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + + // Move insertion point to end of text and then move focus away to verify insertion point is still at zero + tf.InsertionPoint = tf.Text.Length; + Assert.Equal (11, tf.InsertionPoint); + Assert.Equal (7, tf.ScrollOffset); + + otherView.SetFocus (); + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + } + + [Fact] + public void ReadOnly_ShouldSetInsertionPointAndScrollOffsetToZero_AfterCopyingText () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + tf.BeginInit (); + tf.EndInit (); + + // Select all text + tf.SelectAll (); + Assert.Equal (11, tf.InsertionPoint); + Assert.Equal (7, tf.ScrollOffset); + Assert.Equal ("hello world", tf.SelectedText); + + // Copy text and verify insertion point and scroll offset are still at zero + tf.Copy (); + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + Assert.Null (tf.SelectedText); + } + + [Fact] + public void ReadOnly_ShouldSetInsertionPointAndScrollOffsetToZeroAndClearAllSelection_OnLeavingFocus () + { + TextField tf = new () { Width = 5, ReadOnly = true, Text = "hello world" }; + + // Create another view to take focus away + View otherView = new () { CanFocus = true }; + + // Create a container to hold both views + Runnable container = new () { Id = "container", Width = 20, Height = 5 }; + container.Add (tf, otherView); + container.BeginInit (); + container.EndInit (); + + // Set focus to TextField and select all text + tf.SetFocus (); + tf.SelectAll (); + Assert.Equal (11, tf.InsertionPoint); + Assert.Equal (7, tf.ScrollOffset); + Assert.Equal ("hello world", tf.SelectedText); + + // Move focus away and verify insertion point and scroll offset are at zero and selection is cleared + otherView.SetFocus (); + Assert.Equal (0, tf.InsertionPoint); + Assert.Equal (0, tf.ScrollOffset); + Assert.Null (tf.SelectedText); + } }