Kaynağa Gözat

Fixes #998. Added a cancelable TextChanging event to prevent the TextChanged event being called if the changing is canceled.

BDisp 4 yıl önce
ebeveyn
işleme
945a456ec0
1 değiştirilmiş dosya ile 92 ekleme ve 72 silme
  1. 92 72
      Terminal.Gui/Views/TextField.cs

+ 92 - 72
Terminal.Gui/Views/TextField.cs

@@ -31,11 +31,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public bool ReadOnly { get; set; } = false;
 
+		/// <summary>
+		/// Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.
+		/// </summary>
+		public event Action<TextChangingEventArgs> TextChanging;
+
 		/// <summary>
 		///   Changed event, raised when the text has changed.
 		/// </summary>
 		/// <remarks>
-		///   This event is raised when the <see cref="Text"/> changes. 
+		///   This event is raised when the <see cref="Text"/> changes.
 		/// </remarks>
 		/// <remarks>
 		///   The passed <see cref="EventArgs"/> is a <see cref="ustring"/> 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<ustring> () { 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<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
+		// Returns the size in a range of the string.
+		int DisplaySize (List<Rune> 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<Rune> 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<Rune> 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 ();
 		}
 
 		/// <summary>
@@ -903,23 +892,54 @@ namespace Terminal.Gui {
 		/// </summary>
 		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 ();
 		}
+
+		/// <summary>
+		/// Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.
+		/// </summary>
+		/// <param name="newText">The new text to be replaced.</param>
+		/// <returns>Returns the <see cref="TextChangingEventArgs"/></returns>
+		public virtual TextChangingEventArgs OnTextChanging (ustring newText)
+		{
+			var ev = new TextChangingEventArgs (newText);
+			TextChanging?.Invoke (ev);
+			return ev;
+		}
+	}
+
+	/// <summary>
+	/// An <see cref="EventArgs"/> which allows passing a cancelable new text value event.
+	/// </summary>
+	public class TextChangingEventArgs : EventArgs {
+		/// <summary>
+		/// The new text to be replaced.
+		/// </summary>
+		public ustring NewText { get; set; }
+		/// <summary>
+		/// Flag which allows to cancel the new text value.
+		/// </summary>
+		public bool Cancel { get; set; }
+
+		/// <summary>
+		/// Initializes a new instance of <see cref="TextChangingEventArgs"/>
+		/// </summary>
+		/// <param name="newText">The new <see cref="TextField.Text"/> to be replaced.</param>
+		public TextChangingEventArgs (ustring newText)
+		{
+			NewText = newText;
+		}
 	}
 }