浏览代码

Work on TextView

Miguel de Icaza 7 年之前
父节点
当前提交
c940f0b518
共有 5 个文件被更改,包括 482 次插入2 次删除
  1. 2 1
      Terminal.Gui/Core.cs
  2. 11 0
      Terminal.Gui/Driver.cs
  3. 5 0
      Terminal.Gui/Event.cs
  4. 459 0
      Terminal.Gui/Views/TextView.cs
  5. 5 1
      demo.cs

+ 2 - 1
Terminal.Gui/Core.cs

@@ -1536,7 +1536,8 @@ namespace Terminal.Gui {
 						DrawBounds (state.Toplevel);
 						DrawBounds (state.Toplevel);
 					state.Toplevel.PositionCursor ();
 					state.Toplevel.PositionCursor ();
 					Driver.Refresh ();
 					Driver.Refresh ();
-				}
+				} else
+					Driver.UpdateCursor ();
 			}
 			}
 		}
 		}
 
 

+ 11 - 0
Terminal.Gui/Driver.cs

@@ -264,6 +264,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public abstract void Refresh ();
 		public abstract void Refresh ();
 
 
+		/// <summary>
+		/// Updates the location of the cursor position
+		/// </summary>
+		public abstract void UpdateCursor ();
+
 		/// <summary>
 		/// <summary>
 		/// Ends the execution of the console driver.
 		/// Ends the execution of the console driver.
 		/// </summary>
 		/// </summary>
@@ -495,6 +500,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public override void Refresh () => Curses.refresh ();
 		public override void Refresh () => Curses.refresh ();
+		public override void UpdateCursor () => Curses.refresh ();
 		public override void End () => Curses.endwin ();
 		public override void End () => Curses.endwin ();
 		public override void UpdateScreen () => window.redrawwin ();
 		public override void UpdateScreen () => window.redrawwin ();
 		public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
 		public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
@@ -1017,6 +1023,11 @@ namespace Terminal.Gui {
 			Console.CursorLeft = savedCol;
 			Console.CursorLeft = savedCol;
 		}
 		}
 
 
+		public override void UpdateCursor ()
+		{
+			//
+		}
+
 		public override void StartReportingMouseMoves()
 		public override void StartReportingMouseMoves()
 		{
 		{
 		}
 		}

+ 5 - 0
Terminal.Gui/Event.cs

