Pārlūkot izejas kodu

Added AdvancingFocus events. Fixed hexview bugs

Tig 9 mēneši atpakaļ
vecāks
revīzija
37e983a84e

+ 2 - 0
Terminal.Gui/View/CancelEventArgs.cs

@@ -27,6 +27,8 @@ public class CancelEventArgs<T> : CancelEventArgs where T : notnull
         NewValue = newValue;
     }
 
+    protected CancelEventArgs () { }
+
     /// <summary>The current value of the property.</summary>
     public T CurrentValue { get; }
 

+ 19 - 0
Terminal.Gui/View/Navigation/AdvanceFocusEventArgs.cs

@@ -0,0 +1,19 @@
+namespace Terminal.Gui;
+
+/// <summary>The event arguments for <see cref="View.AdvanceFocus"/> events.</summary>
+public class AdvanceFocusEventArgs : CancelEventArgs<bool>
+{
+    /// <summary>Initializes a new instance.</summary>
+    public AdvanceFocusEventArgs (NavigationDirection direction, TabBehavior? behavior)
+    {
+        Direction = direction;
+        Behavior = behavior;
+    }
+
+    /// <summary>Gets or sets the view that is losing focus.</summary>
+    public NavigationDirection Direction { get; set; }
+
+    /// <summary>Gets or sets the view that is gaining focus.</summary>
+    public TabBehavior? Behavior { get; set; }
+
+}

+ 1 - 1
Terminal.Gui/View/Navigation/FocusEventArgs.cs

@@ -20,4 +20,4 @@ public class HasFocusEventArgs : CancelEventArgs<bool>
     /// <summary>Gets or sets the view that is gaining focus.</summary>
     public View NewFocused { get; set; }
 
-}
+}

+ 62 - 6
Terminal.Gui/View/View.Navigation.cs

@@ -34,6 +34,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
             return false;
         }
 
+        if (RaiseAdvancingFocus (direction, behavior))
+        {
+            return true;
+        }
+
         View? focused = Focused;
 
         if (focused is { } && focused.AdvanceFocus (direction, behavior))
@@ -128,6 +133,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
         if (view.HasFocus)
         {
             // We could not advance
+            if (view != this)
+            {
+                // Tell it to try the other way.
+                return view.RaiseAdvancingFocus (direction == NavigationDirection.Forward ? NavigationDirection.Backward : NavigationDirection.Forward, behavior);
+            }
             return view == this;
         }
 
@@ -137,6 +147,52 @@ public partial class View // Focus and cross-view navigation management (TabStop
         return focusSet;
     }
 
