Merge branch 'master' into progress_invoke_fix

This commit is contained in:
Charlie Kindel
2020-05-24 19:18:45 -06:00
5 changed files with 203 additions and 287 deletions

View File

@@ -190,7 +190,7 @@ static class Demo {
new DateField (3, 22, DateTime.Now), new DateField (3, 22, DateTime.Now),
new DateField (23, 22, DateTime.Now, true), new DateField (23, 22, DateTime.Now, true),
progress, progress,
new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"), new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) or Ctrl+T to activate the menubar"),
menuKeysStyle, menuKeysStyle,
menuAutoMouseNav menuAutoMouseNav
@@ -636,10 +636,23 @@ static class Demo {
}; };
#endif #endif
win.KeyPress += Win_KeyPress;
top.Add (win); top.Add (win);
//top.Add (menu); //top.Add (menu);
top.Add (menu, statusBar); top.Add (menu, statusBar);
Application.Run (); Application.Run ();
} }
private static void Win_KeyPress (object sender, View.KeyEventEventArgs e)
{
if (e.KeyEvent.Key == Key.ControlT) {
if (menu.IsMenuOpen)
menu.CloseMenu ();
else
menu.OpenMenu ();
e.Handled = true;
}
}
} }

View File

@@ -1087,6 +1087,11 @@ namespace Terminal.Gui {
/// The <see cref="KeyEvent"/> for the event. /// The <see cref="KeyEvent"/> for the event.
/// </summary> /// </summary>
public KeyEvent KeyEvent { get; set; } public KeyEvent KeyEvent { get; set; }
/// <summary>
/// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
/// </summary>
public bool Handled { get; set; } = false;
} }
/// <summary> /// <summary>
@@ -1097,7 +1102,11 @@ namespace Terminal.Gui {
/// <inheritdoc cref="ProcessKey"/> /// <inheritdoc cref="ProcessKey"/>
public override bool ProcessKey (KeyEvent keyEvent) public override bool ProcessKey (KeyEvent keyEvent)
{ {
KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent));
KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyPress?.Invoke (this, args);
if (args.Handled)
return true;
if (Focused?.ProcessKey (keyEvent) == true) if (Focused?.ProcessKey (keyEvent) == true)
return true; return true;
@@ -1107,7 +1116,10 @@ namespace Terminal.Gui {
/// <inheritdoc cref="ProcessHotKey"/> /// <inheritdoc cref="ProcessHotKey"/>
public override bool ProcessHotKey (KeyEvent keyEvent) public override bool ProcessHotKey (KeyEvent keyEvent)
{ {
KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent)); KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyPress?.Invoke (this, args);
if (args.Handled)
return true;
if (subviews == null || subviews.Count == 0) if (subviews == null || subviews.Count == 0)
return false; return false;
foreach (var view in subviews) foreach (var view in subviews)
@@ -1119,7 +1131,10 @@ namespace Terminal.Gui {
/// <inheritdoc cref="ProcessColdKey"/> /// <inheritdoc cref="ProcessColdKey"/>
public override bool ProcessColdKey (KeyEvent keyEvent) public override bool ProcessColdKey (KeyEvent keyEvent)
{ {
KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyPress?.Invoke (this, args);
if (args.Handled)
return true;
if (subviews == null || subviews.Count == 0) if (subviews == null || subviews.Count == 0)
return false; return false;
foreach (var view in subviews) foreach (var view in subviews)
@@ -1136,7 +1151,10 @@ namespace Terminal.Gui {
/// <param name="keyEvent">Contains the details about the key that produced the event.</param> /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
public override bool OnKeyDown (KeyEvent keyEvent) public override bool OnKeyDown (KeyEvent keyEvent)
{ {
KeyDown?.Invoke (this, new KeyEventEventArgs (keyEvent)); KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyDown?.Invoke (this, args);
if (args.Handled)
return true;
if (subviews == null || subviews.Count == 0) if (subviews == null || subviews.Count == 0)
return false; return false;
foreach (var view in subviews) foreach (var view in subviews)
@@ -1154,7 +1172,10 @@ namespace Terminal.Gui {
/// <param name="keyEvent">Contains the details about the key that produced the event.</param> /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
public override bool OnKeyUp (KeyEvent keyEvent) public override bool OnKeyUp (KeyEvent keyEvent)
{ {
KeyUp?.Invoke (this, new KeyEventEventArgs (keyEvent)); KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyUp?.Invoke (this, args);
if (args.Handled)
return true;
if (subviews == null || subviews.Count == 0) if (subviews == null || subviews.Count == 0)
return false; return false;
foreach (var view in subviews) foreach (var view in subviews)

View File

@@ -784,7 +784,7 @@ namespace Terminal.Gui {
/// <summary> /// <summary>
/// Closes the current Menu programatically, if open. /// Closes the current Menu programatically, if open.
/// </summary> /// </summary>
public void CloseMenu() public void CloseMenu ()
{ {
CloseMenu (false, false); CloseMenu (false, false);
} }

View File

@@ -1,280 +0,0 @@
using NStack;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Terminal.Gui;
namespace UICatalog {
/// <summary>
/// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows
/// for a calalog of UI demos, examples, and tests.
/// </summary>
internal class Program {
private static Toplevel _top;
private static MenuBar _menu;
private static int _nameColumnWidth;
private static Window _leftPane;
private static List<string> _categories;
private static ListView _categoryListView;
private static Window _rightPane;
private static List<Type> _scenarios;
private static ListView _scenarioListView;
private static StatusBar _statusBar;
private static Scenario _selectedScenario = null;
static void Main (string [] args)
{
if (Debugger.IsAttached)
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
_scenarios = Scenario.GetDerivedClassesCollection ().OrderBy (t => Scenario.ScenarioMetadata.GetName (t)).ToList();
if (args.Length > 0) {
var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase));
_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item]);
_selectedScenario.Init (Application.Top);
_selectedScenario.Setup ();
_selectedScenario.Run ();
_selectedScenario = null;
return;
}
Scenario scenario = GetScenarioToRun ();
while (scenario != null) {
scenario.Init (Application.Top);
scenario.Setup ();
scenario.Run ();
scenario = GetScenarioToRun ();
}
}
/// <summary>
/// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
/// </summary>
private static void Setup ()
{
_menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Quit", "", () => Application.RequestStop() )
}),
new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 10, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")),
});
_leftPane = new Window ("Categories") {
X = 0,
Y = 1, // for menu
Width = 25,
Height = Dim.Fill (),
CanFocus = false,
};
_categories = Scenario.GetAllCategories ().OrderBy(c => c).ToList();
_categoryListView = new ListView (_categories) {
X = 1,
Y = 0,
Width = Dim.Fill (0),
Height = Dim.Fill (2),
AllowsMarking = false,
CanFocus = true,
};
_categoryListView.OpenSelectedItem += (o, a) => {
_top.SetFocus (_rightPane);
};
_categoryListView.SelectedChanged += CategoryListView_SelectedChanged;
_leftPane.Add (_categoryListView);
_rightPane = new Window ("Scenarios") {
X = 25,
Y = 1, // for menu
Width = Dim.Fill (),
Height = Dim.Fill (),
CanFocus = false,
};
_nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length;
_scenarioListView = new ListView () {
X = 0,
Y = 0,
Width = Dim.Fill (0),
Height = Dim.Fill (0),
AllowsMarking = false,
CanFocus = true,
};
//_scenarioListView.OnKeyPress += (KeyEvent ke) => {
// if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) {
// _scenarioListView_OpenSelectedItem (null, null);
// }
//};
_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
_rightPane.Add (_scenarioListView);
_categoryListView.SelectedItem = 0;
_categoryListView.OnSelectedChanged ();
_statusBar = new StatusBar (new StatusItem [] {
//new StatusItem(Key.F1, "~F1~ Help", () => Help()),
new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => {
if (_selectedScenario is null){
// This causes GetScenarioToRun to return null
_selectedScenario = null;
Application.RequestStop();
} else {
_selectedScenario.RequestStop();
}
}),
});
}
/// <summary>
/// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
/// </summary>
/// <returns></returns>
private static Scenario GetScenarioToRun ()
{
Application.Init ();
if (_menu == null) {
Setup ();
}
_top = Application.Top;
_top.KeyUp += KeyUpHandler;
_top.Add (_menu);
_top.Add (_leftPane);
_top.Add (_rightPane);
_top.Add (_statusBar);
// HACK: There is no other way to SetFocus before Application.Run. See Issue #445
#if false
if (_runningScenario != null)
Application.Iteration += Application_Iteration;
#else
_top.Ready += (o, a) => {
if (_selectedScenario != null) {
_top.SetFocus (_rightPane);
_selectedScenario = null;
}
};
#endif
Application.Run (_top);
Application.Shutdown ();
return _selectedScenario;
}
#if false
private static void Application_Iteration (object sender, EventArgs e)
{
Application.Iteration -= Application_Iteration;
_top.SetFocus (_rightPane);
}
#endif
private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e)
{
if (_selectedScenario is null) {
var source = _scenarioListView.Source as ScenarioListDataSource;
_selectedScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]);
Application.RequestStop ();
}
}
internal class ScenarioListDataSource : IListDataSource {
public List<Type> Scenarios { get; set; }
public bool IsMarked (int item) => false;// Scenarios [item].IsMarked;
public int Count => Scenarios.Count;
public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
{
container.Move (col, line);
// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
}
public void SetMark (int item, bool value)
{
}
// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
{
int used = 0;
int index = 0;
while (index < ustr.Length) {
(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
var count = Rune.ColumnWidth (rune);
if (used + count >= width) break;
driver.AddRune (rune);
used += count;
index += size;
}
while (used < width) {
driver.AddRune (' ');
used++;
}
}
public IList ToList ()
{
return Scenarios;
}
}
/// <summary>
/// When Scenarios are running we need to override the behavior of the Menu
/// and Statusbar to enable Scenarios that use those (or related key input)
/// to not be impacted. Same as for tabs.
/// </summary>
/// <param name="ke"></param>
private static void KeyUpHandler (object sender, View.KeyEventEventArgs a)
{
if (_selectedScenario != null) {
//switch (ke.Key) {
//case Key.Esc:
// //_runningScenario.RequestStop ();
// break;
//case Key.Enter:
// break;
//}<
} else if (a.KeyEvent.Key == Key.Tab || a.KeyEvent.Key == Key.BackTab) {
// BUGBUG: Work around Issue #434 by implementing our own TAB navigation
if (_top.MostFocused == _categoryListView)
_top.SetFocus (_rightPane);
else
_top.SetFocus (_leftPane);
}
}
private static void CategoryListView_SelectedChanged (object sender, ListViewItemEventArgs e)
{
var item = _categories [_categoryListView.SelectedItem];
List<Type> newlist;
if (item.Equals ("All")) {
newlist = _scenarios;
} else {
newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList ();
}
_scenarioListView.Source = new ScenarioListDataSource (newlist);
_scenarioListView.SelectedItem = 0;
}
}
}

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Threading", Description: "Demonstration of how to use threading in different ways")]
[ScenarioCategory ("Threading")]
class Threading : Scenario {
private Action _action;
private Action _lambda;
private EventHandler _handler;
private Action _sync;
private ListView _itemsList;
private Button _btnActionCancel;
List<string> log = new List<string> ();
private ListView _logJob;
public override void Setup ()
{
_action = LoadData;
_lambda = async () => {
_itemsList.Source = null;
LogJob ("Loading task lambda");
var items = await LoadDataAsync ();
LogJob ("Returning from task lambda");
_itemsList.SetSource (items);
};
_handler = async (s, e) => {
_itemsList.Source = null;
LogJob ("Loading task handler");
var items = await LoadDataAsync ();
LogJob ("Returning from task handler");
_itemsList.SetSource (items);
};
_sync = () => {
_itemsList.Source = null;
LogJob ("Loading task synchronous");
List<string> items = new List<string> () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
LogJob ("Returning from task synchronous");
_itemsList.SetSource (items);
};
Application.Init ();
_btnActionCancel = new Button (1, 1, "Cancelable Load Items");
_btnActionCancel.Clicked += () => Application.MainLoop.Invoke (CallLoadItemsAsync);
_itemsList = new ListView {
X = Pos.X (_btnActionCancel),
Y = Pos.Y (_btnActionCancel) + 4,
Width = 10,
Height = 10
};
_logJob = new ListView (log) {
X = Pos.Right (_itemsList) + 10,
Y = Pos.Y (_itemsList),
Width = 50,
Height = Dim.Fill ()
};
var text = new TextField (1, 3, 100, "Type anything after press the button");
var _btnAction = new Button (80, 10, "Load Data Action");
_btnAction.Clicked += () => _action.Invoke ();
var _btnLambda = new Button (80, 12, "Load Data Lambda");
_btnLambda.Clicked += () => _lambda.Invoke ();
var _btnHandler = new Button (80, 14, "Load Data Handler");
_btnHandler.Clicked += () => _handler.Invoke (null, new EventArgs ());
var _btnSync = new Button (80, 16, "Load Data Synchronous");
_btnSync.Clicked += () => _sync.Invoke ();
var _btnMethod = new Button (80, 18, "Load Data Method");
_btnMethod.Clicked += async () => await MethodAsync ();
var _btnClearData = new Button (80, 20, "Clear Data");
_btnClearData.Clicked += () => { _itemsList.Source = null; LogJob ("Cleaning Data"); };
var _btnQuit = new Button (80, 22, "Quit");
_btnQuit.Clicked += Application.RequestStop;
Win.Add (_itemsList, _btnActionCancel, _logJob, text, _btnAction, _btnLambda, _btnHandler, _btnSync, _btnMethod, _btnClearData, _btnQuit);
Application.Top.Add (Win);
Application.Run ();
}
private async void LoadData ()
{
_itemsList.Source = null;
LogJob ("Loading task");
var items = await LoadDataAsync ();
LogJob ("Returning from task");
_itemsList.SetSource (items);
}
private void LogJob (string job)
{
log.Add (job);
_logJob.MoveDown ();
}
private async Task<List<string>> LoadDataAsync ()
{
_itemsList.Source = null;
LogJob ("Starting delay");
await Task.Delay (3000);
LogJob ("Finished delay");
return new List<string> () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
}
private async Task MethodAsync ()
{
_itemsList.Source = null;
LogJob ("Loading task method");
List<string> items = new List<string> () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
await Task.Delay (3000);
LogJob ("Returning from task method");
await _itemsList.SetSourceAsync (items);
}
private CancellationTokenSource cancellationTokenSource;
private async void CallLoadItemsAsync ()
{
cancellationTokenSource = new CancellationTokenSource ();
_itemsList.Source = null;
LogJob ($"Clicked the button");
if (_btnActionCancel.Text == "Cancel") {
_btnActionCancel.Text = "Cancelable Load Items";
cancellationTokenSource.Cancel ();
} else
_btnActionCancel.Text = "Cancel";
try {
if (cancellationTokenSource.Token.IsCancellationRequested)
cancellationTokenSource.Token.ThrowIfCancellationRequested ();
LogJob ($"Calling task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
var items = await Task.Run (LoadItemsAsync, cancellationTokenSource.Token);
if (!cancellationTokenSource.IsCancellationRequested) {
LogJob ($"Returned from task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
_itemsList.SetSource (items);
LogJob ($"Finished populate list view Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
_btnActionCancel.Text = "Load Items";
} else {
LogJob ("Task was canceled!");
}
} catch (OperationCanceledException ex) {
LogJob (ex.Message);
}
}
private async Task<List<string>> LoadItemsAsync ()
{
// Do something that takes lot of times.
LogJob ($"Starting delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
await Task.Delay (5000);
LogJob ($"Finished delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
return new List<string> () { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" };
}
}
}