From 945a456ec04d567517c31a3ba93daabb06ccbf1b Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 15 Nov 2020 13:44:08 +0000 Subject: [PATCH] Fixes #998. Added a cancelable TextChanging event to prevent the TextChanged event being called if the changing is canceled. --- Terminal.Gui/Views/TextField.cs | 164 ++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 72 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 42e75ddbb..7d33fbef8 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -31,11 +31,16 @@ namespace Terminal.Gui { /// public bool ReadOnly { get; set; } = false; + /// + /// Changing event, raised before the changes and can be canceled or changing the new text. + /// + public event Action TextChanging; + /// /// Changed event, raised when the text has changed. /// /// - /// This event is raised when the changes. + /// This event is raised when the changes. /// /// /// The passed is a containing the old value. @@ -136,7 +141,11 @@ namespace Terminal.Gui { if (oldText == value) return; - text = TextModel.ToRunes (value); + var newText = OnTextChanging (value); + if (newText.Cancel) { + return; + } + text = TextModel.ToRunes (newText.NewText); if (!Secret && !isFromHistory) { if (historyText == null) historyText = new List () { oldText }; @@ -147,10 +156,11 @@ namespace Terminal.Gui { } TextChanged?.Invoke (oldText); - if (point > text.Count) { - point = Math.Max (DisplaySize (text, 0).size - 1, 0); - } + if (point > text.Count) + point = Math.Max (DisplaySize (text, 0) - 1, 0); + // FIXME: this needs to be updated to use Rune.ColumnWidth + //first = point > Frame.Width ? point - Frame.Width : 0; Adjust (); SetNeedsDisplay (); } @@ -205,8 +215,11 @@ namespace Terminal.Gui { int width = Frame.Width; var tcount = text.Count; var roc = new Attribute (Color.DarkGray, Color.Gray); - for (int idx = p; idx < tcount; idx++) { + for (int idx = 0; idx < tcount; idx++) { var rune = text [idx]; + if (idx < p) { + continue; + } var cols = Rune.ColumnWidth (rune); if (idx == point && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) { Driver.SetAttribute (Colors.Menu.HotFocus); @@ -219,13 +232,10 @@ namespace Terminal.Gui { Driver.AddRune ((Rune)(Secret ? '*' : rune)); } col = SetCol (col, width, cols); - if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) { - break; - } } Driver.SetAttribute (ColorScheme.Focus); - for (int i = col; i < width; i++) { + for (int i = col; i < Frame.Width; i++) { Driver.AddRune (' '); } @@ -241,26 +251,20 @@ namespace Terminal.Gui { return col; } - // Returns the size and length in a range of the string. - (int size, int length) DisplaySize (List t, int start = -1, int end = -1, bool checkNextRune = true) + // Returns the size in a range of the string. + int DisplaySize (List t, int start = -1, int end = -1) { if (t == null || t.Count == 0) { - return (0, 0); + return 0; } int size = 0; - int len = 0; int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; int i = start == -1 ? 0 : start; for (; i < tcount; i++) { var rune = t [i]; size += Rune.ColumnWidth (rune); - len += Rune.RuneLen (rune); - if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) { - size += Rune.ColumnWidth (t [i + 1]); - len += Rune.RuneLen (t [i + 1]); - } } - return (size, len); + return size; } void Adjust () @@ -269,9 +273,10 @@ namespace Terminal.Gui { if (point < first) { first = point; } else if (first + point - (Frame.Width + offB) == 0 || - DisplaySize (text, first, point).size >= Frame.Width + offB) { + DisplaySize (text, first, point) >= Frame.Width + offB) { first = Math.Max (CalculateFirst (text, first, point, Frame.Width - 1 + offB), 0); } + SetNeedsDisplay (); } @@ -287,29 +292,22 @@ namespace Terminal.Gui { int CalculateFirst (List t, int start, int end, int width) { - if (t == null) { - return 0; - } - (var dSize, _) = DisplaySize (t, start, end); - if (dSize < width) { - return start; + if (start + end - width >= width) { + return end - width; } int size = 0; - int tcount = end > t.Count - 1 ? t.Count - 1 : end; + int tcount = end > width || end > t.Count - 1 ? t.Count - 1 : end; int col = 0; - for (int i = tcount; i > start; i--) { + for (int i = tcount; i > -1; i--) { var rune = t [i]; var s = Rune.ColumnWidth (rune); size += s; - if (size >= dSize - width) { - col = tcount - i + start; - if (start == 0 || col == start || (point == t.Count && (point - col > width))) { - col++; - } + if (size > width) { + col += size - width; break; } } - return col; + return col + start; } void SetText (List newText) @@ -360,7 +358,6 @@ namespace Terminal.Gui { var oldCursorPos = point; switch (ShortcutHelper.GetModifiersKey (kb)) { - case Key.Delete: case Key.DeleteChar: case Key.D | Key.CtrlMask: if (ReadOnly) @@ -378,6 +375,7 @@ namespace Terminal.Gui { } break; + case Key.Delete: case Key.Backspace: if (ReadOnly) return true; @@ -407,7 +405,7 @@ namespace Terminal.Gui { case Key.End | Key.ShiftMask: case Key.End | Key.ShiftMask | Key.CtrlMask: case Key.E | Key.ShiftMask | Key.CtrlMask: - if (point <= text.Count) { + if (point < text.Count) { int x = point; point = text.Count; PrepareSelection (x, point - x); @@ -440,21 +438,19 @@ namespace Terminal.Gui { case Key.CursorLeft | Key.ShiftMask | Key.CtrlMask: case Key.CursorUp | Key.ShiftMask | Key.CtrlMask: if (point > 0) { - int x = start > -1 && start > point ? start : point; - if (x > 0) { - int sbw = WordBackward (x); - if (sbw != -1) - point = sbw; - PrepareSelection (x, sbw - x); - } + int x = start > -1 ? start : point; + int sbw = WordBackward (point); + if (sbw != -1) + point = sbw; + PrepareSelection (x, sbw - x); } break; case Key.CursorRight | Key.ShiftMask | Key.CtrlMask: case Key.CursorDown | Key.ShiftMask | Key.CtrlMask: if (point < text.Count) { - int x = start > -1 && start > point ? start : point; - int sfw = WordForward (x); + int x = start > -1 ? start : point; + int sfw = WordForward (point); if (sfw != -1) point = sfw; PrepareSelection (x, sfw - x); @@ -820,15 +816,12 @@ namespace Terminal.Gui { if (SelectedStart > -1) { SelectedLength = x + direction <= text.Count ? x + direction - SelectedStart : text.Count - SelectedStart; SetSelectedStartSelectedLength (); - if (start > -1) { - SelectedText = length > 0 ? ustring.Make (text).ToString ().Substring ( - start < 0 ? 0 : start, length > text.Count ? text.Count : length) : ""; - if (first > start) { - first = start; - } - } else { - ClearAllSelection (); + SelectedText = length > 0 ? ustring.Make (text).ToString ().Substring ( + start < 0 ? 0 : start, length > text.Count ? text.Count : length) : ""; + if (first > start) { + first = start; } + point = start + length; } Adjust (); } @@ -844,7 +837,6 @@ namespace Terminal.Gui { SelectedLength = 0; SelectedText = ""; start = 0; - length = 0; SetNeedsDisplay (); } @@ -888,14 +880,11 @@ namespace Terminal.Gui { ustring actualText = Text; int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart; int selLength = Math.Abs (SelectedLength); - (var _, var len) = DisplaySize (text, 0, selStart, false); - (var _, var len2) = DisplaySize (text, selStart, selStart + selLength, false); - (var _, var len3) = DisplaySize (text, selStart + selLength, actualText.RuneCount, false); - Text = actualText[0, len] + - actualText[len + len2, len + len2 + len3]; + Text = actualText[0, selStart] + + actualText[selStart + selLength, actualText.RuneCount - selLength]; ClearAllSelection (); - point = selStart >= Text.RuneCount ? Text.RuneCount : selStart; - Adjust (); + CursorPosition = selStart >= Text.RuneCount ? Text.RuneCount : selStart; + SetNeedsDisplay (); } /// @@ -903,23 +892,54 @@ namespace Terminal.Gui { /// public virtual void Paste () { - if (ReadOnly) { + if (ReadOnly) return; - } - SetSelectedStartSelectedLength (); - int selStart = start == -1 ? CursorPosition : start; ustring actualText = Text; - (int _, int len) = DisplaySize (text, 0, selStart, false); - (var _, var len2) = DisplaySize (text, selStart, selStart + length, false); - (var _, var len3) = DisplaySize (text, selStart + length, actualText.RuneCount, false); + int start = SelectedStart == -1 ? CursorPosition : SelectedStart; ustring cbTxt = Clipboard.Contents ?? ""; - Text = actualText [0, len] + + Text = actualText[0, start] + cbTxt + - actualText [len + len2, len + len2 + len3]; - point = selStart + cbTxt.RuneCount; + actualText[start + SelectedLength, actualText.RuneCount - SelectedLength]; + point = start + cbTxt.RuneCount; + SelectedLength = 0; ClearAllSelection (); SetNeedsDisplay (); } + + /// + /// Virtual method that invoke the event if it's defined. + /// + /// The new text to be replaced. + /// Returns the + public virtual TextChangingEventArgs OnTextChanging (ustring newText) + { + var ev = new TextChangingEventArgs (newText); + TextChanging?.Invoke (ev); + return ev; + } + } + + /// + /// An which allows passing a cancelable new text value event. + /// + public class TextChangingEventArgs : EventArgs { + /// + /// The new text to be replaced. + /// + public ustring NewText { get; set; } + /// + /// Flag which allows to cancel the new text value. + /// + public bool Cancel { get; set; } + + /// + /// Initializes a new instance of + /// + /// The new to be replaced. + public TextChangingEventArgs (ustring newText) + { + NewText = newText; + } } }