@@ -25,6 +25,11 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </para>
 	/// </remarks>
 	/// </remarks>
 	public enum Key : uint {
 	public enum Key : uint {
+		/// <summary>
+		/// Mask that indictes that this is a character value, values outside this range
+		/// indicate special characters like Alt-key combinations or special keys on the
+		/// keyboard like function keys, arrows keys and so on.
+		/// </summary>
 		CharMask = 0xfffff,
 		CharMask = 0xfffff,
 
 
 		/// <summary>
 		/// <summary>

+ 459 - 0
Terminal.Gui/Views/TextView.cs

@@ -0,0 +1,459 @@
+//
+// TextView.cs: multi-line text editing
+//
+// Authors:
+//   Miguel de Icaza ([email protected])
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using NStack;
+
+namespace Terminal.Gui {
+	class TextModel {
+		List<List<Rune>> lines;
+		List<int> lineLength;
+
+		public bool LoadFile (string file)
+		{
+			if (file == null)
+				throw new ArgumentNullException (nameof (file));
+			try {
+				var stream = File.OpenRead (file);
+				if (stream == null)
+					return false;
+			} catch {
+				return false;
+			}
+			LoadStream (File.OpenRead (file));
+			return true;
+		}
+
+		List<Rune> ToRunes (ustring str)
+		{
+			List<Rune> runes = new List<Rune> ();
+			foreach (var x in str.ToRunes ()) {
+				runes.Add (x);
+			}
+			return runes;
+		}
+
+		void Append (List<byte> line)
+		{
+			var str = ustring.Make (line.ToArray ());
+			lines.Add (ToRunes (str));
+		}
+
+		public void LoadStream (Stream input)
+		{
+			if (input == null)
+				throw new ArgumentNullException (nameof (input));
+
+			lines = new List<List<Rune>> ();
+			var buff = new BufferedStream (input);
+			int v;
+			var line = new List<byte> ();
+			while ((v = buff.ReadByte ()) != -1) {
+				if (v == 10) {
+					Append (line);
+					line.Clear ();
+					continue;
+				}
+				line.Add ((byte)v);
+			}
+			if (line.Count > 0)
+				Append (line);
+		}
+
+		public void LoadString (ustring content)
+		{
+			lines = new List<List<Rune>> ();
+			int start = 0, i = 0;
+			for (; i < content.Length; i++) {
+				if (content [i] == 10) {
+					if (i - start > 0)
+						lines.Add (ToRunes (content [start, i]));
+					else
+						lines.Add (ToRunes (ustring.Empty));
+					start = i + 1;
+				}
+			}
+			if (i - start > 0)
+				lines.Add (ToRunes (content [start, null]));
+		}
+
+		public override string ToString ()
+		{
+			var sb = new StringBuilder ();
+			foreach (var line in lines) {
+				sb.Append (line);
+				sb.AppendLine ();
+			}
+			return sb.ToString ();
+		}
+
+		public int Count => lines.Count;
+
+		public List<Rune> GetLine (int line) => lines [line];
+	}
+
+	/// <summary>
+	///   Text data entry widget
+	/// </summary>
+	/// <remarks>
+	///   The Entry widget provides Emacs-like editing
+	///   functionality,  and mouse support.
+	/// </remarks>
+	public class TextView : View {
+		TextModel model = new TextModel ();
+		int topRow;
+		int leftColumn;
+		int currentRow;
+		int currentColumn;
+		bool used;
+
+		/// <summary>
+		///   Changed event, raised when the text has clicked.
+		/// </summary>
+		/// <remarks>
+		///   Client code can hook up to this event, it is
+		///   raised when the text in the entry changes.
+		/// </remarks>
+		public event EventHandler Changed;
+
+		/// <summary>
+		///   Public constructor.
+		/// </summary>
+		/// <remarks>
+		/// </remarks>
+		public TextView (Rect frame) : base (frame)
+		{
+			CanFocus = true;
+		}
+
+		void ResetPosition ()
+		{
+			topRow = leftColumn = currentRow = currentColumn = 0;
+		}
+
+		/// <summary>
+		///   Sets or gets the text in the entry.
+		/// </summary>
+		/// <remarks>
+		/// </remarks>
+		public ustring Text {
+			get {
+				return model.ToString ();
+			}
+
+			set {
+				ResetPosition ();
+				model.LoadString (value);
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		///    The current cursor row.
+		/// </summary>
+		public int CurrentRow => currentRow;
+
+		/// <summary>
+		/// Gets the cursor column.
+		/// </summary>
+		/// <value>The cursor column.</value>
+		public int CurrentColumn => currentColumn;
+
+		/// <summary>
+		///   Sets the cursor position.
+		/// </summary>
+		public override void PositionCursor ()
+		{
+			Move (CurrentColumn - leftColumn, CurrentRow - topRow);
+		}
+
+		void ClearRegion (int left, int top, int right, int bottom)
+		{
+			for (int row = top; row < bottom; row++) {
+				Move (left, row);
+				for (int col = left; col < right; col++)
+					AddRune (col, row, ' ');
+			}
+		}
+
+		public override void Redraw (Rect region)
+		{
+			Driver.SetAttribute (ColorScheme.Focus);
+			Move (0, 0);
+
+			int bottom = region.Bottom;
+			int right = region.Right;
+			for (int row = region.Top; row < bottom; row++) {
+				int textLine = topRow + row;
+				if (textLine >= model.Count) {
+					ClearRegion (region.Left, row, region.Right, row + 1);
+					continue;
+				}
+				var line = model.GetLine (textLine);
+				int lineRuneCount = line.Count;
+				if (line.Count < region.Left){
+					ClearRegion (region.Left, row, region.Right, row + 1);
+					continue;
+				}
+
+				Move (region.Left, row);
+				for (int col = region.Left; col < right; col++) {
+					var lineCol = leftColumn + col;
+					var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
+					AddRune (col, row, rune);
+				}
+			}
+			PositionCursor ();
+		}
+
+		public override bool CanFocus {
+			get => true;
+			set { base.CanFocus = value; }
+		}
+
+		void SetClipboard (ustring text)
+		{
+			Clipboard.Contents = text;
+		}
+
+		public void Insert (Rune rune)
+		{
+			var line = model.GetLine (currentRow);
+			line.Insert (currentColumn, rune);
+			var prow = currentRow - topRow;
+
+			SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
+		}
+
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			switch (kb.Key) {
+			case Key.ControlN:
+			case Key.CursorDown:
+				if (currentRow + 1 < model.Count) {
+					currentRow++;
+					if (currentRow >= topRow + Frame.Height) {
+						topRow++;
+						SetNeedsDisplay ();
+					}
+					PositionCursor ();
+				}
+				break;
+
+			case Key.ControlP:
+			case Key.CursorUp:
+				if (currentRow > 0) {
+					currentRow--;
+					if (currentRow < topRow) {
+						topRow--;
+						SetNeedsDisplay ();
+					}
+					PositionCursor ();
+				}
+				break;
+
+			case Key.ControlF:
+			case Key.CursorRight:
+				var currentLine = model.GetLine (currentRow);
+				if (currentColumn < currentLine.Count) {
+					currentColumn++;
+					if (currentColumn >= leftColumn + Frame.Width) {
+						leftColumn++;
+						SetNeedsDisplay ();
+					}
+					PositionCursor ();
+				} else {
+					if (currentRow + 1 < model.Count) {
+						currentRow++;
+						currentColumn = 0;
+						leftColumn = 0;
+						if (currentRow >= topRow + Frame.Height) {
+							topRow++;
+						}
+						SetNeedsDisplay ();
+						PositionCursor ();
+					}
+					break;
+				}
+				break;
+
+			case Key.ControlB:
+			case Key.CursorLeft:
+				if (currentColumn > 0) {
+					currentColumn--;
+					if (currentColumn < leftColumn) {
+						leftColumn--;
+						SetNeedsDisplay ();
+					}
+					PositionCursor ();
+				} else {
+					if (currentRow > 0) {
+						currentRow--;
+						if (currentRow < topRow) {
+							topRow--;
+
+						}
+						currentLine = model.GetLine (currentRow);
+						currentColumn = currentLine.Count;
+						int prev = leftColumn;
+						leftColumn = currentColumn - Frame.Width + 1;
+						if (leftColumn < 0)
+							leftColumn = 0;
+						if (prev != leftColumn)
+							SetNeedsDisplay ();
+						PositionCursor ();
+					}
+				}
+				break;
+
+			case Key.Delete:
+			case Key.Backspace:
+				break;
+
+			// Home, C-A
+			case Key.Home:
+			case Key.ControlA:
+				currentColumn = 0;
+				if (currentColumn < leftColumn) {
+					leftColumn = 0;
+					SetNeedsDisplay ();
+				} else
+					PositionCursor ();
+				break;
+
+			case Key.ControlD: // Delete
+				break;
+
+			case Key.ControlE: // End
+				currentLine = model.GetLine (currentRow);
+				currentColumn = currentLine.Count;
+				int pcol = leftColumn;
+				leftColumn = currentColumn - Frame.Width + 1;
+				if (leftColumn < 0)
+					leftColumn = 0;
+				if (pcol != leftColumn)
+					SetNeedsDisplay ();
+				PositionCursor ();
+				break;
+
+			case Key.ControlK: // kill-to-end
+				break;
+
+			case Key.ControlY: // Control-y, yank
+
+			case (Key)((int)'b' + Key.AltMask):
+				break;
+
+			case (Key)((int)'f' + Key.AltMask):
+				break;
+
+			default:
+				// Ignore control characters and other special keys
+				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+					return false;
+				Insert ((uint)kb.Key);
+				currentColumn++;
+				if (currentColumn >= leftColumn + Frame.Width) {
+					leftColumn++;
+					SetNeedsDisplay ();
+				}
+				PositionCursor ();
+				return true;
+			}
+			return true;
+		}
+
+#if false
+		int WordForward (int p)
+		{
+			if (p >= text.Length)
+				return -1;
+
+			int i = p;
+			if (Rune.IsPunctuation (text [p]) || Rune.IsWhiteSpace (text [p])) {
+				for (; i < text.Length; i++) {
+					var r = text [i];
+					if (Rune.IsLetterOrDigit (r))
+						break;
+				}
+				for (; i < text.Length; i++) {
+					var r = text [i];
+					if (!Rune.IsLetterOrDigit (r))
+						break;
+				}
+			} else {
+				for (; i < text.Length; i++) {
+					var r = text [i];
+					if (!Rune.IsLetterOrDigit (r))
+						break;
+				}
+			}
+			if (i != p)
+				return i;
+			return -1;
+		}
+
+		int WordBackward (int p)
+		{
+			if (p == 0)
+				return -1;
+
+			int i = p - 1;
+			if (i == 0)
+				return 0;
+
+			var ti = text [i];
+			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
+				for (; i >= 0; i--) {
+					if (Rune.IsLetterOrDigit (text [i]))
+						break;
+				}
+				for (; i >= 0; i--) {
+					if (!Rune.IsLetterOrDigit (text [i]))
+						break;
+				}
+			} else {
+				for (; i >= 0; i--) {
+					if (!Rune.IsLetterOrDigit (text [i]))
+						break;
+				}
+			}
+			i++;
+
+			if (i != p)
+				return i;
+
+			return -1;
+		}
+
+		public override bool MouseEvent (MouseEvent ev)
+		{
+			if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
+				return false;
+
+			if (!HasFocus)
+				SuperView.SetFocus (this);
+
+			// We could also set the cursor position.
+			point = first + ev.X;
+			if (point > text.Length)
+				point = text.Length;
+			if (point < first)
+				point = 0;
+
+			SetNeedsDisplay ();
+			return true;
+		}
+		#endif
+	}
+
+}
+

+ 5 - 1
demo.cs

@@ -169,7 +169,11 @@ class Demo {
 			})
 			})
 		});
 		});
 
 
-		ShowEntries (win);
+		//ShowEntries (win);
+		var text = new TextView (new Rect (1, 1, 60, 14));
+		text.Text = System.IO.File.ReadAllText ("/etc/passwd");
+		win.Add (text);
+
 		int count = 0;
 		int count = 0;
 		ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
 		ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
 		Application.RootMouseEvent += delegate (MouseEvent me) {
 		Application.RootMouseEvent += delegate (MouseEvent me) {