diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index 2d27d66a3..57bcf9fdc 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -140,18 +140,74 @@ namespace Terminal.Gui {
}
}
+ static Key alternateForwardKey = Key.PageDown | Key.CtrlMask;
+
///
/// Alternative key to navigate forwards through all views. Ctrl+Tab is always used.
///
- public static Key AlternateForwardKey { get; set; } = Key.PageDown | Key.CtrlMask;
+ public static Key AlternateForwardKey {
+ get => alternateForwardKey;
+ set {
+ if (alternateForwardKey != value) {
+ var oldKey = alternateForwardKey;
+ alternateForwardKey = value;
+ OnAlternateForwardKeyChanged (oldKey);
+ }
+ }
+ }
+
+ static void OnAlternateForwardKeyChanged (Key oldKey)
+ {
+ foreach (var top in toplevels) {
+ top.OnAlternateForwardKeyChanged (oldKey);
+ }
+ }
+
+ static Key alternateBackwardKey = Key.PageUp | Key.CtrlMask;
+
///
/// Alternative key to navigate backwards through all views. Shift+Ctrl+Tab is always used.
///
- public static Key AlternateBackwardKey { get; set; } = Key.PageUp | Key.CtrlMask;
+ public static Key AlternateBackwardKey {
+ get => alternateBackwardKey;
+ set {
+ if (alternateBackwardKey != value) {
+ var oldKey = alternateBackwardKey;
+ alternateBackwardKey = value;
+ OnAlternateBackwardKeyChanged (oldKey);
+ }
+ }
+ }
+
+ static void OnAlternateBackwardKeyChanged (Key oldKey)
+ {
+ foreach (var top in toplevels) {
+ top.OnAlternateBackwardKeyChanged (oldKey);
+ }
+ }
+
+ static Key quitKey = Key.Q | Key.CtrlMask;
+
///
/// Gets or sets the key to quit the application.
///
- public static Key QuitKey { get; set; } = Key.Q | Key.CtrlMask;
+ public static Key QuitKey {
+ get => quitKey;
+ set {
+ if (quitKey != value) {
+ var oldKey = quitKey;
+ quitKey = value;
+ OnQuitKeyChanged (oldKey);
+ }
+ }
+ }
+
+ static void OnQuitKeyChanged (Key oldKey)
+ {
+ foreach (var top in toplevels) {
+ top.OnQuitKeyChanged (oldKey);
+ }
+ }
///
/// The driver for the application
diff --git a/Terminal.Gui/Core/Autocomplete.cs b/Terminal.Gui/Core/Autocomplete.cs
deleted file mode 100644
index 3b9660649..000000000
--- a/Terminal.Gui/Core/Autocomplete.cs
+++ /dev/null
@@ -1,305 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using Rune = System.Rune;
-
-namespace Terminal.Gui {
-
- ///
- /// Renders an overlay on another view at a given point that allows selecting
- /// from a range of 'autocomplete' options.
- ///
- public class Autocomplete {
-
- ///
- /// The maximum width of the autocomplete dropdown
- ///
- public int MaxWidth { get; set; } = 10;
-
- ///
- /// The maximum number of visible rows in the autocomplete dropdown to render
- ///
- public int MaxHeight { get; set; } = 6;
-
- ///
- /// True if the autocomplete should be considered open and visible
- ///
- protected bool Visible { get; set; } = true;
-
- ///
- /// The strings that form the current list of suggestions to render
- /// based on what the user has typed so far.
- ///
- public ReadOnlyCollection Suggestions { get; protected set; } = new ReadOnlyCollection(new string[0]);
-
- ///
- /// The full set of all strings that can be suggested.
- ///
- ///
- public List AllSuggestions { get; set; } = new List();
-
- ///
- /// The currently selected index into that the user has highlighted
- ///
- public int SelectedIdx { get; set; }
-
- ///
- /// When more suggestions are available than can be rendered the user
- /// can scroll down the dropdown list. This indicates how far down they
- /// have gone
- ///
- public int ScrollOffset {get;set;}
-
- ///
- /// The colors to use to render the overlay. Accessing this property before
- /// the Application has been initialised will cause an error
- ///
- public ColorScheme ColorScheme {
- get
- {
- if(colorScheme == null)
- {
- colorScheme = Colors.Menu;
- }
- return colorScheme;
- }
- set
- {
- colorScheme = value;
- }
- }
-
- private ColorScheme colorScheme;
-
- ///
- /// The key that the user must press to accept the currently selected autocomplete suggestion
- ///
- public Key SelectionKey { get; set; } = Key.Enter;
-
- ///
- /// The key that the user can press to close the currently popped autocomplete menu
- ///
- public Key CloseKey {get;set;} = Key.Esc;
-
- ///
- /// Renders the autocomplete dialog inside the given at the
- /// given point.
- ///
- /// The view the overlay should be rendered into
- ///
- public void RenderOverlay (View view, Point renderAt)
- {
- if (!Visible || !view.HasFocus || Suggestions.Count == 0) {
- return;
- }
-
- view.Move (renderAt.X, renderAt.Y);
-
- // don't overspill vertically
- var height = Math.Min(view.Bounds.Height - renderAt.Y,MaxHeight);
-
- var toRender = Suggestions.Skip(ScrollOffset).Take(height).ToArray();
-
- if(toRender.Length == 0)
- {
- return;
- }
-
- var width = Math.Min(MaxWidth,toRender.Max(s=>s.Length));
-
- // don't overspill horizontally
- width = Math.Min(view.Bounds.Width - renderAt.X ,width);
-
- for(int i=0;i
- /// Updates to be a valid index within
- ///
- public void EnsureSelectedIdxIsValid()
- {
- SelectedIdx = Math.Max (0,Math.Min (Suggestions.Count - 1, SelectedIdx));
-
- // if user moved selection up off top of current scroll window
- if(SelectedIdx < ScrollOffset)
- {
- ScrollOffset = SelectedIdx;
- }
-
- // if user moved selection down past bottom of current scroll window
- while(SelectedIdx >= ScrollOffset + MaxHeight ){
- ScrollOffset++;
- }
- }
-
- ///
- /// Handle key events before e.g. to make key events like
- /// up/down apply to the autocomplete control instead of changing the cursor position in
- /// the underlying text view.
- ///
- ///
- ///
- ///
- public bool ProcessKey (TextView hostControl, KeyEvent kb)
- {
- if(IsWordChar((char)kb.Key))
- {
- Visible = true;
- }
-
- if(!Visible || Suggestions.Count == 0) {
- return false;
- }
-
- if (kb.Key == Key.CursorDown) {
- SelectedIdx++;
- EnsureSelectedIdxIsValid();
- hostControl.SetNeedsDisplay ();
- return true;
- }
-
- if (kb.Key == Key.CursorUp) {
- SelectedIdx--;
- EnsureSelectedIdxIsValid();
- hostControl.SetNeedsDisplay ();
- return true;
- }
-
- if(kb.Key == SelectionKey && SelectedIdx >=0 && SelectedIdx < Suggestions.Count) {
-
- var accepted = Suggestions [SelectedIdx];
-
- var typedSoFar = GetCurrentWord (hostControl) ?? "";
-
- if(typedSoFar.Length < accepted.Length) {
-
- // delete the text
- for(int i=0;i
- /// Clears
- ///
- public void ClearSuggestions ()
- {
- Suggestions = Enumerable.Empty ().ToList ().AsReadOnly ();
- }
-
-
- ///
- /// Populates with all strings in that
- /// match with the current cursor position/text in the
- ///
- /// The text view that you want suggestions for
- public void GenerateSuggestions (TextView hostControl)
- {
- // if there is nothing to pick from
- if(AllSuggestions.Count == 0) {
- ClearSuggestions ();
- return;
- }
-
- var currentWord = GetCurrentWord (hostControl);
-
- if(string.IsNullOrWhiteSpace(currentWord)) {
- ClearSuggestions ();
- }
- else {
- Suggestions = AllSuggestions.Where (o =>
- o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) &&
- !o.Equals(currentWord,StringComparison.CurrentCultureIgnoreCase)
- ).ToList ().AsReadOnly();
-
- EnsureSelectedIdxIsValid();
- }
- }
-
- private string GetCurrentWord (TextView hostControl)
- {
- var currentLine = hostControl.GetCurrentLine ();
- var cursorPosition = Math.Min (hostControl.CurrentColumn, currentLine.Count);
- return IdxToWord (currentLine, cursorPosition);
- }
-
- private string IdxToWord (List line, int idx)
- {
- StringBuilder sb = new StringBuilder ();
-
- // do not generate suggestions if the cursor is positioned in the middle of a word
- bool areMidWord;
-
- if(idx == line.Count) {
- // the cursor positioned at the very end of the line
- areMidWord = false;
- }
- else {
- // we are in the middle of a word if the cursor is over a letter/number
- areMidWord = IsWordChar (line [idx]);
- }
-
- // if we are in the middle of a word then there is no way to autocomplete that word
- if(areMidWord) {
- return null;
- }
-
- // we are at the end of a word. Work out what has been typed so far
- while(idx-- > 0) {
-
- if(IsWordChar(line [idx])) {
- sb.Insert(0,(char)line [idx]);
- }
- else {
- break;
- }
- }
- return sb.ToString ();
- }
-
- ///
- /// Return true if the given symbol should be considered part of a word
- /// and can be contained in matches. Base behaviour is to use
- ///
- ///
- ///
- public virtual bool IsWordChar (Rune rune)
- {
- return Char.IsLetterOrDigit ((char)rune);
- }
- }
-}
diff --git a/Terminal.Gui/Core/Autocomplete/Autocomplete.cs b/Terminal.Gui/Core/Autocomplete/Autocomplete.cs
new file mode 100644
index 000000000..f351b8424
--- /dev/null
+++ b/Terminal.Gui/Core/Autocomplete/Autocomplete.cs
@@ -0,0 +1,642 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using Rune = System.Rune;
+
+namespace Terminal.Gui {
+
+ ///
+ /// Renders an overlay on another view at a given point that allows selecting
+ /// from a range of 'autocomplete' options.
+ ///
+ public abstract class Autocomplete : IAutocomplete {
+
+ private class Popup : View {
+ Autocomplete autocomplete;
+
+ public Popup (Autocomplete autocomplete)
+ {
+ this.autocomplete = autocomplete;
+ CanFocus = true;
+ WantMousePositionReports = true;
+ }
+
+ public override Rect Frame {
+ get => base.Frame;
+ set {
+ base.Frame = value;
+ X = value.X;
+ Y = value.Y;
+ Width = value.Width;
+ Height = value.Height;
+ }
+ }
+
+ public override void Redraw (Rect bounds)
+ {
+ if (autocomplete.LastPopupPos == null) {
+ return;
+ }
+
+ autocomplete.RenderOverlay ((Point)autocomplete.LastPopupPos);
+ }
+
+ public override bool MouseEvent (MouseEvent mouseEvent)
+ {
+ return autocomplete.MouseEvent (mouseEvent);
+ }
+ }
+
+ private View top, popup;
+ private bool closed;
+ int toRenderLength;
+
+ private Point? LastPopupPos { get; set; }
+
+ private ColorScheme colorScheme;
+ private View hostControl;
+
+ ///
+ /// The host control to handle.
+ ///
+ public virtual View HostControl {
+ get => hostControl;
+ set {
+ hostControl = value;
+ top = hostControl.SuperView;
+ if (top != null) {
+ top.DrawContent += Top_DrawContent;
+ top.DrawContentComplete += Top_DrawContentComplete;
+ top.Removed += Top_Removed;
+ }
+ }
+ }
+
+ private void Top_Removed (View obj)
+ {
+ Visible = false;
+ ManipulatePopup ();
+ }
+
+ private void Top_DrawContentComplete (Rect obj)
+ {
+ ManipulatePopup ();
+ }
+
+ private void Top_DrawContent (Rect obj)
+ {
+ if (!closed) {
+ ReopenSuggestions ();
+ }
+ ManipulatePopup ();
+ if (Visible) {
+ top.BringSubviewToFront (popup);
+ }
+ }
+
+ private void ManipulatePopup ()
+ {
+ if (Visible && popup == null) {
+ popup = new Popup (this) {
+ Frame = Rect.Empty
+ };
+ top?.Add (popup);
+ }
+
+ if (!Visible && popup != null) {
+ top.Remove (popup);
+ popup.Dispose ();
+ popup = null;
+ }
+ }
+
+ ///
+ /// Gets or sets If the popup is displayed inside or outside the host limits.
+ ///
+ public bool PopupInsideContainer { get; set; } = true;
+
+ ///
+ /// The maximum width of the autocomplete dropdown
+ ///
+ public virtual int MaxWidth { get; set; } = 10;
+
+ ///
+ /// The maximum number of visible rows in the autocomplete dropdown to render
+ ///
+ public virtual int MaxHeight { get; set; } = 6;
+
+ ///
+ /// True if the autocomplete should be considered open and visible
+ ///
+ public virtual bool Visible { get; set; }
+
+ ///
+ /// The strings that form the current list of suggestions to render
+ /// based on what the user has typed so far.
+ ///
+ public virtual ReadOnlyCollection Suggestions { get; set; } = new ReadOnlyCollection (new string [0]);
+
+ ///
+ /// The full set of all strings that can be suggested.
+ ///
+ ///
+ public virtual List AllSuggestions { get; set; } = new List ();
+
+ ///
+ /// The currently selected index into that the user has highlighted
+ ///
+ public virtual int SelectedIdx { get; set; }
+
+ ///
+ /// When more suggestions are available than can be rendered the user
+ /// can scroll down the dropdown list. This indicates how far down they
+ /// have gone
+ ///
+ public virtual int ScrollOffset { get; set; }
+
+ ///
+ /// The colors to use to render the overlay. Accessing this property before
+ /// the Application has been initialized will cause an error
+ ///
+ public virtual ColorScheme ColorScheme {
+ get {
+ if (colorScheme == null) {
+ colorScheme = Colors.Menu;
+ }
+ return colorScheme;
+ }
+ set {
+ colorScheme = value;
+ }
+ }
+
+ ///
+ /// The key that the user must press to accept the currently selected autocomplete suggestion
+ ///
+ public virtual Key SelectionKey { get; set; } = Key.Enter;
+
+ ///
+ /// The key that the user can press to close the currently popped autocomplete menu
+ ///
+ public virtual Key CloseKey { get; set; } = Key.Esc;
+
+ ///
+ /// The key that the user can press to reopen the currently popped autocomplete menu
+ ///
+ public virtual Key Reopen { get; set; } = Key.Space | Key.CtrlMask | Key.AltMask;
+
+ ///
+ /// Renders the autocomplete dialog inside or outside the given at the
+ /// given point.
+ ///
+ ///
+ public virtual void RenderOverlay (Point renderAt)
+ {
+ if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0) {
+ LastPopupPos = null;
+ Visible = false;
+ return;
+ }
+
+ LastPopupPos = renderAt;
+
+ int height, width;
+
+ if (PopupInsideContainer) {
+ // don't overspill vertically
+ height = Math.Min (HostControl.Bounds.Height - renderAt.Y, MaxHeight);
+ // There is no space below, lets see if can popup on top
+ if (height < Suggestions.Count && HostControl.Bounds.Height - renderAt.Y >= height) {
+ // Verifies that the upper limit available is greater than the lower limit
+ if (renderAt.Y > HostControl.Bounds.Height - renderAt.Y) {
+ renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0);
+ height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
+ }
+ }
+ } else {
+ // don't overspill vertically
+ height = Math.Min (Math.Min (top.Bounds.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
+ // There is no space below, lets see if can popup on top
+ if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height) {
+ // Verifies that the upper limit available is greater than the lower limit
+ if (HostControl.Frame.Y > top.Bounds.Height - HostControl.Frame.Y) {
+ renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
+ height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
+ }
+ } else {
+ renderAt.Y = HostControl.Frame.Bottom;
+ }
+ }
+
+ if (ScrollOffset > Suggestions.Count - height) {
+ ScrollOffset = 0;
+ }
+ var toRender = Suggestions.Skip (ScrollOffset).Take (height).ToArray ();
+ toRenderLength = toRender.Length;
+
+ if (toRender.Length == 0) {
+ return;
+ }
+
+ width = Math.Min (MaxWidth, toRender.Max (s => s.Length));
+
+ if (PopupInsideContainer) {
+ // don't overspill horizontally, let's see if can be displayed on the left
+ if (width > HostControl.Bounds.Width - renderAt.X) {
+ // Verifies that the left limit available is greater than the right limit
+ if (renderAt.X > HostControl.Bounds.Width - renderAt.X) {
+ renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
+ width = Math.Min (width, LastPopupPos.Value.X);
+ } else {
+ width = Math.Min (width, HostControl.Bounds.Width - renderAt.X);
+ }
+ }
+ } else {
+ // don't overspill horizontally, let's see if can be displayed on the left
+ if (width > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) {
+ // Verifies that the left limit available is greater than the right limit
+ if (renderAt.X + HostControl.Frame.X > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) {
+ renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
+ width = Math.Min (width, LastPopupPos.Value.X);
+ } else {
+ width = Math.Min (width, top.Bounds.Width - renderAt.X);
+ }
+ }
+ }
+
+ if (PopupInsideContainer) {
+ popup.Frame = new Rect (
+ new Point (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
+ new Size (width, height));
+ } else {
+ popup.Frame = new Rect (
+ new Point (HostControl.Frame.X + renderAt.X, renderAt.Y),
+ new Size (width, height));
+ }
+
+ popup.Move (0, 0);
+
+ for (int i = 0; i < toRender.Length; i++) {
+
+ if (i == SelectedIdx - ScrollOffset) {
+ Application.Driver.SetAttribute (ColorScheme.Focus);
+ } else {
+ Application.Driver.SetAttribute (ColorScheme.Normal);
+ }
+
+ popup.Move (0, i);
+
+ var text = TextFormatter.ClipOrPad (toRender [i], width);
+
+ Application.Driver.AddStr (text);
+ }
+ }
+
+ ///
+ /// Updates to be a valid index within
+ ///
+ public virtual void EnsureSelectedIdxIsValid ()
+ {
+ SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx));
+
+ // if user moved selection up off top of current scroll window
+ if (SelectedIdx < ScrollOffset) {
+ ScrollOffset = SelectedIdx;
+ }
+
+ // if user moved selection down past bottom of current scroll window
+ while (toRenderLength > 0 && SelectedIdx >= ScrollOffset + toRenderLength) {
+ ScrollOffset++;
+ }
+ }
+
+ ///
+ /// Handle key events before e.g. to make key events like
+ /// up/down apply to the autocomplete control instead of changing the cursor position in
+ /// the underlying text view.
+ ///
+ /// The key event.
+ /// trueif the key can be handled falseotherwise.
+ public virtual bool ProcessKey (KeyEvent kb)
+ {
+ if (IsWordChar ((char)kb.Key)) {
+ Visible = true;
+ closed = false;
+ }
+
+ if (kb.Key == Reopen) {
+ return ReopenSuggestions ();
+ }
+
+ if (closed || Suggestions.Count == 0) {
+ Visible = false;
+ return false;
+ }
+
+ if (kb.Key == Key.CursorDown) {
+ MoveDown ();
+ return true;
+ }
+
+ if (kb.Key == Key.CursorUp) {
+ MoveUp ();
+ return true;
+ }
+
+ if (kb.Key == SelectionKey) {
+ return Select ();
+ }
+
+ if (kb.Key == CloseKey) {
+ Close ();
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Handle mouse events before e.g. to make mouse events like
+ /// report/click apply to the autocomplete control instead of changing the cursor position in
+ /// the underlying text view.
+ ///
+ /// The mouse event.
+ /// If was called from the popup or from the host.
+ /// trueif the mouse can be handled falseotherwise.
+ public virtual bool MouseEvent (MouseEvent me, bool fromHost = false)
+ {
+ if (fromHost) {
+ GenerateSuggestions ();
+ if (Visible && Suggestions.Count == 0) {
+ Visible = false;
+ HostControl?.SetNeedsDisplay ();
+ return true;
+ } else if (!Visible && Suggestions.Count > 0) {
+ Visible = true;
+ HostControl?.SetNeedsDisplay ();
+ Application.UngrabMouse ();
+ return false;
+ } else {
+ // not in the popup
+ if (Visible && HostControl != null) {
+ Visible = false;
+ closed = false;
+ }
+ HostControl?.SetNeedsDisplay ();
+ }
+ return false;
+ }
+
+ if (popup == null || Suggestions.Count == 0) {
+ ManipulatePopup ();
+ return false;
+ }
+
+ if (me.Flags == MouseFlags.ReportMousePosition) {
+ RenderSelectedIdxByMouse (me);
+ return true;
+ }
+
+ if (me.Flags == MouseFlags.Button1Clicked) {
+ SelectedIdx = me.Y - ScrollOffset;
+ return Select ();
+ }
+
+ if (me.Flags == MouseFlags.WheeledDown) {
+ MoveDown ();
+ return true;
+ }
+
+ if (me.Flags == MouseFlags.WheeledUp) {
+ MoveUp ();
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Render the current selection in the Autocomplete context menu by the mouse reporting.
+ ///
+ ///
+ protected void RenderSelectedIdxByMouse (MouseEvent me)
+ {
+ if (SelectedIdx != me.Y - ScrollOffset) {
+ SelectedIdx = me.Y - ScrollOffset;
+ if (LastPopupPos != null) {
+ RenderOverlay ((Point)LastPopupPos);
+ }
+ }
+ }
+
+ ///
+ /// Clears
+ ///
+ public virtual void ClearSuggestions ()
+ {
+ Suggestions = Enumerable.Empty ().ToList ().AsReadOnly ();
+ }
+
+
+ ///
+ /// Populates with all strings in that
+ /// match with the current cursor position/text in the
+ ///
+ public virtual void GenerateSuggestions ()
+ {
+ // if there is nothing to pick from
+ if (AllSuggestions.Count == 0) {
+ ClearSuggestions ();
+ return;
+ }
+
+ var currentWord = GetCurrentWord ();
+
+ if (string.IsNullOrWhiteSpace (currentWord)) {
+ ClearSuggestions ();
+ } else {
+ Suggestions = AllSuggestions.Where (o =>
+ o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) &&
+ !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase)
+ ).ToList ().AsReadOnly ();
+
+ EnsureSelectedIdxIsValid ();
+ }
+ }
+
+
+ ///
+ /// Return true if the given symbol should be considered part of a word
+ /// and can be contained in matches. Base behavior is to use
+ ///
+ ///
+ ///
+ public virtual bool IsWordChar (Rune rune)
+ {
+ return Char.IsLetterOrDigit ((char)rune);
+ }
+
+ ///
+ /// Completes the autocomplete selection process. Called when user hits the .
+ ///
+ ///
+ protected bool Select ()
+ {
+ if (SelectedIdx >= 0 && SelectedIdx < Suggestions.Count) {
+ var accepted = Suggestions [SelectedIdx];
+
+ return InsertSelection (accepted);
+
+ }
+
+ return false;
+ }
+
+ ///
+ /// Called when the user confirms a selection at the current cursor location in
+ /// the . The string
+ /// is the full autocomplete word to be inserted. Typically a host will have to
+ /// remove some characters such that the string
+ /// completes the word instead of simply being appended.
+ ///
+ ///
+ /// True if the insertion was possible otherwise false
+ protected virtual bool InsertSelection (string accepted)
+ {
+ var typedSoFar = GetCurrentWord () ?? "";
+
+ if (typedSoFar.Length < accepted.Length) {
+
+ // delete the text
+ for (int i = 0; i < typedSoFar.Length; i++) {
+ DeleteTextBackwards ();
+ }
+
+ InsertText (accepted);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns the currently selected word from the .
+ ///
+ /// When overriding this method views can make use of
+ ///
+ ///
+ ///
+ protected abstract string GetCurrentWord ();
+
+ ///
+ ///
+ /// Given a of characters, returns the word which ends at
+ /// or null. Also returns null if the is positioned in the middle of a word.
+ ///
+ ///
+ /// Use this method to determine whether autocomplete should be shown when the cursor is at
+ /// a given point in a line and to get the word from which suggestions should be generated.
+ ///
+ ///
+ ///
+ ///
+ protected virtual string IdxToWord (List line, int idx)
+ {
+ StringBuilder sb = new StringBuilder ();
+
+ // do not generate suggestions if the cursor is positioned in the middle of a word
+ bool areMidWord;
+
+ if (idx == line.Count) {
+ // the cursor positioned at the very end of the line
+ areMidWord = false;
+ } else {
+ // we are in the middle of a word if the cursor is over a letter/number
+ areMidWord = IsWordChar (line [idx]);
+ }
+
+ // if we are in the middle of a word then there is no way to autocomplete that word
+ if (areMidWord) {
+ return null;
+ }
+
+ // we are at the end of a word. Work out what has been typed so far
+ while (idx-- > 0) {
+
+ if (IsWordChar (line [idx])) {
+ sb.Insert (0, (char)line [idx]);
+ } else {
+ break;
+ }
+ }
+ return sb.ToString ();
+ }
+
+ ///
+ /// Deletes the text backwards before insert the selected text in the .
+ ///
+ protected abstract void DeleteTextBackwards ();
+
+ ///
+ /// Inser the selected text in the .
+ ///
+ ///
+ protected abstract void InsertText (string accepted);
+
+ ///
+ /// Closes the Autocomplete context menu if it is showing and
+ ///
+ protected void Close ()
+ {
+ ClearSuggestions ();
+ Visible = false;
+ closed = true;
+ HostControl?.SetNeedsDisplay ();
+ ManipulatePopup ();
+ }
+
+ ///
+ /// Moves the selection in the Autocomplete context menu up one
+ ///
+ protected void MoveUp ()
+ {
+ SelectedIdx--;
+ if (SelectedIdx < 0) {
+ SelectedIdx = Suggestions.Count - 1;
+ }
+ EnsureSelectedIdxIsValid ();
+ HostControl?.SetNeedsDisplay ();
+ }
+
+ ///
+ /// Moves the selection in the Autocomplete context menu down one
+ ///
+ protected void MoveDown ()
+ {
+ SelectedIdx++;
+ if (SelectedIdx > Suggestions.Count - 1) {
+ SelectedIdx = 0;
+ }
+ EnsureSelectedIdxIsValid ();
+ HostControl?.SetNeedsDisplay ();
+ }
+
+ ///
+ /// Reopen the popup after it has been closed.
+ ///
+ ///
+ protected bool ReopenSuggestions ()
+ {
+ GenerateSuggestions ();
+ if (Suggestions.Count > 0) {
+ Visible = true;
+ closed = false;
+ HostControl?.SetNeedsDisplay ();
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs b/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs
new file mode 100644
index 000000000..32e7046e7
--- /dev/null
+++ b/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Rune = System.Rune;
+
+namespace Terminal.Gui {
+
+ ///
+ /// Renders an overlay on another view at a given point that allows selecting
+ /// from a range of 'autocomplete' options.
+ ///
+ public interface IAutocomplete {
+
+ ///
+ /// The host control that will use autocomplete.
+ ///
+ View HostControl { get; set; }
+
+ ///
+ /// Gets or sets where the popup will be displayed.
+ ///
+ bool PopupInsideContainer { get; set; }
+
+ ///
+ /// The maximum width of the autocomplete dropdown
+ ///
+ int MaxWidth { get; set; }
+
+ ///
+ /// The maximum number of visible rows in the autocomplete dropdown to render
+ ///
+ int MaxHeight { get; set; }
+
+ ///
+ /// True if the autocomplete should be considered open and visible
+ ///
+ bool Visible { get; set; }
+
+ ///
+ /// The strings that form the current list of suggestions to render
+ /// based on what the user has typed so far.
+ ///
+ ReadOnlyCollection Suggestions { get; set; }
+
+ ///
+ /// The full set of all strings that can be suggested.
+ ///
+ List AllSuggestions { get; set; }
+
+ ///
+ /// The currently selected index into that the user has highlighted
+ ///
+ int SelectedIdx { get; set; }
+
+ ///
+ /// The colors to use to render the overlay. Accessing this property before
+ /// the Application has been initialized will cause an error
+ ///
+ ColorScheme ColorScheme { get; set; }
+
+ ///
+ /// The key that the user must press to accept the currently selected autocomplete suggestion
+ ///
+ Key SelectionKey { get; set; }
+
+ ///
+ /// The key that the user can press to close the currently popped autocomplete menu
+ ///
+ Key CloseKey { get; set; }
+
+ ///
+ /// The key that the user can press to reopen the currently popped autocomplete menu
+ ///
+ Key Reopen { get; set; }
+
+ ///
+ /// Renders the autocomplete dialog inside the given at the
+ /// given point.
+ ///
+ ///
+ void RenderOverlay (Point renderAt);
+
+
+ ///
+ /// Handle key events before e.g. to make key events like
+ /// up/down apply to the autocomplete control instead of changing the cursor position in
+ /// the underlying text view.
+ ///
+ /// The key event.
+ /// trueif the key can be handled falseotherwise.
+ bool ProcessKey (KeyEvent kb);
+
+ ///
+ /// Handle mouse events before e.g. to make mouse events like
+ /// report/click apply to the autocomplete control instead of changing the cursor position in
+ /// the underlying text view.
+ ///
+ /// The mouse event.
+ /// If was called from the popup or from the host.
+ /// trueif the mouse can be handled falseotherwise.
+ bool MouseEvent (MouseEvent me, bool fromHost = false);
+
+ ///
+ /// Clears
+ ///
+ void ClearSuggestions ();
+
+ ///
+ /// Populates with all strings in that
+ /// match with the current cursor position/text in the .
+ ///
+ void GenerateSuggestions ();
+ }
+}
diff --git a/Terminal.Gui/Core/Command.cs b/Terminal.Gui/Core/Command.cs
new file mode 100644
index 000000000..54d6d65a4
--- /dev/null
+++ b/Terminal.Gui/Core/Command.cs
@@ -0,0 +1,388 @@
+// These classes use a keybinding system based on the design implemented in Scintilla.Net which is an MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
+
+using System;
+
+namespace Terminal.Gui {
+
+ ///
+ /// Actions which can be performed by the application or bound to keys in a control.
+ ///
+ public enum Command {
+
+ ///
+ /// Moves the caret down one line.
+ ///
+ LineDown,
+
+ ///
+ /// Extends the selection down one line.
+ ///
+ LineDownExtend,
+
+ ///
+ /// Moves the caret down to the last child node of the branch that holds the current selection
+ ///
+ LineDownToLastBranch,
+
+ ///
+ /// Scrolls down one line (without changing the selection).
+ ///
+ ScrollDown,
+
+ // --------------------------------------------------------------------
+
+ ///
+ /// Moves the caret up one line.
+ ///
+ LineUp,
+
+ ///
+ /// Extends the selection up one line.
+ ///
+ LineUpExtend,
+
+ ///
+ /// Moves the caret up to the first child node of the branch that holds the current selection
+ ///
+ LineUpToFirstBranch,
+
+ ///
+ /// Scrolls up one line (without changing the selection).
+ ///
+ ScrollUp,
+
+ ///
+ /// Moves the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
+ ///
+ Left,
+
+ ///
+ /// Scrolls one character to the left
+ ///
+ ScrollLeft,
+
+ ///
+ /// Extends the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
+ ///
+ LeftExtend,
+
+ ///
+ /// Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
+ ///
+ Right,
+
+ ///
+ /// Scrolls one character to the right.
+ ///
+ ScrollRight,
+
+ ///
+ /// Extends the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
+ ///
+ RightExtend,
+
+ ///
+ /// Moves the caret to the start of the previous word.
+ ///
+ WordLeft,
+
+ ///
+ /// Extends the selection to the start of the previous word.
+ ///
+ WordLeftExtend,
+
+ ///
+ /// Moves the caret to the start of the next word.
+ ///
+ WordRight,
+
+ ///
+ /// Extends the selection to the start of the next word.
+ ///
+ WordRightExtend,
+
+ ///
+ /// Deletes and copies to the clipboard the characters from the current position to the end of the line.
+ ///
+ CutToEndLine,
+
+ ///
+ /// Deletes and copies to the clipboard the characters from the current position to the start of the line.
+ ///
+ CutToStartLine,
+
+ ///
+ /// Deletes the characters forwards.
+ ///
+ KillWordForwards,
+
+ ///
+ /// Deletes the characters backwards.
+ ///
+ KillWordBackwards,
+
+ ///
+ /// Toggles overwrite mode such that newly typed text overwrites the text that is
+ /// already there (typically associated with the Insert key).
+ ///
+ ToggleOverwrite,
+
+
+ ///
+ /// Enables overwrite mode such that newly typed text overwrites the text that is
+ /// already there (typically associated with the Insert key).
+ ///
+ EnableOverwrite,
+
+ ///
+ /// Disables overwrite mode ()
+ ///
+ DisableOverwrite,
+
+ ///
+ /// Move the page down.
+ ///
+ PageDown,
+
+ ///
+ /// Move the page down increase selection area to cover revealed objects/characters.
+ ///
+ PageDownExtend,
+
+ ///
+ /// Move the page up.
+ ///
+ PageUp,
+
+ ///
+ /// Move the page up increase selection area to cover revealed objects/characters.
+ ///
+ PageUpExtend,
+
+ ///
+ /// Moves to top begin.
+ ///
+ TopHome,
+
+ ///
+ /// Extends the selection to the top begin.
+ ///
+ TopHomeExtend,
+
+ ///
+ /// Moves to bottom end.
+ ///
+ BottomEnd,
+
+ ///
+ /// Extends the selection to the bottom end.
+ ///
+ BottomEndExtend,
+
+ ///
+ /// Open selected item.
+ ///
+ OpenSelectedItem,
+
+ ///
+ /// Toggle the checked state.
+ ///
+ ToggleChecked,
+
+ ///
+ /// Accepts the current state (e.g. selection, button press etc)
+ ///
+ Accept,
+
+ ///
+ /// Toggles the Expanded or collapsed state of a a list or item (with subitems)
+ ///
+ ToggleExpandCollapse,
+
+ ///
+ /// Expands a list or item (with subitems)
+ ///
+ Expand,
+
+ ///
+ /// Recursively Expands all child items and their child items (if any)
+ ///
+ ExpandAll,
+
+ ///
+ /// Collapses a list or item (with subitems)
+ ///
+ Collapse,
+
+ ///
+ /// Recursively collapses a list items of their children (if any)
+ ///
+ CollapseAll,
+
+ ///
+ /// Cancels any current temporary states on the control e.g. expanding
+ /// a combo list
+ ///
+ Cancel,
+
+ ///
+ /// Unix emulation
+ ///
+ UnixEmulation,
+
+ ///
+ /// Deletes the character on the right.
+ ///
+ DeleteCharRight,
+
+ ///
+ /// Deletes the character on the left.
+ ///
+ DeleteCharLeft,
+
+ ///
+ /// Selects all objects in the control
+ ///
+ SelectAll,
+
+ ///
+ /// Moves the cursor to the start of line.
+ ///
+ StartOfLine,
+
+ ///
+ /// Extends the selection to the start of line.
+ ///
+ StartOfLineExtend,
+
+ ///
+ /// Moves the cursor to the end of line.
+ ///
+ EndOfLine,
+
+ ///
+ /// Extends the selection to the end of line.
+ ///
+ EndOfLineExtend,
+
+ ///
+ /// Moves the cursor to the top of page.
+ ///
+ StartOfPage,
+
+ ///
+ /// Moves the cursor to the bottom of page.
+ ///
+ EndOfPage,
+
+ ///
+ /// Moves to the left page.
+ ///
+ PageLeft,
+
+ ///
+ /// Moves to the right page.
+ ///
+ PageRight,
+
+ ///
+ /// Moves to the left begin.
+ ///
+ LeftHome,
+
+ ///
+ /// Extends the selection to the left begin.
+ ///
+ LeftHomeExtend,
+
+ ///
+ /// Moves to the right end.
+ ///
+ RightEnd,
+
+ ///
+ /// Extends the selection to the right end.
+ ///
+ RightEndExtend,
+
+ ///
+ /// Undo changes.
+ ///
+ Undo,
+
+ ///
+ /// Redo changes.
+ ///
+ Redo,
+
+ ///
+ /// Copies the current selection.
+ ///
+ Copy,
+
+ ///
+ /// Cuts the current selection.
+ ///
+ Cut,
+
+ ///
+ /// Pastes the current selection.
+ ///
+ Paste,
+
+ ///
+ /// Quit a toplevel.
+ ///
+ QuitToplevel,
+
+ ///
+ /// Suspend a application (used on Linux).
+ ///
+ Suspend,
+
+ ///
+ /// Moves focus to the next view.
+ ///
+ NextView,
+
+ ///
+ /// Moves focuss to the previous view.
+ ///
+ PreviousView,
+
+ ///
+ /// Moves focus to the next view or toplevel (case of Mdi).
+ ///
+ NextViewOrTop,
+
+ ///
+ /// Moves focus to the next previous or toplevel (case of Mdi).
+ ///
+ PreviousViewOrTop,
+
+ ///
+ /// Refresh the application.
+ ///
+ Refresh,
+
+ ///
+ /// Toggles the extended selection.
+ ///
+ ToggleExtend,
+
+ ///
+ /// Inserts a new line.
+ ///
+ NewLine,
+
+ ///
+ /// Inserts a tab.
+ ///
+ Tab,
+
+ ///
+ /// Inserts a shift tab.
+ ///
+ BackTab
+ }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Core/Event.cs b/Terminal.Gui/Core/Event.cs
index c85b7761a..181724b1c 100644
--- a/Terminal.Gui/Core/Event.cs
+++ b/Terminal.Gui/Core/Event.cs
@@ -237,7 +237,110 @@ namespace Terminal.Gui {
/// The key code for the user pressing Shift-Z
///
Z,
-
+ ///
+ /// The key code for the user pressing A
+ ///
+ a = 97,
+ ///
+ /// The key code for the user pressing B
+ ///
+ b,
+ ///
+ /// The key code for the user pressing C
+ ///
+ c,
+ ///
+ /// The key code for the user pressing D
+ ///
+ d,
+ ///
+ /// The key code for the user pressing E
+ ///
+ e,
+ ///
+ /// The key code for the user pressing F
+ ///
+ f,
+ ///
+ /// The key code for the user pressing G
+ ///
+ g,
+ ///
+ /// The key code for the user pressing H
+ ///
+ h,
+ ///
+ /// The key code for the user pressing I
+ ///
+ i,
+ ///
+ /// The key code for the user pressing J
+ ///
+ j,
+ ///
+ /// The key code for the user pressing K
+ ///
+ k,
+ ///
+ /// The key code for the user pressing L
+ ///
+ l,
+ ///
+ /// The key code for the user pressing M
+ ///
+ m,
+ ///
+ /// The key code for the user pressing N
+ ///
+ n,
+ ///
+ /// The key code for the user pressing O
+ ///
+ o,
+ ///
+ /// The key code for the user pressing P
+ ///
+ p,
+ ///
+ /// The key code for the user pressing Q
+ ///
+ q,
+ ///
+ /// The key code for the user pressing R
+ ///
+ r,
+ ///
+ /// The key code for the user pressing S
+ ///
+ s,
+ ///
+ /// The key code for the user pressing T
+ ///
+ t,
+ ///
+ /// The key code for the user pressing U
+ ///
+ u,
+ ///
+ /// The key code for the user pressing V
+ ///
+ v,
+ ///
+ /// The key code for the user pressing W
+ ///
+ w,
+ ///
+ /// The key code for the user pressing X
+ ///
+ x,
+ ///
+ /// The key code for the user pressing Y
+ ///
+ y,
+ ///
+ /// The key code for the user pressing Z
+ ///
+ z,
///
/// The key code for the user pressing the delete key.
///
diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs
index c6f5f475a..66da40daf 100644
--- a/Terminal.Gui/Core/TextFormatter.cs
+++ b/Terminal.Gui/Core/TextFormatter.cs
@@ -123,6 +123,11 @@ namespace Terminal.Gui {
Key hotKey;
Size size;
+ ///
+ /// Event invoked when the is changed.
+ ///
+ public event Action HotKeyChanged;
+
///
/// The text to be displayed. This text is never modified.
///
@@ -270,7 +275,16 @@ namespace Terminal.Gui {
///
/// Gets the hotkey. Will be an upper case letter or digit.
///
- public Key HotKey { get => hotKey; internal set => hotKey = value; }
+ public Key HotKey {
+ get => hotKey;
+ internal set {
+ if (hotKey != value) {
+ var oldKey = hotKey;
+ hotKey = value;
+ HotKeyChanged?.Invoke (oldKey);
+ }
+ }
+ }
///
/// Specifies the mask to apply to the hotkey to tag it as the hotkey. The default value of 0x100000 causes
@@ -304,7 +318,8 @@ namespace Terminal.Gui {
if (NeedsFormat) {
var shown_text = text;
- if (FindHotKey (text, HotKeySpecifier, true, out hotKeyPos, out hotKey)) {
+ if (FindHotKey (text, HotKeySpecifier, true, out hotKeyPos, out Key newHotKey)) {
+ HotKey = newHotKey;
shown_text = RemoveHotKeySpecifier (Text, hotKeyPos, HotKeySpecifier);
shown_text = ReplaceHotKeyWithTag (shown_text, hotKeyPos);
}
diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs
index e191169fe..d32abb3de 100644
--- a/Terminal.Gui/Core/Toplevel.cs
+++ b/Terminal.Gui/Core/Toplevel.cs
@@ -197,6 +197,85 @@ namespace Terminal.Gui {
void Initialize ()
{
ColorScheme = Colors.TopLevel;
+
+ // Things this view knows how to do
+ AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; });
+ AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; });
+ AddCommand (Command.NextView, () => { MoveNextView (); return true; });
+ AddCommand (Command.PreviousView, () => { MovePreviousView (); return true; });
+ AddCommand (Command.NextViewOrTop, () => { MoveNextViewOrTop (); return true; });
+ AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; });
+ AddCommand (Command.Refresh, () => { Application.Refresh (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Application.QuitKey, Command.QuitToplevel);
+ AddKeyBinding (Key.Z | Key.CtrlMask, Command.Suspend);
+
+ AddKeyBinding (Key.Tab, Command.NextView);
+
+ AddKeyBinding (Key.CursorRight, Command.NextView);
+ AddKeyBinding (Key.F | Key.CtrlMask, Command.NextView);
+
+ AddKeyBinding (Key.CursorDown, Command.NextView);
+ AddKeyBinding (Key.I | Key.CtrlMask, Command.NextView); // Unix
+
+ AddKeyBinding (Key.BackTab | Key.ShiftMask, Command.PreviousView);
+ AddKeyBinding (Key.CursorLeft, Command.PreviousView);
+ AddKeyBinding (Key.CursorUp, Command.PreviousView);
+ AddKeyBinding (Key.B | Key.CtrlMask, Command.PreviousView);
+
+ AddKeyBinding (Key.Tab | Key.CtrlMask, Command.NextViewOrTop);
+ AddKeyBinding (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
+
+ AddKeyBinding (Key.Tab | Key.ShiftMask | Key.CtrlMask, Command.PreviousViewOrTop);
+ AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+
+ AddKeyBinding (Key.L | Key.CtrlMask, Command.Refresh);
+ }
+
+ ///
+ /// Invoked when the is changed.
+ ///
+ public event Action AlternateForwardKeyChanged;
+
+ ///
+ /// Virtual method to invoke the event.
+ ///
+ ///
+ public virtual void OnAlternateForwardKeyChanged (Key oldKey)
+ {
+ ReplaceKeyBinding (oldKey, Application.AlternateForwardKey);
+ AlternateForwardKeyChanged?.Invoke (oldKey);
+ }
+
+ ///
+ /// Invoked when the is changed.
+ ///
+ public event Action AlternateBackwardKeyChanged;
+
+ ///
+ /// Virtual method to invoke the event.
+ ///
+ ///
+ public virtual void OnAlternateBackwardKeyChanged (Key oldKey)
+ {
+ ReplaceKeyBinding (oldKey, Application.AlternateBackwardKey);
+ AlternateBackwardKeyChanged?.Invoke (oldKey);
+ }
+
+ ///
+ /// Invoked when the is changed.
+ ///
+ public event Action QuitKeyChanged;
+
+ ///
+ /// Virtual method to invoke the event.
+ ///
+ ///
+ public virtual void OnQuitKeyChanged (Key oldKey)
+ {
+ ReplaceKeyBinding (oldKey, Application.QuitKey);
+ QuitKeyChanged?.Invoke (oldKey);
}
///
@@ -293,87 +372,86 @@ namespace Terminal.Gui {
if (base.ProcessKey (keyEvent))
return true;
- switch (ShortcutHelper.GetModifiersKey (keyEvent)) {
- case Key k when k == Application.QuitKey:
- // FIXED: stop current execution of this container
- if (Application.MdiTop != null) {
- Application.MdiTop.RequestStop ();
- } else {
- Application.RequestStop ();
- }
- break;
- case Key.Z | Key.CtrlMask:
- Driver.Suspend ();
- return true;
+ var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (keyEvent),
+ new KeyModifiers () { Alt = keyEvent.IsAlt, Ctrl = keyEvent.IsCtrl, Shift = keyEvent.IsShift }));
+ if (result != null)
+ return (bool)result;
#if false
- case Key.F5:
+ if (keyEvent.Key == Key.F5) {
Application.DebugDrawBounds = !Application.DebugDrawBounds;
SetNeedsDisplay ();
return true;
-#endif
- case Key.Tab:
- case Key.CursorRight:
- case Key.CursorDown:
- case Key.I | Key.CtrlMask: // Unix
- var old = GetDeepestFocusedSubview (Focused);
- if (!FocusNext ())
- FocusNext ();
- if (old != Focused && old != Focused?.Focused) {
- old?.SetNeedsDisplay ();
- Focused?.SetNeedsDisplay ();
- } else {
- FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
- }
- return true;
- case Key.BackTab | Key.ShiftMask:
- case Key.CursorLeft:
- case Key.CursorUp:
- old = GetDeepestFocusedSubview (Focused);
- if (!FocusPrev ())
- FocusPrev ();
- if (old != Focused && old != Focused?.Focused) {
- old?.SetNeedsDisplay ();
- Focused?.SetNeedsDisplay ();
- } else {
- FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
- }
- return true;
- case Key.Tab | Key.CtrlMask:
- case Key key when key == Application.AlternateForwardKey: // Needed on Unix
- if (Application.MdiTop == null) {
- var top = Modal ? this : Application.Top;
- top.FocusNext ();
- if (top.Focused == null) {
- top.FocusNext ();
- }
- top.SetNeedsDisplay ();
- Application.EnsuresTopOnFront ();
- } else {
- MoveNext ();
- }
- return true;
- case Key.Tab | Key.ShiftMask | Key.CtrlMask:
- case Key key when key == Application.AlternateBackwardKey: // Needed on Unix
- if (Application.MdiTop == null) {
- var top = Modal ? this : Application.Top;
- top.FocusPrev ();
- if (top.Focused == null) {
- top.FocusPrev ();
- }
- top.SetNeedsDisplay ();
- Application.EnsuresTopOnFront ();
- } else {
- MovePrevious ();
- }
- return true;
- case Key.L | Key.CtrlMask:
- Application.Refresh ();
- return true;
}
+#endif
return false;
}
+ private void MovePreviousViewOrTop ()
+ {
+ if (Application.MdiTop == null) {
+ var top = Modal ? this : Application.Top;
+ top.FocusPrev ();
+ if (top.Focused == null) {
+ top.FocusPrev ();
+ }
+ top.SetNeedsDisplay ();
+ Application.EnsuresTopOnFront ();
+ } else {
+ MovePrevious ();
+ }
+ }
+
+ private void MoveNextViewOrTop ()
+ {
+ if (Application.MdiTop == null) {
+ var top = Modal ? this : Application.Top;
+ top.FocusNext ();
+ if (top.Focused == null) {
+ top.FocusNext ();
+ }
+ top.SetNeedsDisplay ();
+ Application.EnsuresTopOnFront ();
+ } else {
+ MoveNext ();
+ }
+ }
+
+ private void MovePreviousView ()
+ {
+ var old = GetDeepestFocusedSubview (Focused);
+ if (!FocusPrev ())
+ FocusPrev ();
+ if (old != Focused && old != Focused?.Focused) {
+ old?.SetNeedsDisplay ();
+ Focused?.SetNeedsDisplay ();
+ } else {
+ FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
+ }
+ }
+
+ private void MoveNextView ()
+ {
+ var old = GetDeepestFocusedSubview (Focused);
+ if (!FocusNext ())
+ FocusNext ();
+ if (old != Focused && old != Focused?.Focused) {
+ old?.SetNeedsDisplay ();
+ Focused?.SetNeedsDisplay ();
+ } else {
+ FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
+ }
+ }
+
+ private void QuitToplevel ()
+ {
+ if (Application.MdiTop != null) {
+ Application.MdiTop.RequestStop ();
+ } else {
+ Application.RequestStop ();
+ }
+ }
+
///
public override bool ProcessColdKey (KeyEvent keyEvent)
{
diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs
index 883ebe12b..2aef4ef49 100644
--- a/Terminal.Gui/Core/View.cs
+++ b/Terminal.Gui/Core/View.cs
@@ -178,6 +178,11 @@ namespace Terminal.Gui {
///
public event Action VisibleChanged;
+ ///
+ /// Event invoked when the is changed.
+ ///
+ public event Action HotKeyChanged;
+
///
/// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
///
@@ -250,6 +255,12 @@ namespace Terminal.Gui {
// This is null, and allocated on demand.
List tabIndexes;
+ ///
+ /// Configurable keybindings supported by the control
+ ///
+ private Dictionary KeyBindings { get; set; } = new Dictionary ();
+ private Dictionary> CommandImplementations { get; set; } = new Dictionary> ();
+
///
/// This returns a tab index list of the subviews contained by this view.
///
@@ -709,6 +720,7 @@ namespace Terminal.Gui {
TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null)
{
textFormatter = new TextFormatter ();
+ textFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
TextDirection = direction;
Border = border;
if (Border != null) {
@@ -736,6 +748,11 @@ namespace Terminal.Gui {
Text = text;
}
+ private void TextFormatter_HotKeyChanged (Key obj)
+ {
+ HotKeyChanged?.Invoke (obj);
+ }
+
///
/// Sets a flag indicating this view needs to be redisplayed because its state has changed.
///
@@ -1315,7 +1332,7 @@ namespace Terminal.Gui {
/// The color scheme for this view, if it is not defined, it returns the 's
/// color scheme.
///
- public ColorScheme ColorScheme {
+ public virtual ColorScheme ColorScheme {
get {
if (colorScheme == null)
return SuperView?.ColorScheme;
@@ -1426,6 +1443,10 @@ namespace Terminal.Gui {
}
}
}
+
+ // Invoke DrawContentCompleteEvent
+ OnDrawContentComplete (bounds);
+
ClearLayoutNeeded ();
ClearNeedsDisplay ();
}
@@ -1455,6 +1476,31 @@ namespace Terminal.Gui {
DrawContent?.Invoke (viewport);
}
+ ///
+ /// Event invoked when the content area of the View is completed drawing.
+ ///
+ ///
+ ///
+ /// Will be invoked after any subviews removed with have been completed drawing.
+ ///
+ ///
+ /// Rect provides the view-relative rectangle describing the currently visible viewport into the .
+ ///
+ ///
+ public event Action DrawContentComplete;
+
+ ///
+ /// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
+ ///
+ /// The view-relative rectangle describing the currently visible viewport into the
+ ///
+ /// This method will be called after any subviews removed with have been completed drawing.
+ ///
+ public virtual void OnDrawContentComplete (Rect viewport)
+ {
+ DrawContentComplete?.Invoke (viewport);
+ }
+
///
/// Causes the specified subview to have focus.
///
@@ -1551,6 +1597,131 @@ namespace Terminal.Gui {
return false;
}
+ ///
+ /// Invokes any binding that is registered on this
+ /// and matches the
+ ///
+ /// The key event passed.
+ protected bool? InvokeKeybindings (KeyEvent keyEvent)
+ {
+ if (KeyBindings.ContainsKey (keyEvent.Key)) {
+ var command = KeyBindings [keyEvent.Key];
+
+ if (!CommandImplementations.ContainsKey (command)) {
+ throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})");
+ }
+
+ return CommandImplementations [command] ();
+ }
+
+ return null;
+ }
+
+
+ ///
+ /// Adds a new key combination that will trigger the given
+ /// (if supported by the View - see )
+ ///
+ /// If the key is already bound to a different it will be
+ /// rebound to this one
+ ///
+ ///
+ ///
+ public void AddKeyBinding (Key key, Command command)
+ {
+ if (KeyBindings.ContainsKey (key)) {
+ KeyBindings [key] = command;
+ } else {
+ KeyBindings.Add (key, command);
+ }
+ }
+
+ ///
+ /// Replaces a key combination already bound to .
+ ///
+ /// The key to be replaced.
+ /// The new key to be used.
+ protected void ReplaceKeyBinding (Key fromKey, Key toKey)
+ {
+ if (KeyBindings.ContainsKey (fromKey)) {
+ Command value = KeyBindings [fromKey];
+ KeyBindings.Remove (fromKey);
+ KeyBindings [toKey] = value;
+ }
+ }
+
+ ///
+ /// Checks if key combination already exist.
+ ///
+ /// The key to check.
+ /// true If the key already exist, falseotherwise.
+ public bool ContainsKeyBinding (Key key)
+ {
+ return KeyBindings.ContainsKey (key);
+ }
+
+ ///
+ /// Removes all bound keys from the View making including the default
+ /// key combinations such as cursor navigation, scrolling etc
+ ///
+ public void ClearKeybindings ()
+ {
+ KeyBindings.Clear ();
+ }
+
+ ///
+ /// Clears the existing keybinding (if any) for the given
+ ///
+ ///
+ public void ClearKeybinding (Key key)
+ {
+ KeyBindings.Remove (key);
+ }
+
+ ///
+ /// Removes all key bindings that trigger the given command. Views can have multiple different
+ /// keys bound to the same command and this method will clear all of them.
+ ///
+ ///
+ public void ClearKeybinding (Command command)
+ {
+ foreach(var kvp in KeyBindings.Where(kvp=>kvp.Value == command).ToArray())
+ {
+ KeyBindings.Remove (kvp.Key);
+ }
+
+ }
+
+ ///
+ /// States that the given supports a given
+ /// and what to perform to make that command happen
+ ///
+ /// If the already has an implementation the
+ /// will replace the old one
+ ///
+ /// The command.
+ /// The function.
+ protected void AddCommand (Command command, Func f)
+ {
+ // if there is already an implementation of this command
+ if (CommandImplementations.ContainsKey (command)) {
+ // replace that implementation
+ CommandImplementations [command] = f;
+ } else {
+ // else record how to perform the action (this should be the normal case)
+ CommandImplementations.Add (command, f);
+ }
+ }
+
+ ///
+ /// Returns all commands that are supported by this
+ ///
+ ///
+ public IEnumerable GetSupportedCommands ()
+ {
+ return CommandImplementations.Keys;
+ }
+
///
public override bool ProcessHotKey (KeyEvent keyEvent)
{
@@ -2559,5 +2730,21 @@ namespace Terminal.Gui {
{
return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
}
+
+ ///
+ /// Get the top superview of a given .
+ ///
+ /// The superview view.
+ public View GetTopSuperView ()
+ {
+ View top = Application.Top;
+ for (var v = this?.SuperView; v != null; v = v.SuperView) {
+ if (v != null) {
+ top = v;
+ }
+ }
+
+ return top;
+ }
}
}
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index a4a608093..ae50617a8 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -57,7 +57,7 @@ namespace Terminal.Gui {
///
public Button (ustring text, bool is_default = false) : base (text)
{
- Init (text, is_default);
+ Initialize (text, is_default);
}
///
@@ -89,7 +89,7 @@ namespace Terminal.Gui {
public Button (int x, int y, ustring text, bool is_default)
: base (new Rect (x, y, text.RuneCount + 4 + (is_default ? 2 : 0), 1), text)
{
- Init (text, is_default);
+ Initialize (text, is_default);
}
Rune _leftBracket;
@@ -97,7 +97,7 @@ namespace Terminal.Gui {
Rune _leftDefault;
Rune _rightDefault;
- void Init (ustring text, bool is_default)
+ void Initialize (ustring text, bool is_default)
{
TextAlignment = TextAlignment.Centered;
@@ -112,6 +112,29 @@ namespace Terminal.Gui {
this.is_default = is_default;
this.text = text ?? string.Empty;
Update ();
+
+ HotKeyChanged += Button_HotKeyChanged;
+
+ // Things this view knows how to do
+ AddCommand (Command.Accept, () => AcceptKey ());
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.Enter, Command.Accept);
+ AddKeyBinding (Key.Space, Command.Accept);
+ if (HotKey != Key.Null) {
+ AddKeyBinding (Key.Space | HotKey, Command.Accept);
+ }
+ }
+
+ private void Button_HotKeyChanged (Key obj)
+ {
+ if (HotKey != Key.Null) {
+ if (ContainsKeyBinding (obj)) {
+ ReplaceKeyBinding (Key.Space | obj, Key.Space | HotKey);
+ } else {
+ AddKeyBinding (Key.Space | HotKey, Command.Accept);
+ }
+ }
}
///
@@ -171,16 +194,6 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
}
- bool CheckKey (KeyEvent key)
- {
- if (key.Key == (Key.AltMask | HotKey)) {
- SetFocus ();
- Clicked?.Invoke ();
- return true;
- }
- return false;
- }
-
///
public override bool ProcessHotKey (KeyEvent kb)
{
@@ -188,10 +201,7 @@ namespace Terminal.Gui {
return false;
}
- if (kb.IsAlt)
- return CheckKey (kb);
-
- return false;
+ return ExecuteHotKey (kb);
}
///
@@ -201,11 +211,7 @@ namespace Terminal.Gui {
return false;
}
- if (IsDefault && kb.KeyValue == '\n') {
- Clicked?.Invoke ();
- return true;
- }
- return CheckKey (kb);
+ return ExecuteColdKey (kb);
}
///
@@ -215,14 +221,45 @@ namespace Terminal.Gui {
return false;
}
- var c = kb.KeyValue;
- if (c == '\n' || c == ' ' || kb.Key == HotKey) {
- Clicked?.Invoke ();
- return true;
- }
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
+
return base.ProcessKey (kb);
}
+ bool ExecuteHotKey (KeyEvent ke)
+ {
+ if (ke.Key == (Key.AltMask | HotKey)) {
+ return AcceptKey ();
+ }
+ return false;
+ }
+
+ bool ExecuteColdKey (KeyEvent ke)
+ {
+ if (IsDefault && ke.KeyValue == '\n') {
+ return AcceptKey ();
+ }
+ return ExecuteHotKey (ke);
+ }
+
+ bool AcceptKey ()
+ {
+ if (!HasFocus) {
+ SetFocus ();
+ }
+ OnClicked ();
+ return true;
+ }
+
+ ///
+ /// Virtual method to invoke the event.
+ ///
+ public virtual void OnClicked ()
+ {
+ Clicked?.Invoke ();
+ }
///
/// Clicked , raised when the user clicks the primary mouse button within the Bounds of this
@@ -245,7 +282,7 @@ namespace Terminal.Gui {
SetFocus ();
SetNeedsDisplay ();
}
- Clicked?.Invoke ();
+ OnClicked ();
}
return true;
diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs
index 97bf09e7d..b79737f1a 100644
--- a/Terminal.Gui/Views/Checkbox.cs
+++ b/Terminal.Gui/Views/Checkbox.cs
@@ -47,11 +47,7 @@ namespace Terminal.Gui {
/// If set to true is checked.
public CheckBox (ustring s, bool is_checked = false) : base ()
{
- Checked = is_checked;
- Text = s;
- CanFocus = true;
- Height = 1;
- Width = s.RuneCount + 4;
+ Initialize (s, is_checked);
}
///
@@ -73,11 +69,24 @@ namespace Terminal.Gui {
/// text length.
///
public CheckBox (int x, int y, ustring s, bool is_checked) : base (new Rect (x, y, s.Length + 4, 1))
+ {
+ Initialize (s, is_checked);
+ }
+
+ void Initialize (ustring s, bool is_checked)
{
Checked = is_checked;
Text = s;
-
CanFocus = true;
+ Height = 1;
+ Width = s.RuneCount + 4;
+
+ // Things this view knows how to do
+ AddCommand (Command.ToggleChecked, () => ToggleChecked ());
+
+ // Default keybindings for this view
+ AddKeyBinding ((Key)' ', Command.ToggleChecked);
+ AddKeyBinding (Key.Space, Command.ToggleChecked);
}
///
@@ -138,31 +147,34 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent kb)
{
- if (kb.KeyValue == ' ') {
- var previousChecked = Checked;
- Checked = !Checked;
- OnToggled (previousChecked);
- SetNeedsDisplay ();
- return true;
- }
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
+
return base.ProcessKey (kb);
}
///
- public override bool ProcessHotKey (KeyEvent ke)
+ public override bool ProcessHotKey (KeyEvent kb)
{
- if (ke.Key == (Key.AltMask | HotKey)) {
- SetFocus ();
- var previousChecked = Checked;
- Checked = !Checked;
- OnToggled (previousChecked);
- SetNeedsDisplay ();
- return true;
- }
+ if (kb.Key == (Key.AltMask | HotKey))
+ return ToggleChecked ();
return false;
}
+ bool ToggleChecked ()
+ {
+ if (!HasFocus) {
+ SetFocus ();
+ }
+ var previousChecked = Checked;
+ Checked = !Checked;
+ OnToggled (previousChecked);
+ SetNeedsDisplay ();
+ return true;
+ }
+
///
public override bool MouseEvent (MouseEvent me)
{
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index aa8a82245..a63de4371 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -156,6 +156,32 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
Search_Changed (Text);
};
+
+ // Things this view knows how to do
+ AddCommand (Command.Accept, () => ActivateSelected ());
+ AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ());
+ AddCommand (Command.Expand, () => Expand ());
+ AddCommand (Command.Collapse, () => Collapse ());
+ AddCommand (Command.LineDown, () => MoveDown ());
+ AddCommand (Command.LineUp, () => MoveUp ());
+ AddCommand (Command.PageDown, () => PageDown ());
+ AddCommand (Command.PageUp, () => PageUp ());
+ AddCommand (Command.TopHome, () => MoveHome ());
+ AddCommand (Command.BottomEnd, () => MoveEnd ());
+ AddCommand (Command.Cancel, () => CancelSelected ());
+ AddCommand (Command.UnixEmulation, () => UnixEmulation ());
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.Enter, Command.Accept);
+ AddKeyBinding (Key.F4, Command.ToggleExpandCollapse);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+ AddKeyBinding (Key.Home, Command.TopHome);
+ AddKeyBinding (Key.End, Command.BottomEnd);
+ AddKeyBinding (Key.Esc, Command.Cancel);
+ AddKeyBinding (Key.U | Key.CtrlMask, Command.UnixEmulation);
}
private bool isShow = false;
@@ -182,6 +208,11 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Gets the drop down list state, expanded or collapsed.
+ ///
+ public bool IsShow => isShow;
+
///
public new ColorScheme ColorScheme {
get {
@@ -318,89 +349,153 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent e)
{
- if (e.Key == Key.Enter && listview.SelectedItem > -1) {
- Selected ();
+ var result = InvokeKeybindings (e);
+ if (result != null)
+ return (bool)result;
+
+ return base.ProcessKey (e);
+ }
+
+ bool UnixEmulation ()
+ {
+ // Unix emulation
+ Reset ();
+ return true;
+ }
+
+ bool CancelSelected ()
+ {
+ search.SetFocus ();
+ search.Text = text = "";
+ OnSelectedChanged ();
+ Collapse ();
+ return true;
+ }
+
+ bool MoveEnd ()
+ {
+ if (HasItems ()) {
+ listview.MoveEnd ();
+ }
+ return true;
+ }
+
+ bool MoveHome ()
+ {
+ if (HasItems ()) {
+ listview.MoveHome ();
+ }
+ return true;
+ }
+
+ bool PageUp ()
+ {
+ if (HasItems ()) {
+ listview.MovePageUp ();
+ }
+ return true;
+ }
+
+ bool PageDown ()
+ {
+ if (HasItems ()) {
+ listview.MovePageDown ();
+ }
+ return true;
+ }
+
+ bool? MoveUp ()
+ {
+ if (search.HasFocus) { // stop odd behavior on KeyUp when search has focus
return true;
}
- if (e.Key == Key.F4 && (search.HasFocus || listview.HasFocus)) {
- if (!isShow) {
- SetSearchSet ();
- isShow = true;
- ShowList ();
- FocusSelectedItem ();
- } else {
- isShow = false;
- HideList ();
- }
- return true;
- }
-
- if (e.Key == Key.CursorDown && search.HasFocus) { // jump to list
- if (searchset?.Count > 0) {
- listview.TabStop = true;
- listview.SetFocus ();
- SetValue (searchset [listview.SelectedItem]);
- return true;
- } else {
- listview.TabStop = false;
- SuperView.FocusNext ();
- }
- }
-
- if (e.Key == Key.CursorUp && search.HasFocus) { // stop odd behavior on KeyUp when search has focus
- return true;
- }
-
- if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0 && searchset?.Count > 0) // jump back to search
+ if (listview.HasFocus && listview.SelectedItem == 0 && searchset?.Count > 0) // jump back to search
{
search.CursorPosition = search.Text.RuneCount;
search.SetFocus ();
return true;
}
+ return null;
+ }
- if (e.Key == Key.PageDown) {
- if (listview.SelectedItem != -1) {
- listview.MovePageDown ();
+ bool? MoveDown ()
+ {
+ if (search.HasFocus) { // jump to list
+ if (searchset?.Count > 0) {
+ listview.TabStop = true;
+ listview.SetFocus ();
+ SetValue (searchset [listview.SelectedItem]);
+ } else {
+ listview.TabStop = false;
+ SuperView?.FocusNext ();
}
return true;
}
+ return null;
+ }
- if (e.Key == Key.PageUp) {
- if (listview.SelectedItem != -1) {
- listview.MovePageUp ();
+ ///
+ /// Toggles the expand/collapse state of the sublist in the combo box
+ ///
+ ///
+ bool ExpandCollapse ()
+ {
+ if (search.HasFocus || listview.HasFocus) {
+ if (!isShow) {
+ return Expand ();
+ } else {
+ return Collapse ();
}
+ }
+ return false;
+ }
+
+ bool ActivateSelected ()
+ {
+ if (HasItems ()) {
+ Selected ();
return true;
}
+ return false;
+ }
- if (e.Key == Key.Home) {
- if (listview.SelectedItem != -1) {
- listview.MoveHome ();
- }
- return true;
+ bool HasItems ()
+ {
+ return Source?.Count > 0;
+ }
+
+ ///
+ /// Collapses the drop down list. Returns true if the state chagned or false
+ /// if it was already collapsed and no action was taken
+ ///
+ public virtual bool Collapse ()
+ {
+ if (!isShow) {
+ return false;
}
- if (e.Key == Key.End) {
- if (listview.SelectedItem != -1) {
- listview.MoveEnd ();
- }
- return true;
+ isShow = false;
+ HideList ();
+ return true;
+ }
+
+ ///
+ /// Expands the drop down list. Returns true if the state chagned or false
+ /// if it was already expanded and no action was taken
+ ///
+ public virtual bool Expand ()
+ {
+ if (isShow) {
+ return false;
}
- if (e.Key == Key.Esc) {
- search.SetFocus ();
- search.Text = text = "";
- OnSelectedChanged ();
- return true;
- }
+ SetSearchSet ();
+ isShow = true;
+ ShowList ();
+ FocusSelectedItem ();
- // Unix emulation
- if (e.Key == (Key.U | Key.CtrlMask)) {
- Reset ();
- return true;
- }
-
- return base.ProcessKey (e);
+ return true;
}
///
@@ -489,6 +584,7 @@ namespace Terminal.Gui {
private void SetSearchSet ()
{
+ if (Source == null) { return; }
// force deep copy
foreach (var item in Source.ToList ()) {
searchset.Add (item);
diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs
index 14178e572..3093eb59f 100644
--- a/Terminal.Gui/Views/DateField.cs
+++ b/Terminal.Gui/Views/DateField.cs
@@ -26,8 +26,8 @@ namespace Terminal.Gui {
string longFormat;
string shortFormat;
- int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
- string Format { get { return isShort ? shortFormat : longFormat; } }
+ int fieldLen => isShort ? shortFieldLen : longFieldLen;
+ string format => isShort ? shortFormat : longFormat;
///
/// DateChanged event, raised when the property has changed.
@@ -49,8 +49,7 @@ namespace Terminal.Gui {
/// If true, shows only two digits for the year.
public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "")
{
- this.isShort = isShort;
- Initialize (date);
+ Initialize (date, isShort);
}
///
@@ -64,20 +63,47 @@ namespace Terminal.Gui {
///
public DateField (DateTime date) : base ("")
{
- this.isShort = true;
- Width = FieldLen + 2;
+ Width = fieldLen + 2;
Initialize (date);
}
- void Initialize (DateTime date)
+ void Initialize (DateTime date, bool isShort = false)
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
sepChar = cultureInfo.DateTimeFormat.DateSeparator;
longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
shortFormat = GetShortFormat (longFormat);
- CursorPosition = 1;
+ this.isShort = isShort;
Date = date;
+ CursorPosition = 1;
TextChanged += DateField_Changed;
+
+ // Things this view knows how to do
+ AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
+ AddCommand (Command.LeftHome, () => MoveHome ());
+ AddCommand (Command.Left, () => MoveLeft ());
+ AddCommand (Command.RightEnd, () => MoveEnd ());
+ AddCommand (Command.Right, () => MoveRight ());
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+ AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+
+ AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
+ AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
+
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+
+ AddKeyBinding (Key.End, Command.RightEnd);
+ AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
+
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
}
void DateField_Changed (ustring e)
@@ -129,8 +155,8 @@ namespace Terminal.Gui {
var oldData = date;
date = value;
- this.Text = value.ToString (Format);
- var args = new DateTimeEventArgs (oldData, value, Format);
+ this.Text = value.ToString (format);
+ var args = new DateTimeEventArgs (oldData, value, format);
if (oldData != value) {
OnDateChanged (args);
}
@@ -157,12 +183,20 @@ namespace Terminal.Gui {
}
}
+ ///
+ public override int CursorPosition {
+ get => base.CursorPosition;
+ set {
+ base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
+ }
+ }
+
bool SetText (Rune key)
{
var text = TextModel.ToRunes (Text);
var newText = text.GetRange (0, CursorPosition);
newText.Add (key);
- if (CursorPosition < FieldLen)
+ if (CursorPosition < fieldLen)
newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
return SetText (ustring.Make (newText));
}
@@ -174,7 +208,7 @@ namespace Terminal.Gui {
}
ustring [] vals = text.Split (ustring.Make (sepChar));
- ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
+ ustring [] frm = ustring.Make (format).Split (ustring.Make (sepChar));
bool isValidDate = true;
int idx = GetFormatIndex (frm, "y");
int year = Int32.Parse (vals [idx].ToString ());
@@ -204,7 +238,7 @@ namespace Terminal.Gui {
day = Int32.Parse (vals [idx].ToString ());
string d = GetDate (month, day, year, frm);
- if (!DateTime.TryParseExact (d, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
+ if (!DateTime.TryParseExact (d, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
!isValidDate)
return false;
Date = result;
@@ -238,7 +272,7 @@ namespace Terminal.Gui {
ustring GetDate (ustring text)
{
ustring [] vals = text.Split (ustring.Make (sepChar));
- ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
+ ustring [] frm = ustring.Make (format).Split (ustring.Make (sepChar));
ustring [] date = { null, null, null };
for (int i = 0; i < frm.Length; i++) {
@@ -274,7 +308,7 @@ namespace Terminal.Gui {
void IncCursorPosition ()
{
- if (CursorPosition == FieldLen)
+ if (CursorPosition == fieldLen)
return;
if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
CursorPosition++;
@@ -297,60 +331,69 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent kb)
{
- switch (kb.Key) {
- case Key.DeleteChar:
- case Key.D | Key.CtrlMask:
- if (ReadOnly)
- return true;
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
- SetText ('0');
- break;
+ // Ignore non-numeric characters.
+ if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
+ return false;
- case Key.Delete:
- case Key.Backspace:
- if (ReadOnly)
- return true;
-
- SetText ('0');
- DecCursorPosition ();
- break;
-
- // Home, C-A
- case Key.Home:
- case Key.A | Key.CtrlMask:
- CursorPosition = 1;
- break;
-
- case Key.CursorLeft:
- case Key.B | Key.CtrlMask:
- DecCursorPosition ();
- break;
-
- case Key.End:
- case Key.E | Key.CtrlMask: // End
- CursorPosition = FieldLen;
- break;
-
- case Key.CursorRight:
- case Key.F | Key.CtrlMask:
- IncCursorPosition ();
- break;
-
- default:
- // Ignore non-numeric characters.
- if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
- return false;
-
- if (ReadOnly)
- return true;
-
- if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
- IncCursorPosition ();
+ if (ReadOnly)
return true;
- }
+
+ if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
+ IncCursorPosition ();
+
return true;
}
+ bool MoveRight ()
+ {
+ IncCursorPosition ();
+ return true;
+ }
+
+ bool MoveEnd ()
+ {
+ CursorPosition = fieldLen;
+ return true;
+ }
+
+ bool MoveLeft ()
+ {
+ DecCursorPosition ();
+ return true;
+ }
+
+ bool MoveHome ()
+ {
+ // Home, C-A
+ CursorPosition = 1;
+ return true;
+ }
+
+ ///
+ public override void DeleteCharLeft (bool useOldCursorPos = true)
+ {
+ if (ReadOnly)
+ return;
+
+ SetText ('0');
+ DecCursorPosition ();
+ return;
+ }
+
+ ///
+ public override void DeleteCharRight ()
+ {
+ if (ReadOnly)
+ return;
+
+ SetText ('0');
+ return;
+ }
+
///
public override bool MouseEvent (MouseEvent ev)
{
@@ -360,8 +403,8 @@ namespace Terminal.Gui {
SetFocus ();
var point = ev.X;
- if (point > FieldLen)
- point = FieldLen;
+ if (point > fieldLen)
+ point = fieldLen;
if (point < 1)
point = 1;
CursorPosition = point;
@@ -386,7 +429,7 @@ namespace Terminal.Gui {
///
/// The old or value.
///
- public T OldValue {get;}
+ public T OldValue { get; }
///
/// The new or value.
diff --git a/Terminal.Gui/Views/GraphView.cs b/Terminal.Gui/Views/GraphView.cs
index f192da841..80cb9702e 100644
--- a/Terminal.Gui/Views/GraphView.cs
+++ b/Terminal.Gui/Views/GraphView.cs
@@ -74,6 +74,23 @@ namespace Terminal.Gui {
AxisX = new HorizontalAxis ();
AxisY = new VerticalAxis ();
+
+ // Things this view knows how to do
+ AddCommand (Command.ScrollUp, () => { Scroll (0, CellSize.Y); return true; });
+ AddCommand (Command.ScrollDown, () => { Scroll (0, -CellSize.Y); return true; });
+ AddCommand (Command.ScrollRight, () => { Scroll (CellSize.X, 0); return true; });
+ AddCommand (Command.ScrollLeft, () => { Scroll (-CellSize.X, 0); return true; });
+ AddCommand (Command.PageUp, () => { PageUp (); return true; });
+ AddCommand (Command.PageDown, () => { PageDown(); return true; });
+
+ AddKeyBinding (Key.CursorRight, Command.ScrollRight);
+ AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
+ AddKeyBinding (Key.CursorUp, Command.ScrollUp);
+ AddKeyBinding (Key.CursorDown, Command.ScrollDown);
+
+ // Not bound by default (preserves backwards compatibility)
+ //AddKeyBinding (Key.PageUp, Command.PageUp);
+ //AddKeyBinding (Key.PageDown, Command.PageDown);
}
///
@@ -228,48 +245,37 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
- //&& Focused == tabsBar
-
if (HasFocus && CanFocus) {
- switch (keyEvent.Key) {
-
- case Key.CursorLeft:
- Scroll (-CellSize.X, 0);
- return true;
- case Key.CursorLeft | Key.CtrlMask:
- Scroll (-CellSize.X * 5, 0);
- return true;
- case Key.CursorRight:
- Scroll (CellSize.X, 0);
- return true;
- case Key.CursorRight | Key.CtrlMask:
- Scroll (CellSize.X * 5, 0);
- return true;
- case Key.CursorDown:
- Scroll (0, -CellSize.Y);
- return true;
- case Key.CursorDown | Key.CtrlMask:
- Scroll (0, -CellSize.Y * 5);
- return true;
- case Key.CursorUp:
- Scroll (0, CellSize.Y);
- return true;
- case Key.CursorUp | Key.CtrlMask:
- Scroll (0, CellSize.Y * 5);
- return true;
- }
+ var result = InvokeKeybindings (keyEvent);
+ if (result != null)
+ return (bool)result;
}
return base.ProcessKey (keyEvent);
}
+ ///
+ /// Scrolls the graph up 1 page
+ ///
+ public void PageUp()
+ {
+ Scroll (0, CellSize.Y * Bounds.Height);
+ }
+
+ ///
+ /// Scrolls the graph down 1 page
+ ///
+ public void PageDown()
+ {
+ Scroll(0, -1 * CellSize.Y * Bounds.Height);
+ }
///
/// Scrolls the view by a given number of units in graph space.
/// See to translate this into rows/cols
///
///
///
- private void Scroll (float offsetX, float offsetY)
+ public void Scroll (float offsetX, float offsetY)
{
ScrollOffset = new PointF (
ScrollOffset.X + offsetX,
diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs
index fd086c3b2..e92a17041 100644
--- a/Terminal.Gui/Views/HexView.cs
+++ b/Terminal.Gui/Views/HexView.cs
@@ -58,6 +58,41 @@ namespace Terminal.Gui {
CanFocus = true;
leftSide = true;
firstNibble = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.Left, () => MoveLeft ());
+ AddCommand (Command.Right, () => MoveRight ());
+ AddCommand (Command.LineDown, () => MoveDown (bytesPerLine));
+ AddCommand (Command.LineUp, () => MoveUp (bytesPerLine));
+ AddCommand (Command.ToggleChecked, () => ToggleSide ());
+ AddCommand (Command.PageUp, () => MoveUp (bytesPerLine * Frame.Height));
+ AddCommand (Command.PageDown, () => MoveDown (bytesPerLine * Frame.Height));
+ AddCommand (Command.TopHome, () => MoveHome ());
+ AddCommand (Command.BottomEnd, () => MoveEnd ());
+ AddCommand (Command.StartOfLine, () => MoveStartOfLine ());
+ AddCommand (Command.EndOfLine, () => MoveEndOfLine ());
+ AddCommand (Command.StartOfPage, () => MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine)));
+ AddCommand (Command.EndOfPage, () => MoveDown (bytesPerLine * (Frame.Height - 1 - ((int)(position - displayStart) / bytesPerLine))));
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.Enter, Command.ToggleChecked);
+
+ AddKeyBinding ('v' + Key.AltMask, Command.PageUp);
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+
+ AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+
+ AddKeyBinding (Key.Home, Command.TopHome);
+ AddKeyBinding (Key.End, Command.BottomEnd);
+ AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.StartOfLine);
+ AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.EndOfLine);
+ AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.StartOfPage);
+ AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.EndOfPage);
}
///
@@ -390,76 +425,49 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
- switch (keyEvent.Key) {
- case Key.CursorLeft:
- return MoveLeft ();
- case Key.CursorRight:
- return MoveRight ();
- case Key.CursorDown:
- return MoveDown (bytesPerLine);
- case Key.CursorUp:
- return MoveUp (bytesPerLine);
- case Key.Enter:
- return ToggleSide ();
- case ((int)'v' + Key.AltMask):
- case Key.PageUp:
- return MoveUp (bytesPerLine * Frame.Height);
- case Key.V | Key.CtrlMask:
- case Key.PageDown:
- return MoveDown (bytesPerLine * Frame.Height);
- case Key.Home:
- return MoveHome ();
- case Key.End:
- return MoveEnd ();
- case Key.CursorLeft | Key.CtrlMask:
- return MoveStartOfLine ();
- case Key.CursorRight | Key.CtrlMask:
- return MoveEndOfLine ();
- case Key.CursorUp | Key.CtrlMask:
- return MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine));
- case Key.CursorDown | Key.CtrlMask:
- return MoveDown (bytesPerLine * (Frame.Height - 1 - ((int)(position - displayStart) / bytesPerLine)));
- default:
- if (!AllowEdits)
+ var result = InvokeKeybindings (keyEvent);
+ if (result != null)
+ return (bool)result;
+
+ if (!AllowEdits)
+ return false;
+
+ // Ignore control characters and other special keys
+ if (keyEvent.Key < Key.Space || keyEvent.Key > Key.CharMask)
+ return false;
+
+ if (leftSide) {
+ int value;
+ var k = (char)keyEvent.Key;
+ if (k >= 'A' && k <= 'F')
+ value = k - 'A' + 10;
+ else if (k >= 'a' && k <= 'f')
+ value = k - 'a' + 10;
+ else if (k >= '0' && k <= '9')
+ value = k - '0';
+ else
return false;
- // Ignore control characters and other special keys
- if (keyEvent.Key < Key.Space || keyEvent.Key > Key.CharMask)
- return false;
-
- if (leftSide) {
- int value;
- var k = (char)keyEvent.Key;
- if (k >= 'A' && k <= 'F')
- value = k - 'A' + 10;
- else if (k >= 'a' && k <= 'f')
- value = k - 'a' + 10;
- else if (k >= '0' && k <= '9')
- value = k - '0';
- else
- return false;
-
- byte b;
- if (!edits.TryGetValue (position, out b)) {
- source.Position = position;
- b = (byte)source.ReadByte ();
- }
- RedisplayLine (position);
- if (firstNibble) {
- firstNibble = false;
- b = (byte)(b & 0xf | (value << bsize));
- edits [position] = b;
- OnEdited (new KeyValuePair (position, edits [position]));
- } else {
- b = (byte)(b & 0xf0 | value);
- edits [position] = b;
- OnEdited (new KeyValuePair (position, edits [position]));
- MoveRight ();
- }
- return true;
- } else
- return false;
- }
+ byte b;
+ if (!edits.TryGetValue (position, out b)) {
+ source.Position = position;
+ b = (byte)source.ReadByte ();
+ }
+ RedisplayLine (position);
+ if (firstNibble) {
+ firstNibble = false;
+ b = (byte)(b & 0xf | (value << bsize));
+ edits [position] = b;
+ OnEdited (new KeyValuePair (position, edits [position]));
+ } else {
+ b = (byte)(b & 0xf0 | value);
+ edits [position] = b;
+ OnEdited (new KeyValuePair (position, edits [position]));
+ MoveRight ();
+ }
+ return true;
+ } else
+ return false;
}
///
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index df7bcd7af..7ce153ad5 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -317,6 +317,38 @@ namespace Terminal.Gui {
{
Source = source;
CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.LineUp, () => MoveUp ());
+ AddCommand (Command.LineDown, () => MoveDown ());
+ AddCommand (Command.ScrollUp, () => ScrollUp (1));
+ AddCommand (Command.ScrollDown, () => ScrollDown (1));
+ AddCommand (Command.PageUp, () => MovePageUp ());
+ AddCommand (Command.PageDown, () => MovePageDown ());
+ AddCommand (Command.TopHome, () => MoveHome ());
+ AddCommand (Command.BottomEnd, () => MoveEnd ());
+ AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
+ AddCommand (Command.ToggleChecked, () => MarkUnmarkRow ());
+
+ // Default keybindings for all ListViews
+ AddKeyBinding (Key.CursorUp,Command.LineUp);
+ AddKeyBinding (Key.P | Key.CtrlMask, Command.LineUp);
+
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.N | Key.CtrlMask, Command.LineDown);
+
+ AddKeyBinding(Key.PageUp,Command.PageUp);
+
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+
+ AddKeyBinding (Key.Home, Command.TopHome);
+
+ AddKeyBinding (Key.End, Command.BottomEnd);
+
+ AddKeyBinding (Key.Enter, Command.OpenSelectedItem);
+
+ AddKeyBinding (Key.Space, Command.ToggleChecked);
}
///
@@ -383,42 +415,11 @@ namespace Terminal.Gui {
if (source == null)
return base.ProcessKey (kb);
- switch (kb.Key) {
- case Key.CursorUp:
- case Key.P | Key.CtrlMask:
- return MoveUp ();
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
- case Key.CursorDown:
- case Key.N | Key.CtrlMask:
- return MoveDown ();
-
- case Key.V | Key.CtrlMask:
- case Key.PageDown:
- return MovePageDown ();
-
- case Key.PageUp:
- return MovePageUp ();
-
- case Key.Space:
- if (MarkUnmarkRow ())
- return true;
- else
- break;
-
- case Key.Enter:
- return OnOpenSelectedItem ();
-
- case Key.End:
- return MoveEnd ();
-
- case Key.Home:
- return MoveHome ();
-
- default:
- return false;
- }
-
- return true;
+ return false;
}
///
@@ -602,40 +603,44 @@ namespace Terminal.Gui {
/// Scrolls the view down.
///
/// Number of lines to scroll down.
- public virtual void ScrollDown (int lines)
+ public virtual bool ScrollDown (int lines)
{
top = Math.Max (Math.Min (top + lines, source.Count - 1), 0);
SetNeedsDisplay ();
+ return true;
}
///
/// Scrolls the view up.
///
/// Number of lines to scroll up.
- public virtual void ScrollUp (int lines)
+ public virtual bool ScrollUp (int lines)
{
top = Math.Max (top - lines, 0);
SetNeedsDisplay ();
+ return true;
}
///
/// Scrolls the view right.
///
/// Number of columns to scroll right.
- public virtual void ScrollRight (int cols)
+ public virtual bool ScrollRight (int cols)
{
left = Math.Max (Math.Min (left + cols, Maxlength - 1), 0);
SetNeedsDisplay ();
+ return true;
}
///
/// Scrolls the view left.
///
/// Number of columns to scroll left.
- public virtual void ScrollLeft (int cols)
+ public virtual bool ScrollLeft (int cols)
{
left = Math.Max (left - cols, 0);
SetNeedsDisplay ();
+ return true;
}
int lastSelectedItem = -1;
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index bf073e4d1..9dfe567c4 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -413,6 +413,25 @@ namespace Terminal.Gui {
WantMousePositionReports = host.WantMousePositionReports;
}
+ // Things this view knows how to do
+ AddCommand (Command.LineUp, () => MoveUp ());
+ AddCommand (Command.LineDown, () => MoveDown ());
+ AddCommand (Command.Left, () => { this.host.PreviousMenu (true); return true; });
+ AddCommand (Command.Right, () => {
+ this.host.NextMenu (this.barItems.IsTopLevel || (this.barItems.Children != null
+ && current > -1 && current < this.barItems.Children.Length && this.barItems.Children [current].IsFromSubMenu)
+ ? true : false); return true;
+ });
+ AddCommand (Command.Cancel, () => { CloseAllMenus (); return true; });
+ AddCommand (Command.Accept, () => { RunSelected (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.Esc, Command.Cancel);
+ AddKeyBinding (Key.Enter, Command.Accept);
}
internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
@@ -550,40 +569,21 @@ namespace Terminal.Gui {
public override bool ProcessKey (KeyEvent kb)
{
- switch (kb.Key) {
- case Key.Tab:
- host.CleanUp ();
- return true;
- case Key.CursorUp:
- return MoveUp ();
- case Key.CursorDown:
- return MoveDown ();
- case Key.CursorLeft:
- host.PreviousMenu (true);
- return true;
- case Key.CursorRight:
- host.NextMenu (barItems.IsTopLevel || (barItems.Children != null && current > -1 && current < barItems.Children.Length && barItems.Children [current].IsFromSubMenu) ? true : false);
- return true;
- case Key.Esc:
- CloseAllMenus ();
- return true;
- case Key.Enter:
- RunSelected ();
- return true;
- default:
- // TODO: rune-ify
- if (barItems.Children != null && Char.IsLetterOrDigit ((char)kb.KeyValue)) {
- var x = Char.ToUpper ((char)kb.KeyValue);
- foreach (var item in barItems.Children) {
- if (item == null) continue;
- if (item.IsEnabled () && item.HotKey == x) {
- host.CloseMenu ();
- Run (item.Action);
- return true;
- }
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
+
+ // TODO: rune-ify
+ if (barItems.Children != null && Char.IsLetterOrDigit ((char)kb.KeyValue)) {
+ var x = Char.ToUpper ((char)kb.KeyValue);
+ foreach (var item in barItems.Children) {
+ if (item == null) continue;
+ if (item.IsEnabled () && item.HotKey == x) {
+ host.CloseMenu ();
+ Run (item.Action);
+ return true;
}
}
- break;
}
return false;
}
@@ -832,6 +832,20 @@ namespace Terminal.Gui {
ColorScheme = Colors.Menu;
WantMousePositionReports = true;
IsMenuOpen = false;
+
+ // Things this view knows how to do
+ AddCommand (Command.Left, () => { MoveLeft (); return true; });
+ AddCommand (Command.Right, () => { MoveRight (); return true; });
+ AddCommand (Command.Cancel, () => { CloseMenuBar (); return true; });
+ AddCommand (Command.Accept, () => { ProcessMenu (selected, Menus [selected]); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.Esc, Command.Cancel);
+ AddKeyBinding (Key.C | Key.CtrlMask, Command.Cancel);
+ AddKeyBinding (Key.CursorDown, Command.Accept);
+ AddKeyBinding (Key.Enter, Command.Accept);
}
bool openedByAltKey;
@@ -1491,48 +1505,30 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent kb)
{
- switch (kb.Key) {
- case Key.CursorLeft:
- MoveLeft ();
+ if (InvokeKeybindings (kb) == true)
return true;
- case Key.CursorRight:
- MoveRight ();
- return true;
+ var key = kb.KeyValue;
+ if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
+ char c = Char.ToUpper ((char)key);
- case Key.Esc:
- case Key.C | Key.CtrlMask:
- CloseMenuBar ();
- return true;
+ if (selected == -1 || Menus [selected].IsTopLevel)
+ return false;
- case Key.CursorDown:
- case Key.Enter:
- ProcessMenu (selected, Menus [selected]);
- return true;
-
- default:
- var key = kb.KeyValue;
- if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
- char c = Char.ToUpper ((char)key);
-
- if (selected == -1 || Menus [selected].IsTopLevel)
- return false;
-
- foreach (var mi in Menus [selected].Children) {
- if (mi == null)
- continue;
- int p = mi.Title.IndexOf ('_');
- if (p != -1 && p + 1 < mi.Title.RuneCount) {
- if (mi.Title [p + 1] == c) {
- Selected (mi);
- return true;
- }
+ foreach (var mi in Menus [selected].Children) {
+ if (mi == null)
+ continue;
+ int p = mi.Title.IndexOf ('_');
+ if (p != -1 && p + 1 < mi.Title.RuneCount) {
+ if (mi.Title [p + 1] == c) {
+ Selected (mi);
+ return true;
}
}
}
-
- return false;
}
+
+ return false;
}
void CloseMenuBar ()
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index 2e4a2fe66..e215af55b 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -14,20 +14,6 @@ namespace Terminal.Gui {
int horizontalSpace = 2;
List<(int pos, int length)> horizontal;
- void Init (Rect rect, ustring [] radioLabels, int selected)
- {
- if (radioLabels == null) {
- this.radioLabels = new List ();
- } else {
- this.radioLabels = radioLabels.ToList ();
- }
-
- this.selected = selected;
- SetWidthHeight (this.radioLabels);
- CanFocus = true;
- }
-
-
///
/// Initializes a new instance of the class using layout.
///
@@ -40,7 +26,7 @@ namespace Terminal.Gui {
/// The index of the item to be selected, the value is clamped to the number of items.
public RadioGroup (ustring [] radioLabels, int selected = 0) : base ()
{
- Init (Rect.Empty, radioLabels, selected);
+ Initialize (radioLabels, selected);
}
///
@@ -51,7 +37,7 @@ namespace Terminal.Gui {
/// The index of item to be selected, the value is clamped to the number of items.
public RadioGroup (Rect rect, ustring [] radioLabels, int selected = 0) : base (rect)
{
- Init (rect, radioLabels, selected);
+ Initialize (radioLabels, selected);
}
///
@@ -61,11 +47,38 @@ namespace Terminal.Gui {
/// The x coordinate.
/// The y coordinate.
/// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.
- /// The item to be selected, the value is clamped to the number of items.
+ /// The item to be selected, the value is clamped to the number of items.
public RadioGroup (int x, int y, ustring [] radioLabels, int selected = 0) :
this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
{ }
+ void Initialize (ustring [] radioLabels, int selected)
+ {
+ if (radioLabels == null) {
+ this.radioLabels = new List ();
+ } else {
+ this.radioLabels = radioLabels.ToList ();
+ }
+
+ this.selected = selected;
+ SetWidthHeight (this.radioLabels);
+ CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.LineUp, () => { MoveUp (); return true; });
+ AddCommand (Command.LineDown, () => { MoveDown (); return true; });
+ AddCommand (Command.TopHome, () => { MoveHome (); return true; });
+ AddCommand (Command.BottomEnd, () => { MoveEnd (); return true; });
+ AddCommand (Command.Accept, () => { SelectItem (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.Home, Command.TopHome);
+ AddKeyBinding (Key.End, Command.BottomEnd);
+ AddKeyBinding (Key.Space, Command.Accept);
+ }
+
///
/// Gets or sets the for this .
///
@@ -215,33 +228,6 @@ namespace Terminal.Gui {
}
}
- // TODO: Make this a global class
- ///
- /// Event arguments for the SelectedItemChagned event.
- ///
- public class SelectedItemChangedArgs : EventArgs {
- ///
- /// Gets the index of the item that was previously selected. -1 if there was no previous selection.
- ///
- public int PreviousSelectedItem { get; }
-
- ///
- /// Gets the index of the item that is now selected. -1 if there is no selection.
- ///
- public int SelectedItem { get; }
-
- ///
- /// Initializes a new class.
- ///
- ///
- ///
- public SelectedItemChangedArgs (int selectedItem, int previousSelectedItem)
- {
- PreviousSelectedItem = previousSelectedItem;
- SelectedItem = selectedItem;
- }
- }
-
///
/// Invoked when the selected radio label has changed.
///
@@ -311,28 +297,50 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent kb)
{
- switch (kb.Key) {
- case Key.CursorUp:
- if (cursor > 0) {
- cursor--;
- SetNeedsDisplay ();
- return true;
- }
- break;
- case Key.CursorDown:
- if (cursor + 1 < radioLabels.Count) {
- cursor++;
- SetNeedsDisplay ();
- return true;
- }
- break;
- case Key.Space:
- SelectedItem = cursor;
- return true;
- }
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
+
return base.ProcessKey (kb);
}
+ void SelectItem ()
+ {
+ SelectedItem = cursor;
+ }
+
+ void MoveEnd ()
+ {
+ cursor = Math.Max (radioLabels.Count - 1, 0);
+ }
+
+ void MoveHome ()
+ {
+ cursor = 0;
+ }
+
+ void MoveDown ()
+ {
+ if (cursor + 1 < radioLabels.Count) {
+ cursor++;
+ SetNeedsDisplay ();
+ } else if (cursor > 0) {
+ cursor = 0;
+ SetNeedsDisplay ();
+ }
+ }
+
+ void MoveUp ()
+ {
+ if (cursor > 0) {
+ cursor--;
+ SetNeedsDisplay ();
+ } else if (radioLabels.Count - 1 > 0) {
+ cursor = radioLabels.Count - 1;
+ SetNeedsDisplay ();
+ }
+ }
+
///
public override bool MouseEvent (MouseEvent me)
{
@@ -379,4 +387,30 @@ namespace Terminal.Gui {
///
Horizontal
}
+
+ ///
+ /// Event arguments for the SelectedItemChagned event.
+ ///
+ public class SelectedItemChangedArgs : EventArgs {
+ ///
+ /// Gets the index of the item that was previously selected. -1 if there was no previous selection.
+ ///
+ public int PreviousSelectedItem { get; }
+
+ ///
+ /// Gets the index of the item that is now selected. -1 if there is no selection.
+ ///
+ public int SelectedItem { get; }
+
+ ///
+ /// Initializes a new class.
+ ///
+ ///
+ ///
+ public SelectedItemChangedArgs (int selectedItem, int previousSelectedItem)
+ {
+ PreviousSelectedItem = previousSelectedItem;
+ SelectedItem = selectedItem;
+ }
+ }
}
diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs
index 943d2dbcb..a77a8f2aa 100644
--- a/Terminal.Gui/Views/ScrollView.cs
+++ b/Terminal.Gui/Views/ScrollView.cs
@@ -39,7 +39,7 @@ namespace Terminal.Gui {
///
public ScrollView (Rect frame) : base (frame)
{
- Init (frame);
+ Initialize (frame);
}
@@ -48,10 +48,10 @@ namespace Terminal.Gui {
///
public ScrollView () : base ()
{
- Init (new Rect (0, 0, 0, 0));
+ Initialize (Rect.Empty);
}
- void Init (Rect frame)
+ void Initialize (Rect frame)
{
contentView = new View (frame);
vertical = new ScrollBarView (1, 0, isVertical: true) {
@@ -74,6 +74,8 @@ namespace Terminal.Gui {
ContentOffset = new Point (horizontal.Position, ContentOffset.Y);
};
horizontal.Host = this;
+ vertical.OtherScrollBarView = horizontal;
+ horizontal.OtherScrollBarView = vertical;
base.Add (contentView);
CanFocus = true;
@@ -81,6 +83,39 @@ namespace Terminal.Gui {
MouseLeave += View_MouseLeave;
contentView.MouseEnter += View_MouseEnter;
contentView.MouseLeave += View_MouseLeave;
+
+ // Things this view knows how to do
+ AddCommand (Command.ScrollUp, () => ScrollUp (1));
+ AddCommand (Command.ScrollDown, () => ScrollDown (1));
+ AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
+ AddCommand (Command.ScrollRight, () => ScrollRight (1));
+ AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height));
+ AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height));
+ AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width));
+ AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width));
+ AddCommand (Command.TopHome, () => ScrollUp (contentSize.Height));
+ AddCommand (Command.BottomEnd, () => ScrollDown (contentSize.Height));
+ AddCommand (Command.LeftHome, () => ScrollLeft (contentSize.Width));
+ AddCommand (Command.RightEnd, () => ScrollRight (contentSize.Width));
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorUp, Command.ScrollUp);
+ AddKeyBinding (Key.CursorDown, Command.ScrollDown);
+ AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
+ AddKeyBinding (Key.CursorRight, Command.ScrollRight);
+
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+ AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp);
+
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+
+ AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft);
+ AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight);
+ AddKeyBinding (Key.Home, Command.TopHome);
+ AddKeyBinding (Key.End, Command.BottomEnd);
+ AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
+ AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
}
Size contentSize;
@@ -451,33 +486,10 @@ namespace Terminal.Gui {
if (base.ProcessKey (kb))
return true;
- switch (kb.Key) {
- case Key.CursorUp:
- return ScrollUp (1);
- case (Key)'v' | Key.AltMask:
- case Key.PageUp:
- return ScrollUp (Bounds.Height);
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
- case Key.V | Key.CtrlMask:
- case Key.PageDown:
- return ScrollDown (Bounds.Height);
-
- case Key.CursorDown:
- return ScrollDown (1);
-
- case Key.CursorLeft:
- return ScrollLeft (1);
-
- case Key.CursorRight:
- return ScrollRight (1);
-
- case Key.Home:
- return ScrollUp (contentSize.Height);
-
- case Key.End:
- return ScrollDown (contentSize.Height);
-
- }
return false;
}
diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
index 2b925d010..d99db86e4 100644
--- a/Terminal.Gui/Views/TabView.cs
+++ b/Terminal.Gui/Views/TabView.cs
@@ -110,6 +110,19 @@ namespace Terminal.Gui {
base.Add (tabsBar);
base.Add (contentView);
+
+ // Things this view knows how to do
+ AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; });
+ AddCommand (Command.Right, () => { SwitchTabBy (1); return true; });
+ AddCommand (Command.LeftHome, () => { SelectedTab = Tabs.FirstOrDefault (); return true; });
+ AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; });
+
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.End, Command.RightEnd);
}
///
@@ -179,7 +192,7 @@ namespace Terminal.Gui {
if (Tabs.Any ()) {
tabsBar.Redraw (tabsBar.Bounds);
- contentView.SetNeedsDisplay();
+ contentView.SetNeedsDisplay ();
contentView.Redraw (contentView.Bounds);
}
}
@@ -216,21 +229,9 @@ namespace Terminal.Gui {
public override bool ProcessKey (KeyEvent keyEvent)
{
if (HasFocus && CanFocus && Focused == tabsBar) {
- switch (keyEvent.Key) {
-
- case Key.CursorLeft:
- SwitchTabBy (-1);
- return true;
- case Key.CursorRight:
- SwitchTabBy (1);
- return true;
- case Key.Home:
- SelectedTab = Tabs.FirstOrDefault ();
- return true;
- case Key.End:
- SelectedTab = Tabs.LastOrDefault ();
- return true;
- }
+ var result = InvokeKeybindings (keyEvent);
+ if (result != null)
+ return (bool)result;
}
return base.ProcessKey (keyEvent);
@@ -673,7 +674,7 @@ namespace Terminal.Gui {
public override bool MouseEvent (MouseEvent me)
{
- if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
+ if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
return false;
diff --git a/Terminal.Gui/Views/TableView.cs b/Terminal.Gui/Views/TableView.cs
index 0a11ff10f..8d51ddcf5 100644
--- a/Terminal.Gui/Views/TableView.cs
+++ b/Terminal.Gui/Views/TableView.cs
@@ -58,6 +58,7 @@ namespace Terminal.Gui {
private int selectedColumn;
private DataTable table;
private TableStyle style = new TableStyle ();
+ private Key cellActivationKey = Key.Enter;
///
/// The default maximum cell width for and
@@ -171,7 +172,15 @@ namespace Terminal.Gui {
///
/// The key which when pressed should trigger event. Defaults to Enter.
///
- public Key CellActivationKey { get; set; } = Key.Enter;
+ public Key CellActivationKey {
+ get => cellActivationKey;
+ set {
+ if (cellActivationKey != value) {
+ ReplaceKeyBinding (cellActivationKey, value);
+ cellActivationKey = value;
+ }
+ }
+ }
///
/// Initialzies a class using layout.
@@ -188,32 +197,84 @@ namespace Terminal.Gui {
public TableView () : base ()
{
CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.Right, () => { ChangeSelectionByOffset (1, 0, false); return true; });
+ AddCommand (Command.Left, () => { ChangeSelectionByOffset (-1, 0, false); return true; });
+ AddCommand (Command.LineUp, () => { ChangeSelectionByOffset (0, -1, false); return true; });
+ AddCommand (Command.LineDown, () => { ChangeSelectionByOffset (0, 1, false); return true; });
+ AddCommand (Command.PageUp, () => { PageUp (false); return true; });
+ AddCommand (Command.PageDown, () => { PageDown (false); return true; });
+ AddCommand (Command.LeftHome, () => { ChangeSelectionToStartOfRow (false); return true; });
+ AddCommand (Command.RightEnd, () => { ChangeSelectionToEndOfRow (false); return true; });
+ AddCommand (Command.TopHome, () => { ChangeSelectionToStartOfTable(false); return true; });
+ AddCommand (Command.BottomEnd, () => { ChangeSelectionToEndOfTable (false); return true; });
+
+ AddCommand (Command.RightExtend, () => { ChangeSelectionByOffset (1, 0, true); return true; });
+ AddCommand (Command.LeftExtend, () => { ChangeSelectionByOffset (-1, 0, true); return true; });
+ AddCommand (Command.LineUpExtend, () => { ChangeSelectionByOffset (0, -1, true); return true; });
+ AddCommand (Command.LineDownExtend, () => { ChangeSelectionByOffset (0, 1, true); return true; });
+ AddCommand (Command.PageUpExtend, () => { PageUp (true); return true; });
+ AddCommand (Command.PageDownExtend, () => { PageDown (true); return true; });
+ AddCommand (Command.LeftHomeExtend, () => { ChangeSelectionToStartOfRow (true); return true; });
+ AddCommand (Command.RightEndExtend, () => { ChangeSelectionToEndOfRow (true); return true; });
+ AddCommand (Command.TopHomeExtend, () => { ChangeSelectionToStartOfTable (true); return true; });
+ AddCommand (Command.BottomEndExtend, () => { ChangeSelectionToEndOfTable (true); return true; });
+
+ AddCommand (Command.SelectAll, () => { SelectAll(); return true; });
+ AddCommand (Command.Accept, () => { new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.End, Command.RightEnd);
+ AddKeyBinding (Key.Home | Key.CtrlMask, Command.TopHome);
+ AddKeyBinding (Key.End | Key.CtrlMask, Command.BottomEnd);
+
+ AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
+ AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
+ AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
+ AddKeyBinding (Key.CursorDown| Key.ShiftMask, Command.LineDownExtend);
+ AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
+ AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
+ AddKeyBinding (Key.Home | Key.ShiftMask, Command.LeftHomeExtend);
+ AddKeyBinding (Key.End | Key.ShiftMask, Command.RightEndExtend);
+ AddKeyBinding (Key.Home | Key.CtrlMask | Key.ShiftMask, Command.TopHomeExtend);
+ AddKeyBinding (Key.End | Key.CtrlMask | Key.ShiftMask, Command.BottomEndExtend);
+
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.SelectAll);
+ AddKeyBinding (CellActivationKey, Command.Accept);
}
///
public override void Redraw (Rect bounds)
- {
- Move (0, 0);
- var frame = Frame;
+ {
+ Move (0, 0);
+ var frame = Frame;
- // What columns to render at what X offset in viewport
- var columnsToRender = CalculateViewport (bounds).ToArray ();
+ // What columns to render at what X offset in viewport
+ var columnsToRender = CalculateViewport (bounds).ToArray ();
- Driver.SetAttribute (GetNormalColor ());
+ Driver.SetAttribute (GetNormalColor ());
- //invalidate current row (prevents scrolling around leaving old characters in the frame
- Driver.AddStr (new string (' ', bounds.Width));
+ //invalidate current row (prevents scrolling around leaving old characters in the frame
+ Driver.AddStr (new string (' ', bounds.Width));
- int line = 0;
+ int line = 0;
- if (ShouldRenderHeaders ()) {
- // Render something like:
- /*
- ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐
- │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│
- └────────────────────┴──────────┴───────────┴──────────────┴─────────┘
- */
- if (Style.ShowHorizontalHeaderOverline) {
+ if (ShouldRenderHeaders ()) {
+ // Render something like:
+ /*
+ ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐
+ │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│
+ └────────────────────┴──────────┴───────────┴──────────────┴─────────┘
+ */
+ if (Style.ShowHorizontalHeaderOverline) {
RenderHeaderOverline (line, bounds.Width, columnsToRender);
line++;
}
@@ -561,76 +622,13 @@ namespace Terminal.Gui {
return false;
}
- if (keyEvent.Key == CellActivationKey && Table != null) {
- OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow));
+ var result = InvokeKeybindings (keyEvent);
+ if (result != null) {
+ PositionCursor ();
return true;
}
- switch (keyEvent.Key) {
- case Key.CursorLeft:
- case Key.CursorLeft | Key.ShiftMask:
- ChangeSelectionByOffset (-1, 0, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.CursorRight:
- case Key.CursorRight | Key.ShiftMask:
- ChangeSelectionByOffset (1, 0, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.CursorDown:
- case Key.CursorDown | Key.ShiftMask:
- ChangeSelectionByOffset (0, 1, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.CursorUp:
- case Key.CursorUp | Key.ShiftMask:
- ChangeSelectionByOffset (0, -1, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.PageUp:
- case Key.PageUp | Key.ShiftMask:
- ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.PageDown:
- case Key.PageDown | Key.ShiftMask:
- ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.Home | Key.CtrlMask:
- case Key.Home | Key.CtrlMask | Key.ShiftMask:
- // jump to table origin
- SetSelection (0, 0, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.Home:
- case Key.Home | Key.ShiftMask:
- // jump to start of line
- SetSelection (0, SelectedRow, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.End | Key.CtrlMask:
- case Key.End | Key.CtrlMask | Key.ShiftMask:
- // jump to end of table
- SetSelection (Table.Columns.Count - 1, Table.Rows.Count - 1, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- case Key.A | Key.CtrlMask:
- SelectAll ();
- Update ();
- break;
- case Key.End:
- case Key.End | Key.ShiftMask:
- //jump to end of row
- SetSelection (Table.Columns.Count - 1, SelectedRow, keyEvent.Key.HasFlag (Key.ShiftMask));
- Update ();
- break;
- default:
- // Not a keystroke we care about
- return false;
- }
- PositionCursor ();
- return true;
+ return false;
}
///
@@ -671,6 +669,68 @@ namespace Terminal.Gui {
public void ChangeSelectionByOffset (int offsetX, int offsetY, bool extendExistingSelection)
{
SetSelection (SelectedColumn + offsetX, SelectedRow + offsetY, extendExistingSelection);
+ Update ();
+ }
+
+ ///
+ /// Moves the selection up by one page
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void PageUp(bool extend)
+ {
+ ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), extend);
+ Update ();
+ }
+
+ ///
+ /// Moves the selection down by one page
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void PageDown(bool extend)
+ {
+ ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), extend);
+ Update ();
+ }
+
+ ///
+ /// Moves or extends the selection to the first cell in the table (0,0)
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void ChangeSelectionToStartOfTable (bool extend)
+ {
+ SetSelection (0, 0, extend);
+ Update ();
+ }
+
+ ///
+ /// Moves or extends the selection to the final cell in the table
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void ChangeSelectionToEndOfTable(bool extend)
+ {
+ SetSelection (Table.Columns.Count - 1, Table.Rows.Count - 1, extend);
+ Update ();
+ }
+
+
+ ///
+ /// Moves or extends the selection to the last cell in the current row
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void ChangeSelectionToEndOfRow (bool extend)
+ {
+ SetSelection (Table.Columns.Count - 1, SelectedRow, extend);
+ Update ();
+ }
+
+ ///
+ /// Moves or extends the selection to the first cell in the current row
+ ///
+ /// true to extend the current selection (if any) instead of replacing
+ public void ChangeSelectionToStartOfRow (bool extend)
+ {
+ SetSelection (0, SelectedRow, extend);
+ Update ();
}
///
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index b0c23de4d..2483d5f66 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NStack;
+using Rune = System.Rune;
namespace Terminal.Gui {
///
@@ -94,6 +95,109 @@ namespace Terminal.Gui {
CanFocus = true;
Used = true;
WantMousePositionReports = true;
+
+ Initialized += TextField_Initialized;
+
+ // Things this view knows how to do
+ AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
+ AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
+ AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
+ AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
+ AddCommand (Command.LeftExtend, () => { MoveLeftExtend (); return true; });
+ AddCommand (Command.RightExtend, () => { MoveRightExtend (); return true; });
+ AddCommand (Command.WordLeftExtend, () => { MoveWordLeftExtend (); return true; });
+ AddCommand (Command.WordRightExtend, () => { MoveWordRightExtend (); return true; });
+ AddCommand (Command.Left, () => { MoveLeft (); return true; });
+ AddCommand (Command.RightEnd, () => { MoveEnd (); return true; });
+ AddCommand (Command.Right, () => { MoveRight (); return true; });
+ AddCommand (Command.CutToEndLine, () => { KillToEnd (); return true; });
+ AddCommand (Command.CutToStartLine, () => { KillToStart (); return true; });
+ AddCommand (Command.Undo, () => { UndoChanges (); return true; });
+ AddCommand (Command.Redo, () => { RedoChanges (); return true; });
+ AddCommand (Command.WordLeft, () => { MoveWordLeft (); return true; });
+ AddCommand (Command.WordRight, () => { MoveWordRight (); return true; });
+ AddCommand (Command.KillWordForwards, () => { KillWordForwards (); return true; });
+ AddCommand (Command.KillWordBackwards, () => { KillWordBackwards (); return true; });
+ AddCommand (Command.ToggleOverwrite, () => { SetOverwrite (!Used); return true; });
+ AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; });
+ AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; });
+ AddCommand (Command.Copy, () => { Copy (); return true; });
+ AddCommand (Command.Cut, () => { Cut (); return true; });
+ AddCommand (Command.Paste, () => { Paste (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+ AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+
+ AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
+ AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+
+ AddKeyBinding (Key.Home | Key.ShiftMask, Command.LeftHomeExtend);
+ AddKeyBinding (Key.Home | Key.ShiftMask | Key.CtrlMask, Command.LeftHomeExtend);
+ AddKeyBinding (Key.A | Key.ShiftMask | Key.CtrlMask, Command.LeftHomeExtend);
+
+ AddKeyBinding (Key.End | Key.ShiftMask, Command.RightEndExtend);
+ AddKeyBinding (Key.End | Key.ShiftMask | Key.CtrlMask, Command.RightEndExtend);
+ AddKeyBinding (Key.E | Key.ShiftMask | Key.CtrlMask, Command.RightEndExtend);
+
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
+
+ AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
+ AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LeftExtend);
+
+ AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
+ AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.RightExtend);
+
+ AddKeyBinding (Key.CursorLeft | Key.ShiftMask | Key.CtrlMask, Command.WordLeftExtend);
+ AddKeyBinding (Key.CursorUp | Key.ShiftMask | Key.CtrlMask, Command.WordLeftExtend);
+ AddKeyBinding ((Key)((int)'B' + Key.ShiftMask | Key.AltMask), Command.WordLeftExtend);
+
+ AddKeyBinding (Key.CursorRight | Key.ShiftMask | Key.CtrlMask, Command.WordRightExtend);
+ AddKeyBinding (Key.CursorDown | Key.ShiftMask | Key.CtrlMask, Command.WordRightExtend);
+ AddKeyBinding ((Key)((int)'F' + Key.ShiftMask | Key.AltMask), Command.WordRightExtend);
+
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+
+ AddKeyBinding (Key.End, Command.RightEnd);
+ AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
+ AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
+
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
+
+ AddKeyBinding (Key.K | Key.CtrlMask, Command.CutToEndLine);
+ AddKeyBinding (Key.K | Key.AltMask, Command.CutToStartLine);
+
+ AddKeyBinding (Key.Z | Key.CtrlMask, Command.Undo);
+ AddKeyBinding (Key.Backspace | Key.AltMask, Command.Undo);
+
+ AddKeyBinding (Key.Y | Key.CtrlMask, Command.Redo);
+
+ AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.WordLeft);
+ AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.WordLeft);
+ AddKeyBinding ((Key)((int)'B' + Key.AltMask), Command.WordLeft);
+
+ AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.WordRight);
+ AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.WordRight);
+ AddKeyBinding ((Key)((int)'F' + Key.AltMask), Command.WordRight);
+
+ AddKeyBinding (Key.DeleteChar | Key.CtrlMask, Command.KillWordForwards);
+ AddKeyBinding (Key.Backspace | Key.CtrlMask, Command.KillWordBackwards);
+ AddKeyBinding (Key.InsertChar, Command.ToggleOverwrite);
+ AddKeyBinding (Key.C | Key.CtrlMask, Command.Copy);
+ AddKeyBinding (Key.X | Key.CtrlMask, Command.Cut);
+ AddKeyBinding (Key.V | Key.CtrlMask, Command.Paste);
+ }
+
+
+ void TextField_Initialized (object sender, EventArgs e)
+ {
+ Autocomplete.HostControl = this;
+ Autocomplete.PopupInsideContainer = false;
}
///
@@ -107,6 +211,12 @@ namespace Terminal.Gui {
return base.OnLeave (view);
}
+ ///
+ /// Provides autocomplete context menu based on suggestions at the current cursor
+ /// position. Populate to enable this feature.
+ ///
+ public IAutocomplete Autocomplete { get; protected set; } = new TextFieldAutocomplete ();
+
///
public override Rect Frame {
get => base.Frame;
@@ -174,7 +284,7 @@ namespace Terminal.Gui {
///
/// Sets or gets the current cursor position.
///
- public int CursorPosition {
+ public virtual int CursorPosition {
get { return point; }
set {
if (value < 0) {
@@ -188,6 +298,11 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Gets the left offset position.
+ ///
+ public int ScrollOffset => first;
+
///
/// Sets the cursor position.
///
@@ -248,6 +363,17 @@ namespace Terminal.Gui {
}
PositionCursor ();
+
+ if (SelectedLength > 0)
+ return;
+
+ // draw autocomplete
+ Autocomplete.GenerateSuggestions ();
+
+ var renderAt = new Point (
+ CursorPosition - ScrollOffset, 0);
+
+ Autocomplete.RenderOverlay (renderAt);
}
Attribute GetReadOnlyColor ()
@@ -330,174 +456,65 @@ namespace Terminal.Gui {
// Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
oldCursorPos = point;
- switch (ShortcutHelper.GetModifiersKey (kb)) {
- case Key.DeleteChar:
- case Key.D | Key.CtrlMask: // Delete
- DeleteCharRight ();
- break;
-
- case Key.Delete:
- case Key.Backspace:
- DeleteCharLeft ();
- break;
-
- case Key.Home | Key.ShiftMask:
- case Key.Home | Key.ShiftMask | Key.CtrlMask:
- case Key.A | Key.ShiftMask | Key.CtrlMask:
- MoveHomeExtend ();
- break;
-
- case Key.End | Key.ShiftMask:
- case Key.End | Key.ShiftMask | Key.CtrlMask:
- case Key.E | Key.ShiftMask | Key.CtrlMask:
- MoveEndExtend ();
- break;
-
- // Home, C-A
- case Key.Home:
- case Key.Home | Key.CtrlMask:
- case Key.A | Key.CtrlMask:
- MoveHome ();
- break;
-
- case Key.CursorLeft | Key.ShiftMask:
- case Key.CursorUp | Key.ShiftMask:
- MoveLeftExtend ();
- break;
-
- case Key.CursorRight | Key.ShiftMask:
- case Key.CursorDown | Key.ShiftMask:
- MoveRightExtend ();
- break;
-
- case Key.CursorLeft | Key.ShiftMask | Key.CtrlMask:
- case Key.CursorUp | Key.ShiftMask | Key.CtrlMask:
- case (Key)((int)'B' + Key.ShiftMask | Key.AltMask):
- MoveWordLeftExtend ();
- break;
-
- case Key.CursorRight | Key.ShiftMask | Key.CtrlMask:
- case Key.CursorDown | Key.ShiftMask | Key.CtrlMask:
- case (Key)((int)'F' + Key.ShiftMask | Key.AltMask):
- MoveWordRightExtend ();
- break;
-
- case Key.CursorLeft:
- case Key.B | Key.CtrlMask:
- MoveLeft ();
- break;
-
- case Key.End:
- case Key.End | Key.CtrlMask:
- case Key.E | Key.CtrlMask: // End
- MoveEnd ();
- break;
-
- case Key.CursorRight:
- case Key.F | Key.CtrlMask:
- MoveRight ();
- break;
-
- case Key.K | Key.CtrlMask: // kill-to-end
- KillToEnd ();
- break;
-
- case Key.K | Key.AltMask: // kill-to-start
- KillToStart ();
- break;
-
- // Undo
- case Key.Z | Key.CtrlMask:
- case Key.Backspace | Key.AltMask:
- UndoChanges ();
- break;
-
- //Redo
- case Key.Y | Key.CtrlMask: // Control-y, yank
- RedoChanges ();
- break;
-
- case Key.CursorLeft | Key.CtrlMask:
- case Key.CursorUp | Key.CtrlMask:
- case (Key)((int)'B' + Key.AltMask):
- MoveWordLeft ();
- break;
-
- case Key.CursorRight | Key.CtrlMask:
- case Key.CursorDown | Key.CtrlMask:
- case (Key)((int)'F' + Key.AltMask):
- MoveWordRight ();
- break;
-
- case Key.DeleteChar | Key.CtrlMask: // kill-word-forwards
- KillWordForwards ();
- break;
-
- case Key.Backspace | Key.CtrlMask: // kill-word-backwards
- KillWordBackwards ();
- break;
-
- case Key.InsertChar:
- InsertChar ();
- break;
-
- case Key.C | Key.CtrlMask:
- Copy ();
- break;
-
- case Key.X | Key.CtrlMask:
- Cut ();
- break;
-
- case Key.V | Key.CtrlMask:
- Paste ();
- break;
-
- // MISSING:
- // Alt-D, Alt-backspace
- // Alt-Y
- // Delete adding to kill buffer
-
- default:
- // Ignore other control characters.
- if (kb.Key < Key.Space || kb.Key > Key.CharMask)
- return false;
-
- if (ReadOnly)
- return true;
-
- if (length > 0) {
- DeleteSelectedText ();
- oldCursorPos = point;
- }
- var kbstr = TextModel.ToRunes (ustring.Make ((uint)kb.Key));
- if (Used) {
- point++;
- if (point == text.Count + 1) {
- SetText (text.Concat (kbstr).ToList ());
- } else {
- if (oldCursorPos > text.Count) {
- oldCursorPos = text.Count;
- }
- SetText (text.GetRange (0, oldCursorPos).Concat (kbstr).Concat (text.GetRange (oldCursorPos, Math.Min (text.Count - oldCursorPos, text.Count))));
- }
- } else {
- SetText (text.GetRange (0, oldCursorPos).Concat (kbstr).Concat (text.GetRange (Math.Min (oldCursorPos + 1, text.Count), Math.Max (text.Count - oldCursorPos - 1, 0))));
- point++;
- }
- Adjust ();
+ // Give autocomplete first opportunity to respond to key presses
+ if (SelectedLength == 0 && Autocomplete.ProcessKey (kb)) {
return true;
}
+
+ var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (kb),
+ new KeyModifiers () { Alt = kb.IsAlt, Ctrl = kb.IsCtrl, Shift = kb.IsShift }));
+ if (result != null)
+ return (bool)result;
+
+ // Ignore other control characters.
+ if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+ return false;
+
+ if (ReadOnly)
+ return true;
+
+ InsertText (kb);
+
return true;
}
- void InsertChar ()
+ void InsertText (KeyEvent kb, bool useOldCursorPos = true)
{
- Used = !Used;
+ if (length > 0) {
+ DeleteSelectedText ();
+ oldCursorPos = point;
+ }
+ if (!useOldCursorPos) {
+ oldCursorPos = point;
+ }
+ var kbstr = TextModel.ToRunes (ustring.Make ((uint)kb.Key));
+ if (Used) {
+ point++;
+ if (point == text.Count + 1) {
+ SetText (text.Concat (kbstr).ToList ());
+ } else {
+ if (oldCursorPos > text.Count) {
+ oldCursorPos = text.Count;
+ }
+ SetText (text.GetRange (0, oldCursorPos).Concat (kbstr).Concat (text.GetRange (oldCursorPos, Math.Min (text.Count - oldCursorPos, text.Count))));
+ }
+ } else {
+ SetText (text.GetRange (0, oldCursorPos).Concat (kbstr).Concat (text.GetRange (Math.Min (oldCursorPos + 1, text.Count), Math.Max (text.Count - oldCursorPos - 1, 0))));
+ point++;
+ }
+ Adjust ();
+ }
+
+ void SetOverwrite (bool overwrite)
+ {
+ Used = overwrite;
SetNeedsDisplay ();
}
- void KillWordBackwards ()
+ ///
+ /// Deletes word backwards.
+ ///
+ public virtual void KillWordBackwards ()
{
ClearAllSelection ();
int bw = WordBackward (point);
@@ -508,7 +525,10 @@ namespace Terminal.Gui {
Adjust ();
}
- void KillWordForwards ()
+ ///
+ /// Deletes word forwards.
+ ///
+ public virtual void KillWordForwards ()
{
ClearAllSelection ();
int fw = WordForward (point);
@@ -702,7 +722,10 @@ namespace Terminal.Gui {
}
}
- void DeleteCharLeft ()
+ ///
+ /// Deletes the left character.
+ ///
+ public virtual void DeleteCharLeft (bool useOldCursorPos = true)
{
if (ReadOnly)
return;
@@ -711,6 +734,9 @@ namespace Terminal.Gui {
if (point == 0)
return;
+ if (!useOldCursorPos) {
+ oldCursorPos = point;
+ }
point--;
if (oldCursorPos < text.Count) {
SetText (text.GetRange (0, oldCursorPos - 1).Concat (text.GetRange (oldCursorPos, text.Count - oldCursorPos)));
@@ -723,7 +749,10 @@ namespace Terminal.Gui {
}
}
- void DeleteCharRight ()
+ ///
+ /// Deletes the right character.
+ ///
+ public virtual void DeleteCharRight ()
{
if (ReadOnly)
return;
@@ -868,6 +897,11 @@ namespace Terminal.Gui {
return true;
}
+ // Give autocomplete first opportunity to respond to mouse clicks
+ if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
+ return true;
+ }
+
if (ev.Flags == MouseFlags.Button1Pressed) {
EnsureHasFocus ();
PositionCursor (ev);
@@ -1099,6 +1133,29 @@ namespace Terminal.Gui {
return base.OnEnter (view);
}
+
+ ///
+ /// Inserts the given text at the current cursor position
+ /// exactly as if the user had just typed it
+ ///
+ /// Text to add
+ /// If uses the .
+ public void InsertText (string toAdd, bool useOldCursorPos = true)
+ {
+ foreach (var ch in toAdd) {
+
+ Key key;
+
+ try {
+ key = (Key)ch;
+ } catch (Exception) {
+
+ throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
+ }
+
+ InsertText (new KeyEvent () { Key = key }, useOldCursorPos);
+ }
+ }
}
///
@@ -1123,4 +1180,33 @@ namespace Terminal.Gui {
NewText = newText;
}
}
+
+ ///
+ /// Renders an overlay on another view at a given point that allows selecting
+ /// from a range of 'autocomplete' options.
+ /// An implementation on a TextField.
+ ///
+ public class TextFieldAutocomplete : Autocomplete {
+
+ ///
+ protected override void DeleteTextBackwards ()
+ {
+ ((TextField)HostControl).DeleteCharLeft (false);
+ }
+
+ ///
+ protected override string GetCurrentWord ()
+ {
+ var host = (TextField)HostControl;
+ var currentLine = host.Text.ToRuneList ();
+ var cursorPosition = Math.Min (host.CursorPosition, currentLine.Count);
+ return IdxToWord (currentLine, cursorPosition);
+ }
+
+ ///
+ protected override void InsertText (string accepted)
+ {
+ ((TextField)HostControl).InsertText (accepted, false);
+ }
+ }
}
diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs
index 2c5314979..6d5ffedcb 100644
--- a/Terminal.Gui/Views/TextValidateField.cs
+++ b/Terminal.Gui/Views/TextValidateField.cs
@@ -391,6 +391,25 @@ namespace Terminal.Gui {
{
Height = 1;
CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.LeftHome, () => { HomeKeyHandler (); return true; });
+ AddCommand (Command.RightEnd, () => { EndKeyHandler (); return true; });
+ AddCommand (Command.DeleteCharRight, () => { DeleteKeyHandler (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { BackspaceKeyHandler (); return true; });
+ AddCommand (Command.Left, () => { CursorLeft (); return true; });
+ AddCommand (Command.Right, () => { CursorRight (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.End, Command.RightEnd);
+
+ AddKeyBinding (Key.Delete, Command.DeleteCharRight);
+ AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+
+ AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.CursorRight, Command.Right);
}
///
@@ -446,7 +465,7 @@ namespace Terminal.Gui {
}
}
- ///inheritdoc/>
+ ///
public override void PositionCursor ()
{
var (left, _) = GetMargins (Frame.Width);
@@ -526,6 +545,7 @@ namespace Terminal.Gui {
{
var current = cursorPosition;
cursorPosition = provider.CursorLeft (cursorPosition);
+ SetNeedsDisplay ();
return current != cursorPosition;
}
@@ -537,6 +557,7 @@ namespace Terminal.Gui {
{
var current = cursorPosition;
cursorPosition = provider.CursorRight (cursorPosition);
+ SetNeedsDisplay ();
return current != cursorPosition;
}
@@ -551,6 +572,7 @@ namespace Terminal.Gui {
}
cursorPosition = provider.CursorLeft (cursorPosition);
provider.Delete (cursorPosition);
+ SetNeedsDisplay ();
return true;
}
@@ -564,6 +586,7 @@ namespace Terminal.Gui {
cursorPosition = provider.CursorLeft (cursorPosition);
}
provider.Delete (cursorPosition);
+ SetNeedsDisplay ();
return true;
}
@@ -574,6 +597,7 @@ namespace Terminal.Gui {
bool HomeKeyHandler ()
{
cursorPosition = provider.CursorStart ();
+ SetNeedsDisplay ();
return true;
}
@@ -584,6 +608,7 @@ namespace Terminal.Gui {
bool EndKeyHandler ()
{
cursorPosition = provider.CursorEnd ();
+ SetNeedsDisplay ();
return true;
}
@@ -594,30 +619,21 @@ namespace Terminal.Gui {
return false;
}
- switch (kb.Key) {
- case Key.Home: HomeKeyHandler (); break;
- case Key.End: EndKeyHandler (); break;
- case Key.Delete:
- case Key.DeleteChar: DeleteKeyHandler (); break;
- case Key.Backspace: BackspaceKeyHandler (); break;
- case Key.CursorLeft: CursorLeft (); break;
- case Key.CursorRight: CursorRight (); break;
- default:
- if (kb.Key < Key.Space || kb.Key > Key.CharMask)
- return false;
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
- var key = new Rune ((uint)kb.KeyValue);
+ if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+ return false;
- var inserted = provider.InsertAt ((char)key, cursorPosition);
+ var key = new Rune ((uint)kb.KeyValue);
- if (inserted) {
- CursorRight ();
- }
+ var inserted = provider.InsertAt ((char)key, cursorPosition);
- break;
+ if (inserted) {
+ CursorRight ();
}
- SetNeedsDisplay ();
return true;
}
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 5753c4d27..436d14025 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -913,7 +913,7 @@ namespace Terminal.Gui {
/// Provides autocomplete context menu based on suggestions at the current cursor
/// position. Populate to enable this feature
///
- public Autocomplete Autocomplete { get; protected set; } = new Autocomplete ();
+ public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
#if false
///
@@ -948,6 +948,162 @@ namespace Terminal.Gui {
{
CanFocus = true;
Used = true;
+
+ Initialized += TextView_Initialized;
+
+ // Things this view knows how to do
+ AddCommand (Command.PageDown, () => { ProcessPageDown (); return true; });
+ AddCommand (Command.PageDownExtend, () => { ProcessPageDownExtend (); return true; });
+ AddCommand (Command.PageUp, () => { ProcessPageUp (); return true; });
+ AddCommand (Command.PageUpExtend, () => { ProcessPageUpExtend (); return true; });
+ AddCommand (Command.LineDown, () => { ProcessMoveDown (); return true; });
+ AddCommand (Command.LineDownExtend, () => { ProcessMoveDownExtend (); return true; });
+ AddCommand (Command.LineUp, () => { ProcessMoveUp (); return true; });
+ AddCommand (Command.LineUpExtend, () => { ProcessMoveUpExtend (); return true; });
+ AddCommand (Command.Right, () => ProcessMoveRight ());
+ AddCommand (Command.RightExtend, () => { ProcessMoveRightExtend (); return true; });
+ AddCommand (Command.Left, () => ProcessMoveLeft ());
+ AddCommand (Command.LeftExtend, () => { ProcessMoveLeftExtend (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { ProcessDeleteCharLeft (); return true; });
+ AddCommand (Command.StartOfLine, () => { ProcessMoveStartOfLine (); return true; });
+ AddCommand (Command.StartOfLineExtend, () => { ProcessMoveStartOfLineExtend (); return true; });
+ AddCommand (Command.DeleteCharRight, () => { ProcessDeleteCharRight (); return true; });
+ AddCommand (Command.EndOfLine, () => { ProcessMoveEndOfLine (); return true; });
+ AddCommand (Command.EndOfLineExtend, () => { ProcessMoveEndOfLineExtend (); return true; });
+ AddCommand (Command.CutToEndLine, () => { KillToEndOfLine (); return true; });
+ AddCommand (Command.CutToStartLine, () => { KillToStartOfLine (); return true; });
+ AddCommand (Command.Paste, () => { ProcessPaste (); return true; });
+ AddCommand (Command.ToggleExtend, () => { ToggleSelecting (); return true; });
+ AddCommand (Command.Copy, () => { ProcessCopy (); return true; });
+ AddCommand (Command.Cut, () => { ProcessCut (); return true; });
+ AddCommand (Command.WordLeft, () => { ProcessMoveWordBackward (); return true; });
+ AddCommand (Command.WordLeftExtend, () => { ProcessMoveWordBackwardExtend (); return true; });
+ AddCommand (Command.WordRight, () => { ProcessMoveWordForward (); return true; });
+ AddCommand (Command.WordRightExtend, () => { ProcessMoveWordForwardExtend (); return true; });
+ AddCommand (Command.KillWordForwards, () => { ProcessKillWordForward (); return true; });
+ AddCommand (Command.KillWordBackwards, () => { ProcessKillWordBackward (); return true; });
+ AddCommand (Command.NewLine, () => ProcessReturn ());
+ AddCommand (Command.BottomEnd, () => { MoveBottomEnd (); return true; });
+ AddCommand (Command.BottomEndExtend, () => { MoveBottomEndExtend (); return true; });
+ AddCommand (Command.TopHome, () => { MoveTopHome (); return true; });
+ AddCommand (Command.TopHomeExtend, () => { MoveTopHomeExtend (); return true; });
+ AddCommand (Command.SelectAll, () => { ProcessSelectAll (); return true; });
+ AddCommand (Command.ToggleOverwrite, () => { ProcessSetOverwrite (); return true; });
+ AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; });
+ AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; });
+ AddCommand (Command.Tab, () => ProcessTab ());
+ AddCommand (Command.BackTab, () => ProcessBackTab ());
+ AddCommand (Command.NextView, () => ProcessMoveNextView ());
+ AddCommand (Command.PreviousView, () => ProcessMovePreviousView ());
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+
+ AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
+
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+ AddKeyBinding (((int)'V' + Key.AltMask), Command.PageUp);
+
+ AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
+
+ AddKeyBinding (Key.N | Key.CtrlMask, Command.LineDown);
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+
+ AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
+
+ AddKeyBinding (Key.P | Key.CtrlMask, Command.LineUp);
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+
+ AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
+
+ AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
+ AddKeyBinding (Key.CursorRight, Command.Right);
+
+ AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
+
+ AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+
+ AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
+
+ AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
+ AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+
+ AddKeyBinding (Key.Home, Command.StartOfLine);
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.StartOfLine);
+
+ AddKeyBinding (Key.Home | Key.ShiftMask, Command.StartOfLineExtend);
+
+ AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+ AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+
+ AddKeyBinding (Key.End, Command.EndOfLine);
+ AddKeyBinding (Key.E | Key.CtrlMask, Command.EndOfLine);
+
+ AddKeyBinding (Key.End | Key.ShiftMask, Command.EndOfLineExtend);
+
+ AddKeyBinding (Key.K | Key.CtrlMask, Command.CutToEndLine); // kill-to-end
+ AddKeyBinding (Key.DeleteChar | Key.CtrlMask | Key.ShiftMask, Command.CutToEndLine); // kill-to-end
+
+ AddKeyBinding (Key.K | Key.AltMask, Command.CutToStartLine); // kill-to-start
+ AddKeyBinding (Key.Backspace | Key.CtrlMask | Key.ShiftMask, Command.CutToStartLine); // kill-to-start
+
+ AddKeyBinding (Key.Y | Key.CtrlMask, Command.Paste); // Control-y, yank
+ AddKeyBinding (Key.Space | Key.CtrlMask, Command.ToggleExtend);
+
+ AddKeyBinding (((int)'C' + Key.AltMask), Command.Copy);
+ AddKeyBinding (Key.C | Key.CtrlMask, Command.Copy);
+
+ AddKeyBinding (((int)'W' + Key.AltMask), Command.Cut);
+ AddKeyBinding (Key.W | Key.CtrlMask, Command.Cut);
+ AddKeyBinding (Key.X | Key.CtrlMask, Command.Cut);
+
+ AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.WordLeft);
+ AddKeyBinding ((Key)((int)'B' + Key.AltMask), Command.WordLeft);
+
+ AddKeyBinding (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, Command.WordLeftExtend);
+
+ AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.WordRight);
+ AddKeyBinding ((Key)((int)'F' + Key.AltMask), Command.WordRight);
+
+ AddKeyBinding (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, Command.WordRightExtend);
+ AddKeyBinding (Key.DeleteChar | Key.CtrlMask, Command.KillWordForwards); // kill-word-forwards
+ AddKeyBinding (Key.Backspace | Key.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
+
+ AddKeyBinding (Key.Enter, Command.NewLine);
+ AddKeyBinding (Key.End | Key.CtrlMask, Command.BottomEnd);
+ AddKeyBinding (Key.End | Key.CtrlMask | Key.ShiftMask, Command.BottomEndExtend);
+ AddKeyBinding (Key.Home | Key.CtrlMask, Command.TopHome);
+ AddKeyBinding (Key.Home | Key.CtrlMask | Key.ShiftMask, Command.TopHomeExtend);
+ AddKeyBinding (Key.T | Key.CtrlMask, Command.SelectAll);
+ AddKeyBinding (Key.InsertChar, Command.ToggleOverwrite);
+ AddKeyBinding (Key.Tab, Command.Tab);
+ AddKeyBinding (Key.BackTab | Key.ShiftMask, Command.BackTab);
+
+ AddKeyBinding (Key.Tab | Key.CtrlMask, Command.NextView);
+ AddKeyBinding (Application.AlternateForwardKey, Command.NextView);
+
+ AddKeyBinding (Key.Tab | Key.CtrlMask | Key.ShiftMask, Command.PreviousView);
+ AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousView);
+ }
+
+ void TextView_Initialized (object sender, EventArgs e)
+ {
+ Autocomplete.HostControl = this;
+
+ Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged;
+ Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged;
+ }
+
+ void Top_AlternateBackwardKeyChanged (Key obj)
+ {
+ ReplaceKeyBinding (obj, Application.AlternateBackwardKey);
+ }
+
+ void Top_AlternateForwardKeyChanged (Key obj)
+ {
+ ReplaceKeyBinding (obj, Application.AlternateForwardKey);
}
///
@@ -1119,6 +1275,9 @@ namespace Terminal.Gui {
if (value == wordWrap) {
return;
}
+ if (value && !multiline) {
+ return;
+ }
wordWrap = value;
ResetPosition ();
if (wordWrap) {
@@ -1234,6 +1393,7 @@ namespace Terminal.Gui {
if (!multiline) {
AllowsReturn = false;
AllowsTab = false;
+ WordWrap = false;
currentColumn = 0;
currentRow = 0;
savedHeight = Height;
@@ -1243,6 +1403,7 @@ namespace Terminal.Gui {
}
Height = 1;
LayoutStyle = lyout;
+ Autocomplete.PopupInsideContainer = false;
SetNeedsDisplay ();
} else if (multiline && savedHeight != null) {
var lyout = LayoutStyle;
@@ -1251,6 +1412,7 @@ namespace Terminal.Gui {
}
Height = savedHeight;
LayoutStyle = lyout;
+ Autocomplete.PopupInsideContainer = true;
SetNeedsDisplay ();
}
}
@@ -1786,14 +1948,19 @@ namespace Terminal.Gui {
PositionCursor ();
+ if (SelectedLength > 0)
+ return;
+
// draw autocomplete
- Autocomplete.GenerateSuggestions (this);
+ Autocomplete.GenerateSuggestions ();
var renderAt = new Point (
CursorPosition.X - LeftColumn,
- (CursorPosition.Y + 1) - TopRow);
+ Autocomplete.PopupInsideContainer
+ ? (CursorPosition.Y + 1) - TopRow
+ : 0);
- Autocomplete.RenderOverlay (this, renderAt);
+ Autocomplete.RenderOverlay (renderAt);
}
///
@@ -2050,551 +2217,719 @@ namespace Terminal.Gui {
return true;
}
- int restCount;
- List rest;
+ // Give autocomplete first opportunity to respond to key presses
+ if (SelectedLength == 0 && Autocomplete.ProcessKey (kb)) {
+ return true;
+ }
+ var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (kb),
+ new KeyModifiers () { Alt = kb.IsAlt, Ctrl = kb.IsCtrl, Shift = kb.IsShift }));
+ if (result != null)
+ return (bool)result;
+
+ ResetColumnTrack ();
+ // Ignore control characters and other special keys
+ if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+ return false;
+
+ InsertText (kb);
+ DoNeededAction ();
+
+ return true;
+ }
+
+ bool ProcessMovePreviousView ()
+ {
+ ResetColumnTrack ();
+ return MovePreviousView ();
+ }
+
+ bool ProcessMoveNextView ()
+ {
+ ResetColumnTrack ();
+ return MoveNextView ();
+ }
+
+ void ProcessSetOverwrite ()
+ {
+ ResetColumnTrack ();
+ SetOverwrite (!Used);
+ }
+
+ void ProcessSelectAll ()
+ {
+ ResetColumnTrack ();
+ SelectAll ();
+ }
+
+ void MoveTopHomeExtend ()
+ {
+ ResetColumnTrack ();
+ StartSelecting ();
+ MoveHome ();
+ }
+
+ void MoveTopHome ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveHome ();
+ }
+
+ void MoveBottomEndExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveEnd ();
+ }
+
+ void MoveBottomEnd ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveEnd ();
+ }
+
+ void ProcessKillWordBackward ()
+ {
+ ResetColumnTrack ();
+ KillWordBackward ();
+ }
+
+ void ProcessKillWordForward ()
+ {
+ ResetColumnTrack ();
+ KillWordForward ();
+ }
+
+ void ProcessMoveWordForwardExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveWordForward ();
+ }
+
+ void ProcessMoveWordForward ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveWordForward ();
+ }
+
+ void ProcessMoveWordBackwardExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveWordBackward ();
+ }
+
+ void ProcessMoveWordBackward ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveWordBackward ();
+ }
+
+ void ProcessCut ()
+ {
+ ResetColumnTrack ();
+ Cut ();
+ }
+
+ void ProcessCopy ()
+ {
+ ResetColumnTrack ();
+ Copy ();
+ }
+
+ void ToggleSelecting ()
+ {
+ ResetColumnTrack ();
+ selecting = !selecting;
+ selectionStartColumn = currentColumn;
+ selectionStartRow = currentRow;
+ }
+
+ void ProcessPaste ()
+ {
+ ResetColumnTrack ();
+ if (isReadOnly)
+ return;
+ Paste ();
+ }
+
+ void ProcessMoveEndOfLineExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveEndOfLine ();
+ }
+
+ void ProcessMoveEndOfLine ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveEndOfLine ();
+ }
+
+ void ProcessDeleteCharRight ()
+ {
+ ResetColumnTrack ();
+ DeleteCharRight ();
+ }
+
+ void ProcessMoveStartOfLineExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveStartOfLine ();
+ }
+
+ void ProcessMoveStartOfLine ()
+ {
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveStartOfLine ();
+ }
+
+ void ProcessDeleteCharLeft ()
+ {
+ ResetColumnTrack ();
+ DeleteCharLeft ();
+ }
+
+ void ProcessMoveLeftExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveLeft ();
+ }
+
+ bool ProcessMoveLeft ()
+ {
// if the user presses Left (without any control keys) and they are at the start of the text
- if (kb.Key == Key.CursorLeft && currentColumn == 0 && currentRow == 0) {
+ if (currentColumn == 0 && currentRow == 0) {
// do not respond (this lets the key press fall through to navigation system - which usually changes focus backward)
return false;
}
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveLeft ();
+ return true;
+ }
+
+ void ProcessMoveRightExtend ()
+ {
+ ResetAllTrack ();
+ StartSelecting ();
+ MoveRight ();
+ }
+
+ bool ProcessMoveRight ()
+ {
// if the user presses Right (without any control keys)
- if (kb.Key == Key.CursorRight) {
+ // determine where the last cursor position in the text is
+ var lastRow = model.Count - 1;
+ var lastCol = model.GetLine (lastRow).Count;
- // determine where the last cursor position in the text is
- var lastRow = model.Count - 1;
- var lastCol = model.GetLine (lastRow).Count;
-
- // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward)
- if (currentColumn == lastCol && currentRow == lastRow) {
- return false;
- }
+ // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward)
+ if (currentColumn == lastCol && currentRow == lastRow) {
+ return false;
}
- // Give autocomplete first opportunity to respond to key presses
- if (Autocomplete.ProcessKey (this, kb)) {
- return true;
+ ResetAllTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveRight ();
+ return true;
+ }
+
+ void ProcessMoveUpExtend ()
+ {
+ ResetColumnTrack ();
+ StartSelecting ();
+ MoveUp ();
+ }
+
+ void ProcessMoveUp ()
+ {
+ ResetContinuousFindTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveUp ();
+ }
+
+ void ProcessMoveDownExtend ()
+ {
+ ResetColumnTrack ();
+ StartSelecting ();
+ MoveDown ();
+ }
+
+ void ProcessMoveDown ()
+ {
+ ResetContinuousFindTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MoveDown ();
+ }
+
+ void ProcessPageUpExtend ()
+ {
+ ResetColumnTrack ();
+ StartSelecting ();
+ MovePageUp ();
+ }
+
+ void ProcessPageUp ()
+ {
+ ResetColumnTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MovePageUp ();
+ }
+
+ void ProcessPageDownExtend ()
+ {
+ ResetColumnTrack ();
+ StartSelecting ();
+ MovePageDown ();
+ }
+
+ void ProcessPageDown ()
+ {
+ ResetColumnTrack ();
+ if (shiftSelecting && selecting) {
+ StopSelecting ();
+ }
+ MovePageDown ();
+ }
+
+ bool MovePreviousView ()
+ {
+ if (Application.MdiTop != null) {
+ return SuperView?.FocusPrev () == true;
}
- // Handle some state here - whether the last command was a kill
- // operation and the column tracking (up/down)
- switch (kb.Key) {
- case Key.N | Key.CtrlMask:
- case Key.CursorDown:
- case Key.P | Key.CtrlMask:
- case Key.CursorUp:
- lastWasKill = false;
- continuousFind = false;
- break;
- case Key.K | Key.CtrlMask:
- break;
- case Key.F | Key.CtrlMask:
- case Key.B | Key.CtrlMask:
- case (Key)((int)'B' + Key.AltMask):
- case Key.A | Key.CtrlMask:
- case Key.E | Key.CtrlMask:
- case Key.CursorRight:
- case Key.CursorLeft:
- case Key.CursorRight | Key.CtrlMask:
- case Key.CursorLeft | Key.CtrlMask:
- case Key.CursorRight | Key.ShiftMask:
- case Key.CursorLeft | Key.ShiftMask:
- case Key.CursorRight | Key.CtrlMask | Key.ShiftMask:
- case Key.CursorLeft | Key.CtrlMask | Key.ShiftMask:
- case Key.Home:
- case Key.Home | Key.CtrlMask:
- case Key.Home | Key.ShiftMask:
- case Key.Home | Key.CtrlMask | Key.ShiftMask:
- case Key.End:
- case Key.End | Key.CtrlMask:
- case Key.End | Key.ShiftMask:
- case Key.End | Key.CtrlMask | Key.ShiftMask:
- lastWasKill = false;
- columnTrack = -1;
- continuousFind = false;
- break;
- default:
- lastWasKill = false;
- columnTrack = -1;
- break;
+ return false;
+ }
+
+ bool MoveNextView ()
+ {
+ if (Application.MdiTop != null) {
+ return SuperView?.FocusNext () == true;
}
- // Dispatch the command.
- switch (kb.Key) {
- case Key.PageDown:
- case Key.V | Key.CtrlMask:
- case Key.PageDown | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- int nPageDnShift = Frame.Height - 1;
- if (currentRow >= 0 && currentRow < model.Count) {
- if (columnTrack == -1)
- columnTrack = currentColumn;
- currentRow = (currentRow + nPageDnShift) > model.Count
- ? model.Count > 0 ? model.Count - 1 : 0
- : currentRow + nPageDnShift;
- if (topRow < currentRow - nPageDnShift) {
- topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift;
- SetNeedsDisplay ();
- }
- TrackColumn ();
- PositionCursor ();
- }
- break;
+ return false;
+ }
- case Key.PageUp:
- case ((int)'V' + Key.AltMask):
- case Key.PageUp | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- int nPageUpShift = Frame.Height - 1;
- if (currentRow > 0) {
- if (columnTrack == -1)
- columnTrack = currentColumn;
- currentRow = currentRow - nPageUpShift < 0 ? 0 : currentRow - nPageUpShift;
- if (currentRow < topRow) {
- topRow = topRow - nPageUpShift < 0 ? 0 : topRow - nPageUpShift;
- SetNeedsDisplay ();
- }
- TrackColumn ();
- PositionCursor ();
- }
- break;
+ bool ProcessBackTab ()
+ {
+ ResetColumnTrack ();
- case Key.N | Key.CtrlMask:
- case Key.CursorDown:
- case Key.CursorDown | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- MoveDown ();
- break;
-
- case Key.P | Key.CtrlMask:
- case Key.CursorUp:
- case Key.CursorUp | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- MoveUp ();
- break;
-
- case Key.F | Key.CtrlMask:
- case Key.CursorRight:
- case Key.CursorRight | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
+ if (!AllowsTab) {
+ return false;
+ }
+ if (currentColumn > 0) {
var currentLine = GetCurrentLine ();
- if (currentColumn < currentLine.Count) {
- currentColumn++;
- } else {
- if (currentRow + 1 < model.Count) {
- currentRow++;
- currentColumn = 0;
- if (currentRow >= topRow + Frame.Height) {
- topRow++;
- SetNeedsDisplay ();
- }
- }
- }
- Adjust ();
- return true;
-
- case Key.B | Key.CtrlMask:
- case Key.CursorLeft:
- case Key.CursorLeft | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- if (currentColumn > 0) {
+ if (currentLine.Count > 0 && currentLine [currentColumn - 1] == '\t') {
+ currentLine.RemoveAt (currentColumn - 1);
currentColumn--;
- } else {
- if (currentRow > 0) {
- currentRow--;
- if (currentRow < topRow) {
- topRow--;
- SetNeedsDisplay ();
- }
- currentLine = GetCurrentLine ();
- currentColumn = currentLine.Count;
- }
}
- Adjust ();
- break;
+ }
+ DoNeededAction ();
+ return true;
+ }
- case Key.Delete:
- case Key.Backspace:
- if (isReadOnly)
- break;
- if (selecting) {
- ClearSelectedRegion ();
- return true;
- }
- if (DeleteTextBackwards ()) {
- return true;
- }
- break;
+ bool ProcessTab ()
+ {
+ ResetColumnTrack ();
- // Home, C-A
- case Key.Home:
- case Key.Home | Key.ShiftMask:
- case Key.A | Key.CtrlMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- currentColumn = 0;
+ if (!AllowsTab) {
+ return false;
+ }
+ InsertText (new KeyEvent ((Key)'\t', null));
+ DoNeededAction ();
+ return true;
+ }
+
+ void SetOverwrite (bool overwrite)
+ {
+ Used = overwrite;
+ SetNeedsDisplay ();
+ DoNeededAction ();
+ }
+
+ bool ProcessReturn ()
+ {
+ ResetColumnTrack ();
+
+ if (!AllowsReturn) {
+ return false;
+ }
+ if (isReadOnly)
+ return true;
+ var currentLine = GetCurrentLine ();
+ var restCount = currentLine.Count - currentColumn;
+ var rest = currentLine.GetRange (currentColumn, restCount);
+ currentLine.RemoveRange (currentColumn, restCount);
+ model.AddLine (currentRow + 1, rest);
+ if (wordWrap) {
+ wrapManager.AddLine (currentRow, currentColumn);
+ wrapNeeded = true;
+ }
+ currentRow++;
+ bool fullNeedsDisplay = false;
+ if (currentRow >= topRow + Frame.Height) {
+ topRow++;
+ fullNeedsDisplay = true;
+ }
+ currentColumn = 0;
+ if (!wordWrap && currentColumn < leftColumn) {
+ fullNeedsDisplay = true;
leftColumn = 0;
- Adjust ();
- break;
- case Key.DeleteChar:
- case Key.D | Key.CtrlMask: // Delete
- if (isReadOnly)
- break;
- if (selecting) {
- ClearSelectedRegion ();
- return true;
- }
- if (DeleteTextForwards ()) {
- return true;
- }
- break;
+ }
- case Key.End:
- case Key.End | Key.ShiftMask:
- case Key.E | Key.CtrlMask: // End
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- currentLine = GetCurrentLine ();
- currentColumn = currentLine.Count;
- Adjust ();
- return true;
+ if (fullNeedsDisplay)
+ SetNeedsDisplay ();
+ else
+ SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height));
- case Key.K | Key.CtrlMask: // kill-to-end
- case Key.DeleteChar | Key.CtrlMask | Key.ShiftMask:
- if (isReadOnly)
- break;
- currentLine = GetCurrentLine ();
- var setLastWasKill = true;
- if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
- DeleteTextForwards ();
- return true;
- }
- if (currentLine.Count == 0) {
- if (currentRow < model.Count - 1) {
- model.RemoveLine (currentRow);
- }
- if (model.Count > 0 || lastWasKill) {
- var val = ustring.Make ((Rune)'\n');
- if (lastWasKill) {
- AppendClipboard (val);
- } else {
- SetClipboard (val);
- }
- }
- if (model.Count == 0) {
- // Prevents from adding line feeds if there is no more lines.
- setLastWasKill = false;
- }
- } else {
- restCount = currentLine.Count - currentColumn;
- rest = currentLine.GetRange (currentColumn, restCount);
- var val = ustring.Empty;
- if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
- val = ustring.Make ((Rune)'\n');
- }
- val += StringFromRunes (rest);
- if (lastWasKill) {
- AppendClipboard (val);
- } else {
- SetClipboard (val);
- }
- currentLine.RemoveRange (currentColumn, restCount);
- }
- SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
- lastWasKill = setLastWasKill;
- break;
+ DoNeededAction ();
+ return true;
+ }
- case Key.K | Key.AltMask: // kill-to-start
- if (isReadOnly)
- break;
- currentLine = GetCurrentLine ();
- setLastWasKill = true;
- if (currentLine.Count > 0 && currentColumn == 0) {
- DeleteTextBackwards ();
- return true;
- }
- if (currentLine.Count == 0) {
- if (currentRow > 0) {
- model.RemoveLine (currentRow);
- currentRow--;
- currentLine = model.GetLine (currentRow);
- currentColumn = currentLine.Count;
- }
- } else {
- restCount = currentColumn;
- rest = currentLine.GetRange (0, restCount);
- var val = ustring.Empty;
- if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
- val = ustring.Make ((Rune)'\n');
- }
- val += StringFromRunes (rest);
- if (lastWasKill) {
- AppendClipboard (val);
- } else {
- SetClipboard (val);
- }
- currentLine.RemoveRange (0, restCount);
- currentColumn = 0;
- }
- SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
- lastWasKill = setLastWasKill;
- break;
-
- case Key.Y | Key.CtrlMask: // Control-y, yank
- if (isReadOnly)
- break;
- Paste ();
- return true;
-
- case Key.Space | Key.CtrlMask:
- selecting = !selecting;
- selectionStartColumn = currentColumn;
- selectionStartRow = currentRow;
- break;
-
- case ((int)'C' + Key.AltMask):
- case Key.C | Key.CtrlMask:
- Copy ();
- return true;
-
- case ((int)'W' + Key.AltMask):
- case Key.W | Key.CtrlMask:
- case Key.X | Key.CtrlMask:
- Cut ();
- return true;
-
- case Key.CtrlMask | Key.CursorLeft:
- case Key.CtrlMask | Key.CursorLeft | Key.ShiftMask:
- case (Key)((int)'B' + Key.AltMask):
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- var newPos = WordBackward (currentColumn, currentRow);
- if (newPos.HasValue) {
- currentColumn = newPos.Value.col;
- currentRow = newPos.Value.row;
- }
- Adjust ();
-
- break;
-
- case Key.CtrlMask | Key.CursorRight:
- case Key.CtrlMask | Key.CursorRight | Key.ShiftMask:
- case (Key)((int)'F' + Key.AltMask):
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- newPos = WordForward (currentColumn, currentRow);
- if (newPos.HasValue) {
- currentColumn = newPos.Value.col;
- currentRow = newPos.Value.row;
- }
- Adjust ();
- break;
-
- case Key.DeleteChar | Key.CtrlMask: // kill-word-forwards
- if (isReadOnly)
- break;
- currentLine = GetCurrentLine ();
- if (currentLine.Count == 0 || currentColumn == currentLine.Count) {
- DeleteTextForwards ();
- return true;
- }
- newPos = WordForward (currentColumn, currentRow);
- restCount = 0;
- if (newPos.HasValue && currentRow == newPos.Value.row) {
- restCount = newPos.Value.col - currentColumn;
- currentLine.RemoveRange (currentColumn, restCount);
- } else if (newPos.HasValue) {
- restCount = currentLine.Count - currentColumn;
- currentLine.RemoveRange (currentColumn, restCount);
+ void KillWordBackward ()
+ {
+ if (isReadOnly)
+ return;
+ var currentLine = GetCurrentLine ();
+ if (currentColumn == 0) {
+ DeleteTextBackwards ();
+ return;
+ }
+ var newPos = WordBackward (currentColumn, currentRow);
+ if (newPos.HasValue && currentRow == newPos.Value.row) {
+ var restCount = currentColumn - newPos.Value.col;
+ currentLine.RemoveRange (newPos.Value.col, restCount);
+ if (wordWrap && wrapManager.RemoveRange (currentRow, newPos.Value.col, restCount)) {
+ wrapNeeded = true;
}
+ currentColumn = newPos.Value.col;
+ } else if (newPos.HasValue) {
+ var restCount = currentLine.Count - currentColumn;
+ currentLine.RemoveRange (currentColumn, restCount);
if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
wrapNeeded = true;
}
- if (wrapNeeded) {
- SetNeedsDisplay ();
- } else {
- SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
- }
- break;
-
- case Key.Backspace | Key.CtrlMask: // kill-word-backwards
- if (isReadOnly)
- break;
- currentLine = GetCurrentLine ();
- if (currentColumn == 0) {
- DeleteTextBackwards ();
- return true;
- }
- newPos = WordBackward (currentColumn, currentRow);
- if (newPos.HasValue && currentRow == newPos.Value.row) {
- restCount = currentColumn - newPos.Value.col;
- currentLine.RemoveRange (newPos.Value.col, restCount);
- if (wordWrap && wrapManager.RemoveRange (currentRow, newPos.Value.col, restCount)) {
- wrapNeeded = true;
- }
- currentColumn = newPos.Value.col;
- } else if (newPos.HasValue) {
- restCount = currentLine.Count - currentColumn;
- currentLine.RemoveRange (currentColumn, restCount);
- if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
- wrapNeeded = true;
- }
- currentColumn = newPos.Value.col;
- currentRow = newPos.Value.row;
- }
- if (wrapNeeded) {
- SetNeedsDisplay ();
- } else {
- SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
- }
- break;
-
- case Key.Enter:
- if (!AllowsReturn) {
- return false;
- }
- if (isReadOnly)
- break;
- currentLine = GetCurrentLine ();
- restCount = currentLine.Count - currentColumn;
- rest = currentLine.GetRange (currentColumn, restCount);
- currentLine.RemoveRange (currentColumn, restCount);
- model.AddLine (currentRow + 1, rest);
- if (wordWrap) {
- wrapManager.AddLine (currentRow, currentColumn);
- wrapNeeded = true;
- }
- currentRow++;
- bool fullNeedsDisplay = false;
- if (currentRow >= topRow + Frame.Height) {
- topRow++;
- fullNeedsDisplay = true;
- }
- currentColumn = 0;
- if (!wordWrap && currentColumn < leftColumn) {
- fullNeedsDisplay = true;
- leftColumn = 0;
- }
-
- if (fullNeedsDisplay)
- SetNeedsDisplay ();
- else
- SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height));
- break;
-
- case Key.CtrlMask | Key.End:
- case Key.CtrlMask | Key.End | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- MoveEnd ();
- break;
-
- case Key.CtrlMask | Key.Home:
- case Key.CtrlMask | Key.Home | Key.ShiftMask:
- if (kb.Key.HasFlag (Key.ShiftMask)) {
- StartSelecting ();
- } else if (shiftSelecting && selecting) {
- StopSelecting ();
- }
- MoveHome ();
- break;
-
- case Key.T | Key.CtrlMask:
- SelectAll ();
- break;
-
- case Key.InsertChar:
- Used = !Used;
+ currentColumn = newPos.Value.col;
+ currentRow = newPos.Value.row;
+ }
+ if (wrapNeeded) {
SetNeedsDisplay ();
- break;
-
- case Key _ when ShortcutHelper.GetModifiersKey (kb) == Key.Tab:
- if (!AllowsTab) {
- return false;
- }
- InsertText (new KeyEvent ((Key)'\t', null));
- break;
-
- case Key _ when (ShortcutHelper.GetModifiersKey (kb) == (Key.BackTab | Key.ShiftMask)):
- if (!AllowsTab) {
- return false;
- }
- if (currentColumn > 0) {
- currentLine = GetCurrentLine ();
- if (currentLine.Count > 0 && currentLine [currentColumn - 1] == '\t') {
- currentLine.RemoveAt (currentColumn - 1);
- currentColumn--;
- }
- }
- break;
-
- case Key _ when ShortcutHelper.GetModifiersKey (kb) == (Key.Tab | Key.CtrlMask):
- case Key _ when ShortcutHelper.GetModifiersKey (kb) == Application.AlternateForwardKey:
- if (Application.MdiTop != null) {
- return SuperView?.FocusNext () == true;
- }
-
- return false;
-
- case Key _ when ShortcutHelper.GetModifiersKey (kb) == (Key.Tab | Key.CtrlMask | Key.ShiftMask):
- case Key _ when ShortcutHelper.GetModifiersKey (kb) == Application.AlternateBackwardKey:
- if (Application.MdiTop != null) {
- return SuperView?.FocusPrev () == true;
- }
-
- return false;
-
- default:
- // Ignore control characters and other special keys
- if (kb.Key < Key.Space || kb.Key > Key.CharMask)
- return false;
-
- InsertText (kb);
- break;
+ } else {
+ SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
}
DoNeededAction ();
+ }
- return true;
+ void KillWordForward ()
+ {
+ if (isReadOnly)
+ return;
+ var currentLine = GetCurrentLine ();
+ if (currentLine.Count == 0 || currentColumn == currentLine.Count) {
+ DeleteTextForwards ();
+ return;
+ }
+ var newPos = WordForward (currentColumn, currentRow);
+ var restCount = 0;
+ if (newPos.HasValue && currentRow == newPos.Value.row) {
+ restCount = newPos.Value.col - currentColumn;
+ currentLine.RemoveRange (currentColumn, restCount);
+ } else if (newPos.HasValue) {
+ restCount = currentLine.Count - currentColumn;
+ currentLine.RemoveRange (currentColumn, restCount);
+ }
+ if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
+ wrapNeeded = true;
+ }
+ if (wrapNeeded) {
+ SetNeedsDisplay ();
+ } else {
+ SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+ }
+ DoNeededAction ();
+ }
+
+ void MoveWordForward ()
+ {
+ var newPos = WordForward (currentColumn, currentRow);
+ if (newPos.HasValue) {
+ currentColumn = newPos.Value.col;
+ currentRow = newPos.Value.row;
+ }
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void MoveWordBackward ()
+ {
+ var newPos = WordBackward (currentColumn, currentRow);
+ if (newPos.HasValue) {
+ currentColumn = newPos.Value.col;
+ currentRow = newPos.Value.row;
+ }
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void KillToStartOfLine ()
+ {
+ ResetColumnTrack ();
+ if (isReadOnly)
+ return;
+ var currentLine = GetCurrentLine ();
+ var setLastWasKill = true;
+ if (currentLine.Count > 0 && currentColumn == 0) {
+ DeleteTextBackwards ();
+ return;
+ }
+ if (currentLine.Count == 0) {
+ if (currentRow > 0) {
+ model.RemoveLine (currentRow);
+ currentRow--;
+ currentLine = model.GetLine (currentRow);
+ currentColumn = currentLine.Count;
+ }
+ } else {
+ var restCount = currentColumn;
+ var rest = currentLine.GetRange (0, restCount);
+ var val = ustring.Empty;
+ if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
+ val = ustring.Make ((Rune)'\n');
+ }
+ val += StringFromRunes (rest);
+ if (lastWasKill) {
+ AppendClipboard (val);
+ } else {
+ SetClipboard (val);
+ }
+ currentLine.RemoveRange (0, restCount);
+ currentColumn = 0;
+ }
+ SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+ lastWasKill = setLastWasKill;
+ DoNeededAction ();
+ }
+
+ void KillToEndOfLine ()
+ {
+ ResetColumnTrack ();
+ if (isReadOnly)
+ return;
+ var currentLine = GetCurrentLine ();
+ var setLastWasKill = true;
+ if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
+ DeleteTextForwards ();
+ return;
+ }
+ if (currentLine.Count == 0) {
+ if (currentRow < model.Count - 1) {
+ model.RemoveLine (currentRow);
+ }
+ if (model.Count > 0 || lastWasKill) {
+ var val = ustring.Make ((Rune)'\n');
+ if (lastWasKill) {
+ AppendClipboard (val);
+ } else {
+ SetClipboard (val);
+ }
+ }
+ if (model.Count == 0) {
+ // Prevents from adding line feeds if there is no more lines.
+ setLastWasKill = false;
+ }
+ } else {
+ var restCount = currentLine.Count - currentColumn;
+ var rest = currentLine.GetRange (currentColumn, restCount);
+ var val = ustring.Empty;
+ if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
+ val = ustring.Make ((Rune)'\n');
+ }
+ val += StringFromRunes (rest);
+ if (lastWasKill) {
+ AppendClipboard (val);
+ } else {
+ SetClipboard (val);
+ }
+ currentLine.RemoveRange (currentColumn, restCount);
+ }
+ SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+ lastWasKill = setLastWasKill;
+ DoNeededAction ();
+ }
+
+ void MoveEndOfLine ()
+ {
+ var currentLine = GetCurrentLine ();
+ currentColumn = currentLine.Count;
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void MoveStartOfLine ()
+ {
+ currentColumn = 0;
+ leftColumn = 0;
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void DeleteCharRight ()
+ {
+ if (isReadOnly)
+ return;
+ if (selecting) {
+ ClearSelectedRegion ();
+ return;
+ }
+ if (DeleteTextForwards ()) {
+ return;
+ }
+ DoNeededAction ();
+ }
+
+ void DeleteCharLeft ()
+ {
+ if (isReadOnly)
+ return;
+ if (selecting) {
+ ClearSelectedRegion ();
+ return;
+ }
+ if (DeleteTextBackwards ()) {
+ return;
+ }
+ DoNeededAction ();
+ }
+
+ void MoveLeft ()
+ {
+ if (currentColumn > 0) {
+ currentColumn--;
+ } else {
+ if (currentRow > 0) {
+ currentRow--;
+ if (currentRow < topRow) {
+ topRow--;
+ SetNeedsDisplay ();
+ }
+ var currentLine = GetCurrentLine ();
+ currentColumn = currentLine.Count;
+ }
+ }
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void MoveRight ()
+ {
+ var currentLine = GetCurrentLine ();
+ if (currentColumn < currentLine.Count) {
+ currentColumn++;
+ } else {
+ if (currentRow + 1 < model.Count) {
+ currentRow++;
+ currentColumn = 0;
+ if (currentRow >= topRow + Frame.Height) {
+ topRow++;
+ SetNeedsDisplay ();
+ }
+ }
+ }
+ Adjust ();
+ DoNeededAction ();
+ }
+
+ void MovePageUp ()
+ {
+ int nPageUpShift = Frame.Height - 1;
+ if (currentRow > 0) {
+ if (columnTrack == -1)
+ columnTrack = currentColumn;
+ currentRow = currentRow - nPageUpShift < 0 ? 0 : currentRow - nPageUpShift;
+ if (currentRow < topRow) {
+ topRow = topRow - nPageUpShift < 0 ? 0 : topRow - nPageUpShift;
+ SetNeedsDisplay ();
+ }
+ TrackColumn ();
+ PositionCursor ();
+ }
+ DoNeededAction ();
+ }
+
+ void MovePageDown ()
+ {
+ int nPageDnShift = Frame.Height - 1;
+ if (currentRow >= 0 && currentRow < model.Count) {
+ if (columnTrack == -1)
+ columnTrack = currentColumn;
+ currentRow = (currentRow + nPageDnShift) > model.Count
+ ? model.Count > 0 ? model.Count - 1 : 0
+ : currentRow + nPageDnShift;
+ if (topRow < currentRow - nPageDnShift) {
+ topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift;
+ SetNeedsDisplay ();
+ }
+ TrackColumn ();
+ PositionCursor ();
+ }
+ DoNeededAction ();
+ }
+
+ void ResetContinuousFindTrack ()
+ {
+ // Handle some state here - whether the last command was a kill
+ // operation and the column tracking (up/down)
+ lastWasKill = false;
+ continuousFind = false;
+ }
+
+ void ResetColumnTrack ()
+ {
+ // Handle some state here - whether the last command was a kill
+ // operation and the column tracking (up/down)
+ lastWasKill = false;
+ columnTrack = -1;
+ }
+
+ void ResetAllTrack ()
+ {
+ // Handle some state here - whether the last command was a kill
+ // operation and the column tracking (up/down)
+ lastWasKill = false;
+ columnTrack = -1;
+ continuousFind = false;
}
bool InsertText (KeyEvent kb)
@@ -2820,6 +3155,7 @@ namespace Terminal.Gui {
TrackColumn ();
PositionCursor ();
}
+ DoNeededAction ();
}
void MoveDown ()
@@ -2838,6 +3174,7 @@ namespace Terminal.Gui {
} else if (currentRow > Frame.Height) {
Adjust ();
}
+ DoNeededAction ();
}
IEnumerable<(int col, int row, Rune rune)> ForwardIterator (int col, int row)
@@ -3094,6 +3431,11 @@ namespace Terminal.Gui {
continuousFind = false;
+ // Give autocomplete first opportunity to respond to mouse clicks
+ if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
+ return true;
+ }
+
if (ev.Flags == MouseFlags.Button1Clicked) {
if (shiftSelecting) {
shiftSelecting = false;
@@ -3250,4 +3592,32 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Renders an overlay on another view at a given point that allows selecting
+ /// from a range of 'autocomplete' options.
+ /// An implementation on a TextView.
+ ///
+ public class TextViewAutocomplete : Autocomplete {
+
+ ///
+ protected override string GetCurrentWord ()
+ {
+ var host = (TextView)HostControl;
+ var currentLine = host.GetCurrentLine ();
+ var cursorPosition = Math.Min (host.CurrentColumn, currentLine.Count);
+ return IdxToWord (currentLine, cursorPosition);
+ }
+
+ ///
+ protected override void DeleteTextBackwards ()
+ {
+ ((TextView)HostControl).DeleteTextBackwards ();
+ }
+
+ ///
+ protected override void InsertText (string accepted)
+ {
+ ((TextView)HostControl).InsertText (accepted);
+ }
+ }
}
diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs
index bf22d9fec..1675a295a 100644
--- a/Terminal.Gui/Views/TimeField.cs
+++ b/Terminal.Gui/Views/TimeField.cs
@@ -26,8 +26,8 @@ namespace Terminal.Gui {
string longFormat;
string shortFormat;
- int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
- string Format { get { return isShort ? shortFormat : longFormat; } }
+ int fieldLen => isShort ? shortFieldLen : longFieldLen;
+ string format => isShort ? shortFormat : longFormat;
///
/// TimeChanged event, raised when the Date has changed.
@@ -49,8 +49,7 @@ namespace Terminal.Gui {
/// If true, the seconds are hidden. Sets the property.
public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
{
- this.isShort = isShort;
- Initialize (time);
+ Initialize (time, isShort);
}
///
@@ -59,8 +58,7 @@ namespace Terminal.Gui {
/// Initial time
public TimeField (TimeSpan time) : base (string.Empty)
{
- this.isShort = true;
- Width = FieldLen + 2;
+ Width = fieldLen + 2;
Initialize (time);
}
@@ -69,21 +67,49 @@ namespace Terminal.Gui {
///
public TimeField () : this (time: TimeSpan.MinValue) { }
- void Initialize (TimeSpan time)
+ void Initialize (TimeSpan time, bool isShort = false)
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
shortFormat = $" hh\\{sepChar}mm";
- CursorPosition = 1;
+ this.isShort = isShort;
Time = time;
+ CursorPosition = 1;
TextChanged += TextField_TextChanged;
+
+ // Things this view knows how to do
+ AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
+ AddCommand (Command.LeftHome, () => MoveHome ());
+ AddCommand (Command.Left, () => MoveLeft ());
+ AddCommand (Command.RightEnd, () => MoveEnd ());
+ AddCommand (Command.Right, () => MoveRight ());
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+ AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+
+ AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
+ AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+
+ AddKeyBinding (Key.Home, Command.LeftHome);
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
+
+ AddKeyBinding (Key.CursorLeft, Command.Left);
+ AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+
+ AddKeyBinding (Key.End, Command.RightEnd);
+ AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
+
+ AddKeyBinding (Key.CursorRight, Command.Right);
+ AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
}
void TextField_TextChanged (ustring e)
{
try {
- if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
+ if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
Text = e;
} catch (Exception) {
Text = e;
@@ -105,8 +131,8 @@ namespace Terminal.Gui {
var oldTime = time;
time = value;
- this.Text = " " + value.ToString (Format.Trim ());
- var args = new DateTimeEventArgs (oldTime, value, Format);
+ this.Text = " " + value.ToString (format.Trim ());
+ var args = new DateTimeEventArgs (oldTime, value, format);
if (oldTime != value) {
OnTimeChanged (args);
}
@@ -133,12 +159,20 @@ namespace Terminal.Gui {
}
}
+ ///
+ public override int CursorPosition {
+ get => base.CursorPosition;
+ set {
+ base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
+ }
+ }
+
bool SetText (Rune key)
{
var text = TextModel.ToRunes (Text);
var newText = text.GetRange (0, CursorPosition);
newText.Add (key);
- if (CursorPosition < FieldLen)
+ if (CursorPosition < fieldLen)
newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
return SetText (ustring.Make (newText));
}
@@ -183,7 +217,7 @@ namespace Terminal.Gui {
}
string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
- if (!TimeSpan.TryParseExact (t.Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
+ if (!TimeSpan.TryParseExact (t.Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
!isValidTime)
return false;
Time = result;
@@ -192,7 +226,7 @@ namespace Terminal.Gui {
void IncCursorPosition ()
{
- if (CursorPosition == FieldLen)
+ if (CursorPosition == fieldLen)
return;
if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
CursorPosition++;
@@ -215,60 +249,69 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent kb)
{
- switch (kb.Key) {
- case Key.DeleteChar:
- case Key.D | Key.CtrlMask:
- if (ReadOnly)
- return true;
+ var result = InvokeKeybindings (kb);
+ if (result != null)
+ return (bool)result;
- SetText ('0');
- break;
+ // Ignore non-numeric characters.
+ if (kb.Key < (Key)((int)Key.D0) || kb.Key > (Key)((int)Key.D9))
+ return false;
- case Key.Delete:
- case Key.Backspace:
- if (ReadOnly)
- return true;
-
- SetText ('0');
- DecCursorPosition ();
- break;
-
- // Home, C-A
- case Key.Home:
- case Key.A | Key.CtrlMask:
- CursorPosition = 1;
- break;
-
- case Key.CursorLeft:
- case Key.B | Key.CtrlMask:
- DecCursorPosition ();
- break;
-
- case Key.End:
- case Key.E | Key.CtrlMask: // End
- CursorPosition = FieldLen;
- break;
-
- case Key.CursorRight:
- case Key.F | Key.CtrlMask:
- IncCursorPosition ();
- break;
-
- default:
- // Ignore non-numeric characters.
- if (kb.Key < (Key)((int)Key.D0) || kb.Key > (Key)((int)Key.D9))
- return false;
-
- if (ReadOnly)
- return true;
-
- if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
- IncCursorPosition ();
+ if (ReadOnly)
return true;
- }
+
+ if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
+ IncCursorPosition ();
+
return true;
}
+ bool MoveRight ()
+ {
+ IncCursorPosition ();
+ return true;
+ }
+
+ bool MoveEnd ()
+ {
+ CursorPosition = fieldLen;
+ return true;
+ }
+
+ bool MoveLeft ()
+ {
+ DecCursorPosition ();
+ return true;
+ }
+
+ bool MoveHome ()
+ {
+ // Home, C-A
+ CursorPosition = 1;
+ return true;
+ }
+
+ ///
+ public override void DeleteCharLeft (bool useOldCursorPos = true)
+ {
+ if (ReadOnly)
+ return;
+
+ SetText ('0');
+ DecCursorPosition ();
+ return;
+ }
+
+ ///
+ public override void DeleteCharRight ()
+ {
+ if (ReadOnly)
+ return;
+
+ SetText ('0');
+ return;
+ }
+
///
public override bool MouseEvent (MouseEvent ev)
{
@@ -278,8 +321,8 @@ namespace Terminal.Gui {
SetFocus ();
var point = ev.X;
- if (point > FieldLen)
- point = FieldLen;
+ if (point > fieldLen)
+ point = fieldLen;
if (point < 1)
point = 1;
CursorPosition = point;
diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs
index bcea9739a..b8908bd8c 100644
--- a/Terminal.Gui/Views/TreeView.cs
+++ b/Terminal.Gui/Views/TreeView.cs
@@ -2,11 +2,11 @@
// by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design
// and code to be used in this library under the MIT license.
+using NStack;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using NStack;
using Terminal.Gui.Trees;
namespace Terminal.Gui {
@@ -122,7 +122,15 @@ namespace Terminal.Gui {
/// Key which when pressed triggers .
/// Defaults to Enter
///
- public Key ObjectActivationKey { get; set; } = Key.Enter;
+ public Key ObjectActivationKey {
+ get => objectActivationKey;
+ set {
+ if (objectActivationKey != value) {
+ ReplaceKeyBinding (ObjectActivationKey, value);
+ objectActivationKey = value;
+ }
+ }
+ }
///
/// Mouse event to trigger .
@@ -148,6 +156,7 @@ namespace Terminal.Gui {
/// (nodes added but no tree builder set)
///
public static ustring NoBuilderError = "ERROR: TreeBuilder Not Set";
+ private Key objectActivationKey = Key.Enter;
///
/// Called when the changes
@@ -227,6 +236,54 @@ namespace Terminal.Gui {
public TreeView () : base ()
{
CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.PageUp, () => { MovePageUp (false); return true; });
+ AddCommand (Command.PageDown, () => { MovePageDown (false); return true; });
+ AddCommand (Command.PageUpExtend, () => { MovePageUp (true); return true; });
+ AddCommand (Command.PageDownExtend, () => { MovePageDown (true); return true; });
+ AddCommand (Command.Expand, () => { Expand (); return true; });
+ AddCommand (Command.ExpandAll, () => { ExpandAll (SelectedObject); return true; });
+ AddCommand (Command.Collapse, () => { CursorLeft (false); return true; });
+ AddCommand (Command.CollapseAll, () => { CursorLeft (true); return true; });
+ AddCommand (Command.LineUp, () => { AdjustSelection (-1, false); return true; });
+ AddCommand (Command.LineUpExtend, () => { AdjustSelection (-1, true); return true; });
+ AddCommand (Command.LineUpToFirstBranch, () => { AdjustSelectionToBranchStart (); return true; });
+
+ AddCommand (Command.LineDown, () => { AdjustSelection (1, false); return true; });
+ AddCommand (Command.LineDownExtend, () => { AdjustSelection (1, true); return true; });
+ AddCommand (Command.LineDownToLastBranch, () => { AdjustSelectionToBranchEnd (); return true; });
+
+ AddCommand (Command.TopHome, () => { GoToFirst (); return true; });
+ AddCommand (Command.BottomEnd, () => { GoToEnd (); return true; });
+ AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
+
+ AddCommand (Command.ScrollUp, () => { ScrollUp (); return true; });
+ AddCommand (Command.ScrollDown, () => { ScrollDown (); return true; });
+ AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
+
+ // Default keybindings for this view
+ AddKeyBinding (Key.PageUp, Command.PageUp);
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
+ AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
+ AddKeyBinding (Key.CursorRight, Command.Expand);
+ AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.ExpandAll);
+ AddKeyBinding (Key.CursorLeft, Command.Collapse);
+ AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.CollapseAll);
+
+ AddKeyBinding (Key.CursorUp, Command.LineUp);
+ AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
+ AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.LineUpToFirstBranch);
+
+ AddKeyBinding (Key.CursorDown, Command.LineDown);
+ AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
+ AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.LineDownToLastBranch);
+
+ AddKeyBinding (Key.Home, Command.TopHome);
+ AddKeyBinding (Key.End, Command.BottomEnd);
+ AddKeyBinding (Key.A | Key.CtrlMask, Command.SelectAll);
+ AddKeyBinding (ObjectActivationKey, Command.Accept);
}
///
@@ -504,87 +561,98 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
- if (keyEvent.Key == ObjectActivationKey) {
- var o = SelectedObject;
-
- if (o != null) {
- OnObjectActivated (new ObjectActivatedEventArgs (this, o));
-
- PositionCursor ();
- return true;
- }
- }
-
- if (keyEvent.KeyValue > 0 && keyEvent.KeyValue < 0xFFFF) {
-
- var character = (char)keyEvent.KeyValue;
-
- // if it is a single character pressed without any control keys
- if (char.IsLetterOrDigit (character) && AllowLetterBasedNavigation && !keyEvent.IsShift && !keyEvent.IsAlt && !keyEvent.IsCtrl) {
- // search for next branch that begins with that letter
- var characterAsStr = character.ToString ();
- AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, StringComparison.CurrentCultureIgnoreCase));
-
- PositionCursor ();
- return true;
- }
-
- }
-
-
- switch (keyEvent.Key) {
-
- case Key.CursorRight:
- Expand (SelectedObject);
- break;
- case Key.CursorRight | Key.CtrlMask:
- ExpandAll (SelectedObject);
- break;
- case Key.CursorLeft:
- case Key.CursorLeft | Key.CtrlMask:
- CursorLeft (keyEvent.Key.HasFlag (Key.CtrlMask));
- break;
-
- case Key.CursorUp:
- case Key.CursorUp | Key.ShiftMask:
- AdjustSelection (-1, keyEvent.Key.HasFlag (Key.ShiftMask));
- break;
- case Key.CursorDown:
- case Key.CursorDown | Key.ShiftMask:
- AdjustSelection (1, keyEvent.Key.HasFlag (Key.ShiftMask));
- break;
- case Key.CursorUp | Key.CtrlMask:
- AdjustSelectionToBranchStart ();
- break;
- case Key.CursorDown | Key.CtrlMask:
- AdjustSelectionToBranchEnd ();
- break;
- case Key.PageUp:
- case Key.PageUp | Key.ShiftMask:
- AdjustSelection (-Bounds.Height, keyEvent.Key.HasFlag (Key.ShiftMask));
- break;
-
- case Key.PageDown:
- case Key.PageDown | Key.ShiftMask:
- AdjustSelection (Bounds.Height, keyEvent.Key.HasFlag (Key.ShiftMask));
- break;
- case Key.A | Key.CtrlMask:
- SelectAll ();
- break;
- case Key.Home:
- GoToFirst ();
- break;
- case Key.End:
- GoToEnd ();
- break;
-
- default:
- // we don't care about this keystroke
+ if (!Enabled) {
return false;
}
+ // if it is a single character pressed without any control keys
+ if (keyEvent.KeyValue > 0 && keyEvent.KeyValue < 0xFFFF) {
+
+ if (char.IsLetterOrDigit ((char)keyEvent.KeyValue) && AllowLetterBasedNavigation && !keyEvent.IsShift && !keyEvent.IsAlt && !keyEvent.IsCtrl) {
+ AdjustSelectionToNextItemBeginningWith ((char)keyEvent.KeyValue);
+ return true;
+ }
+ }
+
+ try {
+ var result = InvokeKeybindings (keyEvent);
+ if (result != null)
+ return (bool)result;
+ } finally {
+
+ PositionCursor ();
+ }
+
+ return base.ProcessKey (keyEvent);
+ }
+
+
+ ///
+ /// Triggers the event with the .
+ ///
+ /// This method also ensures that the selected object is visible
+ ///
+ public void ActivateSelectedObjectIfAny ()
+ {
+ var o = SelectedObject;
+
+ if (o != null) {
+ OnObjectActivated (new ObjectActivatedEventArgs (this, o));
+ PositionCursor ();
+ }
+ }
+
+ ///
+ /// Moves the to the next item that begins with
+ /// This method will loop back to the start of the tree if reaching the end without finding a match
+ ///
+ /// The first character of the next item you want selected
+ /// Case sensitivity of the search
+ public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
+ {
+ // search for next branch that begins with that letter
+ var characterAsStr = character.ToString ();
+ AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, caseSensitivity));
+
PositionCursor ();
- return true;
+ }
+
+ ///
+ /// Moves the selection up by the height of the control (1 page).
+ ///
+ /// True if the navigation should add the covered nodes to the selected current selection
+ ///
+ public void MovePageUp (bool expandSelection = false)
+ {
+ AdjustSelection (-Bounds.Height, expandSelection);
+ }
+
+ ///
+ /// Moves the selection down by the height of the control (1 page).
+ ///
+ /// True if the navigation should add the covered nodes to the selected current selection
+ ///
+ public void MovePageDown (bool expandSelection = false)
+ {
+ AdjustSelection (Bounds.Height, expandSelection);
+ }
+
+ ///
+ /// Scrolls the view area down a single line without changing the current selection
+ ///
+ public void ScrollDown ()
+ {
+ ScrollOffsetVertical++;
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Scrolls the view area up a single line without changing the current selection
+ ///
+ public void ScrollUp ()
+ {
+ ScrollOffsetVertical--;
+ SetNeedsDisplay ();
}
///
@@ -618,13 +686,11 @@ namespace Terminal.Gui {
if (me.Flags == MouseFlags.WheeledDown) {
- ScrollOffsetVertical++;
- SetNeedsDisplay ();
+ ScrollDown ();
return true;
} else if (me.Flags == MouseFlags.WheeledUp) {
- ScrollOffsetVertical--;
- SetNeedsDisplay ();
+ ScrollUp ();
return true;
}
@@ -736,7 +802,7 @@ namespace Terminal.Gui {
if (CanFocus && HasFocus && Visible && SelectedObject != null) {
var map = BuildLineMap ();
- var idx = map.IndexOf(b => b.Model.Equals (SelectedObject));
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
// if currently selected line is visible
if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
@@ -839,7 +905,7 @@ namespace Terminal.Gui {
} else {
var map = BuildLineMap ();
- var idx = map.IndexOf(b => b.Model.Equals (SelectedObject));
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
if (idx == -1) {
@@ -848,7 +914,7 @@ namespace Terminal.Gui {
} else {
var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
- var newBranch = map.ElementAt(newIdx);
+ var newBranch = map.ElementAt (newIdx);
// If it is a multi selection
if (expandSelection && MultiSelect) {
@@ -858,7 +924,7 @@ namespace Terminal.Gui {
multiSelectedRegions.Push (new TreeSelection (head.Origin, newIdx, map));
} else {
// or start a new multi selection region
- multiSelectedRegions.Push (new TreeSelection (map.ElementAt(idx), newIdx, map));
+ multiSelectedRegions.Push (new TreeSelection (map.ElementAt (idx), newIdx, map));
}
}
@@ -884,13 +950,13 @@ namespace Terminal.Gui {
var map = BuildLineMap ();
- int currentIdx = map.IndexOf(b => Equals (b.Model, o));
+ int currentIdx = map.IndexOf (b => Equals (b.Model, o));
if (currentIdx == -1) {
return;
}
- var currentBranch = map.ElementAt(currentIdx);
+ var currentBranch = map.ElementAt (currentIdx);
var next = currentBranch;
for (; currentIdx >= 0; currentIdx--) {
@@ -905,7 +971,7 @@ namespace Terminal.Gui {
// look at next branch up for consideration
currentBranch = next;
- next = map.ElementAt(currentIdx);
+ next = map.ElementAt (currentIdx);
}
// We ran all the way to top of tree
@@ -924,13 +990,13 @@ namespace Terminal.Gui {
var map = BuildLineMap ();
- int currentIdx = map.IndexOf(b => Equals (b.Model, o));
+ int currentIdx = map.IndexOf (b => Equals (b.Model, o));
if (currentIdx == -1) {
return;
}
- var currentBranch = map.ElementAt(currentIdx);
+ var currentBranch = map.ElementAt (currentIdx);
var next = currentBranch;
for (; currentIdx < map.Count; currentIdx++) {
@@ -945,7 +1011,7 @@ namespace Terminal.Gui {
// look at next branch for consideration
currentBranch = next;
- next = map.ElementAt(currentIdx);
+ next = map.ElementAt (currentIdx);
}
GoToEnd ();
@@ -970,7 +1036,7 @@ namespace Terminal.Gui {
// or the current selected branch
if (SelectedObject != null) {
- idxStart = map.IndexOf(b => Equals (b.Model, SelectedObject));
+ idxStart = map.IndexOf (b => Equals (b.Model, SelectedObject));
}
// if currently selected object mysteriously vanished, search from beginning
@@ -980,9 +1046,9 @@ namespace Terminal.Gui {
// loop around all indexes and back to first index
for (int idxCur = (idxStart + 1) % map.Count; idxCur != idxStart; idxCur = (idxCur + 1) % map.Count) {
- if (predicate (map.ElementAt(idxCur))) {
- SelectedObject = map.ElementAt(idxCur).Model;
- EnsureVisible (map.ElementAt(idxCur).Model);
+ if (predicate (map.ElementAt (idxCur))) {
+ SelectedObject = map.ElementAt (idxCur).Model;
+ EnsureVisible (map.ElementAt (idxCur).Model);
SetNeedsDisplay ();
return;
}
@@ -997,7 +1063,7 @@ namespace Terminal.Gui {
{
var map = BuildLineMap ();
- var idx = map.IndexOf(b => Equals (b.Model, model));
+ var idx = map.IndexOf (b => Equals (b.Model, model));
if (idx == -1) {
return;
@@ -1017,6 +1083,14 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Expands the currently
+ ///
+ public void Expand ()
+ {
+ Expand (SelectedObject);
+ }
+
///
/// Expands the supplied object if it is contained in the tree (either as a root object or
/// as an exposed branch object)
@@ -1082,6 +1156,14 @@ namespace Terminal.Gui {
return ObjectToBranch (o)?.IsExpanded ?? false;
}
+ ///
+ /// Collapses the
+ ///
+ public void Collapse ()
+ {
+ Collapse (selectedObject);
+ }
+
///
/// Collapses the supplied object if it is currently expanded
///
@@ -1224,7 +1306,7 @@ namespace Terminal.Gui {
return;
}
- multiSelectedRegions.Push (new TreeSelection (map.ElementAt(0), map.Count, map));
+ multiSelectedRegions.Push (new TreeSelection (map.ElementAt (0), map.Count, map));
SetNeedsDisplay ();
OnSelectionChanged (new SelectionChangedEventArgs (this, SelectedObject, SelectedObject));
@@ -1258,7 +1340,7 @@ namespace Terminal.Gui {
Origin = from;
included.Add (Origin.Model);
- var oldIdx = map.IndexOf(from);
+ var oldIdx = map.IndexOf (from);
var lowIndex = Math.Min (oldIdx, toIndex);
var highIndex = Math.Max (oldIdx, toIndex);
diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs
new file mode 100644
index 000000000..cb2c1b9cf
--- /dev/null
+++ b/UICatalog/KeyBindingsDialog.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Terminal.Gui;
+
+namespace UICatalog {
+
+
+ class KeyBindingsDialog : Dialog {
+
+
+ static Dictionary CurrentBindings = new Dictionary();
+ private Command[] commands;
+ private ListView commandsListView;
+ private Label keyLabel;
+
+ ///
+ /// Tracks views as they are created in UICatalog so that their keybindings can
+ /// be managed.
+ ///
+ private class ViewTracker {
+
+ public static ViewTracker Instance;
+
+ ///
+ /// All views seen so far and a bool to indicate if we have applied keybindings to them
+ ///
+ Dictionary knownViews = new Dictionary ();
+
+ private object lockKnownViews = new object ();
+ private Dictionary keybindings;
+
+ public ViewTracker (View top)
+ {
+ RecordView (top);
+
+ // Refresh known windows
+ Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), (m) => {
+
+ lock (lockKnownViews) {
+ RecordView (Application.Top);
+
+ ApplyKeyBindingsToAllKnownViews ();
+ }
+
+ return true;
+ });
+ }
+
+ private void RecordView (View view)
+ {
+ if (!knownViews.ContainsKey (view)) {
+ knownViews.Add (view, false);
+ }
+
+ // may already have subviews that were added to it
+ // before we got to it
+ foreach (var sub in view.Subviews) {
+ RecordView (sub);
+ }
+
+ view.Added += RecordView;
+ }
+
+ internal static void Initialize ()
+ {
+ Instance = new ViewTracker (Application.Top);
+ }
+
+ internal void StartUsingNewKeyMap (Dictionary currentBindings)
+ {
+ lock (lockKnownViews) {
+
+ // change our knowledge of what keys to bind
+ this.keybindings = currentBindings;
+
+ // Mark that we have not applied the key bindings yet to any views
+ foreach (var view in knownViews.Keys) {
+ knownViews [view] = false;
+ }
+ }
+ }
+
+ private void ApplyKeyBindingsToAllKnownViews ()
+ {
+ if(keybindings == null) {
+ return;
+ }
+
+ // Key is the view Value is whether we have already done it
+ foreach (var viewDone in knownViews) {
+
+ var view = viewDone.Key;
+ var done = viewDone.Value;
+
+ if (done) {
+ // we have already applied keybindings to this view
+ continue;
+ }
+
+ var supported = new HashSet(view.GetSupportedCommands ());
+
+ foreach (var kvp in keybindings) {
+
+ // if the view supports the keybinding
+ if(supported.Contains(kvp.Key))
+ {
+ // if the key was bound to any other commands clear that
+ view.ClearKeybinding (kvp.Key);
+ view.AddKeyBinding (kvp.Value,kvp.Key);
+ }
+
+ // mark that we have done this view so don't need to set keybindings again on it
+ knownViews [view] = true;
+ }
+ }
+ }
+ }
+
+ public KeyBindingsDialog () : base("Keybindings", 50,10)
+ {
+ if(ViewTracker.Instance == null) {
+ ViewTracker.Initialize ();
+ }
+
+ // known commands that views can support
+ commands = Enum.GetValues (typeof (Command)).Cast().ToArray();
+
+ commandsListView = new ListView (commands) {
+ Width = Dim.Percent (50),
+ Height = Dim.Percent (100) - 1,
+ };
+ commandsListView.SelectedItemChanged += CommandsListView_SelectedItemChanged;
+ Add (commandsListView);
+
+ keyLabel = new Label () {
+ Text = "Key: None",
+ Width = Dim.Fill(),
+ X = Pos.Percent(50),
+ Y = 0
+ };
+ Add (keyLabel);
+
+ var btnChange = new Button ("Change") {
+ X = Pos.Percent (50),
+ Y = 1,
+ };
+ Add (btnChange);
+ btnChange.Clicked += RemapKey;
+
+ var close = new Button ("Ok");
+ close.Clicked += () => {
+ Application.RequestStop ();
+ ViewTracker.Instance.StartUsingNewKeyMap (CurrentBindings);
+ };
+ AddButton (close);
+
+ var cancel = new Button ("Cancel");
+ cancel.Clicked += ()=>Application.RequestStop();
+ AddButton (cancel);
+ }
+
+ private void RemapKey ()
+ {
+ var cmd = commands [commandsListView.SelectedItem];
+ Key? key = null;
+
+ // prompt user to hit a key
+ var dlg = new Dialog ("Enter Key");
+ dlg.KeyPress += (k) => {
+ key = k.KeyEvent.Key;
+ Application.RequestStop ();
+ };
+ Application.Run (dlg);
+
+ if(key.HasValue) {
+ CurrentBindings [cmd] = key.Value;
+ SetTextBoxToShowBinding (cmd);
+ }
+ }
+
+ private void SetTextBoxToShowBinding (Command cmd)
+ {
+ if (CurrentBindings.ContainsKey (cmd)) {
+ keyLabel.Text = "Key: " + CurrentBindings [cmd].ToString ();
+ } else {
+ keyLabel.Text = "Key: None";
+ }
+ SetNeedsDisplay ();
+ }
+
+ private void CommandsListView_SelectedItemChanged (ListViewItemEventArgs obj)
+ {
+ SetTextBoxToShowBinding ((Command)obj.Value);
+ }
+ }
+}
diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
index 02923e146..70e9f1111 100644
--- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs
+++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
@@ -367,7 +367,7 @@ namespace UICatalog.Scenarios {
private void OnReportClosed ()
{
- if (Staging.StartStaging != null) {
+ if (Staging?.StartStaging != null) {
ReportClosed?.Invoke (this);
}
RequestStop ();
diff --git a/UICatalog/Scenarios/BordersComparisons.cs b/UICatalog/Scenarios/BordersComparisons.cs
index d7c0a97d8..f10886355 100644
--- a/UICatalog/Scenarios/BordersComparisons.cs
+++ b/UICatalog/Scenarios/BordersComparisons.cs
@@ -41,11 +41,6 @@ namespace UICatalog.Scenarios {
X = Pos.Center (),
Y = Pos.Center () - 3,
};
- var tf2 = new TextField ("1234567890") {
- X = Pos.AnchorEnd (10),
- Y = Pos.AnchorEnd (1),
- Width = 10
- };
var tv = new TextView () {
Y = Pos.AnchorEnd (2),
Width = 10,
@@ -53,7 +48,12 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.Dialog,
Text = "1234567890"
};
- win.Add (tf1, button, label, tf2, tv);
+ var tf2 = new TextField ("1234567890") {
+ X = Pos.AnchorEnd (10),
+ Y = Pos.AnchorEnd (1),
+ Width = 10
+ };
+ win.Add (tf1, button, label, tv, tf2);
top.Add (win);
var top2 = new Border.ToplevelContainer (new Rect (50, 5, 40, 20),
@@ -81,11 +81,6 @@ namespace UICatalog.Scenarios {
X = Pos.Center (),
Y = Pos.Center () - 3,
};
- var tf4 = new TextField ("1234567890") {
- X = Pos.AnchorEnd (10),
- Y = Pos.AnchorEnd (1),
- Width = 10
- };
var tv2 = new TextView () {
Y = Pos.AnchorEnd (2),
Width = 10,
@@ -93,7 +88,12 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.Dialog,
Text = "1234567890"
};
- top2.Add (tf3, button2, label2, tf4, tv2);
+ var tf4 = new TextField ("1234567890") {
+ X = Pos.AnchorEnd (10),
+ Y = Pos.AnchorEnd (1),
+ Width = 10
+ };
+ top2.Add (tf3, button2, label2, tv2, tf4);
top.Add (top2);
var frm = new FrameView (new Rect (95, 5, 40, 20), "Test3", null,
@@ -118,11 +118,6 @@ namespace UICatalog.Scenarios {
X = Pos.Center (),
Y = Pos.Center () - 3,
};
- var tf6 = new TextField ("1234567890") {
- X = Pos.AnchorEnd (10),
- Y = Pos.AnchorEnd (1),
- Width = 10
- };
var tv3 = new TextView () {
Y = Pos.AnchorEnd (2),
Width = 10,
@@ -130,7 +125,12 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.Dialog,
Text = "1234567890"
};
- frm.Add (tf5, button3, label3, tf6, tv3);
+ var tf6 = new TextField ("1234567890") {
+ X = Pos.AnchorEnd (10),
+ Y = Pos.AnchorEnd (1),
+ Width = 10
+ };
+ frm.Add (tf5, button3, label3, tv3, tf6);
top.Add (frm);
Application.Run ();
diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs
index 2224f54b5..42b586f93 100644
--- a/UICatalog/Scenarios/Text.cs
+++ b/UICatalog/Scenarios/Text.cs
@@ -1,5 +1,8 @@
-using System;
+using NStack;
+using System;
+using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using Terminal.Gui;
using Terminal.Gui.TextValidateProviders;
@@ -19,6 +22,14 @@ namespace UICatalog.Scenarios {
Width = Dim.Percent (50),
//ColorScheme = Colors.Dialog
};
+ textField.TextChanging += TextField_TextChanging;
+
+ void TextField_TextChanging (TextChangingEventArgs e)
+ {
+ textField.Autocomplete.AllSuggestions = Regex.Matches (e.NewText.ToString (), "\\w+")
+ .Select (s => s.Value)
+ .Distinct ().ToList ();
+ }
Win.Add (textField);
var labelMirroringTextField = new Label (textField.Text) {
@@ -40,6 +51,14 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.Dialog
};
textView.Text = s;
+ textView.DrawContent += TextView_DrawContent;
+
+ void TextView_DrawContent (Rect e)
+ {
+ textView.Autocomplete.AllSuggestions = Regex.Matches (textView.Text.ToString (), "\\w+")
+ .Select (s => s.Value)
+ .Distinct ().ToList ();
+ }
Win.Add (textView);
var labelMirroringTextView = new Label (textView.Text) {
diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs
new file mode 100644
index 000000000..8bde07405
--- /dev/null
+++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs
@@ -0,0 +1,186 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+ [ScenarioMetadata (Name: "TextView Autocomplete Popup", Description: "Show five TextView Autocomplete Popup effects")]
+ [ScenarioCategory ("Controls")]
+ public class TextViewAutocompletePopup : Scenario {
+
+ TextView textViewTopLeft;
+ TextView textViewTopRight;
+ TextView textViewBottomLeft;
+ TextView textViewBottomRight;
+ TextView textViewCentered;
+ MenuItem miMultiline;
+ MenuItem miWrap;
+ StatusItem siMultiline;
+ StatusItem siWrap;
+ int height = 10;
+
+ public override void Setup ()
+ {
+ Win.Title = GetName ();
+ var width = 20;
+ var colorScheme = Colors.Dialog;
+ var text = " jamp jemp jimp jomp jump";
+
+ var menu = new MenuBar (new MenuBarItem [] {
+ new MenuBarItem ("_File", new MenuItem [] {
+ miMultiline = new MenuItem ("_Multiline", "", () => Multiline()){CheckType = MenuItemCheckStyle.Checked},
+ miWrap = new MenuItem ("_Word Wrap", "", () => WordWrap()){CheckType = MenuItemCheckStyle.Checked},
+ new MenuItem ("_Quit", "", () => Quit())
+ })
+ });
+ Top.Add (menu);
+
+ textViewTopLeft = new TextView () {
+ Width = width,
+ Height = height,
+ ColorScheme = colorScheme,
+ Text = text
+ };
+ textViewTopLeft.DrawContent += TextViewTopLeft_DrawContent;
+ Win.Add (textViewTopLeft);
+
+ textViewTopRight = new TextView () {
+ X = Pos.AnchorEnd (width),
+ Width = width,
+ Height = height,
+ ColorScheme = colorScheme,
+ Text = text
+ };
+ textViewTopRight.DrawContent += TextViewTopRight_DrawContent;
+ Win.Add (textViewTopRight);
+
+ textViewBottomLeft = new TextView () {
+ Y = Pos.AnchorEnd (height),
+ Width = width,
+ Height = height,
+ ColorScheme = colorScheme,
+ Text = text
+ };
+ textViewBottomLeft.DrawContent += TextViewBottomLeft_DrawContent;
+ Win.Add (textViewBottomLeft);
+
+ textViewBottomRight = new TextView () {
+ X = Pos.AnchorEnd (width),
+ Y = Pos.AnchorEnd (height),
+ Width = width,
+ Height = height,
+ ColorScheme = colorScheme,
+ Text = text
+ };
+ textViewBottomRight.DrawContent += TextViewBottomRight_DrawContent;
+ Win.Add (textViewBottomRight);
+
+ textViewCentered = new TextView () {
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = width,
+ Height = height,
+ ColorScheme = colorScheme,
+ Text = text
+ };
+ textViewCentered.DrawContent += TextViewCentered_DrawContent;
+ Win.Add (textViewCentered);
+
+ miMultiline.Checked = textViewTopLeft.Multiline;
+ miWrap.Checked = textViewTopLeft.WordWrap;
+
+ var statusBar = new StatusBar (new StatusItem [] {
+ new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
+ siMultiline = new StatusItem(Key.Null, "", null),
+ siWrap = new StatusItem(Key.Null, "", null)
+ });
+ Top.Add (statusBar);
+
+ Win.LayoutStarted += Win_LayoutStarted;
+ }
+
+ private void Win_LayoutStarted (View.LayoutEventArgs obj)
+ {
+ miMultiline.Checked = textViewTopLeft.Multiline;
+ miWrap.Checked = textViewTopLeft.WordWrap;
+ SetMultilineStatusText ();
+ SetWrapStatusText ();
+
+ if (miMultiline.Checked) {
+ height = 10;
+ } else {
+ height = 1;
+ }
+ textViewBottomLeft.Y = textViewBottomRight.Y = Pos.AnchorEnd (height);
+ }
+
+ private void SetMultilineStatusText ()
+ {
+ siMultiline.Title = $"Multiline: {miMultiline.Checked}";
+ }
+
+ private void SetWrapStatusText ()
+ {
+ siWrap.Title = $"WordWrap: {miWrap.Checked}";
+ }
+
+ private void SetAllSuggestions (TextView view)
+ {
+ view.Autocomplete.AllSuggestions = Regex.Matches (view.Text.ToString (), "\\w+")
+ .Select (s => s.Value)
+ .Distinct ().ToList ();
+ }
+
+ private void TextViewCentered_DrawContent (Rect obj)
+ {
+ SetAllSuggestions (textViewCentered);
+ }
+
+ private void TextViewBottomRight_DrawContent (Rect obj)
+ {
+ SetAllSuggestions (textViewBottomRight);
+ }
+
+ private void TextViewBottomLeft_DrawContent (Rect obj)
+ {
+ SetAllSuggestions (textViewBottomLeft);
+ }
+
+ private void TextViewTopRight_DrawContent (Rect obj)
+ {
+ SetAllSuggestions (textViewTopRight);
+ }
+
+ private void TextViewTopLeft_DrawContent (Rect obj)
+ {
+ SetAllSuggestions (textViewTopLeft);
+ }
+
+ private void Multiline ()
+ {
+ miMultiline.Checked = !miMultiline.Checked;
+ SetMultilineStatusText ();
+ textViewTopLeft.Multiline = miMultiline.Checked;
+ textViewTopRight.Multiline = miMultiline.Checked;
+ textViewBottomLeft.Multiline = miMultiline.Checked;
+ textViewBottomRight.Multiline = miMultiline.Checked;
+ textViewCentered.Multiline = miMultiline.Checked;
+ }
+
+ private void WordWrap ()
+ {
+ miWrap.Checked = !miWrap.Checked;
+ textViewTopLeft.WordWrap = miWrap.Checked;
+ textViewTopRight.WordWrap = miWrap.Checked;
+ textViewBottomLeft.WordWrap = miWrap.Checked;
+ textViewBottomRight.WordWrap = miWrap.Checked;
+ textViewCentered.WordWrap = miWrap.Checked;
+ miWrap.Checked = textViewTopLeft.WordWrap;
+ SetWrapStatusText ();
+ }
+
+ private void Quit ()
+ {
+ Application.RequestStop ();
+ }
+ }
+}
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index ee011a0bf..05c5c7db3 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -313,6 +313,7 @@ namespace UICatalog {
menuItems.Add (CreateSizeStyle ());
menuItems.Add (CreateAlwaysSetPosition ());
menuItems.Add (CreateDisabledEnabledMouse ());
+ menuItems.Add (CreateKeybindings ());
return menuItems;
}
@@ -331,6 +332,22 @@ namespace UICatalog {
return menuItems.ToArray ();
}
+ private static MenuItem[] CreateKeybindings()
+ {
+
+ List