Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2789. StatusItem should have a disabled attribute if it can't execute. #2790

Merged
merged 3 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions Terminal.Gui/Views/StatusBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ public class StatusItem {
/// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
/// <param name="title">Title for the <see cref="StatusItem"/>.</param>
/// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
public StatusItem (Key shortcut, ustring title, Action action)
/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
public StatusItem (Key shortcut, ustring title, Action action, Func<bool> canExecute = null)
{
Title = title ?? "";
Shortcut = shortcut;
Action = action;
CanExecute = canExecute;
}

/// <summary>
Expand All @@ -54,7 +56,22 @@ public StatusItem (Key shortcut, ustring title, Action action)
/// Gets or sets the action to be invoked when the statusbar item is triggered
/// </summary>
/// <value>Action to invoke.</value>
public Action Action { get; }
public Action Action { get; set; }

/// <summary>
/// Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered.
/// If <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be disabled.
/// </summary>
/// <value>Function to determine if the action is can be executed or not.</value>
public Func<bool> CanExecute { get; set; }

/// <summary>
/// Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
/// </summary>
public bool IsEnabled ()
{
return CanExecute == null ? true : CanExecute ();
}

/// <summary>
/// Gets or sets arbitrary data for the status item.
Expand Down Expand Up @@ -116,6 +133,17 @@ Attribute ToggleScheme (Attribute scheme)
return result;
}

Attribute DetermineColorSchemeFor (StatusItem item)
{
if (item != null) {
if (item.IsEnabled ()) {
return GetNormalColor ();
}
return ColorScheme.Disabled;
}
return GetNormalColor ();
}

///<inheritdoc/>
public override void Redraw (Rect bounds)
{
Expand All @@ -129,9 +157,12 @@ public override void Redraw (Rect bounds)
Driver.SetAttribute (scheme);
for (int i = 0; i < Items.Length; i++) {
var title = Items [i].Title.ToString ();
Driver.SetAttribute (DetermineColorSchemeFor (Items [i]));
for (int n = 0; n < Items [i].Title.RuneCount; n++) {
if (title [n] == '~') {
scheme = ToggleScheme (scheme);
if (Items [i].IsEnabled ()) {
scheme = ToggleScheme (scheme);
}
continue;
}
Driver.AddRune (title [n]);
Expand All @@ -149,7 +180,9 @@ public override bool ProcessHotKey (KeyEvent kb)
{
foreach (var item in Items) {
if (kb.Key == item.Shortcut) {
Run (item.Action);
if (item.IsEnabled ()) {
Run (item.Action);
}
return true;
}
}
Expand All @@ -165,7 +198,10 @@ public override bool MouseEvent (MouseEvent me)
int pos = 1;
for (int i = 0; i < Items.Length; i++) {
if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
Run (Items [i].Action);
var item = Items [i];
if (item.IsEnabled ()) {
Run (item.Action);
}
break;
}
pos += GetItemTitleLength (Items [i].Title) + 3;
Expand Down
41 changes: 40 additions & 1 deletion UnitTests/Views/StatusBarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ public void StatusItem_Constructor ()
Assert.Equal (Key.CtrlMask | Key.Q, si.Shortcut);
Assert.Equal ("~^Q~ Quit", si.Title);
Assert.Null (si.Action);
Assert.True (si.IsEnabled ());
si = new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { });
Assert.NotNull (si.Action);
}

[Fact]
public void StatusBar_Contructor_Default ()
public void StatusBar_Constructor_Default ()
{
var sb = new StatusBar ();

Expand Down Expand Up @@ -155,5 +156,43 @@ public void AddItemAt_RemoveItem_Replacing ()
Assert.Equal ("~^A~ Save As", sb.Items [1].Title);
Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title);
}

[Fact, AutoInitShutdown]
public void CanExecute_ProcessHotKey ()
{
Window win = null;
var statusBar = new StatusBar (new StatusItem [] {
new StatusItem (Key.CtrlMask | Key.N, "~^N~ New", New, CanExecuteNew),
new StatusItem (Key.CtrlMask | Key.C, "~^C~ Close", Close, CanExecuteClose)
});
var top = Application.Top;
top.Add (statusBar);

bool CanExecuteNew () => win == null;

void New ()
{
win = new Window ();
}

bool CanExecuteClose () => win != null;

void Close ()
{
win = null;
}

Application.Begin (top);

Assert.Null (win);
Assert.True (CanExecuteNew ());
Assert.False (CanExecuteClose ());

Assert.True (top.ProcessHotKey (new KeyEvent (Key.N | Key.CtrlMask, new KeyModifiers () { Alt = true })));
Application.MainLoop.MainIteration ();
Assert.NotNull (win);
Assert.False (CanExecuteNew ());
Assert.True (CanExecuteClose ());
}
}
}