+    private bool RaiseAdvancingFocus (NavigationDirection direction, TabBehavior? behavior)
+    {
+        // Call the virtual method
+        if (OnAdvancingFocus (direction, behavior))
+        {
+            // The event was cancelled
+            return true;
+        }
+
+        var args = new AdvanceFocusEventArgs (direction, behavior);
+        AdvancingFocus?.Invoke (this, args);
+
+        if (args.Cancel)
+        {
+            // The event was cancelled
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Called when <see cref="View.AdvanceFocus"/> is about to advance focus.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <returns>
+    ///     <see langword="true"/>, if the focus advance is to be cancelled, <see langword="false"/>
+    ///     otherwise.
+    /// </returns>
+    protected virtual bool OnAdvancingFocus (NavigationDirection direction, TabBehavior? behavior) { return false; }
+
+    /// <summary>
+    ///     Raised when <see cref="View.AdvanceFocus"/> is about to advance focus.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Cancel the event to prevent the focus from advancing.
+    ///     </para>
+    ///     <para>
+    ///         Use <see cref="HasFocusChanged"/> to be notified after the focus has changed.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<AdvanceFocusEventArgs>? AdvancingFocus;
+
+
     /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can be focused.</summary>
     /// <remarks>
     ///     <para>
@@ -467,7 +523,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         bool previousValue = HasFocus;
 
-        bool cancelled = NotifyFocusChanging (false, true, currentFocusedView, this);
+        bool cancelled = RaiseFocusChanging (false, true, currentFocusedView, this);
 
         if (cancelled)
         {
@@ -529,7 +585,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         }
 
         // Focus work is done. Notify.
-        NotifyFocusChanged (HasFocus, currentFocusedView, this);
+        RaiseFocusChanged (HasFocus, currentFocusedView, this);
 
         SetNeedsDisplay ();
 
@@ -542,7 +598,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         return (true, false);
     }
 
-    private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
+    private bool RaiseFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
     {
         Debug.Assert (currentFocused is null || currentFocused is { HasFocus: true });
         Debug.Assert (newFocused is null || newFocused is { CanFocus: true });
@@ -750,7 +806,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         Debug.Assert (_hasFocus);
 
         // Note, can't be cancelled.
-        NotifyFocusChanging (HasFocus, !HasFocus, this, newFocusedView);
+        RaiseFocusChanging (HasFocus, !HasFocus, this, newFocusedView);
 
         // Even though the change can't be cancelled, some listener may have changed the focus to another view.
         if (!_hasFocus)
@@ -765,7 +821,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Set HasFocus false
         _hasFocus = false;
 
-        NotifyFocusChanged (HasFocus, this, newFocusedView);
+        RaiseFocusChanged (HasFocus, this, newFocusedView);
 
         if (_hasFocus)
         {
@@ -782,7 +838,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         SetNeedsDisplay ();
     }
 
-    private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
+    private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
     {
         if (newHasFocus && focusedVew?.Focused is null)
         {

+ 15 - 18
Terminal.Gui/Views/HexView.cs

@@ -66,8 +66,6 @@ public class HexView : View, IDesignable
         AddCommand (Command.Right, () => MoveRight ());
         AddCommand (Command.Down, () => MoveDown (BytesPerLine));
         AddCommand (Command.Up, () => MoveUp (BytesPerLine));
-        AddCommand (Command.Tab, () => Navigate (NavigationDirection.Forward));
-        AddCommand (Command.BackTab, () => Navigate (NavigationDirection.Backward));
         AddCommand (Command.PageUp, () => MoveUp (BytesPerLine * Viewport.Height));
         AddCommand (Command.PageDown, () => MoveDown (BytesPerLine * Viewport.Height));
         AddCommand (Command.Start, () => MoveHome ());
@@ -99,9 +97,6 @@ public class HexView : View, IDesignable
         KeyBindings.Add (Key.CursorUp.WithCtrl, Command.StartOfPage);
         KeyBindings.Add (Key.CursorDown.WithCtrl, Command.EndOfPage);
 
-        KeyBindings.Add (Key.Tab, Command.Tab);
-        KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
-
         KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
         KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
         KeyBindings.Add (Key.InsertChar, Command.Insert);
@@ -606,7 +601,7 @@ public class HexView : View, IDesignable
             int value;
             var k = (char)keyEvent.KeyCode;
 
-            if (!char.IsAsciiDigit ((char)keyEvent.KeyCode))
+            if (!char.IsAsciiHexDigit ((char)keyEvent.KeyCode))
             {
                 return false;
             }
@@ -949,23 +944,25 @@ public class HexView : View, IDesignable
         SetNeedsDisplay (new (0, line, Viewport.Width, 1));
     }
 
-    private bool Navigate (NavigationDirection direction)
+    /// <inheritdoc />
+    protected override bool OnAdvancingFocus (NavigationDirection direction, TabBehavior? behavior)
     {
-        switch (direction)
+        if (direction == NavigationDirection.Forward && _leftSideHasFocus)
         {
-            case NavigationDirection.Forward:
-                _leftSideHasFocus = !_leftSideHasFocus;
-                RedisplayLine (Address);
-                _firstNibble = true;
+            _leftSideHasFocus = !_leftSideHasFocus;
+            RedisplayLine (Address);
+            _firstNibble = true;
 
-                return true;
+            return true;
+        }
 
-            case NavigationDirection.Backward:
-                _leftSideHasFocus = !_leftSideHasFocus;
-                RedisplayLine (Address);
-                _firstNibble = true;
+        if (direction == NavigationDirection.Backward && !_leftSideHasFocus)
+        {
+            _leftSideHasFocus = !_leftSideHasFocus;
+            RedisplayLine (Address);
+            _firstNibble = true;
 
-                return true;
+            return true;
         }
 
         return false;

+ 72 - 42
UnitTests/Views/HexViewTests.cs

@@ -31,29 +31,35 @@ public class HexViewTests
     public void AllowEdits_Edits_ApplyEdits ()
     {
         var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
+        Application.Navigation = new ApplicationNavigation ();
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
+        Application.Top.SetFocus ();
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
 
+        Assert.True (Application.OnKeyDown (Key.Tab)); // Move to left side
+
         Assert.Empty (hv.Edits);
         hv.AllowEdits = false;
-        Assert.True (hv.NewKeyDownEvent (Key.Home));
-        Assert.False (hv.NewKeyDownEvent (Key.A));
+        Assert.True (Application.OnKeyDown (Key.Home));
+        Assert.False (Application.OnKeyDown (Key.A));
         Assert.Empty (hv.Edits);
         Assert.Equal (126, hv.Source!.Length);
 
         hv.AllowEdits = true;
-        Assert.True (hv.NewKeyDownEvent (Key.D4));
-        Assert.True (hv.NewKeyDownEvent (Key.D1));
+        Assert.True (Application.OnKeyDown (Key.D4));
+        Assert.True (Application.OnKeyDown (Key.D1));
         Assert.Single (hv.Edits);
         Assert.Equal (65, hv.Edits.ToList () [0].Value);
         Assert.Equal ('A', (char)hv.Edits.ToList () [0].Value);
         Assert.Equal (126, hv.Source.Length);
 
         // Appends byte
-        Assert.True (hv.NewKeyDownEvent (Key.End));
-        Assert.True (hv.NewKeyDownEvent (Key.D4));
-        Assert.True (hv.NewKeyDownEvent (Key.D2));
+        Assert.True (Application.OnKeyDown (Key.End));
+        Assert.True (Application.OnKeyDown (Key.D4));
+        Assert.True (Application.OnKeyDown (Key.D2));
         Assert.Equal (2, hv.Edits.Count);
         Assert.Equal (66, hv.Edits.ToList () [1].Value);
         Assert.Equal ('B', (char)hv.Edits.ToList () [1].Value);
@@ -62,11 +68,18 @@ public class HexViewTests
         hv.ApplyEdits ();
         Assert.Empty (hv.Edits);
         Assert.Equal (127, hv.Source.Length);
+
+        Application.Top.Dispose ();
+        Application.ResetState (true);
+
     }
 
     [Fact]
     public void ApplyEdits_With_Argument ()
     {
+        Application.Navigation = new ApplicationNavigation ();
+        Application.Top = new Toplevel ();
+
         byte [] buffer = Encoding.Default.GetBytes ("Fest");
         var original = new MemoryStream ();
         original.Write (buffer, 0, buffer.Length);
@@ -76,28 +89,40 @@ public class HexViewTests
         original.CopyTo (copy);
         copy.Flush ();
         var hv = new HexView (copy) { Width = Dim.Fill (), Height = Dim.Fill () };
+        Application.Top.Add (hv);
+        Application.Top.SetFocus ();
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
 
-        var readBuffer = new byte [hv.Source.Length];
+        var readBuffer = new byte [hv.Source!.Length];
         hv.Source.Position = 0;
         hv.Source.Read (readBuffer);
         Assert.Equal ("Fest", Encoding.Default.GetString (readBuffer));
 
-        Assert.True (hv.NewKeyDownEvent (Key.D5));
-        Assert.True (hv.NewKeyDownEvent (Key.D4));
+        Assert.True (Application.OnKeyDown (Key.Tab)); // Move to left side
+        Assert.True (Application.OnKeyDown (Key.D5));
+        Assert.True (Application.OnKeyDown (Key.D4));
         readBuffer [hv.Edits.ToList () [0].Key] = hv.Edits.ToList () [0].Value;
         Assert.Equal ("Test", Encoding.Default.GetString (readBuffer));
 
+        Assert.True (Application.OnKeyDown (Key.Tab)); // Move to right side
+        Assert.True (Application.OnKeyDown (Key.CursorLeft)); 
+        Assert.True (Application.OnKeyDown (Key.Z.WithShift));
+        readBuffer [hv.Edits.ToList () [0].Key] = hv.Edits.ToList () [0].Value;
+        Assert.Equal ("Zest", Encoding.Default.GetString (readBuffer));
+
         hv.ApplyEdits (original);
         original.Position = 0;
         original.Read (buffer);
         copy.Position = 0;
         copy.Read (readBuffer);
-        Assert.Equal ("Test", Encoding.Default.GetString (buffer));
-        Assert.Equal ("Test", Encoding.Default.GetString (readBuffer));
+        Assert.Equal ("Zest", Encoding.Default.GetString (buffer));
+        Assert.Equal ("Zest", Encoding.Default.GetString (readBuffer));
         Assert.Equal (Encoding.Default.GetString (buffer), Encoding.Default.GetString (readBuffer));
+
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
 
     [Fact]
@@ -119,6 +144,8 @@ public class HexViewTests
     [Fact]
     public void Position_Encoding_Default ()
     {
+        Application.Navigation = new ApplicationNavigation ();
+
         var hv = new HexView (LoadStream (null, out _)) { Width = 100, Height = 100 };
         Application.Top = new Toplevel ();
         Application.Top.Add (hv);
@@ -130,21 +157,21 @@ public class HexViewTests
 
         Assert.Equal (new (0, 0), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.Tab));
+        Assert.True (Application.OnKeyDown (Key.Tab));
         Assert.Equal (new (0, 0), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorRight.WithCtrl));
         Assert.Equal (hv.BytesPerLine - 1, hv.Position.X);
 
-        Assert.True (hv.NewKeyDownEvent (Key.Home));
+        Assert.True (Application.OnKeyDown (Key.Home));
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
+        Assert.True (Application.OnKeyDown (Key.CursorRight));
         Assert.Equal (new (1, 0), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
+        Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.Equal (new (1, 1), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.End));
+        Assert.True (Application.OnKeyDown (Key.End));
         Assert.Equal (new (3, 3), hv.Position);
 
         Assert.Equal (hv.Source!.Length, hv.Address);
@@ -155,6 +182,8 @@ public class HexViewTests
     [Fact]
     public void Position_Encoding_Unicode ()
     {
+        Application.Navigation = new ApplicationNavigation ();
+
         var hv = new HexView (LoadStream (null, out _, unicode: true)) { Width = 100, Height = 100 };
         Application.Top = new Toplevel ();
         Application.Top.Add (hv);
@@ -166,20 +195,20 @@ public class HexViewTests
 
         Assert.Equal (new (0, 0), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.Tab));
+        Assert.True (Application.OnKeyDown (Key.Tab));
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorRight.WithCtrl));
         Assert.Equal (hv.BytesPerLine - 1, hv.Position.X);
 
-        Assert.True (hv.NewKeyDownEvent (Key.Home));
+        Assert.True (Application.OnKeyDown (Key.Home));
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
+        Assert.True (Application.OnKeyDown (Key.CursorRight));
         Assert.Equal (new (1, 0), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
+        Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.Equal (new (1, 1), hv.Position);
 
-        Assert.True (hv.NewKeyDownEvent (Key.End));
+        Assert.True (Application.OnKeyDown (Key.End));
         Assert.Equal (new (6, 6), hv.Position);
 
         Assert.Equal (hv.Source!.Length, hv.Address);
@@ -200,7 +229,7 @@ public class HexViewTests
         Assert.Single (hv.Edits);
         Assert.Equal (65, hv.Edits.ToList () [0].Value);
         Assert.Equal ('A', (char)hv.Edits.ToList () [0].Value);
-        Assert.Equal (126, hv.Source.Length);
+        Assert.Equal (126, hv.Source!.Length);
 
         hv.DiscardEdits ();
         Assert.Empty (hv.Edits);
@@ -218,7 +247,7 @@ public class HexViewTests
 
         Assert.True (hv.NewKeyDownEvent (Key.PageDown));
         Assert.Equal (4 * hv.Frame.Height, hv.DisplayStart);
-        Assert.Equal (hv.Source.Length, hv.Source.Position);
+        Assert.Equal (hv.Source!.Length, hv.Source.Position);
 
         Assert.True (hv.NewKeyDownEvent (Key.End));
 
@@ -256,8 +285,9 @@ public class HexViewTests
     [Fact]
     public void KeyBindings_Test_Movement_LeftSide ()
     {
-        var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
+        Application.Navigation = new ApplicationNavigation ();
         Application.Top = new Toplevel ();
+        var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
         Application.Top.Add (hv);
 
         hv.LayoutSubviews ();
@@ -266,43 +296,43 @@ public class HexViewTests
         Assert.Equal (0, hv.Address);
         Assert.Equal (4, hv.BytesPerLine);
 
-        // right side only needed to press one time
-        Assert.True (hv.NewKeyDownEvent (Key.Tab));
+        // Default internal focus is on right side. Move back to left.
+        Assert.True (Application.OnKeyDown (Key.Tab.WithShift));
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
+        Assert.True (Application.OnKeyDown (Key.CursorRight));
         Assert.Equal (1, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
+        Assert.True (Application.OnKeyDown (Key.CursorLeft));
         Assert.Equal (0, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
+        Assert.True (Application.OnKeyDown (Key.CursorDown));
         Assert.Equal (4, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorUp));
+        Assert.True (Application.OnKeyDown (Key.CursorUp));
         Assert.Equal (0, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.PageDown));
+        Assert.True (Application.OnKeyDown (Key.PageDown));
         Assert.Equal (40, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.PageUp));
+        Assert.True (Application.OnKeyDown (Key.PageUp));
         Assert.Equal (0, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.End));
+        Assert.True (Application.OnKeyDown (Key.End));
         Assert.Equal (MEM_STRING_LENGTH, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.Home));
+        Assert.True (Application.OnKeyDown (Key.Home));
         Assert.Equal (0, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorRight.WithCtrl));
         Assert.Equal (3, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorLeft.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorLeft.WithCtrl));
         Assert.Equal (0, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorDown.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorDown.WithCtrl));
         Assert.Equal (36, hv.Address);
 
-        Assert.True (hv.NewKeyDownEvent (Key.CursorUp.WithCtrl));
+        Assert.True (Application.OnKeyDown (Key.CursorUp.WithCtrl));
         Assert.Equal (0, hv.Address);
         Application.Top.Dispose ();
         Application.ResetState (true);
@@ -317,7 +347,7 @@ public class HexViewTests
 
         Application.Top.LayoutSubviews ();
 
-        HexViewEventArgs hexViewEventArgs = null;
+        HexViewEventArgs hexViewEventArgs = null!;
         hv.PositionChanged += (s, e) => hexViewEventArgs = e;
 
         Assert.Equal (4, hv.BytesPerLine);